-- Original module located at and .
local p = local arg = ...local i18n
--
--
p.claimCommands =
p.generalCommands =
p.flags =
p.args =
--
--
-- An Ogham space that, just like a normal space, is not accepted by Wikidata as a valid single-character string value,-- but which does not get trimmed as leading/trailing whitespace when passed in an invocation's named argument value.-- This allows it to be used as a special character representing the special value 'somevalue' unambiguously.-- Another advantage of this character is that it is usually visible as a dash instead of whitespace.p.SOMEVALUE = " "p.JULIAN = "Julian"
--
--
local NB_SPACE = " "local ENC_PIPE = "|"local SLASH = "/"local LAT_DIR_N_EN = "N"local LAT_DIR_S_EN = "S"local LON_DIR_E_EN = "E"local LON_DIR_W_EN = "W"local PROP = "prop"local RANK = "rank"local CLAIM = "_claim"local REFERENCE = "_reference"local UNIT = "_unit"local UNKNOWN = "_unknown"
--
--
local aliasesP =
local aliasesQ =
local parameters =
local formats =
local hookNames =
local defaultSeparators =
local rankTable =
--
--
-- used to merge output arrays together;-- note that it currently mutates the first input arraylocal function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end
return a1end
-- used to make frame.args mutable, to replace #frame.args (which is always 0)-- with the actual amount and to simply copy tables;-- does a shallow copy, so nested tables are not copied but linkedlocal function copyTable(tIn) if not tIn then return nil end
local tOut =
for i, v in pairs(tIn) do tOut[i] = v end
return tOutend
-- implementation of pairs that skips numeric keyslocal function npairs(t) return function(t, k) local v
repeat k, v = next(t, k) until k
return k, v end, t, nilend
local function toString(object, insideRef, refs) local mt, value
insideRef = insideRef or false refs = refs or
if not object then refs.squashed = false return "" end
mt = getmetatable(object)
if mt.sep then local array =
for _, obj in ipairs(object) do local ref = refs[1]
if not insideRef and array[1] and mt.sep[1] ~= "" then refs[1] = end
value = toString(obj, insideRef, refs)
if value ~= "" or (refs.squashed and not array[1]) then array[#array + 1] = value else refs[1] = ref end end
value = table.concat(array, mt.sep[1]) else if mt.hash then if refs[1][mt.hash] then refs.squashed = true return "" end
insideRef = true end
if mt.format then local ref, squashed, array
local function processFormat(format) local array = local params =
-- see if there are required parameters to expand if format.req then
-- before expanding any parameters, check that none of them is nil for i, _ in pairs(format.req) do if not object[i] then return array -- empty end end end
-- process the format and childs (+1 is needed to process trailing childs) for i = 1, #format + 1 do if format.childs and format.childs[i] then for _, child in ipairs(format.childs[i]) do local ref = copyTable(refs[1]) local squashed = refs.squashed
local childArray = processFormat(child)
if not childArray[1] then refs[1] = ref refs.squashed = squashed else mergeArrays(array, childArray) end end end
if format.params and format.params[i] then array[#array + 1] = toString(object[format[i]], insideRef, refs)
if array[#array]
if not insideRef then refs[1] = end end end
return array end
ref = copyTable(refs[1]) squashed = refs.squashed
array = processFormat(mt.format)
if not array[1] then refs[1] = ref refs.squashed = squashed end
value = table.concat(array) else if mt.expand then local args =
for i, j in npairs(object) do args[i] = toString(j, insideRef) end
value = mw.getCurrentFrame:expandTemplate elseif object.label then value = object.label else value = table.concat(object) end
if not insideRef and not mt.hash and value ~= "" then refs[1] = end end
if mt.sub then for i, j in pairs(mt.sub) do value = mw.ustring.gsub(value, i, j) end end
if value ~= "" and mt.tag then value = mw.getCurrentFrame:extensionTag(mt.tag[1], value, mt.tag[2])
if mt.hash then refs[1][mt.hash] = true end end
refs.squashed = false end
if mt.trail then value = value .. mt.trail
if not insideRef then refs[1] = refs.squashed = false end end
return valueend
local function loadI18n(aliasesP, frame) local title
if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle else -- current module included by other module, get its title from ... title = arg end
if not i18n then i18n = require(title .. "/i18n").init(aliasesP) endend
local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end
return idend
local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return textend
local function throwError(errorMessage, param) error(errorText(errorMessage, param))end
local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1)end
local function padZeros(num, numDigits) local numZeros local negative = false
if num < 0 then negative = true num = num * -1 end
num = tostring(num) numZeros = numDigits - num:len
for _ = 1, numZeros do num = "0"..num end
if negative then num = "-"..num end
return numend
local function replaceSpecialChar(chr) if chr
local function replaceSpecialChars(str) local chr local esc = false local strOut = ""
for i = 1, #str do chr = str:sub(i,i)
if not esc then if chr
return strOutend
local function isPropertyID(id) return id:match('^P%d+$')end
local function buildLink(target, label) local mt =
if not label then mt.format = return setmetatable(mt), mt else mt.format = return setmetatable(mt), mt endend
local function buildWikilink(target, label) local mt =
if not label or target
-- does a shallow copy of both the object and the metatable's format,-- so nested tables are not copied but linkedlocal function copyValue(vIn) local vOut = copyTable(vIn) local mtIn = getmetatable(vIn) local mtOut = return setmetatable(vOut, mtOut)end
local function split(str, del, from) local i, j
from = from or 1 i, j = str:find(del, from)
if i and j then return str:sub(1, i - 1), str:sub(j + 1), i, j end
return strend
local function urlEncode(url) local i, j, urlSplit, urlPath local urlPre = "" local count = 0 local pathEnc = local delim = ""
i, j = url:find("//", 1, true)
-- check if a hostname is present if i
':') then urlSplit = urlPre = urlSplit[1]
-- split the path from the hostname if urlSplit[2] then urlPath = url:sub(urlSplit[3], urlSplit[4]) .. urlSplit[2] else urlPath = "" end else urlPath = url -- no hostname is present, so it's a path end
-- encode each part of the path for part in mw.text.gsplit(urlPath, "[;/?:@&=+$,#]") do pathEnc[#pathEnc + 1] = delim pathEnc[#pathEnc + 1] = mw.uri.encode(mw.uri.decode(part, "PATH"), "PATH") count = count + #part + 1 delim = urlPath:sub(count, count) end
-- return the properly encoded URL return urlPre .. table.concat(pathEnc)end
local function parseWikidataURL(url) local id
if url:match('^http[s]?://') then id = [2]
if id then return "Q" .. id end end
return nilend
local function parseDate(dateStr, precision) precision = precision or "d"
local i, j, index, ptr local parts =
if dateStr
-- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]")
if i then dateStr = dateStr:sub(1, i-1) end
local from = 1
if dateStr:sub(1,1)
index = 1 ptr = 1
i, j = dateStr:find("-", from)
if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error)
if parts[index]
if precision
index = index + 1 ptr = i + 1
i, j = dateStr:find("-", ptr)
if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10)
if precision
index = index + 1 ptr = i + 1 end end
if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end
return parts[1], parts[2], parts[3] -- year, month, dayend
local function datePrecedesDate(dateA, dateB) if not dateA[1] or not dateB[1] then return nil end
dateA[2] = dateA[2] or 1 dateA[3] = dateA[3] or 1 dateB[2] = dateB[2] or 1 dateB[3] = dateB[3] or 1
if dateA[1] < dateB[1] then return true end
if dateA[1] > dateB[1] then return false end
if dateA[2] < dateB[2] then return true end
if dateA[2] > dateB[2] then return false end
if dateA[3] < dateB[3] then return true end
return falseend
local function newOptionalHook(hooks) return function(state, claim) state:callHooks(hooks, claim)
return true endend
local function newPersistHook(params) return function(state, claim) local param0
if not state.resultsByStatement[claim][1] then local mt = copyTable(state.metaTable) mt.rank = claim.rank state.resultsByStatement[claim][1] = setmetatable(mt)
local rankPos = (rankTable[claim.rank] or)[1]
if rankPos and rankPos < state.conf.foundRank then state.conf.foundRank = rankPos end end
for param, _ in pairs(params) do if not state.resultsByStatement[claim][1][param] then state.resultsByStatement[claim][1][param] = state.resultsByStatement[claim][param] -- persist result
-- if we need to persist "q", then also persist "q1", "q2", etc. if param
if state.resultsByStatement[claim][param0][1] then state.resultsByStatement[claim][1][param0] = state.resultsByStatement[claim][param0] end end end end end
return true endend
local function parseFormat(state, formatStr, i) local iNext, childHooks, param0 local esc = false local param = 0 local str = "" local hooks = local optionalHooks = local parsedFormat = local params = local childs = local req =
i = i or 1
local function flush if str ~= "" then parsedFormat[#parsedFormat + 1] = str
if param > 0 then req[str] = true params[#parsedFormat] = true
if not state.hooksByParam[str] then if state.conf.statesByParam[str] or str
parameters.qualifier and state.conf.statesByParam[str.."1"] then state:newValueHook(str)
for i = 1, state.conf.qualifiersCount do param0 = str..i
if not state.hooksByParam[param0] then state:newValueHook(param0) end end end end
hooks[#hooks + 1] = state.hooksByParam[str] end
str = "" end
param = 0 end
while i <= #formatStr do chr = formatStr:sub(i,i)
if not esc then if chr
esc = true elseif chr
'[' then flush iNext = #parsedFormat + 1 if not childs[iNext] then childs[iNext] = end
childs[iNext][#childs[iNext] + 1], childHooks, i = parseFormat(state, formatStr, i + 1)
if childHooks[1] then optionalHooks[#optionalHooks + 1] = newOptionalHook(childHooks) end elseif chr
1 and not chr:match('%d') then flush end
str = str .. replaceSpecialChar(chr) end else str = str .. chr esc = false end
i = i + 1 end
flush
if hooks[1] then hooks[#hooks + 1] = newPersistHook(req) end
mergeArrays(hooks, optionalHooks)
parsedFormat.params = params parsedFormat.childs = childs parsedFormat.req = req
return parsedFormat, hooks, iend
-- this function must stay in sync with the getValue functionlocal function parseValue(value, datatype) if datatype
'time' then local tail local dateValue =
dateValue.len = 4 -- length used for comparing
value, tail = split(value, SLASH)
if tail and tail:lower
if value:sub(1,1)
dateValue[1] = tonumber(dateValue[1])
if value then dateValue[2], value = split(value, "-") dateValue[2] = tonumber(dateValue[2])
if value then dateValue[3] = tonumber(value) end end
return dateValue elseif datatype
coordValue.len = 6 -- length used for comparing
for i = 1, 4 do part, value = split(value, SLASH) coordValue[i] = tonumber(part)
if not coordValue[i] or not value or i
if part:upper
if value then partsIndex = partsIndex + 3
for i = 4, partsIndex do part, value = split(value, SLASH) coordValue[i] = tonumber(part)
if not coordValue[i] or not value then partsIndex = i - 1 break end end
if value and value:upper
return coordValue elseif datatype
return end
local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil
if arg then if arg:sub(1,1)
"Q" or arg:sub(1,9):lower
if eid then if eid:sub(1,9):lower
if id:sub(1,1):upper ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1)
id = mw.wikibase.getEntityIdForTitle(page) or "" end
if not id then id = mw.wikibase.getEntityIdForCurrentPage or "" end
id = id:upper
if not mw.wikibase.isValidEntityId(id) then id = "" end
return id, propend
local function nextArg(args) local arg = args[args.pointer]
if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil endend
--
--
local Config =
-- allows for recursive callsfunction Config:new local cfg = setmetatable(self) self.__index = self
cfg.separators =
cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = cfg.qualifierIDsAndValues = cfg.qualifiersCount = 0
cfg.bestRank = true cfg.ranks = -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.filterBeforeRank = false
cfg.periods = -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = -- today as cfg.curTime = os.time
cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false
cfg.inSitelinks = false
cfg.emptyAllowed = false
cfg.langCode = mw.language.getContentLanguage.code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode)
cfg.siteID = mw.wikibase.getGlobalSiteId
cfg.movSeparator = cfg.separators["sep%s"] cfg.puncMark = cfg.separators["punc"]
cfg.statesByParam = cfg.statesByID = cfg.curState = nil
cfg.sortKeys =
return cfgend
local State =
function State:new(cfg, level, param, id) local stt = setmetatable(self) self.__index = self
stt.conf = cfg stt.level = level stt.param = param
stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.freeUnit = false stt.freeNumber = false stt.maxResults = 0 -- 0 means unlimited
stt.metaTable = nil stt.results = stt.resultsByStatement = stt.references = stt.hooksByParam = stt.hooksByID = stt.valHooksByIdOrParam = stt.valHooks = stt.sortable = stt.sortPaths = stt.propState = nil
if level and level > 1 then stt.hooks = stt.separator = cfg.separators["sep%"..param] or cfg.separators["sep"] -- fall back to "sep" for getAlias and getBadge stt.resultsDatatype = nil else stt.hooks = stt.separator = cfg.separators["sep"] stt.resultsDatatype = end
if id then cfg:addToStatesByID(stt, id) elseif param then cfg.statesByParam[param] = stt end
return sttend
function Config:addToStatesByID(state, id) if not self.statesByID[id] then self.statesByID[id] = end
self.statesByID[id][#self.statesByID[id] + 1] = stateend
-- if id
if not id then id = mw.wikibase.getEntityIdForCurrentPage
if not id then return value, mt -- empty value end end
id = id:upper -- just to be sure
-- check if given id actually exists if not mw.wikibase.isValidEntityId(id) or not mw.wikibase.entityExists(id) then return value, mt -- empty value end
if raw then label = id else -- try short name first if requested if short then label = p.property -- get short name
if label
-- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end
if not label and not emptyAllowed then return value, mt -- empty value end
value.label = label or "" end
-- split id for potential numeric sorting value[1] = id:sub(1,1) value[2] = tonumber(id:sub(2))
-- build a link if requested if link then if raw or value[1]
if value[1]
value.target = "d:" .. value.target else -- else, value[1]
if value.target and label then mt.format = [2].format end end
return value, mtend
function Config:getEditIcon local value = "" local prefix = "" local front = NB_SPACE local back = ""
if self.entityID:sub(1,1)
if self.editAtEnd then front = '
' back = '' endvalue = ""
return front .. value .. backend
function Config:convertUnit(unit, raw, link, short) local itemID local mt = local value = setmetatable(mt)
if unit
"1" then return value, mt end
itemID = parseWikidataURL(unit)
if itemID then if itemID
if not raw then value.label = "%" elseif link then value.target = "d:" .. itemID mt.format = [2].format end else value, mt = self:getLabel(itemID, raw, link, short)
if value.label then value.unitSep = NB_SPACE end end end
return value, mtend
function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.freeUnit, self.freeNumber, false, self.conf.emptyAllowed, self.param:sub(1, 1))end
-- returns a value object in the general form with metatable ;-- 'format' is the string representation of the value in unconcatenated form to exploit Lua's string internalization to reduce memory usage;-- this function must stay in sync with the parseValue functionfunction Config:getValue(snak, raw, link, short, anyLang, freeUnit, freeNumber, noSpecial, emptyAllowed, param) local mt = local value = setmetatable(mt)
if snak.snaktype
mt.datatype =
if datatype
value[1] = datavalue mt.datatype = datatypes
if subtype
mt.datatype = datatypes return value elseif subtype
return value elseif subtype
'math' and not raw then local attribute = nil
if (param
parameters.qualifier and self.propertyID
aliasesP.definingFormula then attribute = end
mt.tag = return value elseif subtype
'external-id' and link then local url = p.property -- get formatter URL
if url ~= "" then url = urlEncode(mw.ustring.gsub(url, "$1", datavalue)) value, mt = buildLink(url, datavalue) mt.datatype = datatypes end
return value else return value end elseif datatype
self.langCode then value[1] = datavalue['text'] value.language = datavalue['language'] end
return value elseif datatype
if freeNumber or not freeUnit then -- get value and strip + signs from front valueStr = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") value[1] = tonumber(valueStr)
-- assertion; we should always have a value if not value[1] then return value end
if not raw then -- replace decimal mark based on locale valueStr = replaceDecimalMark(valueStr)
-- add delimiters for readability valueStr = i18n.addDelimiters(valueStr)
mt.format = end end
if freeUnit or (not freeNumber and not raw) then local mtUnit
unit, mtUnit = self:convertUnit(datavalue['unit'], raw, link, short)
if freeUnit and not freeNumber then value = unit mt = mtUnit mt.datatype = elseif unit[1] then value[#value + 1] = unit[1] value[#value + 1] = unit[2]
value.len = 1 -- (max) length used for sorting value.target = unit.target value.unitLabel = unit.label value.unitSep = unit.unitSep
if raw then mt.format = mergeArrays(mt.format, mtUnit.format or unit) else mt.format[#mt.format + 1] = unit.unitSep -- may be nil mergeArrays(mt.format, mtUnit.format or) end end end
return value elseif datatype
if precision
10 then p = "m" else p = "y" yFactor = 10^(9-precision) end
y, m, d = parseDate(datavalue['time'], p)
if y < 0 then sign = -1 y = math.abs(y) end
-- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor
-- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true
if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv)
-- take the first year of the century/millennium as the raw year -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRaw = (yRound - 1) * yFactor + 1
if not raw then if precision
suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium yRound = yRaw end else -- precision
if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end
if sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRaw = yRaw + yFactor - 1
if raw then yRound = yRaw end end else local yReFactor, yReDiv, yReRound
-- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5)
if yRound
if yReDiv
if yRound
if precision >= 1 and y ~= 0 then yFull = yRound * yFactor
yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5)
if yReDiv
if yReDiv
yRaw = yRound * yFactor
if not raw then if precision
0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRaw if yRound
value[1] = yRaw * sign value[2] = m value[3] = d value.len = 3 -- (max) length used for sorting value.precision = precision mt.format =
if not raw then if prefix ~= "" then mt.format[1] = prefix end
if m then mStr = self.langObj:formatDate("F", "1-"..m.."-1")
if d then if self.mdyDate then mt.format[#mt.format + 1] = mStr mt.format[#mt.format + 1] = " " mt.format[#mt.format + 1] = tostring(d) mt.format[#mt.format + 1] = "," else mt.format[#mt.format + 1] = tostring(d) mt.format[#mt.format + 1] = " " mt.format[#mt.format + 1] = mStr end else mt.format[#mt.format + 1] = mStr end
mt.format[#mt.format + 1] = " " end
mt.format[#mt.format + 1] = tostring(yRound)
if suffix ~= "" then mt.format[#mt.format + 1] = suffix end
if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end
if ce then mt.format[#mt.format + 1] = " "
if link then target = mw.wikibase.getSitelink(aliasesQ.commonEra)
if target then mergeArrays(mt.format, [2].format) else mt.format[#mt.format + 1] = ce end else mt.format[#mt.format + 1] = ce end end else mt.format[1] = padZeros(yRound * sign, 4)
if m then mt.format[#mt.format + 1] = "-" mt.format[#mt.format + 1] = padZeros(m, 2)
if d then mt.format[#mt.format + 1] = "-" mt.format[#mt.format + 1] = padZeros(d, 2) end end end
calendarID = parseWikidataURL(datavalue['calendarmodel'])
if calendarID and calendarID
3, this will not be taken into account while sorting
if mayAddCalendar then if not raw then mt.format[#mt.format + 1] = " ("
if link then target = mw.wikibase.getSitelink(aliasesQ.julianCalendar)
if target then mergeArrays(mt.format, [2].format) else mt.format[#mt.format + 1] = i18n['datetime']['julian'] end else mt.format[#mt.format + 1] = i18n['datetime']['julian'] end
mt.format[#mt.format + 1] = ")" else mt.format[#mt.format + 1] = SLASH mt.format[#mt.format + 1] = p.JULIAN end end end
return value elseif datatype
local precision, unitsPerDegree, numDigits, strFormat, globe local latitude, latConv, latLink local longitude, lonConv, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees, latMinutes, latSeconds local lonDegrees, lonMinutes, lonSeconds local degSymbol, minSymbol, secSymbol, separator
local latSign = 1 local lonSign = 1
local latFormat = local lonFormat =
if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west']
degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = LAT_DIR_N_EN latDirectionS = LAT_DIR_S_EN lonDirectionE = LON_DIR_E_EN lonDirectionW = LON_DIR_W_EN
degSymbol = SLASH minSymbol = SLASH secSymbol = SLASH separator = SLASH end
latitude = datavalue['latitude'] longitude = datavalue['longitude']
if latitude < 0 then latDirection = latDirectionS latDirectionEN = LAT_DIR_S_EN latSign = -1 latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = LAT_DIR_N_EN end
if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = LON_DIR_W_EN lonSign = -1 longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = LON_DIR_E_EN end
precision = datavalue['precision']
if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end
-- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision
if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end
if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end
numDigits = math.ceil(-math.log10(unitsPerDegree * precision))
if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end
strFormat = "%." .. numDigits .. "f"
if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits
if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv
latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60)
latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60))
if not raw then latFormat[5] = replaceDecimalMark(latSeconds) lonFormat[5] = replaceDecimalMark(lonSeconds) else latFormat[5] = latSeconds lonFormat[5] = lonSeconds end
latFormat[6] = secSymbol lonFormat[6] = secSymbol
value[3] = tonumber(latSeconds) * latSign value[6] = tonumber(lonSeconds) * lonSign end
latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60)
latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60)
if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) else latMinutes = tostring(latMinutes) lonMinutes = tostring(lonMinutes) end
if not raw then latFormat[3] = replaceDecimalMark(latMinutes) lonFormat[3] = replaceDecimalMark(lonMinutes) else latFormat[3] = latMinutes lonFormat[3] = lonMinutes end
latFormat[4] = minSymbol lonFormat[4] = minSymbol
value[2] = tonumber(latMinutes) * latSign value[5] = tonumber(lonMinutes) * lonSign
latDegrees = tostring(latDegrees) lonDegrees = tostring(lonDegrees) end
if not raw then latFormat[1] = replaceDecimalMark(latDegrees) lonFormat[1] = replaceDecimalMark(lonDegrees) else latFormat[1] = latDegrees lonFormat[1] = lonDegrees end
latFormat[2] = degSymbol lonFormat[2] = degSymbol
value[1] = tonumber(latDegrees) * latSign value[4] = tonumber(lonDegrees) * lonSign value.len = 6 -- (max) length used for sorting
latFormat[#latFormat + 1] = latDirection lonFormat[#lonFormat + 1] = lonDirection
if link then globe = parseWikidataURL(datavalue['globe'])
if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower else globe = "earth" end
latLink = table.concat("_") lonLink = table.concat("_")
value.target = "https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."¶ms="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe value.isWebTarget = true
mt.format =