Modul:LinkedWiki: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
Zeile 1: | Zeile 1: | ||
-- | -- module local variables | ||
local wiki = | |||
{ | |||
langcode = mw.language.getContentLanguage().code | |||
} | |||
local | -- internationalisation | ||
local i18n = | |||
{ | |||
["errors"] = | |||
{ | |||
["property-not-found"] = "Eigenschaft nicht gefunden.", | |||
["entity-not-found"] = "Wikidata-Eintrag nicht gefunden.", | |||
["entity-not-valid"] = "Die an die Wikidata-Schnittstelle übergebene Item-ID ist nicht gültig.", | |||
["unknown-claim-type"] = "Unbekannter Aussagentyp.", | |||
["unknown-entity-type"] = "Unbekannter Entity-Typ.", | |||
["qualifier-not-found"] = "Qualifikator nicht gefunden.", | |||
["site-not-found"] = "Wikimedia-Projekt nicht gefunden.", | |||
["invalid-parameters"] = "Ungültige Parameter.", | |||
["module-not-loaded"] = "Loading of additional module failed." | |||
}, | |||
["maintenance-pages"] = | |||
{ | |||
["entity-not-found"] = "Wikidata/Wartung/Fehlendes Datenobjekt", | |||
["entity-not-valid"] = "Wikidata/Wartung/Ungültige Datenobjekt-Identifikationsnummer", | |||
["property-not-existing"] = "Wikidata/Wartung/Eigenschaft existiert nicht" | |||
}, | |||
["datetime"] = | |||
{ | |||
-- $1 is a placeholder for the actual number | |||
[0] = "$1 Mrd. Jahren", -- precision: billion years | |||
[1] = "$100 Mio. Jahren", -- precision: hundred million years | |||
[2] = "$10 Mio. Jahren", -- precision: ten million years | |||
[3] = "$1 Mio. Jahren", -- precision: million years | |||
[4] = "$100.000 Jahren", -- precision: hundred thousand years | |||
[5] = "$10.000 Jahren", -- precision: ten thousand years | |||
[6] = "$1. Jahrtausend", -- precision: millenium | |||
[7] = "$1. Jahrhundert", -- precision: century | |||
[8] = "$1er", -- precision: decade | |||
-- the following use the format of #time parser function | |||
[9] = "Y", -- precision: year, | |||
[10] = "F Y", -- precision: month | |||
[11] = "j. F Y", -- precision: day | |||
[12] = 'j. F Y, G "Uhr"', -- precision: hour | |||
[13] = "j. F Y G:i", -- precision: minute | |||
[14] = "j. F Y G:i:s", -- precision: second | |||
["beforenow"] = "vor $1", -- how to format negative numbers for precisions 0 to 5 | |||
["afternow"] = "in $1", -- how to format positive numbers for precisions 0 to 5 | |||
["bc"] = '$1 "v.Chr."', -- how print negative years | |||
["ad"] = "$1" -- how print positive years | |||
}, | |||
["monolingualtext"] = '<span lang="%language">%text</span>', | |||
["FETCH_WIKIDATA"] = "ABFRAGE_WIKIDATA" | |||
} | |||
--important properties | |||
--local | local propertyId = | ||
{ | |||
["starttime"] = "P580", | |||
["endtime"] = "P582" | |||
} | |||
-- | local formatchar = | ||
{ | |||
[10] = {"n","m","M","F","xg"}, --precision: month | |||
[11] = {"W","j","d","z","D","l","N","w"}, --precision: day | |||
[12] = {"a","A","g","h","G","H"}, --precision: hour | |||
[13] = {"i"}, --precision: minute | |||
[14] = {"s","U"} --precision: second | |||
} | |||
local function | local function printError(code) | ||
return '<span class="error">' .. (i18n.errors[code] or code) .. '</span>' | |||
end | end | ||
local function | -- the "qualifiers" and "snaks" field have a respective "qualifiers-order" and "snaks-order" field | ||
return | -- use these as the second parameter and this function instead of the built-in "pairs" function | ||
end | -- to iterate over all qualifiers and snaks in the intended order. | ||
local function orderedpairs(array, order) | |||
if not order then return pairs(array) end | |||
local function | -- return iterator function | ||
local i = 0 | |||
return function() | |||
i = i + 1 | |||
if order[i] then | |||
return order[i], array[order[i]] | |||
end | |||
end | end | ||
end | end | ||
-- Function to check whether a certain item is a parent of a given item. | |||
-- If pExitItem is reached without finding the searched parent item, the search stops. | |||
-- A parent is connected via P31 or P279. | |||
-- Attention: very intensive function, use carefully! | |||
local function isParent(pItem, pParent, pExitItem, pMaxDepth, pDepth) | |||
if not pDepth then pDepth = 0 end | |||
local function | |||
end | |||
if type(pItem) == "number" then pItem = "Q" .. pItem end | |||
end | |||
local | local entity = mw.wikibase.getEntity(pItem) | ||
return | if not entity then return false end | ||
end | |||
local | local claims31 | ||
local claims279 | |||
if entity.claims then | |||
claims31 = entity.claims[mw.wikibase.resolvePropertyId('P31')] | |||
return | claims279 = entity.claims[mw.wikibase.resolvePropertyId('P279')] | ||
else | |||
return false | |||
end | end | ||
if not claims31 and not claims279 then return false end | |||
local | local parentIds = {} | ||
if | if claims31 and #claims31 > 0 then | ||
for i, v in ipairs(claims31) do parentIds[#parentIds+1] = getSnakValue(v.mainsnak, "numeric-id") end | |||
for i, | |||
end | end | ||
if # | if claims279 and #claims279 > 0 then | ||
for i, v in ipairs(claims279) do parentIds[#parentIds+1] = getSnakValue(v.mainsnak, "numeric-id") end | |||
end | end | ||
-- check if searched parent or exit item is reached or do recursive call | |||
if | if not parentIds[1] or #parentIds == 0 then return false end | ||
local itemString = "" | |||
local result = nil | |||
for i, v in ipairs(parentIds) do | |||
end | if not v then return false end | ||
itemString = "Q" .. v | |||
-- | if itemString == pParent then | ||
-- successful! | |||
return true | |||
elseif itemString == pExitItem or itemString == "Q35120" then | |||
-- exit if either "exit item" or node item (Q35120) is reached | |||
return false | |||
else | |||
if pDepth+1 < pMaxDepth then | |||
result = isParent(itemString, pParent, pExitItem, pMaxDepth, pDepth+1) | |||
else return false end | |||
if result == true then return result end | |||
end | |||
end | end | ||
do return false end | |||
return | |||
end | end | ||
local function | local function printDatavalueCoordinate(data, parameter) | ||
-- data fields: latitude [double], longitude [double], altitude [double], precision [double], globe [wikidata URI, usually http://www.wikidata.org/entity/Q2 [earth]] | |||
if | if parameter then | ||
if parameter == "globe" then data.globe = mw.ustring.match(data.globe, "Q%d+") end -- extract entity id from the globe URI | |||
return data[parameter] | |||
else | |||
return data.latitude .. "/" .. data.longitude -- combine latitude and longitude, which can be decomposed using the #titleparts wiki function | |||
end | end | ||
end | end | ||
local function | local function printDatavalueQuantity(data, parameter) | ||
if not | -- data fields: amount [number], unit [string], upperBound [number], lowerBound [number] | ||
if not parameter or parameter == "amount" then | |||
return tonumber(data.amount) | |||
elseif parameter == "unit" then | |||
return mw.ustring.match(data.unit, "Q%d+") | |||
else | |||
return data[parameter] | |||
end | end | ||
end | end | ||
local function | local function normalizeDate(date) | ||
date = mw.text.trim(date, "+") | |||
-- extract year | |||
local yearstr = mw.ustring.match(date, "^\-?%d+") | |||
local year = tonumber(yearstr) | |||
-- remove leading zeros of year | |||
return year .. mw.ustring.sub(date, #yearstr + 1), year | |||
end | end | ||
-- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millenia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second | |||
function formatDate(date, precision, timezone, formatstr) | |||
precision = precision or 11 | |||
return | date, year = normalizeDate(date) | ||
end | date = string.gsub(date, "-00%f[%D]", "-01") | ||
if year == 0 and precision <= 9 then return "" end | |||
-- precision is 10000 years or more | |||
if | if precision <= 5 then | ||
local factor = 10 ^ ((5 - precision) + 4) | |||
local y2 = math.ceil(math.abs(year) / factor) | |||
local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2)) | |||
if year < 0 then | |||
relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative) | |||
else | |||
relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative) | |||
end | |||
return relative | |||
end | end | ||
local | -- precision is decades, centuries and millenia | ||
if | local era | ||
return | if precision == 6 then era = mw.ustring.gsub(i18n.datetime[6], "$1", tostring(math.floor((math.abs(year) - 1) / 1000) + 1)) end | ||
if precision == 7 then era = mw.ustring.gsub(i18n.datetime[7], "$1", tostring(math.floor((math.abs(year) - 1) / 100) + 1)) end | |||
if precision == 8 then era = mw.ustring.gsub(i18n.datetime[8], "$1", tostring(math.floor(math.abs(year) / 10) * 10)) end | |||
if era then | |||
if year < 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.bc, '"', ""), "$1", era) | |||
elseif year > 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.ad, '"', ""), "$1", era) end | |||
return era | |||
end | end | ||
-- precision is years or less | |||
--[[ | if precision >= 9 then | ||
--[[ the following code replaces the UTC suffix with the given negated timezone to convert the global time to the given local time | |||
timezone = tonumber(timezone) | |||
if timezone and timezone ~= 0 then | |||
timezone = -timezone | |||
timezone = string.format("%.2d%.2d", timezone / 60, timezone % 60) | |||
if timezone[1] ~= '-' then timezone = "+" .. timezone end | |||
] | date = mw.text.trim(date, "Z") .. " " .. timezone | ||
end | |||
]]-- | |||
if formatstr then | |||
for i=(precision+1), 14 do | |||
for _, ch in pairs(formatchar[i]) do | |||
if formatstr:find(ch) then | |||
formatstr = i18n.datetime[precision] | |||
end | |||
end | |||
end | |||
else | |||
formatstr = i18n.datetime[precision] | |||
end | |||
if year == 0 then formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], "") | |||
elseif year < 0 then | |||
-- Mediawiki formatDate doesn't support negative years | |||
date = mw.ustring.sub(date, 2) | |||
formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.bc, "$1", i18n.datetime[9])) | |||
elseif year > 0 and i18n.datetime.ad ~= "$1" then | |||
formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.ad, "$1", i18n.datetime[9])) | |||
end | |||
return mw.language.new(wiki.langcode):formatDate(formatstr, date) | |||
end | end | ||
end | |||
-- if | local function printDatavalueTime(data, parameter) | ||
-- data fields: time [ISO 8601 time], timezone [int in minutes], before [int], after [int], precision [int], calendarmodel [wikidata URI] | |||
return | -- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millenia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second | ||
-- calendarmodel: e.g. http://www.wikidata.org/entity/Q1985727 for the proleptic Gregorian calendar or http://www.wikidata.org/wiki/Q11184 for the Julian calendar] | |||
if parameter then | |||
para, formatstr = parameter:match("([^:]+):([^:]+)") | |||
if parameter == "calendarmodel" then | |||
data.calendarmodel = string.match(data.calendarmodel, "Q%d+") -- extract entity id from the calendar model URI | |||
elseif para and para == "time" then | |||
return formatDate(data.time, data.precision, data.timezone,formatstr) | |||
elseif parameter == "time" then | |||
data.time = normalizeDate(data.time) | |||
end | |||
return data[parameter] | |||
else | |||
return formatDate(data.time, data.precision, data.timezone) | |||
end | end | ||
end | |||
local function printDatavalueEntity(data, parameter) | |||
-- data fields: entity-type [string], numeric-id [int, Wikidata id] | |||
local id | |||
local | |||
if data["entity-type"] == "item" then id = "Q" .. data["numeric-id"] | |||
elseif data["entity-type"] == "property" then id = "P" .. data["numeric-id"] | |||
else return printError("unknown-entity-type") | |||
end | end | ||
if parameter then | |||
if parameter == "link" then | |||
local linkTarget = mw.wikibase.sitelink(id) | |||
local linkName = mw.wikibase.label(id) | |||
if linkTarget then | |||
local link = linkTarget | |||
-- if there is a local Wikipedia article linking to it, use the label or the article title | |||
if linkName and (linkName ~= linkTarget) then link = link .. "|" .. linkName end | |||
return "[[" .. link .. "]]" | |||
else | |||
-- if there is no local Wikipedia article output the label or link to the Wikidata object to input a proper label | |||
if linkName then return linkName else return "[[:d:" .. id .. "|" .. id .. "]]" end | |||
end | |||
else | |||
return data[parameter] | |||
end | end | ||
else | |||
return mw.wikibase.label(id) or id | |||
end | end | ||
end | end | ||
function | local function printDatavalueMonolingualText(data, parameter) | ||
local | -- data fields: language [string], text [string] | ||
if parameter then | |||
return | return data[parameter] | ||
else | |||
local result = mw.ustring.gsub(mw.ustring.gsub(i18n.monolingualtext, "%%language", data["language"]), "%%text", data["text"]) | |||
return result | |||
end | end | ||
end | end | ||
function getSnakValue(snak, parameter) | |||
if | -- snaks have three types: "novalue" for null/nil, "somevalue" for not null/not nil, or "value" for actual data | ||
return | if snak.snaktype == "value" then | ||
-- call the respective snak parser | |||
if snak.datavalue.type == "string" then return snak.datavalue.value | |||
elseif snak.datavalue.type == "globecoordinate" then return printDatavalueCoordinate(snak.datavalue.value, parameter) | |||
elseif snak.datavalue.type == "quantity" then return printDatavalueQuantity(snak.datavalue.value, parameter) | |||
elseif snak.datavalue.type == "time" then return printDatavalueTime(snak.datavalue.value, parameter) | |||
elseif snak.datavalue.type == "wikibase-entityid" then return printDatavalueEntity(snak.datavalue.value, parameter) | |||
elseif snak.datavalue.type == "monolingualtext" then return printDatavalueMonolingualText(snak.datavalue.value, parameter) | |||
end | |||
end | end | ||
return | return mw.wikibase.renderSnak(snak) | ||
end | end | ||
function getQualifierSnak(claim, qualifierId) | |||
if | -- a "snak" is Wikidata terminology for a typed key/value pair | ||
return | -- a claim consists of a main snak holding the main information of this claim, | ||
-- as well as a list of attribute snaks and a list of references snaks | |||
if qualifierId then | |||
-- search the attribute snak with the given qualifier as key | |||
if claim and claim.qualifiers then | |||
local qualifier = claim.qualifiers[qualifierId] | |||
if qualifier then return qualifier[1] end | |||
end | |||
return nil, printError("qualifier-not-found") | |||
else | else | ||
return claim. | -- otherwise return the main snak | ||
return claim.mainsnak | |||
end | end | ||
end | end | ||
local function | local function datavalueTimeToDateObject(data) | ||
local sign, year, month, day, hour, minute, second = string.match(data.time, "(.)(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z") | |||
local result = | |||
{ | |||
year = tonumber(year), | |||
month = tonumber(month), | |||
day = tonumber(day), | |||
hour = tonumber(hour), | |||
min = tonumber(minute), | |||
sec = tonumber(second), | |||
timezone = data.timezone, | |||
julian = data.calendarmodel and string.match(data.calendarmodel, "Q11184$") | |||
if | } | ||
if sign == "-" then result.year = -result.year end | |||
return result | |||
end | end | ||
function julianDay(dateObject) | |||
local year = dateObject.year | |||
local month = dateObject.month or 0 | |||
local day = dateObject.day or 0 | |||
if month == 0 then month = 1 end | |||
if | if day == 0 then day = 1 end | ||
if month <= 2 then | |||
year = year - 1 | |||
month = month + 12 | |||
end | end | ||
local time = ((((dateObject.sec or 0) / 60 + (dateObject.min or 0) + (dateObject.timezone or 0)) / 60) + (dateObject.hour or 0)) / 24 | |||
if | local b | ||
if dateObject.julian then b = 0 else | |||
local century = math.floor(year / 100) | |||
b = 2 - century + math.floor(century / 4) | |||
end | end | ||
return math.floor(365.25 * (year + 4716)) + math.floor(30.6001 * (month + 1)) + day + time + b - 1524.5 | |||
end | |||
function getQualifierSortValue(claim, qualifierId) | |||
local snak = getQualifierSnak(claim, qualifierId) | |||
if snak and snak.snaktype == "value" then | |||
if snak.datavalue.type == "time" then | |||
return julianDay(datavalueTimeToDateObject(snak.datavalue.value)) | |||
local | else | ||
return getSnakValue(snak) | |||
end | end | ||
end | end | ||
end | end | ||
function getValueOfClaim(claim, qualifierId, parameter) | |||
local error | |||
local snak | |||
snak, error = getQualifierSnak(claim, qualifierId) | |||
local | if snak then | ||
return getSnakValue(snak, parameter) | |||
else | |||
return nil, error | |||
end | end | ||
end | end | ||
function formatReference(ref) | |||
local | -- "imported from"-references are useless, skip them: | ||
if | if ref["P143"] or ref["P4656"] then return nil end | ||
-- load [[Modul:Zitation]] | |||
local ZitationSuccess, r = pcall(require, "Modul:Zitation") | |||
if type(r) == "table" then | |||
Zitation = r.Zitation() | |||
-- clear Zitation state from previous invocations | |||
Zitation.o = nil | |||
end | end | ||
local | -- assert (ZitationSuccess, i18n["errors"]["module-not-loaded"]) | ||
table. | -- assignments of Wikidata properties to Zitation parameters | ||
local wdZmap = { | |||
P1433 = {"bas", "Werk"}, | |||
P248 = {"bas", "Werk"}, | |||
P1476 = {"bas", "Titel"}, | |||
P1680 = {"bas", "TitelErg"}, | |||
P407 = {"bas", "Sprache"}, | |||
P364 = {"bas", "Sprache"}, | |||
P2439 = {"bas", "Sprache"}, | |||
P123 = {"bas", "Verlag"}, | |||
P577 = {"bas", "Datum"}, | |||
P98 = {"bas", "Hrsg"}, | |||
P2093 = {"bas", "Autor"}, | |||
P50 = {"bas", "Autor"}, | |||
P1683 = {"bas", "Zitat"}, | |||
P854 = {"www", "URL"}, | |||
P813 = {"www", "Abruf"}, | |||
P1065 = {"www", "ArchivURL"}, | |||
P2960 = {"www", "ArchivDatum"}, | |||
P2701 = {"www", "Format"}, | |||
P393 = {"print", "Auflage"}, | |||
P291 = {"print", "Ort"}, | |||
P304 = {"fragment", "Seiten"}, | |||
P792 = {"fragment", "Kapitel"}, | |||
P629 = {"orig", "Titel"} | |||
} | |||
for prop, value in pairs(ref) do | |||
if wdZmap[prop] then | |||
if type(value) == "table" then | |||
-- More snaks with same property, we concatenate using a comma | |||
value = table.concat(value, ", ") | |||
end | |||
-- value should be string now, so we can call Zitation | |||
if type(value) == "string" and string.len(value) > 0 then | |||
Zitation.fill(wdZmap[prop][1], wdZmap[prop][2], value, prop) | |||
end | |||
end | |||
end | end | ||
-- if no title on Wikidata, try to use the URL as title | |||
if (not ref["P1476"]) and ref["P854"] then | |||
local URLutil = Zitation.fetch("URLutil") | |||
Zitation.fill("bas", "Titel", URLutil.getHost(ref["P854"])) | |||
if | |||
end | end | ||
refFormatted, f = Zitation.format() | |||
return refFormatted, f | |||
return | |||
end | end | ||
function getReferences(frame, claim) | |||
local result = "" | |||
-- traverse through all references | |||
for ref in pairs(claim.references or {}) do | |||
local refTable = {} | |||
for snakkey, snakval in orderedpairs(claim.references[ref].snaks or {}, claim.references[ref]["snaks-order"]) do | |||
if #snakval == 1 then | |||
refTable[snakkey] = getSnakValue(snakval[1]) | |||
else | else | ||
-- | |||
multival = {} | |||
for snakidx = 1, #snakval do | |||
table.insert(multival, getSnakValue(snakval[snakidx])) | |||
end | |||
refTable[snakkey] = multival | |||
end | end | ||
end | end | ||
local formattedRef, f = formatReference(refTable) | |||
-- log errors that occur during formatting | |||
if f then | |||
mw.log(f) | |||
end | |||
if formattedRef and formattedRef ~= "" then | |||
local hash = mw.hash.hashValue('fnv164', formattedRef) | |||
result = result .. frame:extensionTag("ref", formattedRef, { name = '_' .. hash }) | |||
end | end | ||
end | end | ||
return | return result | ||
end | end | ||
function | local function hasqualifier(claim, qualifierproperty) | ||
if | local invert | ||
if string.sub(qualifierproperty, 1, 1) == "!" then invert = true else invert = false end | |||
if not claim.qualifiers and not invert then return false end | |||
if not claim.qualifiers and invert then return true end | |||
end | if qualifierproperty == '' then return true end | ||
return false | if not invert and not claim.qualifiers[qualifierproperty] then return false end | ||
if invert and claim.qualifiers[string.sub(qualifierproperty, 2)] then return false end | |||
return true | |||
end | end | ||
function | local function qualifierhasvalue(claim, property, value) | ||
-- TODO: not yet documented! | |||
if not claim.qualifiers then return false end | |||
if not claim.qualifiers[property] then return false end | |||
for key, snak in pairs(claim.qualifiers[property]) do | |||
if snak.snaktype == "value" then | |||
if snak.datavalue.type == "wikibase-entityid" then | |||
if snak.datavalue.value.id == value then | |||
return true | |||
end | |||
--TODO: elseif other types | |||
end | |||
end | end | ||
end | end | ||
return | return false | ||
end | end | ||
function | local function hassource(claim, sourceproperty) | ||
return | if not claim.references then return false end | ||
end | if sourceproperty == '' then return true end | ||
if string.sub(sourceproperty,1,1) ~= "!" then | |||
for _, source in pairs(claim.references) do | |||
if | if source.snaks[sourceproperty] then return true end | ||
end | |||
return false | |||
else | else | ||
for _, source in pairs(claim.references) do | |||
for key in pairs(source.snaks) do | |||
if key ~= string.sub(sourceproperty,2) then return true end | |||
end | |||
end | |||
return false | |||
end | end | ||
end | end | ||
function | function atdate(claim, mydate) | ||
if | local refdate | ||
if not mydate or mydate == "" then | |||
refdate = os.date("!*t") | |||
else | |||
if string.match(mydate, "^%d+$") then | |||
refdate = { year = tonumber(mydate) } | |||
else | |||
refdate = datavalueTimeToDateObject({ time = mw.language.getContentLanguage():formatDate("+Y-m-d\\TH:i:s\\Z", mydate) }) | |||
end | |||
end | end | ||
local refjd = julianDay(refdate) | |||
local mindate = getQualifierSortValue(claim, propertyId["starttime"]) | |||
local maxdate = getQualifierSortValue(claim, propertyId["endtime"]) | |||
if mindate and mindate > refjd then return false end | |||
if maxdate and maxdate < refjd then return false end | |||
return true | |||
end | |||
local function notdeprecated(claim, sourceproperty) | |||
return not (claim.rank == "deprecated") | |||
end | |||
--returns a table of claims excluding claims not passed the filters | |||
if | function filterClaims(frame, claims) | ||
return | local function filter(condition, filterfunction) | ||
if not frame.args[condition] then | |||
return | |||
end | end | ||
local newclaims = {} | |||
for i, claim in pairs(claims) do | |||
if filterfunction(claim, frame.args[condition]) then | |||
table.insert(newclaims, claim) | |||
end | end | ||
end | end | ||
claims = newclaims | |||
end | end | ||
filter('hasqualifier', hasqualifier) | |||
filter('hassource', hassource) | |||
filter('atdate', atdate) | |||
for | if not frame.args.includedeprecated then | ||
frame.args.notdeprecated = true | |||
filter('notdeprecated', notdeprecated) | |||
end | |||
for | |||
table.insert( | -- use additional unnamed parameters as qualifier conditions (in pairs) | ||
-- not yet documented! | |||
-- TODO: not sure if this is good approach. Maybe use named parameter that has pairs split by semicolon | |||
for key, val in pairs(frame.args) do | |||
if type(key) == "number" and key > 2 and key % 2 == 1 then | |||
-- key = 3, 5, 7 and so on | |||
local newclaims = {} | |||
for i, claim in pairs(claims) do | |||
if qualifierhasvalue(claim, frame.args[key - 1], frame.args[key]) then | |||
table.insert(newclaims, claim) | |||
end | |||
end | end | ||
claims = newclaims | |||
end | end | ||
end | end | ||
return claims | return claims | ||
end | end | ||
function p. | local p = {} | ||
if not args | function p.isSubclass(frame) | ||
if not frame.args["parent"] then return "" end | |||
if type( | local maxDepth | ||
maxDepth = frame.args["maxDepth"] or 5 | |||
if not type(maxDepth) == "number" then maxDepth = 5 end | |||
local result | |||
result = isParent(frame.args["id"], frame.args["parent"], frame.args["exitItem"], maxDepth) | |||
if frame.args["returnInt"] then | |||
if result == true then return 1 else return "" end | |||
else | else | ||
if result then return result else return false end | |||
end | end | ||
end | |||
if | function p.descriptionIn(frame) | ||
local langcode = frame.args[1] | |||
local id = frame.args[2] | |||
-- return description of a Wikidata entity in the given language or the default language of this Wikipedia site | |||
local entity = mw.wikibase.getEntity(id) | |||
if entity and entity.descriptions then | |||
local desc = entity.descriptions[langcode or wiki.langcode] | |||
if desc then return desc.value end | |||
else | |||
return ""; | |||
end | end | ||
end | end | ||
function p. | function p.labelIn(frame) | ||
local langcode = frame.args[1] | |||
local id = frame.args[2] | |||
-- return label of a Wikidata entity in the given language or the default language of this Wikipedia site | |||
local entity = mw.wikibase.getEntity(id) | |||
if entity and entity.labels then | |||
local label = entity.labels[langcode or wiki.langcode] | |||
if label then return label.value end | |||
else | |||
return ""; | |||
end | end | ||
end | end | ||
function p. | function p.claim(frame) | ||
local | local property = frame.args[1] or "" | ||
local id = frame.args["id"] | |||
end | local qualifierId = frame.args["qualifier"] | ||
local parameter = frame.args["parameter"] | |||
local language = frame.args["language"] | |||
local list = frame.args["list"] | |||
local includeempty = frame.args["includeempty"] | |||
local listMaxItems = tonumber(frame.args["listMaxItems"]) or 0 | |||
local references = frame.args["references"] | |||
local sort = frame.args["sort"] | |||
local sortEmptiesFirst = frame.args["sortEmptiesFirst"] | |||
local sortInItem = frame.args["sortInItem"] | |||
local inverse = frame.args["inverse"] | |||
local showerrors = frame.args["showerrors"] | |||
local default = frame.args["default"] | |||
if default then showerrors = nil end | |||
-- get wikidata entity | |||
if | if id then | ||
if not mw.wikibase.isValidEntityId(id) then | |||
if showerrors then | |||
return printError("entity-not-valid") | |||
else | |||
local temp = mw.title.new(i18n["maintenance-pages"]["entity-not-valid"], "Modul").exists | |||
return default | |||
local | |||
end | end | ||
if | elseif not mw.wikibase.entityExists(id) then | ||
if showerrors then | |||
return printError("entity-not-found") | |||
else | |||
local temp = mw.title.new(i18n["maintenance-pages"]["entity-not-found"], "Modul").exists | |||
return default | |||
end | end | ||
end | end | ||
end | end | ||
if | |||
return | local entity = mw.wikibase.getEntity(id) | ||
if not entity then | |||
if showerrors then return printError("entity-not-found") else return default end | |||
end | end | ||
-- check if property exists | |||
if | local realProp = mw.wikibase.resolvePropertyId(property) | ||
local | if not realProp then | ||
if not | local temp = mw.title.new(i18n["maintenance-pages"]["property-not-existing"], "Modul").exists | ||
end | end | ||
-- fetch the first claim of satisfying the given property | |||
local claims | |||
if entity.claims then claims = entity.claims[realProp] end | |||
if not claims or not claims[1] then | |||
if showerrors then return printError("property-not-found") else return default end | |||
end | end | ||
--filter claims | |||
if not | claims = filterClaims(frame, claims) | ||
if not claims[1] then return default end | |||
-- get initial sort indices | |||
local sortindices = {} | |||
for idx in pairs(claims) do | |||
sortindices[#sortindices + 1] = idx | |||
end | end | ||
local | local comparator | ||
if | if sort then | ||
comparator = function(a, b) --comparator function for sorting statements based on qualifier value | |||
-- load qualifier values | |||
local QualifierSortValueA = getQualifierSortValue(claims[a], sort) | |||
local QualifierSortValueB = getQualifierSortValue(claims[b], sort) | |||
-- if either of the two statements does not have this qualifer: | |||
---- if sortEmptiesFirst=true: sort it to the beginning | |||
---- else: always sort it to the end | |||
if not QualifierSortValueB then | |||
if not QualifierSortValueA then | |||
-- if neither of the two statements has this qualifier, arbitrarily but consistently return a < b | |||
return a < b | |||
elseif sortEmptiesFirst then | |||
return false | |||
else | |||
return true | |||
end | |||
elseif not QualifierSortValueA then | |||
if sortEmptiesFirst then return true else return false end | |||
end | |||
if type(QualifierSortValueA) ~= type(QualifierSortValueB) and not (tonumber(QualifierSortValueA) and tonumber(QualifierSortValueB)) then | |||
if tonumber(QualifierSortValueA) then return true | |||
elseif tonumber(QualifierSortValueB) then return false | |||
elseif tostring(QualifierSortValueA) and tostring(QualifierSortValueB) then | |||
if inverse then return tostring(QualifierSortValueA) > tostring(QualifierSortValueB) else return tostring(QualifierSortValueA) < tostring(QualifierSortValueB) end | |||
else return false end -- different types, neither numbers nor strings, no chance to compare => random result to avoid script error | |||
elseif tonumber(QualifierSortValueA) and tonumber(QualifierSortValueB) then | |||
QualifierSortValueA = tonumber(QualifierSortValueA) | |||
QualifierSortValueB = tonumber(QualifierSortValueB) | |||
end | |||
if inverse then | |||
return QualifierSortValueA > QualifierSortValueB | |||
else | |||
return QualifierSortValueA < QualifierSortValueB | |||
end | |||
end | end | ||
local | elseif sortInItem then | ||
-- fill table sortkeys | |||
local sortkeys = {} | |||
local snakSingle | |||
local sortkeyValueId | |||
local claimContainingValue | |||
for idx, claim in pairs(claims) do | |||
snakSingle = getQualifierSnak(claim) | |||
sortkeyValueId = "Q" .. getSnakValue(snakSingle, "numeric-id") | |||
claimContainingValue = mw.wikibase.getEntity(sortkeyValueId).claims[mw.wikibase.resolvePropertyId(sortInItem)] | |||
if claimContainingValue then | |||
sortkeys[#sortkeys + 1] = getValueOfClaim(claimContainingValue[1]) | |||
else | else | ||
sortkeys[#sortkeys + 1] = "" | |||
end | end | ||
end | end | ||
comparator = function(a, b) | |||
if inverse then | |||
return sortkeys[a] > sortkeys [b] | |||
else | |||
return sortkeys[a] < sortkeys [b] | |||
if | |||
end | end | ||
end | end | ||
else | else | ||
-- sort by claim rank | |||
comparator = function(a, b) | |||
local rankmap = { deprecated = 2, normal = 1, preferred = 0 } | |||
local ranka = rankmap[claims[a].rank or "normal"] .. string.format("%08d", a) | |||
local rankb = rankmap[claims[b].rank or "normal"] .. string.format("%08d", b) | |||
return ranka < rankb | |||
end | |||
end | end | ||
table.sort(sortindices, comparator) | |||
local | local result | ||
local error | |||
if list then | |||
list = string.gsub(list, "\\n", "\n") -- if a newline is provided (whose backslash will be escaped) unescape it | |||
local value | |||
-- iterate over all elements and return their value (if existing) | |||
result = {} | |||
for idx in pairs(claims) do | |||
local claim = claims[sortindices[idx]] | |||
value, error = getValueOfClaim(claim, qualifierId, parameter) | |||
if not value and value ~= 0 and showerrors then value = error end | |||
if not value and value ~= 0 and includeempty then value = "" end | |||
if value and references then value = value .. getReferences(frame, claim) end | |||
result[#result + 1] = value | |||
end | |||
if listMaxItems and listMaxItems > 0 then | |||
result = table.concat(result, list, 1, math.min(table.getn(result), listMaxItems)) | |||
else | else | ||
result = table.concat(result, list) | |||
end | end | ||
else | |||
-- return first element | |||
local claim = claims[sortindices[1]] | |||
if language == "Q" then | |||
result, error = "Q" .. getSnakValue(getQualifierSnak(claim), "numeric-id") | |||
elseif language and claim.mainsnak.datatype == "monolingualtext" then | |||
-- iterate over claims to find adequate language | |||
for idx, claim in pairs(claims) do | |||
if claim.mainsnak.datavalue.value.language == language then | |||
result, error = getValueOfClaim(claim, qualifierId, parameter) | |||
break | |||
end | end | ||
end | end | ||
else | |||
result, error = getValueOfClaim(claim, qualifierId, parameter) | |||
end | |||
if references == "only" then | |||
result = getReferences(frame, claim) | |||
elseif result and references then | |||
result = result .. getReferences(frame, claim) | |||
end | end | ||
end | end | ||
if result then return result else | |||
if | if showerrors then return error else return default end | ||
return | |||
end | end | ||
end | end | ||
function p.getValue(frame) | |||
local | local param = frame.args[2] | ||
if | if param == "FETCH_WIKIDATA" or param == i18n["FETCH_WIKIDATA"] then return p.claim(frame) else return param end | ||
end | end | ||
function p. | function p.pageId(frame) | ||
local id = frame.args[1] | |||
local entity = mw.wikibase.getEntity(id) | |||
if not entity then return "" else return entity.id end | |||
local | |||
return | |||
end | end | ||
function p. | function p.labelOf(frame) | ||
local | local id = frame.args[1] | ||
-- returns the label of the given entity/property id | |||
local | -- if no id is given, the one from the entity associated with the calling Wikipedia article is used | ||
if not id then | |||
local entity = mw.wikibase.getEntity() | |||
if not entity then return printError("entity-not-found") end | |||
id = entity.id | |||
end | end | ||
return mw.wikibase.label(id) | |||
end | |||
function p.sitelinkOf(frame) | |||
local id = frame.args[1] | |||
-- returns the Wikipedia article name of the given entity | |||
entity = | -- if no id is given, the one from the entity associated with the calling Wikipedia article is used | ||
if not id then | |||
local entity = mw.wikibase.getEntity() | |||
if not entity then return printError("entity-not-found") end | |||
id = entity.id | |||
end | end | ||
return mw.wikibase.sitelink(id) | |||
end | |||
function p.badges(frame) | |||
local site = frame.args[1] | |||
local id = frame.args[2] | |||
if not site then return printError("site-not-found") end | |||
local entity = mw.wikibase.getEntity(id) | |||
if not entity then return printError("entity-not-found") end | |||
local badges = entity.sitelinks[site].badges | |||
if badges then | |||
local result | |||
for idx = 1, #badges do | |||
if result then result = result .. "/" .. badges[idx] else result = badges[idx] end | |||
end | end | ||
return | return result | ||
end | end | ||
end | end | ||
function p. | function p.sitelinkCount(frame) | ||
local filter = "^.*" .. (frame.args[1] or "") .. "$" | |||
local id = frame.args[2] | |||
local | |||
local entity = mw.wikibase.getEntity(id) | |||
local | local count = 0 | ||
if | if entity and entity.sitelinks then | ||
for project, _ in pairs(entity.sitelinks) do | |||
if string.find(project, filter) then count = count + 1 end | |||
end | |||
end | end | ||
return count | |||
end | end | ||
function p. | -- call this in cases of script errors within a function instead of {{#invoke:Wikidata|<method>|...}} call {{#invoke:Wikidata|debug|<method>|...}} | ||
local | function p.debug(frame) | ||
local func = frame.args[1] | |||
return | if func then | ||
-- create new parameter set, where the first parameter with the function name is removed | |||
local newargs = {} | |||
for key, val in pairs(frame.args) do | |||
if type(key) == "number" then | |||
if key > 1 then newargs[key - 1] = val end | |||
else | |||
newargs[key] = val | |||
end | |||
end | |||
frame.args = newargs | |||
local status, result = pcall(p[func], frame) | |||
-- if status then return tostring(result) or "" else return '<span class="error">' .. result .. '</span>' end -- revert | |||
if status then return result else return '<span class="error">' .. result .. '</span>' end | |||
else | |||
return printError("invalid-parameters") | |||
end | end | ||
end | end | ||
function p. | function p.printEntity(frame) | ||
local | local id = frame.args[1] | ||
local entity = mw.wikibase.getEntity(id) | |||
if entity then return "<pre>" .. mw.text.jsonEncode(entity, mw.text.JSON_PRETTY) .. "</pre>" end | |||
local | |||
if | |||
end | end | ||
function p. | -- formfill Template:Coordinate (NS, EW, name from WikidataEntity) and expand it | ||
-- füllt Vorlage:Coordinate (NS, EW, name mit Wikidata-Werten) + expandiert sie | |||
-- 1st frame.arg .. Q prefixed entity id (mandatory) | |||
-- named frame.arg "type", "region", "text" .. see doc of 'Coordinate' template | |||
function p.ffCoordinate(frame) | |||
local f = frame | |||
local id = f.args[1] or f.args.Q | |||
local name = f.args.name or p.labelIn{ args = { nil, id, id = id }} | |||
local coord = mw.text.split(p.claim{ args = { "P625", id, id = id }}, '/') | |||
coord[1] = tonumber(coord[1]) | |||
coord[2] = tonumber(coord[2]) | |||
local t, r = f.args.type, f.args.region | |||
if not t | |||
then t = p.claim{ args = { "P31", id, id = id, language = "Q" }} | |||
t = t and t:gsub("Q.*", { | |||
Q8502 = "mountain", | |||
Q54050 = "landmark" | |||
}) | |||
if not t or t and t:find("Q", 1, true) | |||
then t="" -- no default, let Coordinate warn about unset type= param | |||
end | |||
end | |||
if not r | |||
then r = p.claim{ args = { "P17", id, id = id, language = "Q" }} | |||
r = r and p.claim{ args = { "P297", r, id = r }} | |||
if not r | |||
then r="" -- no default, let Coordinate warn about unset region= param | |||
end | |||
end | end | ||
return ('<span data-sort-value="%010.6f"></span>'):format((f.args.sortkey | |||
or "EW"):find("EW", 1, true) and coord[2]+360.0 or coord[1]+180.0 | |||
) .. f:expandTemplate{ title = 'Coordinate', args = { | |||
NS = coord[1], EW = coord[2], type = t, region = r, | |||
text = f.args.text or (f.args.maplink and "ICON0" or "/"), | |||
name = name, simple = f.args.simple | |||
}} .. (not f.args.maplink and "" or (" " .. | |||
--f:callParserFunction{ name="#statements", args={ "P625", from = id } } | |||
f:callParserFunction{ name="#tag:maplink", args={ "", | |||
class = "no-icon", text = f.args.mlname and name, | |||
zoom = 12, latitude = coord[1], longitude = coord[2] | |||
}} | |||
)) | |||
end | end | ||
function p. | function p.ffCoordinateAndLatLonMaplink(frame) | ||
return p. | frame.args.maplink = 1 | ||
--frame.args.mlname = nil | |||
return p.ffCoordinate(frame) | |||
end | end | ||
function p. | function p.ffCoordinateAndMaplink(frame) | ||
frame.args.maplink = 1 | |||
frame.args.mlname = 1 | |||
return p.ffCoordinate(frame) | |||
return p. | |||
end | end | ||
return p | return p |
Version vom 14. April 2021, 09:50 Uhr
Die Dokumentation für dieses Modul kann unter Modul:LinkedWiki/Doku erstellt werden
-- module local variables
local wiki =
{
langcode = mw.language.getContentLanguage().code
}
-- internationalisation
local i18n =
{
["errors"] =
{
["property-not-found"] = "Eigenschaft nicht gefunden.",
["entity-not-found"] = "Wikidata-Eintrag nicht gefunden.",
["entity-not-valid"] = "Die an die Wikidata-Schnittstelle übergebene Item-ID ist nicht gültig.",
["unknown-claim-type"] = "Unbekannter Aussagentyp.",
["unknown-entity-type"] = "Unbekannter Entity-Typ.",
["qualifier-not-found"] = "Qualifikator nicht gefunden.",
["site-not-found"] = "Wikimedia-Projekt nicht gefunden.",
["invalid-parameters"] = "Ungültige Parameter.",
["module-not-loaded"] = "Loading of additional module failed."
},
["maintenance-pages"] =
{
["entity-not-found"] = "Wikidata/Wartung/Fehlendes Datenobjekt",
["entity-not-valid"] = "Wikidata/Wartung/Ungültige Datenobjekt-Identifikationsnummer",
["property-not-existing"] = "Wikidata/Wartung/Eigenschaft existiert nicht"
},
["datetime"] =
{
-- $1 is a placeholder for the actual number
[0] = "$1 Mrd. Jahren", -- precision: billion years
[1] = "$100 Mio. Jahren", -- precision: hundred million years
[2] = "$10 Mio. Jahren", -- precision: ten million years
[3] = "$1 Mio. Jahren", -- precision: million years
[4] = "$100.000 Jahren", -- precision: hundred thousand years
[5] = "$10.000 Jahren", -- precision: ten thousand years
[6] = "$1. Jahrtausend", -- precision: millenium
[7] = "$1. Jahrhundert", -- precision: century
[8] = "$1er", -- precision: decade
-- the following use the format of #time parser function
[9] = "Y", -- precision: year,
[10] = "F Y", -- precision: month
[11] = "j. F Y", -- precision: day
[12] = 'j. F Y, G "Uhr"', -- precision: hour
[13] = "j. F Y G:i", -- precision: minute
[14] = "j. F Y G:i:s", -- precision: second
["beforenow"] = "vor $1", -- how to format negative numbers for precisions 0 to 5
["afternow"] = "in $1", -- how to format positive numbers for precisions 0 to 5
["bc"] = '$1 "v.Chr."', -- how print negative years
["ad"] = "$1" -- how print positive years
},
["monolingualtext"] = '<span lang="%language">%text</span>',
["FETCH_WIKIDATA"] = "ABFRAGE_WIKIDATA"
}
--important properties
local propertyId =
{
["starttime"] = "P580",
["endtime"] = "P582"
}
local formatchar =
{
[10] = {"n","m","M","F","xg"}, --precision: month
[11] = {"W","j","d","z","D","l","N","w"}, --precision: day
[12] = {"a","A","g","h","G","H"}, --precision: hour
[13] = {"i"}, --precision: minute
[14] = {"s","U"} --precision: second
}
local function printError(code)
return '<span class="error">' .. (i18n.errors[code] or code) .. '</span>'
end
-- the "qualifiers" and "snaks" field have a respective "qualifiers-order" and "snaks-order" field
-- use these as the second parameter and this function instead of the built-in "pairs" function
-- to iterate over all qualifiers and snaks in the intended order.
local function orderedpairs(array, order)
if not order then return pairs(array) end
-- return iterator function
local i = 0
return function()
i = i + 1
if order[i] then
return order[i], array[order[i]]
end
end
end
-- Function to check whether a certain item is a parent of a given item.
-- If pExitItem is reached without finding the searched parent item, the search stops.
-- A parent is connected via P31 or P279.
-- Attention: very intensive function, use carefully!
local function isParent(pItem, pParent, pExitItem, pMaxDepth, pDepth)
if not pDepth then pDepth = 0 end
if type(pItem) == "number" then pItem = "Q" .. pItem end
local entity = mw.wikibase.getEntity(pItem)
if not entity then return false end
local claims31
local claims279
if entity.claims then
claims31 = entity.claims[mw.wikibase.resolvePropertyId('P31')]
claims279 = entity.claims[mw.wikibase.resolvePropertyId('P279')]
else
return false
end
if not claims31 and not claims279 then return false end
local parentIds = {}
if claims31 and #claims31 > 0 then
for i, v in ipairs(claims31) do parentIds[#parentIds+1] = getSnakValue(v.mainsnak, "numeric-id") end
end
if claims279 and #claims279 > 0 then
for i, v in ipairs(claims279) do parentIds[#parentIds+1] = getSnakValue(v.mainsnak, "numeric-id") end
end
-- check if searched parent or exit item is reached or do recursive call
if not parentIds[1] or #parentIds == 0 then return false end
local itemString = ""
local result = nil
for i, v in ipairs(parentIds) do
if not v then return false end
itemString = "Q" .. v
if itemString == pParent then
-- successful!
return true
elseif itemString == pExitItem or itemString == "Q35120" then
-- exit if either "exit item" or node item (Q35120) is reached
return false
else
if pDepth+1 < pMaxDepth then
result = isParent(itemString, pParent, pExitItem, pMaxDepth, pDepth+1)
else return false end
if result == true then return result end
end
end
do return false end
end
local function printDatavalueCoordinate(data, parameter)
-- data fields: latitude [double], longitude [double], altitude [double], precision [double], globe [wikidata URI, usually http://www.wikidata.org/entity/Q2 [earth]]
if parameter then
if parameter == "globe" then data.globe = mw.ustring.match(data.globe, "Q%d+") end -- extract entity id from the globe URI
return data[parameter]
else
return data.latitude .. "/" .. data.longitude -- combine latitude and longitude, which can be decomposed using the #titleparts wiki function
end
end
local function printDatavalueQuantity(data, parameter)
-- data fields: amount [number], unit [string], upperBound [number], lowerBound [number]
if not parameter or parameter == "amount" then
return tonumber(data.amount)
elseif parameter == "unit" then
return mw.ustring.match(data.unit, "Q%d+")
else
return data[parameter]
end
end
local function normalizeDate(date)
date = mw.text.trim(date, "+")
-- extract year
local yearstr = mw.ustring.match(date, "^\-?%d+")
local year = tonumber(yearstr)
-- remove leading zeros of year
return year .. mw.ustring.sub(date, #yearstr + 1), year
end
-- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millenia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second
function formatDate(date, precision, timezone, formatstr)
precision = precision or 11
date, year = normalizeDate(date)
date = string.gsub(date, "-00%f[%D]", "-01")
if year == 0 and precision <= 9 then return "" end
-- precision is 10000 years or more
if precision <= 5 then
local factor = 10 ^ ((5 - precision) + 4)
local y2 = math.ceil(math.abs(year) / factor)
local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2))
if year < 0 then
relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative)
else
relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative)
end
return relative
end
-- precision is decades, centuries and millenia
local era
if precision == 6 then era = mw.ustring.gsub(i18n.datetime[6], "$1", tostring(math.floor((math.abs(year) - 1) / 1000) + 1)) end
if precision == 7 then era = mw.ustring.gsub(i18n.datetime[7], "$1", tostring(math.floor((math.abs(year) - 1) / 100) + 1)) end
if precision == 8 then era = mw.ustring.gsub(i18n.datetime[8], "$1", tostring(math.floor(math.abs(year) / 10) * 10)) end
if era then
if year < 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.bc, '"', ""), "$1", era)
elseif year > 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.ad, '"', ""), "$1", era) end
return era
end
-- precision is years or less
if precision >= 9 then
--[[ the following code replaces the UTC suffix with the given negated timezone to convert the global time to the given local time
timezone = tonumber(timezone)
if timezone and timezone ~= 0 then
timezone = -timezone
timezone = string.format("%.2d%.2d", timezone / 60, timezone % 60)
if timezone[1] ~= '-' then timezone = "+" .. timezone end
date = mw.text.trim(date, "Z") .. " " .. timezone
end
]]--
if formatstr then
for i=(precision+1), 14 do
for _, ch in pairs(formatchar[i]) do
if formatstr:find(ch) then
formatstr = i18n.datetime[precision]
end
end
end
else
formatstr = i18n.datetime[precision]
end
if year == 0 then formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], "")
elseif year < 0 then
-- Mediawiki formatDate doesn't support negative years
date = mw.ustring.sub(date, 2)
formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.bc, "$1", i18n.datetime[9]))
elseif year > 0 and i18n.datetime.ad ~= "$1" then
formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.ad, "$1", i18n.datetime[9]))
end
return mw.language.new(wiki.langcode):formatDate(formatstr, date)
end
end
local function printDatavalueTime(data, parameter)
-- data fields: time [ISO 8601 time], timezone [int in minutes], before [int], after [int], precision [int], calendarmodel [wikidata URI]
-- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millenia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second
-- calendarmodel: e.g. http://www.wikidata.org/entity/Q1985727 for the proleptic Gregorian calendar or http://www.wikidata.org/wiki/Q11184 for the Julian calendar]
if parameter then
para, formatstr = parameter:match("([^:]+):([^:]+)")
if parameter == "calendarmodel" then
data.calendarmodel = string.match(data.calendarmodel, "Q%d+") -- extract entity id from the calendar model URI
elseif para and para == "time" then
return formatDate(data.time, data.precision, data.timezone,formatstr)
elseif parameter == "time" then
data.time = normalizeDate(data.time)
end
return data[parameter]
else
return formatDate(data.time, data.precision, data.timezone)
end
end
local function printDatavalueEntity(data, parameter)
-- data fields: entity-type [string], numeric-id [int, Wikidata id]
local id
if data["entity-type"] == "item" then id = "Q" .. data["numeric-id"]
elseif data["entity-type"] == "property" then id = "P" .. data["numeric-id"]
else return printError("unknown-entity-type")
end
if parameter then
if parameter == "link" then
local linkTarget = mw.wikibase.sitelink(id)
local linkName = mw.wikibase.label(id)
if linkTarget then
local link = linkTarget
-- if there is a local Wikipedia article linking to it, use the label or the article title
if linkName and (linkName ~= linkTarget) then link = link .. "|" .. linkName end
return "[[" .. link .. "]]"
else
-- if there is no local Wikipedia article output the label or link to the Wikidata object to input a proper label
if linkName then return linkName else return "[[:d:" .. id .. "|" .. id .. "]]" end
end
else
return data[parameter]
end
else
return mw.wikibase.label(id) or id
end
end
local function printDatavalueMonolingualText(data, parameter)
-- data fields: language [string], text [string]
if parameter then
return data[parameter]
else
local result = mw.ustring.gsub(mw.ustring.gsub(i18n.monolingualtext, "%%language", data["language"]), "%%text", data["text"])
return result
end
end
function getSnakValue(snak, parameter)
-- snaks have three types: "novalue" for null/nil, "somevalue" for not null/not nil, or "value" for actual data
if snak.snaktype == "value" then
-- call the respective snak parser
if snak.datavalue.type == "string" then return snak.datavalue.value
elseif snak.datavalue.type == "globecoordinate" then return printDatavalueCoordinate(snak.datavalue.value, parameter)
elseif snak.datavalue.type == "quantity" then return printDatavalueQuantity(snak.datavalue.value, parameter)
elseif snak.datavalue.type == "time" then return printDatavalueTime(snak.datavalue.value, parameter)
elseif snak.datavalue.type == "wikibase-entityid" then return printDatavalueEntity(snak.datavalue.value, parameter)
elseif snak.datavalue.type == "monolingualtext" then return printDatavalueMonolingualText(snak.datavalue.value, parameter)
end
end
return mw.wikibase.renderSnak(snak)
end
function getQualifierSnak(claim, qualifierId)
-- a "snak" is Wikidata terminology for a typed key/value pair
-- a claim consists of a main snak holding the main information of this claim,
-- as well as a list of attribute snaks and a list of references snaks
if qualifierId then
-- search the attribute snak with the given qualifier as key
if claim and claim.qualifiers then
local qualifier = claim.qualifiers[qualifierId]
if qualifier then return qualifier[1] end
end
return nil, printError("qualifier-not-found")
else
-- otherwise return the main snak
return claim.mainsnak
end
end
local function datavalueTimeToDateObject(data)
local sign, year, month, day, hour, minute, second = string.match(data.time, "(.)(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z")
local result =
{
year = tonumber(year),
month = tonumber(month),
day = tonumber(day),
hour = tonumber(hour),
min = tonumber(minute),
sec = tonumber(second),
timezone = data.timezone,
julian = data.calendarmodel and string.match(data.calendarmodel, "Q11184$")
}
if sign == "-" then result.year = -result.year end
return result
end
function julianDay(dateObject)
local year = dateObject.year
local month = dateObject.month or 0
local day = dateObject.day or 0
if month == 0 then month = 1 end
if day == 0 then day = 1 end
if month <= 2 then
year = year - 1
month = month + 12
end
local time = ((((dateObject.sec or 0) / 60 + (dateObject.min or 0) + (dateObject.timezone or 0)) / 60) + (dateObject.hour or 0)) / 24
local b
if dateObject.julian then b = 0 else
local century = math.floor(year / 100)
b = 2 - century + math.floor(century / 4)
end
return math.floor(365.25 * (year + 4716)) + math.floor(30.6001 * (month + 1)) + day + time + b - 1524.5
end
function getQualifierSortValue(claim, qualifierId)
local snak = getQualifierSnak(claim, qualifierId)
if snak and snak.snaktype == "value" then
if snak.datavalue.type == "time" then
return julianDay(datavalueTimeToDateObject(snak.datavalue.value))
else
return getSnakValue(snak)
end
end
end
function getValueOfClaim(claim, qualifierId, parameter)
local error
local snak
snak, error = getQualifierSnak(claim, qualifierId)
if snak then
return getSnakValue(snak, parameter)
else
return nil, error
end
end
function formatReference(ref)
-- "imported from"-references are useless, skip them:
if ref["P143"] or ref["P4656"] then return nil end
-- load [[Modul:Zitation]]
local ZitationSuccess, r = pcall(require, "Modul:Zitation")
if type(r) == "table" then
Zitation = r.Zitation()
-- clear Zitation state from previous invocations
Zitation.o = nil
end
-- assert (ZitationSuccess, i18n["errors"]["module-not-loaded"])
-- assignments of Wikidata properties to Zitation parameters
local wdZmap = {
P1433 = {"bas", "Werk"},
P248 = {"bas", "Werk"},
P1476 = {"bas", "Titel"},
P1680 = {"bas", "TitelErg"},
P407 = {"bas", "Sprache"},
P364 = {"bas", "Sprache"},
P2439 = {"bas", "Sprache"},
P123 = {"bas", "Verlag"},
P577 = {"bas", "Datum"},
P98 = {"bas", "Hrsg"},
P2093 = {"bas", "Autor"},
P50 = {"bas", "Autor"},
P1683 = {"bas", "Zitat"},
P854 = {"www", "URL"},
P813 = {"www", "Abruf"},
P1065 = {"www", "ArchivURL"},
P2960 = {"www", "ArchivDatum"},
P2701 = {"www", "Format"},
P393 = {"print", "Auflage"},
P291 = {"print", "Ort"},
P304 = {"fragment", "Seiten"},
P792 = {"fragment", "Kapitel"},
P629 = {"orig", "Titel"}
}
for prop, value in pairs(ref) do
if wdZmap[prop] then
if type(value) == "table" then
-- More snaks with same property, we concatenate using a comma
value = table.concat(value, ", ")
end
-- value should be string now, so we can call Zitation
if type(value) == "string" and string.len(value) > 0 then
Zitation.fill(wdZmap[prop][1], wdZmap[prop][2], value, prop)
end
end
end
-- if no title on Wikidata, try to use the URL as title
if (not ref["P1476"]) and ref["P854"] then
local URLutil = Zitation.fetch("URLutil")
Zitation.fill("bas", "Titel", URLutil.getHost(ref["P854"]))
end
refFormatted, f = Zitation.format()
return refFormatted, f
end
function getReferences(frame, claim)
local result = ""
-- traverse through all references
for ref in pairs(claim.references or {}) do
local refTable = {}
for snakkey, snakval in orderedpairs(claim.references[ref].snaks or {}, claim.references[ref]["snaks-order"]) do
if #snakval == 1 then
refTable[snakkey] = getSnakValue(snakval[1])
else
--
multival = {}
for snakidx = 1, #snakval do
table.insert(multival, getSnakValue(snakval[snakidx]))
end
refTable[snakkey] = multival
end
end
local formattedRef, f = formatReference(refTable)
-- log errors that occur during formatting
if f then
mw.log(f)
end
if formattedRef and formattedRef ~= "" then
local hash = mw.hash.hashValue('fnv164', formattedRef)
result = result .. frame:extensionTag("ref", formattedRef, { name = '_' .. hash })
end
end
return result
end
local function hasqualifier(claim, qualifierproperty)
local invert
if string.sub(qualifierproperty, 1, 1) == "!" then invert = true else invert = false end
if not claim.qualifiers and not invert then return false end
if not claim.qualifiers and invert then return true end
if qualifierproperty == '' then return true end
if not invert and not claim.qualifiers[qualifierproperty] then return false end
if invert and claim.qualifiers[string.sub(qualifierproperty, 2)] then return false end
return true
end
local function qualifierhasvalue(claim, property, value)
-- TODO: not yet documented!
if not claim.qualifiers then return false end
if not claim.qualifiers[property] then return false end
for key, snak in pairs(claim.qualifiers[property]) do
if snak.snaktype == "value" then
if snak.datavalue.type == "wikibase-entityid" then
if snak.datavalue.value.id == value then
return true
end
--TODO: elseif other types
end
end
end
return false
end
local function hassource(claim, sourceproperty)
if not claim.references then return false end
if sourceproperty == '' then return true end
if string.sub(sourceproperty,1,1) ~= "!" then
for _, source in pairs(claim.references) do
if source.snaks[sourceproperty] then return true end
end
return false
else
for _, source in pairs(claim.references) do
for key in pairs(source.snaks) do
if key ~= string.sub(sourceproperty,2) then return true end
end
end
return false
end
end
function atdate(claim, mydate)
local refdate
if not mydate or mydate == "" then
refdate = os.date("!*t")
else
if string.match(mydate, "^%d+$") then
refdate = { year = tonumber(mydate) }
else
refdate = datavalueTimeToDateObject({ time = mw.language.getContentLanguage():formatDate("+Y-m-d\\TH:i:s\\Z", mydate) })
end
end
local refjd = julianDay(refdate)
local mindate = getQualifierSortValue(claim, propertyId["starttime"])
local maxdate = getQualifierSortValue(claim, propertyId["endtime"])
if mindate and mindate > refjd then return false end
if maxdate and maxdate < refjd then return false end
return true
end
local function notdeprecated(claim, sourceproperty)
return not (claim.rank == "deprecated")
end
--returns a table of claims excluding claims not passed the filters
function filterClaims(frame, claims)
local function filter(condition, filterfunction)
if not frame.args[condition] then
return
end
local newclaims = {}
for i, claim in pairs(claims) do
if filterfunction(claim, frame.args[condition]) then
table.insert(newclaims, claim)
end
end
claims = newclaims
end
filter('hasqualifier', hasqualifier)
filter('hassource', hassource)
filter('atdate', atdate)
if not frame.args.includedeprecated then
frame.args.notdeprecated = true
filter('notdeprecated', notdeprecated)
end
-- use additional unnamed parameters as qualifier conditions (in pairs)
-- not yet documented!
-- TODO: not sure if this is good approach. Maybe use named parameter that has pairs split by semicolon
for key, val in pairs(frame.args) do
if type(key) == "number" and key > 2 and key % 2 == 1 then
-- key = 3, 5, 7 and so on
local newclaims = {}
for i, claim in pairs(claims) do
if qualifierhasvalue(claim, frame.args[key - 1], frame.args[key]) then
table.insert(newclaims, claim)
end
end
claims = newclaims
end
end
return claims
end
local p = {}
function p.isSubclass(frame)
if not frame.args["parent"] then return "" end
local maxDepth
maxDepth = frame.args["maxDepth"] or 5
if not type(maxDepth) == "number" then maxDepth = 5 end
local result
result = isParent(frame.args["id"], frame.args["parent"], frame.args["exitItem"], maxDepth)
if frame.args["returnInt"] then
if result == true then return 1 else return "" end
else
if result then return result else return false end
end
end
function p.descriptionIn(frame)
local langcode = frame.args[1]
local id = frame.args[2]
-- return description of a Wikidata entity in the given language or the default language of this Wikipedia site
local entity = mw.wikibase.getEntity(id)
if entity and entity.descriptions then
local desc = entity.descriptions[langcode or wiki.langcode]
if desc then return desc.value end
else
return "";
end
end
function p.labelIn(frame)
local langcode = frame.args[1]
local id = frame.args[2]
-- return label of a Wikidata entity in the given language or the default language of this Wikipedia site
local entity = mw.wikibase.getEntity(id)
if entity and entity.labels then
local label = entity.labels[langcode or wiki.langcode]
if label then return label.value end
else
return "";
end
end
function p.claim(frame)
local property = frame.args[1] or ""
local id = frame.args["id"]
local qualifierId = frame.args["qualifier"]
local parameter = frame.args["parameter"]
local language = frame.args["language"]
local list = frame.args["list"]
local includeempty = frame.args["includeempty"]
local listMaxItems = tonumber(frame.args["listMaxItems"]) or 0
local references = frame.args["references"]
local sort = frame.args["sort"]
local sortEmptiesFirst = frame.args["sortEmptiesFirst"]
local sortInItem = frame.args["sortInItem"]
local inverse = frame.args["inverse"]
local showerrors = frame.args["showerrors"]
local default = frame.args["default"]
if default then showerrors = nil end
-- get wikidata entity
if id then
if not mw.wikibase.isValidEntityId(id) then
if showerrors then
return printError("entity-not-valid")
else
local temp = mw.title.new(i18n["maintenance-pages"]["entity-not-valid"], "Modul").exists
return default
end
elseif not mw.wikibase.entityExists(id) then
if showerrors then
return printError("entity-not-found")
else
local temp = mw.title.new(i18n["maintenance-pages"]["entity-not-found"], "Modul").exists
return default
end
end
end
local entity = mw.wikibase.getEntity(id)
if not entity then
if showerrors then return printError("entity-not-found") else return default end
end
-- check if property exists
local realProp = mw.wikibase.resolvePropertyId(property)
if not realProp then
local temp = mw.title.new(i18n["maintenance-pages"]["property-not-existing"], "Modul").exists
end
-- fetch the first claim of satisfying the given property
local claims
if entity.claims then claims = entity.claims[realProp] end
if not claims or not claims[1] then
if showerrors then return printError("property-not-found") else return default end
end
--filter claims
claims = filterClaims(frame, claims)
if not claims[1] then return default end
-- get initial sort indices
local sortindices = {}
for idx in pairs(claims) do
sortindices[#sortindices + 1] = idx
end
local comparator
if sort then
comparator = function(a, b) --comparator function for sorting statements based on qualifier value
-- load qualifier values
local QualifierSortValueA = getQualifierSortValue(claims[a], sort)
local QualifierSortValueB = getQualifierSortValue(claims[b], sort)
-- if either of the two statements does not have this qualifer:
---- if sortEmptiesFirst=true: sort it to the beginning
---- else: always sort it to the end
if not QualifierSortValueB then
if not QualifierSortValueA then
-- if neither of the two statements has this qualifier, arbitrarily but consistently return a < b
return a < b
elseif sortEmptiesFirst then
return false
else
return true
end
elseif not QualifierSortValueA then
if sortEmptiesFirst then return true else return false end
end
if type(QualifierSortValueA) ~= type(QualifierSortValueB) and not (tonumber(QualifierSortValueA) and tonumber(QualifierSortValueB)) then
if tonumber(QualifierSortValueA) then return true
elseif tonumber(QualifierSortValueB) then return false
elseif tostring(QualifierSortValueA) and tostring(QualifierSortValueB) then
if inverse then return tostring(QualifierSortValueA) > tostring(QualifierSortValueB) else return tostring(QualifierSortValueA) < tostring(QualifierSortValueB) end
else return false end -- different types, neither numbers nor strings, no chance to compare => random result to avoid script error
elseif tonumber(QualifierSortValueA) and tonumber(QualifierSortValueB) then
QualifierSortValueA = tonumber(QualifierSortValueA)
QualifierSortValueB = tonumber(QualifierSortValueB)
end
if inverse then
return QualifierSortValueA > QualifierSortValueB
else
return QualifierSortValueA < QualifierSortValueB
end
end
elseif sortInItem then
-- fill table sortkeys
local sortkeys = {}
local snakSingle
local sortkeyValueId
local claimContainingValue
for idx, claim in pairs(claims) do
snakSingle = getQualifierSnak(claim)
sortkeyValueId = "Q" .. getSnakValue(snakSingle, "numeric-id")
claimContainingValue = mw.wikibase.getEntity(sortkeyValueId).claims[mw.wikibase.resolvePropertyId(sortInItem)]
if claimContainingValue then
sortkeys[#sortkeys + 1] = getValueOfClaim(claimContainingValue[1])
else
sortkeys[#sortkeys + 1] = ""
end
end
comparator = function(a, b)
if inverse then
return sortkeys[a] > sortkeys [b]
else
return sortkeys[a] < sortkeys [b]
end
end
else
-- sort by claim rank
comparator = function(a, b)
local rankmap = { deprecated = 2, normal = 1, preferred = 0 }
local ranka = rankmap[claims[a].rank or "normal"] .. string.format("%08d", a)
local rankb = rankmap[claims[b].rank or "normal"] .. string.format("%08d", b)
return ranka < rankb
end
end
table.sort(sortindices, comparator)
local result
local error
if list then
list = string.gsub(list, "\\n", "\n") -- if a newline is provided (whose backslash will be escaped) unescape it
local value
-- iterate over all elements and return their value (if existing)
result = {}
for idx in pairs(claims) do
local claim = claims[sortindices[idx]]
value, error = getValueOfClaim(claim, qualifierId, parameter)
if not value and value ~= 0 and showerrors then value = error end
if not value and value ~= 0 and includeempty then value = "" end
if value and references then value = value .. getReferences(frame, claim) end
result[#result + 1] = value
end
if listMaxItems and listMaxItems > 0 then
result = table.concat(result, list, 1, math.min(table.getn(result), listMaxItems))
else
result = table.concat(result, list)
end
else
-- return first element
local claim = claims[sortindices[1]]
if language == "Q" then
result, error = "Q" .. getSnakValue(getQualifierSnak(claim), "numeric-id")
elseif language and claim.mainsnak.datatype == "monolingualtext" then
-- iterate over claims to find adequate language
for idx, claim in pairs(claims) do
if claim.mainsnak.datavalue.value.language == language then
result, error = getValueOfClaim(claim, qualifierId, parameter)
break
end
end
else
result, error = getValueOfClaim(claim, qualifierId, parameter)
end
if references == "only" then
result = getReferences(frame, claim)
elseif result and references then
result = result .. getReferences(frame, claim)
end
end
if result then return result else
if showerrors then return error else return default end
end
end
function p.getValue(frame)
local param = frame.args[2]
if param == "FETCH_WIKIDATA" or param == i18n["FETCH_WIKIDATA"] then return p.claim(frame) else return param end
end
function p.pageId(frame)
local id = frame.args[1]
local entity = mw.wikibase.getEntity(id)
if not entity then return "" else return entity.id end
end
function p.labelOf(frame)
local id = frame.args[1]
-- returns the label of the given entity/property id
-- if no id is given, the one from the entity associated with the calling Wikipedia article is used
if not id then
local entity = mw.wikibase.getEntity()
if not entity then return printError("entity-not-found") end
id = entity.id
end
return mw.wikibase.label(id)
end
function p.sitelinkOf(frame)
local id = frame.args[1]
-- returns the Wikipedia article name of the given entity
-- if no id is given, the one from the entity associated with the calling Wikipedia article is used
if not id then
local entity = mw.wikibase.getEntity()
if not entity then return printError("entity-not-found") end
id = entity.id
end
return mw.wikibase.sitelink(id)
end
function p.badges(frame)
local site = frame.args[1]
local id = frame.args[2]
if not site then return printError("site-not-found") end
local entity = mw.wikibase.getEntity(id)
if not entity then return printError("entity-not-found") end
local badges = entity.sitelinks[site].badges
if badges then
local result
for idx = 1, #badges do
if result then result = result .. "/" .. badges[idx] else result = badges[idx] end
end
return result
end
end
function p.sitelinkCount(frame)
local filter = "^.*" .. (frame.args[1] or "") .. "$"
local id = frame.args[2]
local entity = mw.wikibase.getEntity(id)
local count = 0
if entity and entity.sitelinks then
for project, _ in pairs(entity.sitelinks) do
if string.find(project, filter) then count = count + 1 end
end
end
return count
end
-- call this in cases of script errors within a function instead of {{#invoke:Wikidata|<method>|...}} call {{#invoke:Wikidata|debug|<method>|...}}
function p.debug(frame)
local func = frame.args[1]
if func then
-- create new parameter set, where the first parameter with the function name is removed
local newargs = {}
for key, val in pairs(frame.args) do
if type(key) == "number" then
if key > 1 then newargs[key - 1] = val end
else
newargs[key] = val
end
end
frame.args = newargs
local status, result = pcall(p[func], frame)
-- if status then return tostring(result) or "" else return '<span class="error">' .. result .. '</span>' end -- revert
if status then return result else return '<span class="error">' .. result .. '</span>' end
else
return printError("invalid-parameters")
end
end
function p.printEntity(frame)
local id = frame.args[1]
local entity = mw.wikibase.getEntity(id)
if entity then return "<pre>" .. mw.text.jsonEncode(entity, mw.text.JSON_PRETTY) .. "</pre>" end
end
-- formfill Template:Coordinate (NS, EW, name from WikidataEntity) and expand it
-- füllt Vorlage:Coordinate (NS, EW, name mit Wikidata-Werten) + expandiert sie
-- 1st frame.arg .. Q prefixed entity id (mandatory)
-- named frame.arg "type", "region", "text" .. see doc of 'Coordinate' template
function p.ffCoordinate(frame)
local f = frame
local id = f.args[1] or f.args.Q
local name = f.args.name or p.labelIn{ args = { nil, id, id = id }}
local coord = mw.text.split(p.claim{ args = { "P625", id, id = id }}, '/')
coord[1] = tonumber(coord[1])
coord[2] = tonumber(coord[2])
local t, r = f.args.type, f.args.region
if not t
then t = p.claim{ args = { "P31", id, id = id, language = "Q" }}
t = t and t:gsub("Q.*", {
Q8502 = "mountain",
Q54050 = "landmark"
})
if not t or t and t:find("Q", 1, true)
then t="" -- no default, let Coordinate warn about unset type= param
end
end
if not r
then r = p.claim{ args = { "P17", id, id = id, language = "Q" }}
r = r and p.claim{ args = { "P297", r, id = r }}
if not r
then r="" -- no default, let Coordinate warn about unset region= param
end
end
return ('<span data-sort-value="%010.6f"></span>'):format((f.args.sortkey
or "EW"):find("EW", 1, true) and coord[2]+360.0 or coord[1]+180.0
) .. f:expandTemplate{ title = 'Coordinate', args = {
NS = coord[1], EW = coord[2], type = t, region = r,
text = f.args.text or (f.args.maplink and "ICON0" or "/"),
name = name, simple = f.args.simple
}} .. (not f.args.maplink and "" or (" " ..
--f:callParserFunction{ name="#statements", args={ "P625", from = id } }
f:callParserFunction{ name="#tag:maplink", args={ "",
class = "no-icon", text = f.args.mlname and name,
zoom = 12, latitude = coord[1], longitude = coord[2]
}}
))
end
function p.ffCoordinateAndLatLonMaplink(frame)
frame.args.maplink = 1
--frame.args.mlname = nil
return p.ffCoordinate(frame)
end
function p.ffCoordinateAndMaplink(frame)
frame.args.maplink = 1
frame.args.mlname = 1
return p.ffCoordinate(frame)
end
return p