Module:Sandbox/Masoud.h1368/Wikidata.Ca

-- version 20210304 from master @cawiki

local p = {}

-- Initialization of variables --------------------

local i18n = { -- internationalisation at Module:Wikidata/i18n

["errors"] = {

["property-not-found"] = "Property not found.",

["qualifier-not-found"] = "Qualifier not found.",

},

["datetime"] = {

-- $1 is a placeholder for the actual number

["beforenow"] = "$1 BCE", -- how to format negative numbers for precisions 0 to 5

["afternow"] = "$1 CE", -- how to format positive numbers for precisions 0 to 5

["bc"] = "$1 BCE", -- how print negative years

["ad"] = "$1", -- how print 1st century AD dates

[0] = "$1 billion years", -- precision: billion years

[1] = "$100 million years", -- precision: hundred million years

[2] = "$10 million years", -- precision: ten million years

[3] = "$1 million years", -- precision: million years

[4] = "$100000 years", -- precision: hundred thousand years; thousand separators added afterwards

[5] = "$10000 years", -- precision: ten thousand years; thousand separators added afterwards

[6] = "$1 millennium", -- precision: millennium

[7] = "$1 century", -- precision: century

[8] = "$1s", -- precision: decade

-- the following use the format of #time parser function

[9] = "Y", -- precision: year,

[10] = "F Y", -- precision: month

[11] = "F j, Y", -- precision: day

["hms"] = {["hours"] = "h", ["minutes"] = "m", ["seconds"] = "s"}, -- duration: xh xm xs

},

["years-old"] = {

["singular"] = "", -- year old, as in {{PLURAL:$1|singular|plural}}

["plural"] = "", -- years old

["paucal"] = "", -- for languages with 3 plural forms as in {{PLURAL:$1|singular|paucal|plural}}

},

["cite"] = { -- cite parameters

["title"] = "title",

["author"] = "author",

["date"] = "date",

["pages"] = "pages",

["language"] = "language",

-- cite web parameters

["url"] = "url",

["website"] = "website",

["access-date"] = "access-date",

["archive-url"] = "archive-url",

["archive-date"] = "archive-date",

["publisher"] = "publisher",

["quote"] = "quote",

-- cite journal parameters

["work"] = "work",

["issue"] = "issue",

["issn"] = "issn",

["doi"] = "doi"

},

-- default local wiki settings

["addpencil"] = false, -- adds a pencil icon linked to Wikidata statement, planned to overwrite by Wikidata Bridge

["categorylabels"] = "", -- Category:Pages with Wikidata labels not translated (void for no local category)

["categoryprop"] = "", -- Category:Pages using Wikidata property $1 (void for no local category)

["categoryref"] = "", -- Category:Pages with references from Wikidata (void for no local category)

["addfallback"] = {} -- additional fallback language codes

}

local cases = {} -- functions for local grammatical cases defined at Module:Wikidata/i18n

local required = ... -- variadic arguments from require function

local wiki =

{

langcode = mw.language.getContentLanguage().code,

module_title = required or mw.getCurrentFrame():getTitle()

}

local untranslated -- used in infobox modules: nil or true

local _ -- variable for unused returned values, avoiding globals

-- Module local functions --------------------------------------------

-- Credit to http://stackoverflow.com/a/1283608/2644759, cc-by-sa 3.0

local function tableMerge(t1, t2)

for k, v in pairs(t2) do

if type(v) == "table" then

if type(t1[k] or false) == "table" then

tableMerge(t1[k] or {}, t2[k] or {})

else

t1[k] = v

end

else

t1[k] = v

end

end

return t1

end

local function loadI18n(lang)

local exist, res = pcall(require, wiki.module_title .. "/i18n")

if exist and next(res) ~= nil then

tableMerge(i18n, res.i18n)

cases = res.cases

end

if lang ~= wiki.langcode then

exist, res = pcall(require, wiki.module_title .. "/i18n/" .. lang)

if exist and next(res) ~= nil then

tableMerge(i18n, res.i18n)

tableMerge(cases, res.cases)

end

end

end

-- Table of language codes: requested or default and its fallbacks

local function findLang(langcode)

if mw.language.isKnownLanguageTag(langcode or '') == false then

local cframe = mw.getCurrentFrame()

local pframe = cframe:getParent()

langcode = pframe and pframe.args.lang

if mw.language.isKnownLanguageTag(langcode or '') == false then

if not mw.title.getCurrentTitle().isContentPage then

langcode = cframe:preprocess('{{int:lang}}')

end

if mw.language.isKnownLanguageTag(langcode or '') == false then

langcode = wiki.langcode

end

end

end

loadI18n(langcode)

local languages = mw.language.getFallbacksFor(langcode)

table.insert(languages, 1, langcode)

if langcode == wiki.langcode then

for _, l in ipairs(i18n.addfallback) do

table.insert(languages, l)

end

end

return languages

end

-- Argument is 'set' when it exists (not nil) or when it is not an empty string.

local function isSet(var)

return not (var == nil or (type(var) == 'string' and mw.text.trim(var) == ''))

end

-- Set local case to a label

local function case(localcase, label, ...)

if not isSet(label) then return label end

if type(localcase) == "function" then

return localcase(label)

elseif localcase == "smallcaps" then

return '' .. label .. ''

elseif cases[localcase] then

return cases[localcase](label, ...)

end

return label

end

-- get safely a serialized snak

local function getSnak(statement, snaks)

local ret = statement

for i, v in ipairs(snaks) do

if not ret then return end

ret = ret[v]

end

return ret

end

-- mw.wikibase.getLabelWithLang or getLabelByLang with a table of languages

local function getLabelByLangs(id, languages)

local label

local lang

for _, l in ipairs(languages) do

if l == wiki.langcode then

-- using getLabelWithLang when possible instead of getLabelByLang, do not solve redirects pending phab:T157868

label, l = mw.wikibase.getLabelWithLang(id)

else

label = mw.wikibase.getLabelByLang(id, l)

end

if label then

lang = l

break

end

end

return label, lang

end

-- getBestStatements if bestrank=true, else getAllStatements with no deprecated

local function getStatements(entityId, property, bestrank)

local claims = {}

if not (entityId and mw.ustring.match(property, "^P%d+$")) then return claims end

if bestrank then

claims = mw.wikibase.getBestStatements(entityId, property)

else

local allclaims = mw.wikibase.getAllStatements(entityId, property)

for _, c in ipairs(allclaims) do

if c.rank ~= "deprecated" then

table.insert(claims, c)

end

end

end

return claims

end

-- Is gender femenine? true or false

local function feminineGender(id)

local claims = mw.wikibase.getBestStatements(id or mw.wikibase.getEntityIdForCurrentPage(),'P21')

local gender_id = getSnak(claims, {1, "mainsnak", "datavalue", "value", "id"})

if gender_id == "Q6581072" or gender_id == "Q1052281" or gender_id == "Q43445" then -- female, transgender female, female organism

return true

end

return false

end

-- Fetch female form of label

local function feminineForm(id, lang)

local feminine_claims = getStatements(id, 'P2521')

for _, feminine_claim in ipairs(feminine_claims) do

if getSnak(feminine_claim, {'mainsnak', 'datavalue', 'value', 'language'}) == lang then

return feminine_claim.mainsnak.datavalue.value.text

end

end

end

-- Add an icon for no label in requested language

local function addLabelIcon(label_id, lang, uselang, icon)

local ret_lang, ret_icon = ,

if icon then

if lang and lang ~= uselang then

ret_lang = " (" .. lang .. ")"

end

if label_id and (lang == nil or lang ~= uselang) then

local namespace = ''

if string.sub(label_id, 1, 1) == 'P' then

namespace = 'Property:'

end

ret_icon = " [[File:Noun Project label icon 1116097 cc mirror.svg|10px|baseline|"

.. mw.message.new('Translate-taction-translate'):inLanguage(uselang):plain()

.. "|link=https://www.wikidata.org/wiki/" .. namespace .. label_id .. "?uselang=" .. uselang .. "]]"

untranslated = true

end

if isSet(i18n.categorylabels) and lang ~= uselang and uselang == wiki.langcode then

ret_icon = ret_icon .. '' .. i18n.categorylabels .. (lang and '' or '/Q]]')

end

end

return ret_lang .. ret_icon

end

-- editicon values: true/false (no=false), right, void defaults to i18n.addpencil

-- labelicon only by parameter

local function setIcons(arg, parg)

local val = arg == nil and parg or arg

local edit_icon, label_icon

if not isSet(val) then

edit_icon, label_icon = i18n.addpencil, true

elseif val == false or val == "false" or val == "no" then

edit_icon, label_icon = false, false

else

edit_icon, label_icon = val, true

end

return edit_icon, label_icon

end

-- Add an icon for editing a statement with requirements for Wikidata Bridge

local function addEditIcon(parameters)

local ret = ''

if parameters.editicon and parameters.id and parameters.property then

local icon_style = parameters.editicon == "right" and ' style="float: right;"' or ''

ret = ' '

.. "[[File:Arbcom ru editing.svg|10px|baseline|"

.. string.gsub(mw.message.new('Wikibase-client-data-bridge-bailout-suggestion-go-to-repo-button'):inLanguage(parameters.lang[1]):plain(), '{{WBREPONAME}}', 'Wikidata')

.. "|link=https://www.wikidata.org/wiki/" .. parameters.id .. "?uselang=" .. parameters.lang[1] .. "#" .. parameters.property .. "]]"

.. ""

if isSet(i18n.categoryprop) then

ret = ret .. "" .. string.gsub(i18n.categoryprop, '$1', parameters.property) .. ""

end

end

return ret

end

-- add edit icon to the last element of a table

local function addEditIconTable(thetable, parameters)

if #thetable == 0 or parameters.editicon == false then

return thetable

end

local last_element = thetable[#thetable]

local the_icon = addEditIcon(parameters)

-- add it before last html closing tags

local tags = ''

local rev_element = string.reverse(last_element)

for tag in string.gmatch(rev_element, '(>%l+/<)') do

if string.match(rev_element, '^' .. tags .. tag) then

tags = tags .. tag

else

break

end

end

local last_tags = string.reverse(tags)

local offset = string.find(last_element, last_tags .. '$')

if offset then

thetable[#thetable] = string.sub(last_element, 1, offset - 1) .. the_icon .. last_tags

else

thetable[#thetable] = last_element .. the_icon

end

return thetable

end

-- Escape Lua captures

local function captureEscapes(text)

return mw.ustring.gsub(text, "(%%%d)", "%%%1")

end

-- expandTemplate or callParserFunction

local function expandBraces(text, formatting)

if text == nil or formatting == nil then return text end

-- only expand braces if provided in argument, not included in value as in Q1164668

if mw.ustring.find(formatting, '{{', 1, true) == nil then return text end

if type(text) ~= "string" then

text = tostring(text)

end

for braces in mw.ustring.gmatch(text, "{{(.-)}}") do

local parts = mw.text.split(braces, "|")

local title_part = parts[1]

local parameters = {}

for i = 2, #parts do

local subparts = mw.ustring.find(parts[i], "=")

if subparts then

local param_name = mw.ustring.sub(parts[i], 1, subparts - 1)

local param_value = mw.ustring.sub(parts[i], subparts + 1, -1)

-- reconstruct broken links by parts

if i < #parts and mw.ustring.find(param_value, "", 1, true) and not mw.ustring.find(param_value, "", 1, true) then

parameters[param_name] = param_value

local part_next = i + 1

while parts[part_next] and mw.ustring.find(parts[part_next], "]]", 1, true) do

parameters[param_name] = parameters[param_name] .. "|" .. parts[part_next]

part_next = part_next + 1

end

else

parameters[param_name] = param_value

end

elseif not mw.ustring.find(parts[i], "]]", 1, true) then

table.insert(parameters, parts[i])

end

end

local braces_expanded

if mw.ustring.find(title_part, ":")

and mw.text.split(title_part, ":")[1] ~= mw.site.namespaces[10].name -- not a prefix Template:

then

braces_expanded = mw.getCurrentFrame():callParserFunction{name=title_part, args=parameters}

else

braces_expanded = mw.getCurrentFrame():expandTemplate{title=title_part, args=parameters}

end

braces = mw.ustring.gsub(braces, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- escape magic characters

braces_expanded = captureEscapes(braces_expanded)

text = mw.ustring.gsub(text, "{{" .. braces .. "}}", braces_expanded)

end

return text

end

-- Resolve Wikidata redirects, pending phab:T157868

local function resolveEntityId(id)

if not id or not mw.wikibase.isValidEntityId(id) then return id end

-- if no label in English, maybe it is a redirect

-- not using mw.title.new(id).isRedirect as it is expensive

-- currently getLabelByLang does not follows redirects

if mw.wikibase.getLabelByLang(id, 'en') == nil then

local entity = mw.wikibase.getEntity(id) -- expensive function

if not entity then return nil end

if id ~= entity.id then

-- Qid redirected to be fixed

-- see Special:WhatLinksHere/Template:Track/wikidata/redirect

require(wiki.module_title .. '/debug').track('redirect')

require(wiki.module_title .. '/debug').track('redirect/' .. id)

else

-- no redirect and no English label, fix it to avoid expensive functions

require(wiki.module_title .. '/debug').track('label')

require(wiki.module_title .. '/debug').track('label/' .. id)

end

return entity.id

end

return id

end

-- format data type math

local function printDatatypeMath(data)

return mw.getCurrentFrame():callParserFunction('#tag:math', data)

end

-- format data type musical-notation

local function printDatatypeMusical(data, formatting)

local attr = {}

if formatting == 'sound' then

attr.sound = 1

end

return mw.getCurrentFrame():extensionTag('score', data, attr)

end

-- format data type string

local function printDatatypeString(data, parameters)

if mw.ustring.find((parameters.formatting or ''), '$1', 1, true) then -- formatting = a pattern

return expandBraces(mw.ustring.gsub(parameters.formatting, '$1', {['$1'] = data}), parameters.formatting)

elseif parameters.case then

return case(parameters.case, data, parameters.lang[1], feminineGender(parameters.id))

end

local data_number = string.match(data, "^%d+")

if data_number then -- sort key by initial number and remaining string

local sortkey = string.format("%019d", data_number * 1000)

return data, sortkey .. string.sub(data, #data_number + 1)

end

return data

end

-- format data type url

local function printDatatypeUrl(data, parameters)

if parameters.formatting == 'weblink' then

local label_parts = mw.text.split(string.gsub(data, '/$', ''), '/')

local label = string.gsub(label_parts[3], '^www%.', '')

if #label_parts > 3 then

label = label .. '…'

end

return '[' .. data .. ' ' .. label .. ']'

end

return printDatatypeString(data, parameters)

end

-- format data type external-id

local function printDatatypeExternal(data, parameters)

if parameters.formatting == 'externalid' then

local p_stat = mw.wikibase.getBestStatements(parameters.property, 'P1630') -- formatter URL

local p_link_pattern = getSnak(p_stat, {1, "mainsnak", "datavalue", "value"})

if p_link_pattern then

local p_link = mw.ustring.gsub(p_link_pattern, '$1', {['$1'] = data})

return '[' .. p_link .. ' ' .. data .. ']'

end

end

return printDatatypeString(data, parameters)

end

-- format data type commonsMedia and geo-shape

local function printDatatypeMedia(data, parameters)

local icon

if not string.find((parameters.formatting or ''), '$1', 1, true) then

icon = "no-icon"

if not string.find(data, '^Data:') then

data = mw.uri.encode(data, 'PATH') -- encode special characters in filename

end

end

return printDatatypeString(data, parameters), icon

end

-- format data type globe-coordinate

local function printDatatypeCoordinate(data, formatting)

local function globes(globe_id)

local globes = {['Q3134']='callisto',['Q596']='ceres',['Q15040']='dione',['Q2']='earth',['Q3303']='enceladus',

['Q3143']='europa',['Q17975']='phoebe',['Q3169']='ganymede',['Q3123']='io',['Q17958']='iapetus',

['Q308']='mercury',['Q15034']='mimas',['Q405']='moon',['Q15050']='rhea',['Q15047']='tethys',

['Q111']='mars',['Q2565']='titan',['Q3359']='triton',['Q313']='venus',['Q3030']='vesta'}

return globes[globe_id]

end

local function roundPrecision(num, prec)

if prec == nil or prec <= 0 then return num end

local sig = 10^math.floor(math.log10(prec)+.5) -- significant figure from sexagesimal precision: 0.00123 -> 0.001

return math.floor(num / sig + 0.5) * sig

end

local precision = data.precision

local latitude = roundPrecision(data.latitude, precision)

local longitude = roundPrecision(data.longitude, precision)

if formatting and string.find(formatting, '$lat', 1, true) and string.find(formatting, '$lon', 1, true) then

local ret = mw.ustring.gsub(formatting, '$l[ao][tn]', {['$lat'] = latitude, ['$lon'] = longitude})

if string.find(formatting, '$globe', 1, true) then

local myglobe = 'earth'

if isSet(data.globe) then

local globenum = mw.text.split(data.globe, 'entity/')[2] -- http://www.wikidata.org/wiki/Q2

myglobe = globes(globenum) or 'earth'

end

ret = mw.ustring.gsub(ret, '$globe', myglobe)

end

return expandBraces(ret, formatting)

elseif formatting == 'latitude' then

return latitude, "no-icon"

elseif formatting == 'longitude' then

return longitude, "no-icon"

elseif formatting == 'dimension' then

return data.dimension, "no-icon"

else --default formatting='globe'

if isSet(data.globe) == false or data.globe == 'http://www.wikidata.org/entity/Q2' then

return 'earth', "no-icon"

else

local globenum = mw.text.split(data.globe, 'entity/')[2]

return globes(globenum) or globenum, "no-icon"

end

end

end

-- Local functions for data value quantity

local function unitSymbol(id, lang) -- get unit symbol or code

local unit_symbol = ''

if lang == wiki.langcode and pcall(require, wiki.module_title .. "/Units") then

unit_symbol = require(wiki.module_title .. "/Units").getUnit(0, '', id, true)

end

if unit_symbol == '' then

-- fetch it

local claims = mw.wikibase.getBestStatements(id, 'P5061')

if #claims > 0 then

local langclaims = {}

table.insert(lang, 'mul') -- multilingual as last try

for _, snak in ipairs(claims) do

local snak_language = getSnak(snak, {"mainsnak", "datavalue", "value", "language"})

if snak_language and not langclaims[snak_language] then -- just the first one by language

langclaims[snak_language] = snak.mainsnak.datavalue.value.text

end

end

for _, l in ipairs(lang) do

if langclaims[l] then

return langclaims[l]

end

end

end

end

return unit_symbol

end

local function getUnit(amount, id, parameters) -- get unit symbol or name

local suffix = ''

if parameters.formatting == "unitcode" then

-- get unit symbol

local unit_symbol = unitSymbol(id, parameters.lang)

if isSet(unit_symbol) then

suffix = unit_symbol

end

end

if suffix == '' then -- formatting=unit, or formatting=unitcode not found

-- get unit name

local unit_label, lang = getLabelByLangs(id, parameters.lang)

if lang == wiki.langcode and pcall(require, wiki.module_title .. "/Units") then

suffix = require(wiki.module_title .. "/Units").getUnit(amount, unit_label, id, false)

else

suffix = (unit_label or id) .. addLabelIcon(id, lang, parameters.lang[1], parameters.labelicon)

end

end

if suffix ~= '' then

suffix = ' ' .. suffix

end

return suffix

end

local function roundDefPrecision(in_num, factor)

-- rounds out_num with significant figures of in_num (default precision)

local out_num = in_num * factor

if factor/60 == math.floor(factor/60) or out_num == 0 then -- sexagesimal integer or avoiding NaN

return out_num

end

-- first, count digits after decimal mark, handling cases like '12.345e6'

local exponent, prec

local integer, dot, decimals, expstr = in_num:match('^(%d*)(%.?)(%d*)(.*)')

local e = expstr:sub(1, 1)

if e == 'e' or e == 'E' then

exponent = tonumber(expstr:sub(2))

end

if dot == '' then

prec = -integer:match('0*$'):len()

else

prec = #decimals

end

if exponent then

-- So '1230' and '1.23e3' both give prec = -1, and '0.00123' and '1.23e-3' give 5.

prec = prec - exponent

end

-- significant figures

local in_bracket = 10^-prec -- -1 -> 10, 5 -> 0.00001

local out_bracket = in_bracket * out_num / in_num

out_bracket = 10^math.floor(math.log10(out_bracket)+.5) -- 1230 -> 1000, 0.00123 -> 0.001

-- round it (credit to Luc Bloom from http://lua-users.org/wiki/SimpleRound)

return math.floor(out_num/out_bracket + (out_num >=0 and 1 or -1) * 0.5) * out_bracket

end

-- format data type quantity

local function printDatatypeQuantity(data, parameters)

local amount = data.amount

amount = mw.ustring.gsub(amount, "%+", "")

local suffix = ""

local conv_amount, conv_suffix

if string.sub(parameters.formatting or , 1, 4) == "unit" or string.sub(parameters.formatting or , 1, 8) == "duration" or parameters.convert then

local unit_id = data.unit

unit_id = mw.ustring.sub(unit_id, mw.ustring.find(unit_id, "Q"), -1)

if string.sub(unit_id, 1, 1) == "Q" then

suffix = getUnit(amount, unit_id, parameters)

local convert_to

if parameters.convert == "default" or parameters.convert == "default2" then

local exist, units = pcall(require, wiki.module_title .. "/Units")

if exist and units.convert_default and next(units.convert_default) ~= nil then

convert_to = units.convert_default[unit_id]

end

elseif string.sub(parameters.convert or '', 1, 1) == "Q" then

convert_to = resolveEntityId(parameters.convert)

elseif string.sub(parameters.formatting or '', 1, 8) == "duration" then

convert_to = 'Q11574' -- seconds

end

if convert_to and convert_to ~= unit_id then

-- convert units

local conv_temp = { -- formulae for temperatures ºC, ºF, ªK: [from] = {[to] = 'formula'}

['Q25267'] = {['Q42289'] = '$1*1.8+32', ['Q11597'] = '$1+273.15'},

['Q42289'] = {['Q25267'] = '($1-32)/1.8', ['Q11597'] = '($1+459.67)*5/9'},

['Q11597'] = {['Q25267'] = '$1-273.15', ['Q42289'] = '($1-273.15)*1.8000+32.00'}

}

if conv_temp[unit_id] and conv_temp[unit_id][convert_to] then

local amount_f = mw.getCurrentFrame():callParserFunction('#expr', mw.ustring.gsub(conv_temp[unit_id][convert_to], "$1", amount))

conv_amount = math.floor(tonumber(amount_f) + 0.5)

else

local conversions = getStatements(unit_id, 'P2442') -- conversion to standard unit

table.insert(conversions, mw.wikibase.getBestStatements(unit_id, 'P2370')[1]) -- conversion to SI unit

for _, conv in ipairs(conversions) do

if conv.mainsnak.snaktype == 'value' then -- no somevalue nor novalue

if conv.mainsnak.datavalue.value.unit == "http://www.wikidata.org/entity/" .. convert_to then

conv_amount = roundDefPrecision(amount, tonumber(conv.mainsnak.datavalue.value.amount))

break

end

end

end

end

if conv_amount then

conv_suffix = getUnit(conv_amount, convert_to, parameters)

end

elseif parameters.convert == 'M' and tonumber(amount) > 10^8 then

conv_amount = math.floor(amount/10^6 + 0.5)

conv_suffix = ' M' .. string.sub(suffix, 2)

end

end

end

local lang_obj = mw.language.new(parameters.lang[1])

local sortkey = string.format("%019d", tonumber(amount) * 1000)

if string.sub(parameters.formatting or '', 1, 8) == "duration" then

local sec = tonumber(conv_amount or amount)

if parameters.formatting == 'durationhms' or parameters.formatting == 'durationh:m:s' then

local intervals = {"hours", "minutes", "seconds"}

local sec2table = lang_obj:getDurationIntervals(sec, intervals)

sec2table["seconds"] = (sec2table["seconds"] or 0) + tonumber("." .. (tostring(sec):match("%.(%d+)") or "0")) -- add decimals

local duration = ''

for i, v in ipairs(intervals) do

if parameters.formatting == 'durationh:m:s' then

if i == 1 and sec2table[v] then

duration = duration .. sec2table[v] .. ":"

elseif i == 2 then

duration = duration .. string.format("%02d", sec2table[v] or 0) .. ":"

elseif i == 3 then

local sec_str = tostring(lang_obj:formatNum(sec2table[v] or 0))

duration = duration .. (sec2table[v] < 10 and "0" or "") .. sec_str

end

elseif sec2table[v] then

duration = duration .. lang_obj:formatNum(sec2table[v]) .. i18n.datetime.hms[v] .. (i < 3 and " " or "")

end

end

return duration

else

return lang_obj:formatDuration(sec)

end

end

if parameters.case then

amount = case(parameters.case, amount, parameters.lang[1])

elseif parameters.formatting ~= 'raw' then

amount = lang_obj:formatNum(tonumber(amount))

end

if conv_amount then

local conv_sortkey = string.format("%019d", conv_amount * 1000)

conv_amount = lang_obj:formatNum(conv_amount)

if parameters.convert == 'default2' then

return conv_amount .. conv_suffix .. ' (' .. amount .. suffix .. ')', conv_sortkey

else

return conv_amount .. conv_suffix, conv_sortkey

end

elseif mw.ustring.find((parameters.formatting or ''), '$1', 1, true) then -- formatting with pattern

amount = mw.ustring.gsub(parameters.formatting, '$1', {['$1'] = amount})

end

return amount .. suffix, sortkey

end

-- format data type time

local function printDatatypeTime(data, parameters)

-- Dates and times are stored in ISO 8601 format

local timestamp = data.time

local post_format

local calendar_add = ""

local precision = data.precision or 11

if string.sub(timestamp, 1, 1) == '-' then

post_format = i18n.datetime["bc"]

elseif string.sub(timestamp, 2, 3) == '00' then

post_format = i18n.datetime["ad"]

elseif precision > 8 then

-- calendar model

local calendar_model = {["Q12138"] = "gregorian", ["Q1985727"] = "gregorian", ["Q11184"] = "julian", ["Q1985786"] = "julian"}

local calendar_id = mw.text.split(data.calendarmodel, 'entity/')[2]

if (timestamp < "+1582-10-15T00:00:00Z" and calendar_model[calendar_id] == "gregorian")

or (timestamp > "+1582-10-04T00:00:00Z" and calendar_model[calendar_id] == "julian")

then

calendar_add = " (" .. mw.message.new('Wikibase-time-calendar-' .. calendar_model[calendar_id]):inLanguage(parameters.lang[1]):plain() .. ")"

end

end

local function formatTime(form, stamp)

local pattern

if type(form) == "function" then

pattern = form(stamp)

else

pattern = form

end

stamp = tostring(stamp)

if mw.ustring.find(pattern, "$1") then

return mw.ustring.gsub(pattern, "$1", stamp)

elseif string.sub(stamp, 1, 1) == '-' then -- formatDate() only supports years from 0

stamp = '+' .. string.sub(stamp, 2)

elseif string.sub(stamp, 1, 1) ~= '+' then -- not a valid timestamp, it is a number

stamp = string.format("%04d", stamp)

end

local ret = mw.language.new(parameters.lang[1]):formatDate(pattern, stamp)

ret = string.gsub(ret, "^(%[?%[?)0+", "%1") -- supress leading zeros

ret = string.gsub(ret, "( %[?%[?)0+", "%1")

return ret

end

local function postFormat(t)

if post_format and mw.ustring.find(post_format, "$1") then

return mw.ustring.gsub(post_format, "$1", t)

end

return t

end

local intyear = tonumber(string.match(timestamp, "[+-](%d+)"))

local ret = ""

if precision <= 5 then -- precision is 10000 years or more

local factor = 10 ^ ((5 - precision) + 4)

local y2 = math.ceil(math.abs(intyear) / factor)

local relative = formatTime(i18n.datetime[precision], y2)

if post_format == i18n.datetime["bc"] then

ret = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative)

else

ret = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative)

end

local ret_number = string.match(ret, "%d+")

if ret_number ~= nil then

ret = mw.ustring.gsub(ret, ret_number, mw.language.new(parameters.lang[1]):formatNum(tonumber(ret_number)))

end

elseif precision == 6 or precision == 7 then -- millennia or centuries

local card = math.floor((intyear - 1) / 10^(9 - precision)) + 1

ret = formatTime(i18n.datetime[precision], card)

ret = postFormat(ret)

elseif precision == 8 then -- decades

local card = math.floor(math.abs(intyear) / 10) * 10

ret = formatTime(i18n.datetime[8], card)

ret = postFormat(ret)

elseif intyear > 9999 then -- not a valid timestamp

return

elseif precision == 9 or parameters.formatting == 'Y' then -- precision is year

ret = formatTime(i18n.datetime[9], intyear)

ret = postFormat(ret) .. calendar_add

elseif precision == 10 then -- month

ret = formatTime(i18n.datetime[10], timestamp .. " + 1 day") -- formatDate yyyy-mm-00 returns the previous month

ret = postFormat(ret) .. calendar_add

else -- precision 11, day

ret = formatTime(parameters.formatting or i18n.datetime[11], timestamp)

ret = postFormat(ret) .. calendar_add

end

return ret, timestamp

end

-- format data value wikibase-entityid: types wikibase-item, wikibase-property

local function printDatatypeEntity(data, parameters)

local entity_id = data['id']

if parameters.formatting == 'raw' then

return entity_id, entity_id

end

local entity_page = 'Special:EntityPage/' .. entity_id

local label, lang = getLabelByLangs(entity_id, parameters.lang)

local sitelink = mw.wikibase.getSitelink(entity_id)

local parameter = parameters.formatting

local labelcase = label or sitelink

if parameters.gender == 'feminineform' then

labelcase = feminineForm(entity_id, lang) or labelcase

end

if parameters.case ~= 'gender' then

labelcase = case(parameters.case, labelcase, lang, parameters.lang[1], entity_id, parameters.id)

end

local ret1, ret2

if parameter == 'label' then

ret1 = labelcase or entity_id

ret2 = labelcase or entity_id

elseif parameter == 'sitelink' then

ret1 = (sitelink or 'd:' .. entity_page)

ret2 = sitelink or entity_id

elseif mw.ustring.find((parameter or ''), '$1', 1, true) then -- formatting = a pattern

ret1 = mw.ustring.gsub(parameter, '$1', labelcase or entity_id)

ret1 = expandBraces(ret1, parameter)

ret2 = labelcase or entity_id

else

if parameter == "ucfirst" or parameter == "ucinternallink" then

if labelcase and lang then

labelcase = mw.language.new(lang):ucfirst(labelcase)

end

-- only first of a list, reset formatting for next ones

if parameter == "ucinterlanllink" then

parameters.formatting = 'internallink'

else

parameters.formatting = nil -- default format

end

end

if sitelink then

ret1 = '' .. labelcase .. ''

ret2 = labelcase

elseif label and string.match(parameter or '', 'internallink$') and not mw.wikibase.getEntityIdForTitle(label) then

ret1 = '' .. labelcase .. ''

ret2 = labelcase

else

ret1 = '' .. (labelcase or entity_id) .. ''

ret2 = labelcase or entity_id

end

end

return ret1 .. addLabelIcon(entity_id, lang, parameters.lang[1], parameters.labelicon), ret2

end

-- format data type monolingualtext

local function printDatatypeMonolingual(data, parameters)

-- data fields: language [string], text [string]

if parameters.list == "lang" and data["language"] ~= parameters.lang[1] then

return

elseif parameters.list == "notlang" and data["language"] == parameters.lang[1] then

return

elseif parameters.formatting == "language" or parameters.formatting == "text" then

return data[parameters.formatting]

end

local result = data["text"]

if data["language"] ~= wiki.langcode then

result = mw.ustring.gsub('$2', '$[12]', {["$1"]=data["language"], ["$2"]=data["text"]})

end

if mw.ustring.find((parameters.formatting or ''), '$', 1, true) then

-- output format defined with $text, $language

result = mw.ustring.gsub(parameters.formatting, '$text', result)

result = mw.ustring.gsub(result, '$language', data["language"])

end

return result

end

local function getSnakValue(snak, parameters)

if snak.snaktype == 'value' then

-- see Special:ListDatatypes

if snak.datatype == "string" then

return printDatatypeString(snak.datavalue.value, parameters)

-- other data value string, tabular-data not implemented

elseif snak.datatype == "commonsMedia" or snak.datatype == "geo-shape" then

return printDatatypeMedia(snak.datavalue.value, parameters)

elseif snak.datatype == "url" then

return printDatatypeUrl(snak.datavalue.value, parameters)

elseif snak.datatype == "external-id" then

return printDatatypeExternal(snak.datavalue.value, parameters)

elseif snak.datatype == 'math' then

return printDatatypeMath(snak.datavalue.value)

elseif snak.datatype == 'musical-notation' then

return printDatatypeMusical(snak.datavalue.value, parameters.formatting)

-- other data types

elseif snak.datatype == 'wikibase-item' or snak.datatype == 'wikibase-property' then

return printDatatypeEntity(snak.datavalue.value, parameters)

elseif snak.datatype == 'monolingualtext' then

return printDatatypeMonolingual(snak.datavalue.value, parameters)

elseif snak.datatype == "globe-coordinate" then

return printDatatypeCoordinate(snak.datavalue.value, parameters.formatting)

elseif snak.datatype == "quantity" then

return printDatatypeQuantity(snak.datavalue.value, parameters)

elseif snak.datatype == "time" then

return printDatatypeTime(snak.datavalue.value, parameters)

end

elseif snak.snaktype == 'novalue' then

if parameters.formatting == 'raw' or parameters.shownovalue == false then return end

return mw.message.new('Wikibase-snakview-snaktypeselector-novalue'):inLanguage(parameters.lang[1]):plain()

elseif snak.snaktype == 'somevalue' then

if parameters.formatting == 'raw' then return end

return mw.message.new('Wikibase-snakview-snaktypeselector-somevalue'):inLanguage(parameters.lang[1]):plain()

end

return mw.wikibase.renderSnak(snak)

end

local function printError(key)

return '' .. i18n.errors[key] .. ''

end

local function getQualifierSnak(claim, qualifierId, parameters)

-- 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.qualifiers then

local qualifier = claim.qualifiers[qualifierId]

if qualifier then

if qualifier[1].datatype == "monolingualtext" then

-- iterate over monolingualtext qualifiers to get local language

for idx in pairs(qualifier) do

if getSnak(qualifier[idx], {"datavalue", "value", "language"}) == parameters.lang[1] then

return qualifier[idx]

end

end

elseif parameters.list then

return qualifier

else

return qualifier[1]

end

end

end

return nil, printError("qualifier-not-found")

else

-- otherwise return the main snak

return claim.mainsnak

end

end

local function getValueOfClaim(claim, qualifierId, parameters)

local snak, error = getQualifierSnak(claim, qualifierId, parameters)

if not snak then

return nil, nil, error

elseif snak[1] then -- a multi qualifier

local result, sortkey = {}, {}

local maxvals = tonumber(parameters.list)

for idx in pairs(snak) do

result[#result + 1], sortkey[#sortkey + 1] = getSnakValue(snak[idx], parameters)

if maxvals and maxvals == #result then break end

end

return mw.text.listToText(result, parameters.qseparator, parameters.qconjunction), sortkey[1]

else -- a property or a qualifier

return getSnakValue(snak, parameters)

end

end

local function getValueOfParentClaim(claim, qualifierId, parameters)

local qids = mw.text.split(qualifierId, '/', true)

local value, sortkey, valueraw = {}, {}, {}

local parent_raw, value_text

if qids[1] == parameters.property then

parent_raw, _, _ = getValueOfClaim(claim, nil, {["formatting"]="raw", ["lang"]=parameters.lang})

else

parent_raw, _, _ = getValueOfClaim(claim, qids[1], {["formatting"]="raw", ["lang"]=parameters.lang, ["list"]=true, ["qseparator"]='/', ["qconjunction"]='/'})

end

if string.sub(parent_raw or '', 1, 1) == "Q" then -- protection for 'no value'

local parent_qids = mw.text.split(parent_raw, '/', true)

for idx, p_qid in ipairs(parent_qids) do

local parent_claims = mw.wikibase.getBestStatements(p_qid, qids[2])

if parent_claims[1] then

value[idx], sortkey[idx], _ = getValueOfClaim(parent_claims[1], nil, parameters)

-- raw parent value needed for while/black lists, lang for avoiding an error on types other than entity

valueraw[idx], _, _ = getValueOfClaim(parent_claims[1], nil, {["formatting"]="raw", ["lang"]=parameters.lang})

end

end

end

if value[1] then

value_text = mw.text.listToText(value, parameters.qseparator, parameters.qconjunction)

end

return value_text, sortkey[1], valueraw[1]

end

-- see d:Help:Sources

local function getReferences(claim, lang)

local notproperref = {

["P143"] = true, -- imported from

["P3452"] = true, -- inferred from

["P887"] = true, -- based on heuristic

["P4656"] = true -- Wikimedia import URL

}

local result = ""

-- traverse through all references

for ref in pairs(claim.references or {}) do

local refparts

local refs = {}

local validref = true

local ref_name

-- traverse through all parts of the current reference

for snakkey, snakval in pairs(claim.references[ref].snaks or {}) do

for partkey, _ in pairs(claim.references[ref].snaks[snakkey] or {}) do

if notproperref[snakkey] then -- not a proper reference

validref = false

break

end

end

if validref then

for snakidx = 1, #snakval do

if snakidx > 1 then refparts = refparts .. ", " end

refparts = refparts or .. (getSnakValue(snakval[snakidx], {lang=lang}) or )

end

refs[snakkey] = refparts

refparts = nil

if snakkey == "P248" then -- stated in

ref_name = getSnak(snakval, {1, "datavalue", "value", "id"})

end

end

end

-- fill missing values with parent item

if ref_name then

local function refParent(qid, pid, formatting)

local snak = getSnak(mw.wikibase.getBestStatements(qid, pid), {1, "mainsnak"})

return snak and getSnakValue(snak, {formatting=formatting, lang=lang})

end

refs['P50'] = refs['P50'] or refParent(ref_name, 'P50', 'label') -- author

refs['P407'] = refs['P407'] or refParent(ref_name, 'P407', 'label') -- language of work

refs['P123'] = refs['P123'] or refParent(ref_name, 'P123', 'label') -- publisher

refs['P577'] = refs['P577'] or refParent(ref_name, 'P577') -- date

refs['P1433'] = refs['P1433'] or refParent(ref_name, 'P1433', 'label') -- published in

refs['P304'] = refs['P304'] or refParent(ref_name, 'P304') -- page(s)

refs['P433'] = refs['P433'] or refParent(ref_name, 'P433') -- issue

refs['P236'] = refs['P236'] or refParent(ref_name, 'P236') -- ISSN

refs['P356'] = refs['P356'] or refParent(ref_name, 'P356') -- DOI

ref_name = ref_name .. claim.references[ref].hash

end

-- get title of local templates for citing references

local template_web = mw.wikibase.getSitelink('Q5637226') or ""

template_web = mw.text.split(template_web, ":")[2] -- split off namespace from front

local template_journal = mw.wikibase.getSitelink('Q5624899') or ""

template_journal = mw.text.split(template_journal, ":")[2]

local citeParams = {}

if refs['P854'] and (refs['P1476'] or refs['P248']) and template_web then

-- if both "reference URL" and "title" (or "stated in") are present, then use cite web template

citeParams[i18n['cite']['url']] = refs['P854']

if refs['P248'] and refs['P1476'] == nil then

citeParams[i18n['cite']['title']] = refs['P248']:match("^%[%[.-|(.-)%]%]")

else

citeParams[i18n['cite']['title']] = refs['P1476']

citeParams[i18n['cite']['website']] = refs['P248']

end

citeParams[i18n['cite']['author']] = refs['P50']

citeParams[i18n['cite']['language']] = refs['P407']

citeParams[i18n['cite']['publisher']] = refs['P123']

citeParams[i18n['cite']['date']] = refs['P577']

citeParams[i18n['cite']['pages']] = refs['P304']

citeParams[i18n['cite']['access-date']] = refs['P813']

citeParams[i18n['cite']['archive-url']] = refs['P1065']

citeParams[i18n['cite']['archive-date']] = refs['P2960']

citeParams[i18n['cite']['quote']] = refs['P1683']

refparts = mw.getCurrentFrame():expandTemplate{title=template_web, args=citeParams}

elseif refs['P1433'] and (refs['P1476'] or refs['P248']) and template_journal then

-- if both "published in" and "title" (or "stated in") are present, then use cite journal template

citeParams[i18n['cite']['work']] = refs['P1433']

citeParams[i18n['cite']['title']] = refs['P1476'] or refs['P248']

citeParams[i18n['cite']['author']] = refs['P50']

citeParams[i18n['cite']['date']] = refs['P577']

citeParams[i18n['cite']['issue']] = refs['P433']

citeParams[i18n['cite']['pages']] = refs['P304']

citeParams[i18n['cite']['language']] = refs['P407']

citeParams[i18n['cite']['issn']] = refs['P236']

citeParams[i18n['cite']['doi']] = refs['P356']

refparts = mw.getCurrentFrame():expandTemplate{title=template_journal, args=citeParams}

elseif validref then

-- raw ouput

local snaksorder = claim.references[ref]["snaks-order"]

local function indexed(a)

for _, b in ipairs(snaksorder) do

if b == a then return true end

end

return false

end

for k, _ in pairs(refs or {}) do

if not indexed(k) then

table.insert(snaksorder, k)

end

end

local italics = "''"

for _, k in ipairs(snaksorder) do

if refs[k] then

refparts = refparts and refparts .. " " or ""

refparts = refparts .. mw.ustring.gsub(getLabelByLangs(k, lang), "^%l", mw.ustring.upper) .. ": "

refparts = refparts .. italics .. refs[k] .. italics .. "."

italics = ""

end

end

end

if refparts then result = mw.getCurrentFrame():extensionTag("ref", refparts, {name=ref_name}) end

end

if type(result) == 'string' and result ~= "" and isSet(i18n.categoryref) then

result = result .. "" ..i18n.categoryref .. ""

end

return result

end

-- Set whitelist or blacklist values

local function setWhiteOrBlackList(num_qual, args)

local lists = {['whitelist']={}, ['blacklist']={}, ['ignorevalue']={}, ['selectvalue']={}}

for i = 0, num_qual do

for k, _ in pairs(lists) do

if isSet(args[k .. i]) then

lists[k][tostring(i)] = {}

local pattern = 'Q%d+'

if string.sub(args[k .. i], 1, 1) ~= 'Q' then

pattern = '[^%p%s]+'

end

for q in string.gmatch(args[k .. i], pattern) do

lists[k][tostring(i)][resolveEntityId(q)] = true

end

end

end

end

return lists['whitelist'], lists['blacklist'], lists['ignorevalue'], lists['selectvalue']

end

local function tableParameters(args, parameters, column)

local column_params = mw.clone(parameters)

column_params.formatting = args["colformat"..column]; if column_params.formatting == "" then column_params.formatting = nil end

column_params.convert = args["convert" .. column]

if args["case" .. column] then

column_params.case = args["case" .. column]

end

return column_params

end

local function getEntityId(args, pargs, unnamed)

pargs = pargs or {}

local id = args.item or args.from or (unnamed and mw.text.trim(args[1] or '') or nil)

if not isSet(id) then

id = pargs.item or pargs.from or (unnamed and mw.text.trim(pargs[1] or '') or nil)

end

if isSet(id) then

if string.find(id, ":") then -- remove prefix as Property:Pid

id = mw.text.split(id, ":")[2]

end

else

id = mw.wikibase.getEntityIdForCurrentPage()

end

return id

end

local function getArg(value, default, aliases)

if type(value) == 'boolean' then return value

elseif value == "false" or value == "no" then return false

elseif value == "true" or value == "yes" then return true

elseif value and aliases and aliases[value] then return aliases[value]

elseif isSet(value) then return value

elseif default then return default

else return nil

end

end

-- Main function claim ---------------------------------------------

-- on debug console use: =p.claim{item="Q...", property="P...", ...}

function p.claim(frame)

local args = frame.args or frame -- via invoke or require

local pargs = frame.args and frame:getParent().args or {}

local is_sandbox = isSet(pargs.sandbox)

if not required and is_sandbox then

return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).claim(frame)

end

--If a value is already set, use it

if isSet(args.value) then

if args.value == 'NONE' then

return

else

return args.value

end

end

-- arguments

local parameters = {}

parameters.id = getEntityId(args, pargs)

if parameters.id == nil then return end

parameters.property = string.upper(args.property or "")

local qualifierId = {}

qualifierId[1] = getArg(string.upper(args.qualifier or ""))

local i = 2

while isSet(args["qualifier" .. i]) do

qualifierId[i] = string.upper(args["qualifier" .. i])

i = i + 1

end

parameters.formatting = getArg(args.formatting)

parameters.convert = getArg(args.convert)

parameters.case = args.case

parameters.list = getArg(args.list, true, {firstrank='bestrank'})

parameters.shownovalue = getArg(args.shownovalue, true)

parameters.separator = getArg(args.separator)

parameters.conjunction = getArg(args.conjunction, parameters.separator)

parameters.qseparator = parameters.separator

parameters.qconjunction = parameters.conjunction

local sorting_col = args.tablesort

local sorting_up = (args.sorting or "") ~= "-1"

local rowformat = args.rowformat

local references = args.references

local showerrors = args.showerrors

local default = args.default

if default then showerrors = nil end

parameters.lang = findLang(args.lang)

if parameters.formatting == "raw" then

parameters.editicon, parameters.labelicon = false, false

else

parameters.editicon, parameters.labelicon = setIcons(args.editicon, pargs.editicon) -- needs loadI18n by findLand

end

-- fetch property

local claims = {}

local bestrank = (parameters.list == false or parameters.list == 'bestrank') and parameters.list ~= 'lang'

for p in string.gmatch(parameters.property, 'P%d+') do

claims = getStatements(parameters.id, p, bestrank)

if #claims > 0 then

parameters.property = p

break

end

end

if #claims == 0 then

if showerrors then return printError("property-not-found") else return default end

end

-- defaults for table

local preformat, postformat = "", ""

local whitelisted = false

local whitelist, blacklist, ignorevalue, selectvalue = {}, {}, {}, {}

if parameters.formatting == "table" then

parameters.separator = parameters.separator or "
"

parameters.conjunction = parameters.conjunction or "
"

parameters.qseparator = ", "

parameters.qconjunction = ", "

if not rowformat then

rowformat = "$0 ($1"

i = 2

while qualifierId[i] do

rowformat = rowformat .. ", $" .. i

i = i + 1

end

rowformat = rowformat .. ")"

elseif mw.ustring.find(rowformat, "^[*#]") then

parameters.separator = "

  • "

    parameters.conjunction = "

  • "

    if mw.ustring.match(rowformat, "^[*#]") == "*" then

    preformat = "

    • "

      postformat = "

    "

    else

    preformat = "

    1. "

      postformat = "

    "

    end

    rowformat = mw.ustring.gsub(rowformat, "^[*#] ?", "")

    end

    -- set whitelist and blacklist values

    whitelist, blacklist, ignorevalue, selectvalue = setWhiteOrBlackList(#qualifierId, args)

    local next = next

    if next(whitelist) ~= nil then whitelisted = true end

    end

    -- set feminine case if gender is requested

    local itemgender = args.itemgender

    local idgender

    if itemgender then

    if string.match(itemgender, "^P%d+$") then

    local snak_id = getSnak(mw.wikibase.getBestStatements(parameters.id, itemgender), {1, "mainsnak", "datavalue", "value", "id"})

    if snak_id then

    idgender = snak_id

    end

    elseif string.match(itemgender, "^Q%d+$") then

    idgender = itemgender

    end

    end

    local gender_requested = false

    if parameters.case == "gender" or idgender then

    gender_requested = true

    elseif parameters.formatting == "table" then

    for i=0, #qualifierId do

    if args["case" .. i] and args["case" .. i] == "gender" then

    gender_requested = true

    break

    end

    end

    end

    if gender_requested then

    if feminineGender(idgender or parameters.id) then

    parameters.gender = "feminineform"

    end

    end

    -- get initial sort indices

    local sortindices = {}

    for idx in pairs(claims) do

    sortindices[#sortindices + 1] = idx

    end

    -- sort by claim rank

    local 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

    table.sort(sortindices, comparator)

    local result, result2, result_query

    local error

    if parameters.list or parameters.formatting == "table" then

    -- convert LF to line feed,
    may not work on some cases

    parameters.separator = parameters.separator == "LF" and "\010" or parameters.separator

    parameters.conjunction = parameters.conjunction == "LF" and "\010" or parameters.conjunction

    -- i18n separators

    parameters.separator = parameters.separator or mw.message.new('Comma-separator'):inLanguage(parameters.lang[1]):plain()

    parameters.conjunction = parameters.conjunction or (mw.message.new('And'):inLanguage(parameters.lang[1]):plain() .. mw.message.new('Word-separator'):inLanguage(parameters.lang[1]):plain())

    -- iterate over all elements and return their value (if existing)

    local value, valueq

    local sortkey, sortkeyq

    local values = {}

    local sortkeys = {}

    local refs = {}

    local rowlist = {} -- rows to list with whitelist or blacklist

    for idx in pairs(claims) do

    local claim = claims[sortindices[idx]]

    local reference = {}

    if not whitelisted then rowlist[idx] = true end

    if parameters.formatting == "table" then

    local params = tableParameters(args, parameters, "0")

    value, sortkey, error = getValueOfClaim(claim, nil, params)

    if value then

    values[#values + 1] = {}

    sortkeys[#sortkeys + 1] = {}

    refs[#refs + 1] = {}

    if whitelist["0"] or blacklist["0"] then

    local valueraw, _, _ = getValueOfClaim(claim, nil, {["formatting"]="raw", ["lang"]=params.lang})

    if whitelist["0"] and whitelist["0"][valueraw or ""] then

    rowlist[#values] = true

    elseif blacklist["0"] and blacklist["0"][valueraw or ""] then

    rowlist[#values] = false

    end

    end

    for i, qual in ipairs(qualifierId) do

    local j = tostring(i)

    params = tableParameters(args, parameters, j)

    local valueq, sortkeyq, valueraw

    if qual == parameters.property then -- hack for getting the property with another formatting, i.e. colformat1=raw

    valueq, sortkeyq, _ = getValueOfClaim(claim, nil, params)

    else

    for q in mw.text.gsplit(qual, '%s*OR%s*') do

    if string.find(q, ".+/.+") then

    valueq, sortkeyq, valueraw = getValueOfParentClaim(claim, q, params)

    elseif string.find(q, "^/.+") then

    local claim2 = getStatements(parameters.id, string.sub(q, 2), bestrank)

    if #claim2 > 0 then

    valueq, sortkeyq, _ = getValueOfClaim(claim2[1], nil, params)

    end

    else

    valueq, sortkeyq, _ = getValueOfClaim(claim, q, params)

    end

    if valueq then

    qual = q

    break

    end

    end

    end

    values[#values]["col" .. j] = valueq

    sortkeys[#sortkeys]["col" .. j] = sortkeyq or valueq

    if whitelist[j] or blacklist[j] or ignorevalue[j] or selectvalue[j] then

    valueq = valueraw or getValueOfClaim(claim, qual, {["formatting"]="raw", ["lang"]=params.lang})

    if whitelist[j] and whitelist[j][valueq or ""] then

    rowlist[#values] = true

    elseif blacklist[j] and blacklist[j][valueq or ""] then

    rowlist[#values] = false

    elseif ignorevalue[j] and ignorevalue[j][valueq or ""] then

    values[#values]["col" .. j] = nil

    elseif selectvalue[j] and not selectvalue[j][valueq or ""] then

    values[#values]["col" .. j] = nil

    end

    end

    end

    end

    else

    value, sortkey, error = getValueOfClaim(claim, qualifierId[1], parameters)

    values[#values + 1] = {}

    sortkeys[#sortkeys + 1] = {}

    refs[#refs + 1] = {}

    end

    if not value and showerrors then value = error end

    if value then

    if references and claim.references then reference = claim.references end

    refs[#refs]["col0"] = reference

    values[#values]["col0"] = value

    sortkeys[#sortkeys]["col0"] = sortkey or value

    end

    end

    -- sort and format results

    sortindices = {}

    for idx in pairs(values) do

    sortindices[#sortindices + 1] = idx

    end

    if sorting_col then

    local sorting_table = mw.text.split(sorting_col, '%D+')

    local comparator = function(a, b)

    local valuea, valueb

    local i = 1

    while valuea == valueb and i <= #sorting_table do

    valuea = sortkeys[a]["col" .. sorting_table[i]] or ''

    valueb = sortkeys[b]["col" .. sorting_table[i]] or ''

    i = i + 1

    end

    if sorting_up then

    return valueb > valuea

    end

    return valueb < valuea

    end

    table.sort(sortindices, comparator)

    end

    local maxvals = tonumber(parameters.list)

    result = {}

    for idx in pairs(values) do

    local valuerow = values[sortindices[idx]]

    local reference = getReferences({["references"] = refs[sortindices[idx]]["col0"]}, parameters.lang)

    value = valuerow["col0"]

    if parameters.formatting == "table" then

    if not rowlist[sortindices[idx]] then

    value = nil

    else

    local rowformatting = rowformat .. "$" -- fake end character added for easy gsub

    value = mw.ustring.gsub(rowformatting, "$0", {["$0"] = value})

    value = mw.ustring.gsub(value, "$R0", reference) -- add reference

    for i, _ in ipairs(qualifierId) do

    local valueq = valuerow["col" .. i]

    if args["rowsubformat" .. i] and isSet(valueq) then

    -- add fake end character $

    -- gsub $i not followed by a number so $1 doesn't match $10, $11...

    -- remove fake end character

    valueq = captureEscapes(valueq)

    valueq = mw.ustring.gsub(args["rowsubformat" .. i] .. "$", "$" .. i .. "(%D)", valueq .. "%1")

    valueq = string.sub(valueq, 1, -2)

    rowformatting = mw.ustring.gsub(rowformatting, "$" .. i .. "(%D)", args["rowsubformat" .. i] .. "%1")

    end

    valueq = valueq and captureEscapes(valueq) or ''

    value = mw.ustring.gsub(value, "$" .. i .. "(%D)", valueq .. "%1")

    end

    value = string.sub(value, 1, -2) -- remove fake end character

    value = expandBraces(value, rowformatting)

    end

    elseif value then

    value = expandBraces(value, parameters.formatting)

    value = value .. reference

    end

    if isSet(value) then

    result[#result + 1] = value

    if not parameters.list or (maxvals and maxvals == #result) then

    break

    end

    end

    end

    if args.query == 'num' then

    result_query = 0

    for _, v in pairs(rowlist) do

    result_query = result_query + (v and 1 or 0)

    end

    end

    if #result > 0 then

    if parameters.formatting == 'table' then

    result = addEditIconTable(result, parameters) -- in a table, add edit icon on last element

    end

    result = preformat .. mw.text.listToText(result, parameters.separator, parameters.conjunction) .. postformat

    else

    result = ''

    end

    else

    -- return first element

    local claim = claims[sortindices[1]]

    result, result2, error = getValueOfClaim(claim, qualifierId[1], parameters)

    if result and references then result = result .. getReferences(claim, parameters.lang) end

    if args.query == 'num' then result_query = 1 end

    end

    if isSet(result) then

    if not (parameters.formatting == 'table' or (result2 and result2 == 'no-icon')) then

    -- add edit icon, except table added previously and except explicit no-icon internal flag

    result = result .. addEditIcon(parameters)

    end

    else

    if showerrors then result = error else result = default end

    end

    if args.query == 'untranslated' and required and not is_sandbox then

    result_query = untranslated

    end

    return result, result_query or ''

    end

    -- Local functions for getParentValues -----------------------

    local function uc_first(word)

    if word == nil then return end

    return mw.ustring.upper(mw.ustring.sub(word, 1, 1)) .. mw.ustring.sub(word, 2)

    end

    local function getPropertyValue(id, property, parameter, langs, labelicon, case)

    local snaks = mw.wikibase.getBestStatements(id, property)

    local mysnak = getSnak(snaks, {1, "mainsnak"})

    if mysnak == nil then

    return

    end

    local entity_id

    local result = '-' -- default for 'no value'

    if mysnak.datavalue then

    entity_id = "Q" .. tostring(mysnak.datavalue.value['numeric-id'])

    result, _ = getSnakValue(mysnak, {formatting=parameter, lang=langs, labelicon=labelicon, case=case})

    end

    return entity_id, result

    end

    local function getParentObjects(id,

    prop_format,

    label_format,

    languages,

    propertySupString,

    propertyLabel,

    propertyLink,

    label_show,

    labelicon0,

    labelicon1,

    upto_number,

    upto_label,

    upto_value,

    last_only,

    grammatical_case,

    include_self)

    local propertySups = mw.text.split(propertySupString, '[^P%d]')

    local maxloop = 10

    if upto_number then

    maxloop = tonumber(upto_number)

    elseif next(upto_label) or next(upto_value) then

    maxloop = 50

    end

    local labels_filter = next(label_show)

    local result = {}

    local id_value = id

    for iter = 1, maxloop do

    local link, label, labelwicon, linktext, id_label

    for _, propertySup in pairs(propertySups) do

    local _id_value, _link = getPropertyValue(id_value, propertySup, prop_format, languages, labelicon1, grammatical_case)

    if _id_value and _link then id_value = _id_value; link = _link break end

    end

    if not id_value or not link then break end

    if propertyLink then

    _, linktext = getPropertyValue(id_value, propertyLink, "label", languages)

    if linktext then

    link = mw.ustring.gsub(link, "%[%[(.*)%|.+%]%]", "" .. linktext .. "")

    end

    end

    id_label, label = getPropertyValue(id_value, propertyLabel, label_format, languages, false, "infoboxlabel")

    if labelicon0 then

    _, labelwicon = getPropertyValue(id_value, propertyLabel, label_format, languages, labelicon0, "infoboxlabel")

    else

    labelwicon = label

    end

    if labels_filter == nil or (label_show[id_label] or label_show[label]) then

    result[#result + 1] = {labelwicon, link}

    label_show[id_label or 'none'], label_show[label or 'none'] = nil, nil -- only first label found

    end

    if upto_label[id_label] or upto_label[label] or upto_value[id_value] then

    break

    end

    end

    if last_only then

    result = {result[#result]}

    end

    if include_self then

    local label_self, link_self

    _, label_self = getPropertyValue(id, propertyLabel, label_format, languages, labelicon0, "infoboxlabel")

    link_self, _ = getLabelByLangs(id, languages)

    table.insert(result, 1, {label_self, link_self})

    end

    return result

    end

    local function parentObjectsToString(result,

    rowformat,

    cascade,

    sorting)

    local ret = {}

    local first = 1

    local last = #result

    local iter = 1

    if sorting == "-1" then first = #result; last = 1; iter = -1 end

    for i = first, last, iter do

    local rowtext = mw.ustring.gsub(rowformat, "$[01]", {["$0"] = result[i][1], ["$1"] = result[i][2]})

    ret[#ret + 1] = expandBraces(rowtext, rowformat)

    end

    if cascade then

    local direction = mw.language.new(wiki.langcode):isRTL() and "right" or "left"

    local suffix = ""

    for i = 1, #ret do

    ret[i] = '

    • ' .. ret[i]

      suffix = suffix .. '

    '

    end

    ret[#ret] = ret[#ret] .. suffix

    end

    return ret

    end

    -- Returns pairs of parent label and property value fetching a recursive tree

    function p.getParentValues(frame)

    local args = frame.args or frame -- via invoke or require

    local pargs = frame.args and frame:getParent().args or {}

    if not required and isSet(pargs.sandbox) then

    return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).getParentValues(frame)

    end

    local id = getEntityId(args, pargs)

    if id == nil then return end

    local languages = findLang(args.lang)

    local propertySup = getArg(args.property, "P131") --administrative entity

    local propertyLabel = getArg(args.label, "P31") --instance

    local propertyLink = getArg(args.valuetext)

    local property_format = getArg(args.formatting)

    local label_format = getArg(args.labelformat, "label")

    local upto_number = getArg(args.upto)

    local last_only = getArg(args.last_only, false)

    local editicon, labelicon = setIcons(args.editicon, pargs.editicon)

    local include_self = getArg(args.include_self, false)

    local case = getArg(args.case)

    local upto_label = {}

    for q in string.gmatch(args.uptolabelid or '', 'Q%d+') do

    local checked_q = resolveEntityId(q)

    if checked_q then

    upto_label[checked_q] = true

    end

    end

    if type(upto_number) == 'string' then

    upto_label[uc_first(upto_number)] = true

    upto_number = nil

    require(wiki.module_title .. '/debug').track('upto') -- replace upto by uptolabelid

    end

    local upto_value = {}

    for q in string.gmatch(args.uptovalueid or args.uptolinkid or '', 'Q%d+') do

    local checked_q = resolveEntityId(q)

    if checked_q then

    upto_value[checked_q] = true

    end

    end

    local label_show = {}

    for q in string.gmatch(args.showlabelid or '', 'Q%d+') do

    local checked_q = resolveEntityId(q)

    if checked_q then

    label_show[checked_q] = true

    end

    end

    for _, v in ipairs(mw.text.split(args.labelshow or '', "/")) do

    if v ~= '' then

    label_show[uc_first(v)] = true

    require(wiki.module_title .. '/debug').track('labelshow') -- replace labelshow by showlabelid

    end

    end

    local rowformat = args.rowformat; if not isSet(rowformat) then rowformat = "$0 = $1" end

    local labelicon0, labelicon1 = labelicon, labelicon

    if string.find(label_format, '{{.*$0.*}}') or (string.find(rowformat, '{{.*$0.*}}') and label_format ~= 'raw') then

    labelicon0 = false

    end

    local result = getParentObjects(id,

    property_format,

    label_format,

    languages,

    propertySup,

    propertyLabel,

    propertyLink,

    label_show,

    labelicon0,

    labelicon1,

    upto_number,

    upto_label,

    upto_value,

    last_only,

    case,

    include_self)

    if #result == 0 then return end

    local separator = args.separator; if not isSet(separator) then separator = "
    " end

    local sorting = args.sorting; if sorting == "" then sorting = nil end

    local cascade = (args.cascade == "true" or args.cascade == "yes")

    local ret = parentObjectsToString(result,

    rowformat,

    cascade,

    sorting)

    ret = addEditIconTable(ret, {property=propertySup, editicon=editicon, id=id, lang=languages})

    return mw.text.listToText(ret, separator, separator)

    end

    -- Link with a parent label --------------------

    function p.linkWithParentLabel(frame)

    local pargs = frame.args and frame:getParent().args or {}

    if not required and isSet(pargs.sandbox) then

    return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).linkWithParentLabel(frame)

    end

    local args = {}

    if frame.args then

    for k, v in pairs(frame.args) do -- metatable

    args[k] = v

    end

    else

    args = frame -- via require

    end

    if isSet(args.value) then

    return args.value

    end

    -- get id value of property/qualifier

    local largs = mw.clone(args)

    largs.list = tonumber(args.list) and args.list or true

    largs.formatting = "raw"

    largs.separator = "/·/"

    largs.editicon = false

    local items_list, _ = p.claim(largs)

    if not isSet(items_list) then return end

    local items_table = mw.text.split(items_list, "/·/", true)

    -- get internal link of property/qualifier

    largs.formatting = "internallink"

    local link_list, _ = p.claim(largs)

    local link_table = mw.text.split(link_list, "/·/", true)

    -- get label of parent property

    local parent_claim = getSnak(getStatements(items_table[1], args.parent, true), {1, "mainsnak", "datatype"})

    if parent_claim == 'monolingualtext' then

    largs.formatting = nil

    largs.list = 'lang'

    else

    largs.formatting = "label"

    largs.list = false

    end

    largs.property = args.parent

    largs.qualifier = nil

    for i, v in ipairs(items_table) do

    largs.item = v

    local link_label, _ = p.claim(largs)

    if isSet(link_label) then

    link_table[i] = mw.ustring.gsub(link_table[i] or '', "%[%[(.*)%|.+%]%]", "" .. link_label .. "")

    end

    end

    args.editicon, _ = setIcons(args.editicon, pargs.editicon)

    args.id = getEntityId(args, pargs)

    args.lang = findLang(args.lang)

    return mw.text.listToText(link_table) .. addEditIcon(args)

    end

    -- Calculate number of years old ----------------------------

    function p.yearsOld(frame)

    if not required and frame.args and isSet(frame:getParent().args.sandbox) then

    return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).yearsOld(frame)

    end

    local args = frame.args or frame -- via invoke or require

    local pargs = frame.args and frame:getParent().args or {}

    local id = getEntityId(args, pargs)

    if id == nil then return end

    local lang = mw.language.new('en')

    local function getBestValue(id, prop)

    return getSnak(mw.wikibase.getBestStatements(id, prop), {1, "mainsnak", "datavalue", "value"})

    end

    local birth = getBestValue(id, 'P569')

    if type(birth) ~= 'table' or birth.time == nil or birth.precision == nil or birth.precision < 8 then

    return

    end

    local death = getBestValue(id, 'P570')

    if type(death) ~= 'table' or death.time == nil or death.precision == nil then

    death = {['time'] = lang:formatDate('c'), ['precision'] = 11} -- current date

    elseif death.precision < 8 then

    return

    end

    local dates = {}

    dates[1] = {['min'] = {}, ['max'] = {}, ['precision'] = birth.precision}

    dates[1].min.year = tonumber(mw.ustring.match(birth.time, "^[+-]?%d+"))

    dates[1].min.month = tonumber(mw.ustring.match(birth.time, "\-(%d%d)\-"))

    dates[1].min.day = tonumber(mw.ustring.match(birth.time, "\-(%d%d)T"))

    dates[1].max = mw.clone(dates[1].min)

    dates[2] = {['min'] = {}, ['max'] = {}, ['precision'] = death.precision}

    dates[2].min.year = tonumber(mw.ustring.match(death.time, "^[+-]?%d+"))

    dates[2].min.month = tonumber(mw.ustring.match(death.time, "\-(%d%d)\-"))

    dates[2].min.day = tonumber(mw.ustring.match(death.time, "\-(%d%d)T"))

    dates[2].max = mw.clone(dates[2].min)

    for i, d in ipairs(dates) do

    if d.precision == 10 then -- month

    d.min.day = 1

    local timestamp = string.format("%04d", tostring(math.abs(d.max.year)))

    .. string.format("%02d", tostring(d.max.month))

    .. "01"

    d.max.day = tonumber(lang:formatDate("j", timestamp .. " + 1 month - 1 day"))

    elseif d.precision < 10 then -- year or decade

    d.min.day = 1

    d.min.month = 1

    d.max.day = 31

    d.max.month = 12

    if d.precision == 8 then -- decade

    d.max.year = d.max.year + 9

    end

    end

    end

    local function age(d1, d2)

    local years = d2.year - d1.year

    if d2.month < d1.month or (d2.month == d1.month and d2.day < d1.day) then

    years = years - 1

    end

    if d2.year > 0 and d1.year < 0 then

    years = years - 1 -- no year 0

    end

    return years

    end

    local old_min = age(dates[1].max, dates[2].min)

    local old_max = age(dates[1].min, dates[2].max)

    local old, old_expr

    if old_min == 0 and old_max == 0 then

    old = "< 1"

    old_max = 1 -- expression in singular

    elseif old_min == old_max then

    old = old_min

    else

    old = old_min .. "/" .. old_max

    end

    if args.formatting == 'unit' then

    local langs = findLang(args.lang)

    local yo, yo_sg, yo_pl, yo_pau

    if langs[1] == wiki.langcode then

    yo_sg = i18n["years-old"].singular

    yo_pl = i18n["years-old"].plural

    yo_pau = i18n["years-old"].paucal

    end

    if not isSet(yo_pl) then

    yo_pl, _ = getLabelByLangs('Q24564698', langs)

    yo_sg = yo_pl

    end

    if not isSet(yo_pau) then

    yo_pau = yo_pl

    end

    yo = mw.language.new(langs[1]):plural(old_max, {yo_sg, yo_pau, yo_pl})

    if mw.ustring.find(yo, '$1', 1, true) then

    old_expr = mw.ustring.gsub(yo, "$1", old)

    else

    old_expr = old .. ' ' .. yo

    end

    elseif args.formatting then

    old_expr = expandBraces(mw.ustring.gsub(args.formatting, '$1', old), args.formatting)

    else

    old_expr = old

    end

    return old_expr

    end

    -- Gets a label in a given language (content language by default) or its fallbacks, optionnally linked.

    function p.getLabel(frame)

    local args = frame.args or frame -- via invoke or require

    local pargs = frame.args and frame:getParent().args or {}

    if not required and isSet(pargs.sandbox) then

    return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).getLabel(frame)

    end

    local id = getEntityId(args, pargs, 1)

    if id == nil then return end

    local languages = findLang(args.lang)

    local labelicon = false

    if mw.wikibase.isValidEntityId(id) then

    _, labelicon = setIcons(args.editicon, pargs.editicon)

    end

    local label_icon = ''

    local label, lang

    if args.label then

    label = args.label

    else

    -- exceptions or labels fixed

    local exist, labels = pcall(require, wiki.module_title .. "/labels" .. (languages[1] == wiki.langcode and '' or '/' .. languages[1]))

    if exist and labels.infoboxLabelsFromId and next(labels.infoboxLabelsFromId) ~= nil then

    label = labels.infoboxLabelsFromId[id]

    end

    if label == nil then

    label, lang = getLabelByLangs(id, languages)

    if label then

    if isSet(args.itemgender) and feminineGender(args.itemgender) then

    label = feminineForm(id, lang) or label

    end

    label = mw.language.new(lang):ucfirst(mw.text.nowiki(label)) -- sanitize

    if args.case then

    label = case(args.case, label, lang)

    end

    end

    label_icon = addLabelIcon(id, lang, languages[1], labelicon)

    end

    end

    local linked = args.linked

    local ret2 = required and untranslated or ''

    if isSet(linked) and linked ~= "no" then

    local article = mw.wikibase.getSitelink(id) or ("d:Special:EntityPage/" .. id)

    return "" .. (label or id) .. "" .. label_icon, ret2

    else

    return (label or id) .. label_icon, ret2

    end

    end

    -- Utilities -----------------------------

    -- See also module ../debug.

    -- Copied from Module:Wikibase

    function p.getSiteLink(frame)

    local args = frame.args or frame -- via invoke or require

    local pargs = frame.args and frame:getParent().args or {}

    local id = getEntityId(args, pargs, 1)

    if id == nil then return end

    return mw.wikibase.getSitelink(id, mw.text.trim(args[2] or ''))

    end

    -- Helper function for the default language code used

    function p.lang(frame)

    local lang = frame and frame.args[1] -- nil via require

    return findLang(lang)[1]

    end

    -- Number of statements

    function p.numStatements(frame)

    local args = frame.args or frame -- via invoke or require

    local pargs = frame.args and frame:getParent().args or {}

    local id = getEntityId(args, pargs)

    if id == nil then return 0 end

    local prop = mw.text.trim(args[1] or '')

    local num = {}

    if not isSet(prop) and frame.args then

    args = {}

    for k, v in pairs(pargs) do

    args[k] = v

    end

    for k, v in pairs(frame.args) do

    args[k] = v

    end

    args.query = 'num'

    _, num = p.claim(args)

    return num

    elseif args[2] then -- qualifier

    local qual = mw.text.trim(args[2])

    local values = p.claim{item=id, property=prop, qualifier=qual, formatting='raw', separator='/·/'}

    if values then

    num = mw.text.split(values, '/·/')

    end

    else

    num = mw.wikibase.getBestStatements(id, prop)

    end

    return #num

    end

    -- Returns true if property datavalue is found excluding novalue/somevalue

    function p.validProperty(frame)

    local args = frame.args or frame -- via invoke or require

    local pargs = frame.args and frame:getParent().args or {}

    local item = getEntityId(args, pargs)

    if item == nil then return end

    local property = mw.text.trim(args[1])

    local prop_data = getSnak(mw.wikibase.getBestStatements(item, property), {1, "mainsnak", "datavalue"})

    return prop_data and true or nil

    end

    function p.editAtWikidata(frame)

    local args = frame.args or frame -- via invoke or require

    local pargs = frame.args and frame:getParent().args or {}

    local value = isSet(args[1])

    if value then return end

    local param = {}

    param.id = getEntityId(args, pargs)

    param.property = args.property

    param.lang = findLang(args.lang)

    param.editicon, _ = setIcons(args.editicon)

    return addEditIcon(param)

    end

    function p.formatNum(frame)

    local num = tonumber(mw.text.trim(frame.args[1]))

    local lang = findLang(mw.text.trim(frame.args[2]))

    return mw.language.new(lang[1]):formatNum(num)

    end

    return p