Modul:LinkedWiki: Unterschied zwischen den Versionen

Aus ÖsterreichWiki
Zur Navigation springen Zur Suche springen
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua
-- module local variables
local wiki =
{
langcode = mw.language.getContentLanguage().code
}


local p = {}
-- 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"
}


local linguistic = require('Module:Linguistic')
--important properties
--local formatDate = require('Module:Complex date') only loaded when needed to save memory in large pages like Wikidata:List of properties/all
local propertyId =
local fb = require('Module:Fallback')
{
local i18nmessages = mw.loadData('Module:i18n/wikidata')
["starttime"] = "P580",
["endtime"] = "P582"
}


-- Wiki-specific parameters
local formatchar =
local defaultlang = mw.getCurrentFrame():preprocess("{{int:lang}}")
{
local defaultlink = 'wikidata'
[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 i18n(str)
local function printError(code)
local message = i18nmessages[str]
return '<span class="error">' .. (i18n.errors[code] or code) .. '</span>'
if type(message) == 'string' then
return message
end
return fb._langSwitch(message, defaultlang) .. ''
end
end


local function formatError( key, text )
-- the "qualifiers" and "snaks" field have a respective "qualifiers-order" and "snaks-order" field
return error(i18n(key) .. (text or ''))
-- 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 addTrackingCat(prop, cat)
-- return iterator function
if not prop and not cat then
local i = 0
return error("no property provided")
return function()
i = i + 1
if order[i] then
return order[i], array[order[i]]
end
end
end
if not cat then
cat = i18nmessages.trackingcat .. '/' .. string.upper(prop)
end
return '[[Category:' .. cat .. ']]'
end
end


local function removeBlanks(args)
-- Function to check whether a certain item is a parent of a given item.
for i, j in pairs(args) do -- does not work ??
-- If pExitItem is reached without finding the searched parent item, the search stops.
if (j == '') or (j == '-') then args[i] = nil end
-- A parent is connected via P31 or P279.
end
-- Attention: very intensive function, use carefully!
return args
local function isParent(pItem, pParent, pExitItem, pMaxDepth, pDepth)
end
if not pDepth then pDepth = 0 end
 
local function formatTheUnknown() -- voir si on peut accorder/adapter l'usage de "inconnu"
return i18n('somevalue')
end


local function isSpecial(snak)
if type(pItem) == "number" then pItem = "Q" .. pItem end
return snak.snaktype ~= 'value'
end


local function sameValue(snak, target)
local entity = mw.wikibase.getEntity(pItem)
return not isSpecial(snak) and p.getRawvalue(snak) == target
if not entity then return false end
end


local function showLang(statement, str) -- TODO (not yet in proper format)
local claims31
--adds a lang indication at the start of the string, based on data in statement
local claims279
local mainsnak = statement.mainsnak
if entity.claims then
if isSpecial(mainsnak) then
claims31 = entity.claims[mw.wikibase.resolvePropertyId('P31')]
return str
claims279 = entity.claims[mw.wikibase.resolvePropertyId('P279')]
else
return false
end
end
if not claims31 and not claims279 then return false end


local langlist = {}
local parentIds = {}
if mainsnak.datavalue.type == 'monolingualtext' then
if claims31 and #claims31 > 0 then
langlist = {mainsnak.datavalue.value.language}
for i, v in ipairs(claims31) do parentIds[#parentIds+1] = getSnakValue(v.mainsnak, "numeric-id") end
elseif statement.qualifiers and statement.qualifiers.P407 then
local convertlangcode = mw.loadData('Module:Dictionary/lang codes')
for i, j in pairs( statement.qualifiers.P407 ) do
if not isSpecial(j) then
local val = convertlangcode[j.datavalue.value['numeric-id']]
table.insert(langlist, val)
end
end
end
end
if #langlist == 0 then
if claims279 and #claims279 > 0 then
return str
for i, v in ipairs(claims279) do parentIds[#parentIds+1] = getSnakValue(v.mainsnak, "numeric-id") end
else
return '(' .. table.concat(langlist) .. ')' .. str
end
end
end


function p.getEntity( val )
-- check if searched parent or exit item is reached or do recursive call
if type(val) == 'table' then
if not parentIds[1] or #parentIds == 0 then return false end
return val
local itemString = ""
end
local result = nil
return mw.wikibase.getEntityObject(val)
for i, v in ipairs(parentIds) do
end
if not v then return false end
itemString = "Q" .. v


-- DATE FUNCTIONS
if itemString == pParent then
local function splitTimestamp(timestamp, calendar)
-- successful!
local pattern = "(%W)(%d+)%-(%d+)%-(%d+)"
return true
local era, year, month, day = timestamp:match(pattern)
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 calendar == 'julian' then
if result == true then return result end
--todo  year, month, day = formatdate.gregorianToJulian( era .. year, month, day )
end
end
end
 
do return false end
return {day = day, month = month, year = year, era = era, timestamp = timestamp, type = 'dateobject'}
end
end


local function rangeObject(begin, ending)
local function printDatavalueCoordinate(data, parameter)
local timestamp
-- data fields: latitude [double], longitude [double], altitude [double], precision [double], globe [wikidata URI, usually http://www.wikidata.org/entity/Q2 [earth]]
if begin then
if parameter then
timestamp = begin.timestamp
if parameter == "globe" then data.globe = mw.ustring.match(data.globe, "Q%d+") end -- extract entity id from the globe URI
elseif ending then
return data[parameter]
timestamp = ending.timestamp
else
return data.latitude .. "/" .. data.longitude -- combine latitude and longitude, which can be decomposed using the #titleparts wiki function
end
end
return {begin = begin, ending = ending, timestamp = timestamp, type = 'rangeobject'}
end
end


local function dateObject(orig, params) -- transforme un snak en un nouvel objet utilisable par Module:Date complexe
local function printDatavalueQuantity(data, parameter)
if not params then
-- data fields: amount [number], unit [string], upperBound [number], lowerBound [number]
params = {}
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 newobj = splitTimestamp(orig.time, orig.calendar) -- initalise l'object en mettant la valeur des dates
newobj.precision = params.precision or orig.precision
newobj.type = 'dateobject'
return newobj
end
end


local function formatDatepoint(obj, params) -- TO IMPROVE
local function normalizeDate(date)
if not obj then
date = mw.text.trim(date, "+")
return nil
-- extract year
end
local yearstr = mw.ustring.match(date, "^\-?%d+")
local formatDate = require('Module:Complex date')
local year = tonumber(yearstr)
local lang = params.lang or defaultlang
-- remove leading zeros of year
local precision = math.min(obj.precision, params.precision or 15) -- if we don't want to show the value to its full detail
return year .. mw.ustring.sub(date, #yearstr + 1), year
if precision >= 11 then
return formatDate.complex_date{args={date1 = obj.year .. '-' .. obj.month .. '-' .. obj.day, lang= lang}}
elseif precision == 10 then
return formatDate.complex_date{args={date1 = obj.year .. '-' .. obj.month, lang= lang}}
elseif precision == 9 then
return formatDate.complex_date{args={date1 = tostring(obj.year), lang= lang}}
elseif precision == 8 then
return formatDate.complex_date{args={date1 = string.sub(tostring(obj.year), 1, 3) .. '0', lang = lang, precision = 'decade'}}
elseif precision == 7 then
return formatDate.complex_date{args={date1 = string.sub(tostring(obj.year + 100), 1, 2), lang = lang, precision = 'century'}}
end
return nil
end
end


local function formatDaterange(obj, params) --TODO
-- 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
local begin = formatDatepoint(obj.begin, params) or ''
function formatDate(date, precision, timezone, formatstr)
local ending = formatDatepoint(obj.ending, params) or ''
precision = precision or 11
return begin .. '-' .. ending
date, year = normalizeDate(date)
end
date = string.gsub(date, "-00%f[%D]", "-01")
if year == 0 and precision <= 9 then return "" end


local function objectToText(obj, params)
-- precision is 10000 years or more
if obj.type == 'dateobject' then
if precision <= 5 then
return formatDatepoint(obj, params)
local factor = 10 ^ ((5 - precision) + 4)
elseif obj.type == 'rangeobject' then
local y2 = math.ceil(math.abs(year) / factor)
return formatDaterange(obj, params)
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
return nil
end


local function tableToText(values, params) -- takes a list of already formatted values and make them a text
-- precision is decades, centuries and millenia
if not values or #values == 0 then
local era
return nil
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
return linguistic.conj(values, params.lang or defaultlang, params.conjtype)--linguistic.conj( values, params.lang, params.conjtype )
end


function p.getDate(obj)
-- precision is years or less
--[[
if precision >= 9 then
returns an object containing a timestamp for easy sorting, and other data
--[[ the following code replaces the UTC suffix with the given negated timezone to convert the global time to the given local time
possible types of object:
timezone = tonumber(timezone)
dateobject
if timezone and timezone ~= 0 then
{timestamp = string, year = number, month = number, day = number, calendar = string}
timezone = -timezone
rangeobject
timezone = string.format("%.2d%.2d", timezone / 60, timezone % 60)
{timestamp = string, begin = dateobject, ending = dateobject}
if timezone[1] ~= '-' then timezone = "+" .. timezone end
]]--
date = mw.text.trim(date, "Z") .. " " .. timezone
if not obj then
end
return nil
]]--
end
if formatstr then
if type(obj) == 'string' then
for i=(precision+1), 14 do
obj = p.getEntity(obj)
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 obj is a statement with date, get it
local function printDatavalueTime(data, parameter)
if obj.mainsnak and not isSpecial(obj.mainsnak) and obj.mainsnak.datatype == 'time' then
-- data fields: time [ISO 8601 time], timezone [int in minutes], before [int], after [int], precision [int], calendarmodel [wikidata URI]
return dateObject(obj.mainsnak.datavalue.value)
--   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


-- else preload relevant data
local function printDatavalueEntity(data, parameter)
local qualifs = obj.qualifiers -- when obj is a statement, look in qualifiers
-- data fields: entity-type [string], numeric-id [int, Wikidata id]
local claims = obj.claims -- when obj is an item, look in claims
local id
 
local pointprop = {'P585', 'P571'} -- dates corresponding to a punctual fact
local beginprop = {'P580', 'P569'} -- start date, birth date == start of a date range
local endingprop = {'P582', 'P570'}


local function getval(prop)
if data["entity-type"] == "item" then id = "Q" .. data["numeric-id"]
local val
elseif data["entity-type"] == "property" then id = "P" .. data["numeric-id"]
if claims and claims[prop] and not isSpecial(claims[prop][1].mainsnak) then
else return printError("unknown-entity-type")
val = claims[prop][1].mainsnak.datavalue.value
elseif qualifs and qualifs[prop] and not isSpecial(qualifs[prop][1]) then
val = qualifs[prop][1].datavalue.value
end
if val then
return dateObject(val)
end
return nil
end
end


for i, prop in pairs(pointprop) do
if parameter then
local val = getval(prop)
if parameter == "link" then
if val then return val end
local linkTarget = mw.wikibase.sitelink(id)
end
local linkName = mw.wikibase.label(id)
--if no date has not been found, look for startdate or enddate
if linkTarget then
local begin, ending
local link = linkTarget
for i, prop in pairs(beginprop) do
-- if there is a local Wikipedia article linking to it, use the label or the article title
begin = getval(prop)
if linkName and (linkName ~= linkTarget) then link = link .. "|" .. linkName end
if begin then
return "[[" .. link .. "]]"
break
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
for i, prop in pairs(endingprop) do
ending = getval(prop)
if ending then
break
end
end
if begin or ending then
return rangeObject(begin, ending)
end
return nil
end
end


function p.getFormattedDate(statement, params)
local function printDatavalueMonolingualText(data, parameter)
local datetable = p.getDate(statement)
-- data fields: language [string], text [string]
if not datetable then
if parameter then
return nil
return data[parameter]
else
local result = mw.ustring.gsub(mw.ustring.gsub(i18n.monolingualtext, "%%language", data["language"]), "%%text", data["text"])
return result
end
end
return objectToText(datetable, params)
end
end


local function hasTargetValue(claim, target)
function getSnakValue(snak, parameter)
if target == nil then
-- snaks have three types: "novalue" for null/nil, "somevalue" for not null/not nil, or "value" for actual data
return true
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 sameValue(claim.mainsnak, target)
return mw.wikibase.renderSnak(snak)
end
end


local function hasRank(claim, target)
function getQualifierSnak(claim, qualifierId)
if target == 'valid' then
-- a "snak" is Wikidata terminology for a typed key/value pair
return hasRank(claim, 'preferred') or hasRank(claim, 'normal')
-- 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.rank == target
-- otherwise return the main snak
return claim.mainsnak
end
end
end
end


local function bestRanked(claims)
local function datavalueTimeToDateObject(data)
if not claims then
local sign, year, month, day, hour, minute, second = string.match(data.time, "(.)(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z")
return nil
local result =
end
{
local preferred, normal = {}, {}
year = tonumber(year),
for _, j in ipairs(claims) do
month = tonumber(month),
if j.rank == 'preferred' then
day = tonumber(day),
table.insert(preferred, j)
hour = tonumber(hour),
elseif j.rank == 'normal' then
min = tonumber(minute),
table.insert(normal, j)
sec = tonumber(second),
end
timezone = data.timezone,
end
julian = data.calendarmodel and string.match(data.calendarmodel, "Q11184$")
if #preferred > 0 then
}
return preferred
if sign == "-" then result.year = -result.year end
else
return result
return normal
end
end
end


local function hasQualifier(claim, qualifier, qualifiervalues)
function julianDay(dateObject)
if not qualifier then -- si aucun qualificatif est demandé, ça passe
local year = dateObject.year
return true
local month = dateObject.month or 0
end
local day = dateObject.day or 0


qualifier = string.upper(qualifier)
if month == 0 then month = 1 end
if not claim.qualifiers or not claim.qualifiers[qualifier] then
if day == 0 then day = 1 end
return false
if month <= 2 then
year = year - 1
month = month + 12
end
end


if type(qualifiervalues) == 'string' then
local time = ((((dateObject.sec or 0) / 60 + (dateObject.min or 0) + (dateObject.timezone or 0)) / 60) + (dateObject.hour or 0)) / 24
qualifiervalues = mw.text.split(qualifiervalues, ',')
end


if (not qualifiervalues) or (qualifiervalues == {}) then
local b
return true -- si aucune valeur spécifique n'est exigée
if dateObject.julian then b = 0 else
local century = math.floor(year / 100)
b = 2 - century + math.floor(century / 4)
end
end


for _, j in ipairs(claim.qualifiers[qualifier]) do
return math.floor(365.25 * (year + 4716)) + math.floor(30.6001 * (month + 1)) + day + time + b - 1524.5
for _, l in ipairs(qualifiervalues) do
end
if p.sameValue(j, l) then
return true
end
end
end
return false
end


local function hasSource(statement, source, sourceproperty)
function getQualifierSortValue(claim, qualifierId)
if not statement.references then
local snak = getQualifierSnak(claim, qualifierId)
return false
if snak and snak.snaktype == "value" then
end
if snak.datavalue.type == "time" then
sourceproperty = string.upper(sourceproperty or 'P248')
return julianDay(datavalueTimeToDateObject(snak.datavalue.value))
local sourcevalue = string.upper(source or '')
else
for _, ref in ipairs(statement.references) do
return getSnakValue(snak)
for prop, content in pairs(ref.snaks) do
if prop == sourceproperty then
if sourcevalue == '' then
return true
else
for _, k in ipairs(content) do
if sameValue(k, source) then
return true
end
end
end
end
end
end
end
end
return false
end
end


local function hasDate(statement)
function getValueOfClaim(claim, qualifierId, parameter)
if not statement.qualifiers then
local error
return false
local snak
end
snak, error = getQualifierSnak(claim, qualifierId)
local dateprops = {'P580', 'P585', 'P582'}
if snak then
for i, prop in pairs(dateprops) do
return getSnakValue(snak, parameter)
if statement.qualifiers[prop] then
else
return true
return nil, error
end
end
end
return false
end
end


local function isInLanguage(snak, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ?
return not isSpecial(snak) and snak.datavalue.type == 'monolingualtext' and snak.datavalue.value.language == lang
end


local function numval(claims, numval) -- retourn les numval premières valeurs de la table claims
function formatReference(ref)
local numval = tonumber(numval) or 0 -- raise an error if numval is not a positive integer ?
-- "imported from"-references are useless, skip them:
if #claims <= numval then
if ref["P143"] or ref["P4656"] then return nil end
return claims
-- 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 newclaims = {}
-- assert (ZitationSuccess, i18n["errors"]["module-not-loaded"])
while #newclaims < numval do
table.insert(newclaims, claims[#newclaims + 1])
-- 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
return newclaims
-- if no title on Wikidata, try to use the URL as title
end
if (not ref["P1476"]) and ref["P854"] then
 
local URLutil = Zitation.fetch("URLutil")
local function wikipediaLink(entity, lang)
Zitation.fill("bas", "Titel", URLutil.getHost(ref["P854"]))
local link
if type(entity) == 'table' then
link = entity:getSitelink(lang .. 'wiki')
else
link = mw.wikibase.getSitelink(entity, lang .. 'wiki')
end
end
if link then
refFormatted, f = Zitation.format()
return ':' .. lang .. ':' .. link
return refFormatted, f
end
return nil
end
end


local function getLink(entity, typelink, lang)
function getReferences(frame, claim)
if typelink == 'wikidata' then
local result = ""
if type(entity) == 'table' then
-- traverse through all references
if entity.type == 'property' then
for ref in pairs(claim.references or {}) do
return 'd:P:' .. entity.id
local refTable = {}
elseif entity.type == 'lexeme' then
for snakkey, snakval in orderedpairs(claim.references[ref].snaks or {}, claim.references[ref]["snaks-order"]) do
return 'd:L:' .. entity.id
if #snakval == 1 then
refTable[snakkey] = getSnakValue(snakval[1])
else
else
return 'd:' .. entity.id
--
end
multival = {}
else
for snakidx = 1, #snakval do
if string.sub(entity, 1, 1) == 'P' then
table.insert(multival, getSnakValue(snakval[snakidx]))
return 'd:P:' .. entity
end
elseif string.sub(entity, 1, 1) == 'L' then
refTable[snakkey] = multival
return 'd:L:' .. entity
else
return 'd:' .. entity
end
end
end
end
 
local formattedRef, f = formatReference(refTable)
elseif typelink == 'wikipedia' then
-- log errors that occur during formatting
return wikipediaLink(entity, lang or defaultlang)
if f then
 
mw.log(f)
elseif typelink == 'anywikipedia' then
end
for _, lg in ipairs(fb.fblist(lang or defaultlang, true)) do
if formattedRef and formattedRef ~= "" then
local link = wikipediaLink(entity, lg)
local hash = mw.hash.hashValue('fnv164', formattedRef)
if link then
result = result .. frame:extensionTag("ref", formattedRef, { name = '_' .. hash })
return link
end
end
end
end
end
return nil
return result
end
end


function p.comparedate(a, b) -- returns true if a is earlier than B or if a has a date but not b
local function hasqualifier(claim, qualifierproperty)
if a and b then
local invert
return a.timestamp < b.timestamp
if string.sub(qualifierproperty, 1, 1) == "!" then invert = true else invert = false end
elseif a then
if not claim.qualifiers and not invert then return false end
return true
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 p.chronosort(objs, inverted)
local function qualifierhasvalue(claim, property, value)
table.sort(objs, function(a, b)
-- TODO: not yet documented!
local timeA = p.getDate(a)
if not claim.qualifiers then return false end
local timeB = p.getDate(b)
if not claim.qualifiers[property] then return false end
if inverted then
for key, snak in pairs(claim.qualifiers[property]) do
return p.comparedate(timeB, timeA)
if snak.snaktype == "value" then
else
if snak.datavalue.type == "wikibase-entityid" then
return p.comparedate(timeA, timeB)
if snak.datavalue.value.id == value then
return true
end
--TODO: elseif other types
end
end
end
end)
return objs
end
function p.sortclaims(claims, sorttype)
if type(sorttype) == 'function' then
table.sort(claims, sorttype)
elseif sorttype == 'chronological' then
return p.chronosort(claims)
elseif sorttype == 'inverted' then
return p.chronosort(claims, true)
end
end
return claims
return false
end
end


function p.getRawvalue(snak)
local function hassource(claim, sourceproperty)
return p.getDatavalue(snak, { displayformat = 'raw' })
if not claim.references then return false end
end
if sourceproperty == '' then return true end
 
if string.sub(sourceproperty,1,1) ~= "!" then
function p.showentity(entity, lang)
for _, source in pairs(claim.references) do
if not entity then
if source.snaks[sourceproperty] then return true end
return nil
end
end
return false
local label, link, id = p._getLabel(entity, lang), getLink(entity, 'wikidata')
if type(entity) == 'table' then
id = entity.id
else
else
id = entity
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
return '[[' .. link .. '|' .. label .. ']] <small>(' .. id .. ')</small>'
end
end


function p.getDatavalue(snak, params)
function atdate(claim, mydate)
if isSpecial(snak) then
local refdate
return nil
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)


if not params then
local mindate = getQualifierSortValue(claim, propertyId["starttime"])
params = {}
local maxdate = getQualifierSortValue(claim, propertyId["endtime"])
end


local displayformat = params.displayformat
if mindate and mindate > refjd then return false end
local valuetype = snak.datavalue.type
if maxdate and maxdate < refjd then return false end
local value = snak.datavalue.value


if valuetype == 'wikibase-entityid' then
return true
if type(displayformat) == 'function' then
end
return displayformat(snak, params)
end
local id = snak.datavalue.value.id
if displayformat == 'raw' then
return id
elseif displayformat == 'wikidatastyle' then
return p.showentity(id, params.lang)
else
return p.formatEntity(id, params)
end


elseif valuetype == 'string' then
local function notdeprecated(claim, sourceproperty)
local showntext = params.showntext
  return not (claim.rank == "deprecated")
if displayformat == 'weblink' then
end
if showntext then
return '[' .. value .. ' ' .. showntext .. ']'
else
return value
end
end
if ({['math'] = 1, ['musical-notation'] = 1})[snak.datatype] == 1 and displayformat ~= 'raw' then
value = mw.wikibase.formatValue(snak)
else
if params.urlpattern then
showntext = mw.text.nowiki(showntext or value)
value = mw.ustring.gsub(value, '%%', '%%%%') -- escape '%'
value = '[' .. mw.ustring.gsub(mw.ustring.gsub(params.urlpattern, '$1', value), ' ', '%%20') .. ' ' .. showntext .. ']'
elseif params.pattern then
local pattern = mw.ustring.gsub(params.pattern, '%%', '%%%%')
value = mw.ustring.gsub(value, '%%', '%%%%')
value = mw.ustring.gsub(pattern, '$1', value)
else
if displayformat ~= 'raw' then
value = mw.text.nowiki(value)
end
end
end
return value


elseif valuetype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
--returns a table of claims excluding claims not passed the filters
if displayformat == 'raw' then
function filterClaims(frame, claims)
return value.time
local function filter(condition, filterfunction)
else
if not frame.args[condition] then
return objectToText(dateObject(value), params)
return
end
end
 
local newclaims = {}
elseif valuetype == 'globecoordinate' then
for i, claim in pairs(claims) do
-- retourne une table avec clés latitude, longitude, précision et globe à formater par un autre module (à changer ?)
if filterfunction(claim, frame.args[condition]) then
if displayformat == 'latitude' then
table.insert(newclaims, claim)
return value.latitude
elseif displayformat == 'longitude' then
return value.longitude
elseif displayformat == 'qualifier' then
local coord = require 'Module:Coordinates'
value.globe = mw.loadData('Module:Wikidata/Globes')[value.globe]
value.precision = nil
return coord._coord(value)
else
value.globe = mw.loadData('Module:Wikidata/Globes')[value.globe] -- transforme l'ID du globe en nom anglais utilisable par geohack
return value -- note : les coordonnées Wikidata peuvent être utilisée depuis Module:Coordinates. Faut-il aussi autoriser à appeler Module:Coordiantes ici ?
end
 
elseif valuetype == 'quantity' then -- todo : gérer les paramètre précision
if displayformat == 'raw' then
return tonumber(value.amount)
else
local formatNum = require 'Module:Formatnum'
local number = formatNum.formatNum(value.amount, params.lang)
local unit = mw.ustring.match(value.unit, '(Q%d+)')
if unit then
number = number .. '&nbsp;' .. p.formatEntity(unit, params)
end
end
return number
end
end
elseif valuetype == 'monolingualtext' then
claims = newclaims
return '<span lang="' .. value.language .. '">' .. value.text .. '</span>'
else
return formatError( 'unknown-datavalue-type', valuetype )
end
end
end


local function getMultipleClaims(args)
filter('hasqualifier', hasqualifier)
local newargs = args
filter('hassource', hassource)
local claims = {}
filter('atdate', atdate)
for i, j in pairs(args.property) do
if not frame.args.includedeprecated then
newargs.property = j
frame.args.notdeprecated = true
local newclaims = p.getClaims(args)
    filter('notdeprecated', notdeprecated)
if newclaims then
    end
for k, l in pairs(newclaims) do
table.insert(claims, l)
-- 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.getClaims( args ) -- returns a table of the claims matching some conditions given in args
local p = {}
args = removeBlanks(args)
 
if not args.property then
function p.isSubclass(frame)
return formatError( 'property-param-not-provided' )
if not frame.args["parent"] then return "" end
end
 
if type(args.property) == 'table' then
local maxDepth
return getMultipleClaims(args)
maxDepth = frame.args["maxDepth"] or 5
end
if not type(maxDepth) == "number" then maxDepth = 5 end
--Get entity
 
if args.item then -- synonyms
local result
args.entity = args.item
result = isParent(frame.args["id"], frame.args["parent"], frame.args["exitItem"], maxDepth)
end
local property = string.upper(args.property)
if frame.args["returnInt"] then
local allClaims
if result == true then return 1 else return "" end
local entity = args.entity
if type(entity) == 'table' then
allClaims = (entity and entity.claims and entity.claims[property]) or {}
else
else
allClaims = mw.wikibase.getAllStatements(entity, property)
if result then return result else return false end
end
if #allClaims == 0 then
return nil
end
end
end


if not args.rank then
function p.descriptionIn(frame)
args.rank = 'best'
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
local claims = {}
for _, statement in ipairs(allClaims) do
if
(
not args.excludespecial
or
not (isSpecial(statement.mainsnak))
)
and
(
not args.targetvalue
or
hasTargetValue(statement, args.targetvalue)
)
and
(
not args.qualifier
or
hasQualifier(statement, args.qualifier, args.qualifiervalues or args.qualifiervalue)
)
and
(
not args.withsource or args.withsource == '-'
or
hasSource(statement, args.withsource, args.sourceproperty)
)
and
(
not args.isinlanguage
or
isInLanguage(statement.mainsnak, args.isinlanguage)
)
and
(
args.rank == 'best' -- rank == best est traité à a fin
or
hasRank(statement, args.rank)
)
then
table.insert(claims, statement)
end
end
if #claims == 0 then
return nil
end
if args.rank == 'best' then
claims = bestRanked(claims)
end
if args.sorttype then
claims = p.sortclaims(claims, args.sorttype)
end
if args.numval then
return numval(claims, args.numval)
end
return claims
end
end


function p.formatClaimList(claims, args)
function p.labelIn(frame)
if not claims then
local langcode = frame.args[1]
return nil
local id = frame.args[2]
end
-- return label of a Wikidata entity in the given language or the default language of this Wikipedia site
for i, j in pairs(claims) do
local entity = mw.wikibase.getEntity(id)
claims[i] = p.formatStatement(j, args)
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
return claims
end
end


function p.stringTable(args) -- like getClaims, but get a list of string rather than a list of snaks, for easier manipulation
function p.claim(frame)
local claims = p.getClaims(args)
local property = frame.args[1] or ""
return p.formatClaimList(claims, args)
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


local function getQualifiers(statement, qualifs, params)
-- get wikidata entity
if not statement.qualifiers then
if id then
return nil
if not mw.wikibase.isValidEntityId(id) then
end
if showerrors then  
local vals = {}
return printError("entity-not-valid")
for i, j in pairs(qualifs) do
else
j = string.upper(j)
local temp = mw.title.new(i18n["maintenance-pages"]["entity-not-valid"], "Modul").exists
if statement.qualifiers[j] then
return default
local inserted = false
if statement.qualifiers[j][1].datatype == 'monolingualtext' then
local in_preferred_lang
for _, language in ipairs(fb.fblist(params.lang or defaultlang, true)) do
for _, snak in ipairs(statement.qualifiers[j]) do
if isInLanguage(snak, language) then
in_preferred_lang = snak
break
end
end
if in_preferred_lang then
break
end
end
if in_preferred_lang then
table.insert(vals, in_preferred_lang)
inserted = true
end
end
end
if not inserted then
elseif not mw.wikibase.entityExists(id) then
for _, snak in pairs(statement.qualifiers[j]) do
if showerrors then
table.insert(vals, snak)
return printError("entity-not-found")  
end
else
local temp = mw.title.new(i18n["maintenance-pages"]["entity-not-found"], "Modul").exists
return default
end
end
end
end
end
end
if #vals == 0 then
return nil
local entity = mw.wikibase.getEntity(id)
if not entity then
if showerrors then return printError("entity-not-found") else return default end
end
end
return vals
end


function p.getFormattedQualifiers(statement, qualifs, params)
-- check if property exists
if not params then params = {} end
local realProp = mw.wikibase.resolvePropertyId(property)
local qualiftable = getQualifiers(statement, qualifs, params)
if not realProp then
if not qualiftable then
local temp = mw.title.new(i18n["maintenance-pages"]["property-not-existing"], "Modul").exists
return nil
end
end
for i, j in pairs(qualiftable) do
 
local params = params
-- fetch the first claim of satisfying the given property
if j.datatype == 'globe-coordinate' then
local claims
params.displayformat = 'qualifier'
if entity.claims then claims = entity.claims[realProp] end
end
if not claims or not claims[1] then
qualiftable[i] = p.formatSnak(j, params)
if showerrors then return printError("property-not-found") else return default end
end
end
return linguistic.conj(qualiftable, params.lang or defaultlang, params.conjtype)
end


function p.formatStatement( statement, args )
--filter claims
if not statement.type or statement.type ~= 'statement' then
claims = filterClaims(frame, claims)
return formatError( 'unknown-claim-type', statement.type )
if not claims[1] then return default end
end
 
if not args then args = {} end
-- get initial sort indices
local lang = args.lang or defaultlang
local sortindices = {}
local str = p.formatSnak( statement.mainsnak, args )
for idx in pairs(claims) do
if args.showlang == true then
sortindices[#sortindices + 1] = idx
str = showLang(statement, str)
end
end


local qualifs = args.showqualifiers
local comparator
if qualifs then
if sort then
if type(qualifs) == 'string' then
comparator = function(a, b) --comparator function for sorting statements based on qualifier value
qualifs = mw.text.split(qualifs, ',')
-- 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 foundvalues = p.getFormattedQualifiers(statement, qualifs, args)
elseif sortInItem then
if foundvalues then
-- fill table sortkeys
if args.delimiter then
local sortkeys = {}
str = str .. args.delimiter .. foundvalues
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
str = str .. linguistic.inparentheses(foundvalues, lang)
sortkeys[#sortkeys + 1] = ""
end
end
end
end
end
comparator = function(a, b)
 
if inverse then
if args.showdate then -- when "showdate and p.chronosort are both set, date retrieval is performed twice
return sortkeys[a] > sortkeys [b]
local timedata = p.getDate(statement)
else
if timedata then
return sortkeys[a] < sortkeys [b]
local formatteddate = objectToText(timedata, args)
formatteddate = linguistic.inparentheses(formatteddate, lang)
str = str .. '<small>' .. formatteddate ..'</small>'
end
end
 
if args.showsource and statement.references then
local cite = require 'Module:Cite'
local frame = mw.getCurrentFrame()
local sourcestring = ''
local s
for _, ref in ipairs(statement.references) do
if ref.snaks.P248 then
for j, source in pairs(ref.snaks.P248) do
if not isSpecial(source) then
local page
if ref.snaks.P304 and not isSpecial(ref.snaks.P304[1]) then
page = ref.snaks.P304[1].datavalue.value
end
s = cite.citeitem(source.datavalue.value.id, lang, page)
s = frame:extensionTag( 'ref', s )
sourcestring = sourcestring .. s
end
end
elseif ref.snaks.P854 and not isSpecial(ref.snaks.P854[1]) then
s = frame:extensionTag( 'ref', p.getDatavalue(ref.snaks.P854[1]) )
sourcestring = sourcestring .. s
end
end
end
end
str = str .. sourcestring
end
return str
end
function p.getmainid(claim)
if claim and not isSpecial(claim.mainsnak) then
return claim.mainsnak.datavalue.value.id
end
return nil
end
function p.formatSnak(snak, params)
--local params = params or {} pour faciliter l'appel depuis d'autres modules
if snak.snaktype == 'value' then
return p.getDatavalue(snak, params)
elseif snak.snaktype == 'somevalue' then
return formatTheUnknown()
elseif snak.snaktype == 'novalue' then
return i18n('novalue') --todo
else
else
return formatError( 'unknown-snak-type', snak.snaktype )
-- 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
end
table.sort(sortindices, comparator)


local function defaultLabel(entity, displayformat) -- label when no label is available
local result
if displayformat == 'id' then
local error
if type(entity) ~= 'table' then
if list then
return entity
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
return entity.id
result = table.concat(result, list)
end
end
end
else
return i18n('no-label')
-- return first element
end


function p._getLabel(entity, lang, default, fallback)
local claim = claims[sortindices[1]]
if not entity then
if language == "Q" then
return nil
result, error = "Q" .. getSnakValue(getQualifierSnak(claim), "numeric-id")
end
elseif language and claim.mainsnak.datatype == "monolingualtext" then
if not lang then
-- iterate over claims to find adequate language
lang = defaultlang
for idx, claim in pairs(claims) do
end
if claim.mainsnak.datavalue.value.language == language then
if type(entity) ~= 'table' and lang == defaultlang then
result, error = getValueOfClaim(claim, qualifierId, parameter)
local label, lg = mw.wikibase.getLabelWithLang(entity)
break
if label and (fallback ~= '-' or lg == lang) then
return label
end
else
entity = p.getEntity(entity)
if entity and entity.labels then
if fallback ~= '-' then
for _, lg in ipairs(fb.fblist(lang, true)) do
if entity.labels[lg] then
return entity.labels[lg].value
end
end
else
if entity.labels[lang] then
return entity.labels[lang].value
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
return defaultLabel(entity, default)
end


function p._getDescription(entity, lang, fallback)
if result then return result else
if not entity then
if showerrors then return error else return default end
return i18n('no description')
end
end
if not lang then
lang = defaultlang
end
if type(entity) ~= 'table' and lang == defaultlang then
local description, lg = mw.wikibase.getDescriptionWithLang(entity)
if description and (fallback ~= '-' or lg == lang) then
return description
end
else
entity = p.getEntity(entity)
if entity and entity.descriptions then
if fallback ~= '-' then
for _, lg in ipairs(fb.fblist(lang, true)) do
if entity.descriptions[lg] then
return entity.descriptions[lg].value
end
end
else
if entity.descriptions[lang] then
return entity.descriptions[lang].value
end
end
end
end
return i18n('no description')
end
end


local function formattedLabel(label, entity, args)
function p.getValue(frame)
local link = getLink(entity, args.link, args.lang)
local param = frame.args[2]
if not link then
if param == "FETCH_WIKIDATA" or param == i18n["FETCH_WIKIDATA"] then return p.claim(frame) else return param end
link = getLink(entity, defaultlink, args.lang)
end
if not link then
return label
else
return '[[' .. link .. '|' .. label .. ']]'
end
end
end


function p.formatEntity( entity, args )
function p.pageId(frame)
if not entity then
local id = frame.args[1]
return nil
local entity = mw.wikibase.getEntity(id)
end
if not entity then return "" else return entity.id end
if not args then
args = {}
end
local label = p._getLabel(entity, args.lang, 'id', args.fallback)
return formattedLabel(label, entity, args)
end
end


function p.getLabel(frame) -- simple for simple templates like {{Q|}}}
function p.labelOf(frame)
local args = frame.args
local id = frame.args[1]
local entity = args.entity
-- returns the label of the given entity/property id
local lang = args.lang
-- if no id is given, the one from the entity associated with the calling Wikipedia article is used
if not entity then
if not id then
return i18n('invalid-id')
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


if string.sub(entity, 1, 10) == 'Property:P' then
function p.sitelinkOf(frame)
entity = string.sub(entity, 10)
local id = frame.args[1]
elseif string.sub(entity, 1, 8) == 'Lexeme:L' then
-- returns the Wikipedia article name of the given entity
entity = string.sub(entity, 8)
-- if no id is given, the one from the entity associated with the calling Wikipedia article is used
elseif not ({L = 1, P = 1, Q = 1})[string.sub(entity, 1, 1)] or not tonumber(string.sub(entity, 2)) then
if not id then
return i18n('invalid-id')
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


if not args.link or args.link == '' or args.link == '-' then -- by default: no link
function p.badges(frame)
if lang == '' then
local site = frame.args[1]
lang = defaultlang
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 p._getLabel(entity, lang, args.default, args.fallback)
return result
else
return p.formatEntity(entity, args)
end
end
end
end


function p._formatStatements( args )--Format statements and concat them cleanly
function p.sitelinkCount(frame)
if args.value == '-' then
local filter = "^.*" .. (frame.args[1] or "") .. "$"
return nil
local id = frame.args[2]
end
--If a value is already set, use it
if args.value and args.value ~= '' then
return args.value
end
local valuetable = p.stringTable(args)
return tableToText(valuetable, args)
end


function p.showQualifier( args )
local entity = mw.wikibase.getEntity(id)
local qualifs = args.qualifiers or args.qualifier
local count = 0
if type(qualifs) == 'string' then
if entity and entity.sitelinks then
qualifs = mw.text.split(qualifs, ',')
for project, _ in pairs(entity.sitelinks) do
if string.find(project, filter) then count = count + 1 end
end
end
end
if not qualifs then
return count
return formatError( 'property-param-not-provided' )
end
local claims = p.getClaims(args)
if not claims then
return nil
end
local str = ''
local new
for _, cl in ipairs(claims) do
new = p.getFormattedQualifiers(cl, qualifs, args) or ''
str = str .. new
end
return str
end
end


function p._formatAndCat(args)
-- call this in cases of script errors within a function instead of {{#invoke:Wikidata|<method>|...}} call {{#invoke:Wikidata|debug|<method>|...}}
local val = p._formatStatements(args)
function p.debug(frame)
if val then
local func = frame.args[1]
return val .. addTrackingCat(args.property)
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
return nil
end
end


function p.getTheDate(args)
function p.printEntity(frame)
local claims = p.getClaims(args)
local id = frame.args[1]
if not claims then
local entity = mw.wikibase.getEntity(id)
return nil
if entity then return "<pre>" .. mw.text.jsonEncode(entity, mw.text.JSON_PRETTY) .. "</pre>" end
end
local formattedvalues = {}
for _, cl in ipairs(claims) do
table.insert(formattedvalues, p.getFormattedDate(cl))
end
local val = linguistic.conj(formattedvalues)
if val and args.addcat == true then
return val .. addTrackingCat(args.property)
else
return val
end
end
---FONCTIONS depuis le FRAME
function p.getaDate(frame)
return p.getTheDate(frame.args)
end
end


function p.getQualifier(frame)
-- formfill Template:Coordinate (NS, EW, name from WikidataEntity) and expand it
return p.showQualifier(frame.args)
-- füllt Vorlage:Coordinate (NS, EW, name mit Wikidata-Werten) + expandiert sie
end
--  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])


function p.getDescription(frame) -- simple for simple templates like {{Q|}}}
local t, r = f.args.type, f.args.region
local entity = frame.args.entity
if not t
if not entity then
then t = p.claim{ args = { "P31", id, id = id, language = "Q" }}
return i18n('invalid-id')
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
local lang = frame.args.lang
local fallback = frame.args.fallback
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
return p._getDescription(entity, lang, fallback)
) .. 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.formatStatements( args )
function p.ffCoordinateAndLatLonMaplink(frame)
return p._formatStatements( args )
frame.args.maplink = 1
--frame.args.mlname = nil
return p.ffCoordinate(frame)
end
end


function p.formatStatementsE(frame)
function p.ffCoordinateAndMaplink(frame)
local args = {}
frame.args.maplink = 1
if frame == mw.getCurrentFrame() then
frame.args.mlname = 1
args = frame:getParent().args -- paramètres du modèle appelant (est-ce vraiment une bonne idée ?)
return p.ffCoordinate(frame)
for k, v in pairs(frame.args) do
args[k] = v
end
else
args = frame
end
return p._formatStatements( args )
end
 
function p.formatAndCat(frame)
local args = {}
if frame == mw.getCurrentFrame() then
args = frame:getParent().args -- paramètres du modèle appelant (est-ce vraiment une bonne idée ?)
for k, v in pairs(frame.args) do
args[k] = v
end
else
args = frame
end
return p._formatAndCat( args )
end
end


function p.getEntityFromId(id)
return p.getEntity(id)
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