Modul:Wikidata: Unterschied zwischen den Versionen
(Aktualisierung Modul nach https://de.wikipedia.org/wiki/Modul:Wikidata , Autoren siehe https://de.wikipedia.org/w/index.php?title=Modul:Wikidata&action=history) |
(Modul aktualisiert nach https://de.wikipedia.org/wiki/Modul:Wikidata vom 06.01.2021, Autoren siehe https://de.wikipedia.org/w/index.php?title=Modul:Wikidata&action=history) |
||
Zeile 12: | Zeile 12: | ||
["property-not-found"] = "Eigenschaft nicht gefunden.", | ["property-not-found"] = "Eigenschaft nicht gefunden.", | ||
["entity-not-found"] = "Wikidata-Eintrag 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-claim-type"] = "Unbekannter Aussagentyp.", | ||
["unknown-entity-type"] = "Unbekannter Entity-Typ.", | ["unknown-entity-type"] = "Unbekannter Entity-Typ.", | ||
Zeile 18: | Zeile 19: | ||
["invalid-parameters"] = "Ungültige Parameter.", | ["invalid-parameters"] = "Ungültige Parameter.", | ||
["module-not-loaded"] = "Loading of additional module failed." | ["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"] = | ["datetime"] = | ||
Zeile 389: | Zeile 396: | ||
function formatReference(ref) | function formatReference(ref) | ||
-- "imported from"-references are useless, skip them: | -- "imported from"-references are useless, skip them: | ||
if ref["P143"] then return nil end | if ref["P143"] or ref["P4656"] then return nil end | ||
-- load [[Modul:Zitation]] | -- load [[Modul:Zitation]] | ||
Zeile 395: | Zeile 402: | ||
if type(r) == "table" then | if type(r) == "table" then | ||
Zitation = r.Zitation() | Zitation = r.Zitation() | ||
-- clear Zitation state from previous invocations | |||
Zitation.o = nil | |||
end | end | ||
-- assert (ZitationSuccess, i18n["errors"]["module-not-loaded"]) | -- assert (ZitationSuccess, i18n["errors"]["module-not-loaded"]) | ||
Zeile 441: | Zeile 450: | ||
local URLutil = Zitation.fetch("URLutil") | local URLutil = Zitation.fetch("URLutil") | ||
Zitation.fill("bas", "Titel", URLutil.getHost(ref["P854"])) | Zitation.fill("bas", "Titel", URLutil.getHost(ref["P854"])) | ||
end | end | ||
refFormatted, f = Zitation.format() | refFormatted, f = Zitation.format() | ||
return refFormatted | return refFormatted, f | ||
end | end | ||
Zeile 465: | Zeile 472: | ||
end | end | ||
end | end | ||
local formattedRef = formatReference(refTable) | local formattedRef, f = formatReference(refTable) | ||
if formattedRef then result = result .. frame:extensionTag("ref", formattedRef) end | -- 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 | ||
return result | return result | ||
Zeile 483: | Zeile 497: | ||
local function qualifierhasvalue(claim, property, value) | local function qualifierhasvalue(claim, property, value) | ||
-- TODO: not yet documented! | |||
if not claim.qualifiers then return false end | if not claim.qualifiers then return false end | ||
if not claim.qualifiers[property] then return false end | if not claim.qualifiers[property] then return false end | ||
Zeile 536: | Zeile 551: | ||
return true | return true | ||
end | |||
local function notdeprecated(claim, sourceproperty) | |||
return not (claim.rank == "deprecated") | |||
end | end | ||
Zeile 556: | Zeile 575: | ||
filter('hassource', hassource) | filter('hassource', hassource) | ||
filter('atdate', atdate) | 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 | for key, val in pairs(frame.args) do | ||
if type(key) == "number" and key > 2 and key % 2 == 1 then | if type(key) == "number" and key > 2 and key % 2 == 1 then | ||
Zeile 630: | Zeile 656: | ||
local references = frame.args["references"] | local references = frame.args["references"] | ||
local sort = frame.args["sort"] | local sort = frame.args["sort"] | ||
local sortEmptiesFirst = frame.args["sortEmptiesFirst"] | |||
local sortInItem = frame.args["sortInItem"] | local sortInItem = frame.args["sortInItem"] | ||
local inverse = frame.args["inverse"] | local inverse = frame.args["inverse"] | ||
Zeile 637: | Zeile 664: | ||
-- get wikidata entity | -- 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) | local entity = mw.wikibase.getEntity(id) | ||
if not entity then | if not entity then | ||
if showerrors then return printError("entity-not-found") else return default end | if showerrors then return printError("entity-not-found") else return default end | ||
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 | -- fetch the first claim of satisfying the given property | ||
local claims | local claims | ||
if entity.claims then claims = entity.claims[ | if entity.claims then claims = entity.claims[realProp] end | ||
if not claims or not claims[1] then | if not claims or not claims[1] then | ||
if showerrors then return printError("property-not-found") else return default end | if showerrors then return printError("property-not-found") else return default end | ||
Zeile 660: | Zeile 712: | ||
local comparator | local comparator | ||
if sort then | if sort then | ||
comparator = function(a, b) --comparator function for sorting statements based on qualifier value | |||
comparator = function(a, b) | -- load qualifier values | ||
local | local QualifierSortValueA = getQualifierSortValue(claims[a], sort) | ||
local | local QualifierSortValueB = getQualifierSortValue(claims[b], sort) | ||
if type( | |||
if tonumber( | -- if either of the two statements does not have this qualifer: | ||
elseif tonumber( | ---- if sortEmptiesFirst=true: sort it to the beginning | ||
elseif tostring( | ---- else: always sort it to the end | ||
if inverse then return tostring( | 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 | else return false end -- different types, neither numbers nor strings, no chance to compare => random result to avoid script error | ||
elseif tonumber( | elseif tonumber(QualifierSortValueA) and tonumber(QualifierSortValueB) then | ||
QualifierSortValueA = tonumber(QualifierSortValueA) | |||
QualifierSortValueB = tonumber(QualifierSortValueB) | |||
end | end | ||
if inverse then | if inverse then | ||
return | return QualifierSortValueA > QualifierSortValueB | ||
else | else | ||
return | return QualifierSortValueA < QualifierSortValueB | ||
end | end | ||
end | end | ||
Zeile 738: | Zeile 807: | ||
local claim = claims[sortindices[1]] | local claim = claims[sortindices[1]] | ||
if language and claim.mainsnak.datatype == "monolingualtext" then | 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 | -- iterate over claims to find adequate language | ||
for idx, claim in pairs(claims) do | for idx, claim in pairs(claims) do | ||
Zeile 853: | Zeile 924: | ||
if entity then return "<pre>" .. mw.text.jsonEncode(entity, mw.text.JSON_PRETTY) .. "</pre>" end | if entity then return "<pre>" .. mw.text.jsonEncode(entity, mw.text.JSON_PRETTY) .. "</pre>" end | ||
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 | return p |
Aktuelle Version vom 6. Februar 2021, 10:46 Uhr
- Dieses Modul enthält Code in der Programmiersprache Lua.
- Module werden über
#invoke
in Vorlagen eingebunden. - Dieses Modul wird über Vorlagen auf folgenden Seiten verwendet: Seiten anzeigen
-- 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