Module:FAMEDataInfobox

-- version 20211026 from master @famepediawiki

local p = {}

-- Initialization of variables

local i18n = {						-- internationalisation at Module:FAMEDataInfobox/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"] = {"", ""},		-- year(s) old, as in NaN plurals -- two values for most languages, up to six values for some languages -- see documentation of PLURAL magic word in your language, examples: -- ["years-old"] = {"singular", "paucal", "plural"} in Russian and other Slavic languages -- ["years-old"] = {"zero", "one", "two", "few 3-10", "many 11-99", "other 100-102"} in Arabic ["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 FAMEData statement, planned to overwrite by FAMEData Bridge ["categorylabels"] = "", -- Category:Pages with FAMEData labels not translated (void for no local category) ["categoryprop"] = "", -- Category:Pages using FAMEData property $1 (void for no local category) ["categoryref"] = "", -- Category:Pages with references from FAMEData (void for no local category) ["addfallback"] = {} -- additional fallback language codes }

local cases = {} -- functions for local grammatical cases defined at Module:FAMEData/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('') 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 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,'P4') local gender_id = getSnak(claims, {1, "mainsnak", "datavalue", "value", "id"}) if gender_id == "Q6" or gender_id == "Q959" or gender_id == "Q960" 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, 'P391') 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 = " " 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 FAMEData 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 = ' '			.. ""			.. " "		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 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

-- 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 tabular-data local function printDatatypeTabular(data, parameters) local icon if parameters.formatting == 'raw' then icon = "no-icon" data = string.gsub(data, '^Data:', '') -- remove prefix, i.e. see Module:Tabular data end return printDatatypeString(data, parameters), icon 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, 'P8') -- 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 = {['Q961']='callisto',['Q962']='ceres',['Q963']='dione',['Q476']='earth',['Q964']='enceladus', ['Q965']='europa',['Q966']='phoebe',['Q967']='ganymede',['Q990']='io',['Q989']='iapetus', ['Q987']='mercury',['Q986']='mimas',['Q985']='moon',['Q988']='rhea',['Q984']='tethys', ['Q806']='mars',['Q983']='titan',['Q982']='triton',['Q981']='venus',['Q980']='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://famedata.org/wiki/Q476 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://famedata.org/entity/Q476' 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, 'P171') 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 string.sub(parameters.formatting or '', 1, 8) == "unitcode" then -- get unit symbol local unit_symbol = unitSymbol(id, parameters.lang) if isSet(unit_symbol) then if string.sub(parameters.formatting or '', -6) == "linked" then suffix = "" .. unit_symbol .. "" else suffix = unit_symbol end end end if suffix == '' then -- formatting=unit, or formatting=unitcode not found -- get unit label 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) if string.sub(parameters.formatting or '', -6) == "linked" then suffix = "" .. suffix .. "" end 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 = parameters.convert elseif string.sub(parameters.formatting or '', 1, 8) == "duration" then convert_to = 'Q968' -- 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'} ['Q969'] = {['Q970'] = '$1*1.8+32', ['Q971'] = '$1+273.15'}, ['Q970'] = {['Q969'] = '($1-32)/1.8', ['Q971'] = '($1+459.67)*5/9'}, ['Q971'] = {['Q969'] = '$1-273.15', ['Q970'] = '($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, 'P392') -- conversion to standard unit table.insert(conversions, mw.wikibase.getBestStatements(unit_id, 'Q979')[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://famedata.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 == 'duration' then return lang_obj:formatDuration(sec) elseif parameters.formatting == 'durationm:s' then local mm = math.floor(sec / 60) local ss = sec - (mm * 60) return string.format("%02d:%02d", mm, ss) else -- durationhms or durationh:m:s 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 end end if parameters.case then amount = case(parameters.case, amount, parameters.lang[1]) elseif parameters.formatting ~= 'raw' then if parameters.numformat then amount = lang_obj:formatNum(tonumber(string.format(parameters.numformat, amount))) else amount = lang_obj:formatNum(tonumber(amount)) end 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 = {["Q972"] = "gregorian", ["Q471"] = "gregorian", ["Q973"] = "julian", ["Q481"] = "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 = " {{sup|(" .. 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 -- data value string if snak.datatype == "string" then return printDatatypeString(snak.datavalue.value, parameters) elseif snak.datatype == "commonsMedia" or snak.datatype == "geo-shape" then return printDatatypeMedia(snak.datavalue.value, parameters) elseif snak.datatype == "tabular-data" then return printDatatypeTabular(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) -- data types other than string value 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 FAMEData 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, parameters) if not isSet(parameters.references) then return '' end local lang = parameters.lang local maxrefs = tonumber(parameters.references) or 1 local notproperref = { ["P193"] = true, -- imported from ["P184"] = true, -- inferred from ["P393"] = true, -- based on heuristic ["P394"] = true -- Miraheze 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_id -- 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 if snakval[snakidx].datatype == 'external-id' then refparts = refparts or '' .. (getSnakValue(snakval[snakidx], {formatting='externalid', property=snakval[snakidx].property, lang=lang}) or '') else refparts = refparts or '' .. (getSnakValue(snakval[snakidx], {lang=lang}) or '') end end refs[snakkey] = refparts refparts = nil if snakkey == "P75" then -- stated in					ref_id = getSnak(snakval, {1, "datavalue", "value", "id"}) end end end -- fill missing values with parent item if ref_id 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['P127'] = refs['P127'] or refParent(ref_id, 'P127', 'label') -- author refs['P35'] = refs['P35'] or refParent(ref_id, 'P35', 'label') -- language of work refs['P125'] = refs['P125'] or refParent(ref_id, 'P125', 'label') -- publisher refs['P110'] = refs['P110'] or refParent(ref_id, 'P110') -- date refs['P400'] = refs['P400'] or refParent(ref_id, 'P400', 'label') -- published in			refs['P197'] = refs['P197'] or refParent(ref_id, 'P197') -- page(s) refs['P399'] = refs['P399'] or refParent(ref_id, 'P399') -- issue refs['P398'] = refs['P398'] or refParent(ref_id, 'P398') -- ISSN refs['P397'] = refs['P397'] or refParent(ref_id, 'P397') -- DOI end -- get title of local templates for citing references local template_web = mw.wikibase.getSitelink('Q521') or "" template_web = mw.text.split(template_web, ":")[2] -- split off namespace from front local template_journal = mw.wikibase.getSitelink('Q978') or "" template_journal = mw.text.split(template_journal, ":")[2] local citeParams = {} if refs['P15'] and (refs['P106'] or refs['P75']) 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['P75'] and refs['P106'] == nil then citeParams[i18n['cite']['title']] = refs['P75']:match("^%[%[.-|(.-)%]%]") else citeParams[i18n['cite']['title']] = refs['P106'] citeParams[i18n['cite']['website']] = refs['P75'] end citeParams[i18n['cite']['author']] = refs['P127'] citeParams[i18n['cite']['language']] = refs['P35'] citeParams[i18n['cite']['publisher']] = refs['P125'] citeParams[i18n['cite']['date']] = refs['P110'] citeParams[i18n['cite']['pages']] = refs['P197'] citeParams[i18n['cite']['access-date']] = refs['P200'] citeParams[i18n['cite']['archive-url']] = refs['P191'] citeParams[i18n['cite']['archive-date']] = refs['P205'] citeParams[i18n['cite']['quote']] = refs['P170'] refparts = mw.getCurrentFrame:expandTemplate{title=template_web, args=citeParams} elseif refs['P1433'] and (refs['P106'] or refs['P75']) 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['P400'] citeParams[i18n['cite']['title']] = refs['P106'] or refs['P75'] citeParams[i18n['cite']['author']] = refs['P127'] citeParams[i18n['cite']['date']] = refs['P110'] citeParams[i18n['cite']['issue']] = refs['P399'] citeParams[i18n['cite']['pages']] = refs['P197'] citeParams[i18n['cite']['language']] = refs['P35'] citeParams[i18n['cite']['issn']] = refs['P398'] citeParams[i18n['cite']['doi']] = refs['P397'] 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 local ref_name = claim.references[ref].hash result[#result + 1] = mw.getCurrentFrame:extensionTag("ref", refparts, {name=ref_name}) if maxrefs and maxrefs == #result then break end end end if type(result) == 'table' and #result > 0 and isSet(i18n.categoryref) then return table.concat(result) .. "" ..i18n.categoryref .. "" end return table.concat(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)][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.numformat = getArg(args.numformat) 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 parameters.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 local ret = showerrors and printError("property-not-found") or default return ret, args.query == 'num' and 0 or '' 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 = "" 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 isSet(parameters.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) 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 isSet(parameters.references) then result = result .. getReferences(claim, parameters) end if args.query == 'num' then result_query = result and 1 or 0 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 .. '</li></ul>' 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		upto_label[q] = true 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		upto_value[q] = true end local label_show = {} for q in string.gmatch(args.showlabelid or '', 'Q%d+') do		label_show[q] = true 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, 'P26') if type(birth) ~= 'table' or birth.time == nil or birth.precision == nil or birth.precision < 8 then return end local death = getBestValue(id, 'P132') 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		local yo_pl = {} if langs[1] == wiki.langcode then yo_pl = i18n["years-old"] end if not isSet(yo_pl[2]) then local yo_label, _ = getLabelByLangs('Q976', langs) yo_pl = {yo_label, yo_label} end yo = mw.language.new(langs[1]):plural(old_max, 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) then local largs = {} for k, v in pairs(pargs) do			largs[k] = v		end for k, v in pairs(args) do			largs[k] = v		end largs.query = 'num' _, num = p.claim(largs) 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.editAtFAMEDdata(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