Super

-- 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.",
 ["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."
 },
 ["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"] = '%text',
 ["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 '' .. (i18n.errors[code] or code) .. ''
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 "" .. 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"] then return nil end

 -- load Modul:Zitation
 local ZitationSuccess, r = pcall(require, "Modul:Zitation")
 if type(r) == "table" then
 Zitation = r.Zitation()
 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"]))
 elseif not (ref["P1476"]) then
 Zitation.fill("bas", "Titel", ref["P854"])
 end
 refFormatted, f = Zitation.format()
 return refFormatted
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 = formatReference(refTable)
 if formattedRef then result = result .. frame:extensionTag("ref", formattedRef) 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)
 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

--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)

 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 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
 local entity = mw.wikibase.getEntity(id)
 if not entity then
 if showerrors then return printError("entity-not-found") else return default end
 end
 -- fetch the first claim of satisfying the given property
 local claims
 if entity.claims then claims = entity.claims[mw.wikibase.resolvePropertyId(property)] 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
 -- sort by time qualifier
 comparator = function(a, b)
 local timea = getQualifierSortValue(claims[a], sort)
 local timeb = getQualifierSortValue(claims[b], sort)
 if type(timea) ~= type(timeb) and not (tonumber(timea) and tonumber(timeb)) then
 if tonumber(timea) then return true
 elseif tonumber(timeb) then return false
 elseif tostring(timea) and tostring(timeb) then
 if inverse then return tostring(timea) > tostring(timeb) else return tostring(timea) < tostring(timeb) end
 else return false end -- different types, neither numbers nor strings, no chance to compare => random result to avoid script error
 elseif tonumber(timea) and tonumber(timeb) then
 timea = tonumber(timea)
 timeb = tonumber(timeb)
 end
 if inverse then
 return timea > timeb
 else
 return timea < timeb
 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 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 **Script error: No such module "Wikidata".** call **Script error: No such module "Wikidata".**
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 '' .. result .. '' end -- revert
 if status then return result else return '' .. result .. '' 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 "

```
" .. mw.text.jsonEncode(entity, mw.text.JSON_PRETTY) .. "
```
" end

end

return p