Module:WikidataIB

-- Module to implement use of a blacklist and whitelist for infobox fields -- Can take a named parameter |qid which is the Wikidata ID for the article -- if not supplied, it will use the Wikidata ID associated with the current page. -- Fields in blacklist are never to be displayed, i.e. module must return nil in all circumstances -- Fields in whitelist return local value if it exists or the Wikidata value otherwise -- The name of the field that this function is called from is passed in named parameter |name -- The name is compulsory when blacklist or whitelist is used, -- so the module returns nil if it is not supplied. -- blacklist is passed in named parameter |suppressfields (or |spf) -- whitelist is passed in named parameter |fetchwikidata (or |fwd)

local p = {}

local cdate = require("Module:Complex date")._complex_date -- Module:Complex date has the following dependencies: -- Module:i18n/complex date, Module:ISOdate, Module:DateI18n (Module:Date), Module:Formatnum, -- Module:I18n/date, Module:Yesno, Module:Linguistic, Module:Calendar -- The following, taken from https://www.mediawiki.org/wiki/Wikibase/DataModel#Dates_and_times, -- is needed to use Module:Complex date which seemingly requires date precision as a string. -- It would work better if only the authors of the mediawiki page could spell 'millennium'. local dp = { [6] = "millennium", [7] = "century", [8] = "decade", [9] = "year", [10] = "month", [11] = "day", }

local i18n = {	["errors"] = {		["property-not-found"] = "Property not found.", ["No property supplied"] = "No property supplied", ["entity-not-found"] = "Wikidata entity not found.", ["unknown-claim-type"] = "Unknown claim type.", ["unknown-entity-type"] = "Unknown entity type.", ["qualifier-not-found"] = "Qualifier not found.", ["site-not-found"] = "Wikimedia project not found.", ["labels-not-found"] = "No labels found.", ["descriptions-not-found"] = "No descriptions found.", ["aliases-not-found"] = "No aliases found.", ["unknown-datetime-format"] = "Unknown datetime format.", ["local-article-not-found"] = "Article is available on Wikidata, but not on Wikipedia", ["dab-page"] = " (dab)", },	["months"] = {		"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" },	["century"] = "century", ["BC"] = "BC", ["BCE"] = "BCE", ["ordinal"] = {		[1] = "st", [2] = "nd", [3] = "rd", ["default"] = "th" },	["filespace"] = "File", ["Unknown"] = "Unknown", ["NaN"] = "Not a number", -- set the following to the name of a tracking category, -- e.g. "", or "" to disable: ["missinginfocat"] = "", ["editonwikidata"] = "Edit this on Wikidata", ["latestdatequalifier"] = function (date) return "before " .. date end, -- some languages, e.g. Bosnian use a period as a suffix after each number in a date ["datenumbersuffix"] = "", ["list separator"] = ", ", ["multipliers"] = { [0] = "",		[3]  = " thousand", [6] = " million", [9] = " billion", [12] = " trillion", } } -- This allows a internationisation module to override the above table require("Module:i18n").loadI18n("Module:WikidataIB/i18n", i18n)

-- This piece of html implements a collapsible container. Check the classes exist on your wiki. local collapsediv = ''

--- -- Private functions --- -- --- -- makeOrdinal needs to be internationalised along with the above: -- takes cardinal numer as a numeric and returns the ordinal as a string -- we need three exceptions in English for 1st, 2nd, 3rd, 21st, .. 31st, etc. --- -- Dependencies: none --- local makeOrdinal = function(cardinal) local ordsuffix = i18n.ordinal.default if cardinal % 10 == 1 then ordsuffix = i18n.ordinal[1] elseif cardinal % 10 == 2 then ordsuffix = i18n.ordinal[2] elseif cardinal % 10 == 3 then ordsuffix = i18n.ordinal[3] end -- In English, 1, 21, 31, etc. use 'st', but 11, 111, etc. use 'th' -- similarly for 12 and 13, etc.	if (cardinal % 100 == 11) or (cardinal % 100 == 12) or (cardinal % 100 == 13) then ordsuffix = i18n.ordinal.default end return tostring(cardinal) .. ordsuffix end

--- -- findLang takes a "langcode" parameter if if supplied and valid -- otherwise it tries to create it from the user's set language -- failing that it uses the wiki's content language. -- It returns a language object --- -- Dependencies: none --- local findLang = function(langcode) local langobj langcode = mw.text.trim(langcode or "") if mw.language.isKnownLanguageTag(langcode) then langobj = mw.language.new( langcode ) else langcode = mw.getCurrentFrame:preprocess( '' ) if mw.language.isKnownLanguageTag(langcode) then langobj = mw.language.new( langcode ) else langobj = mw.language.getContentLanguage end end return langobj end

--- -- roundto takes a number (x) -- and returns it rounded to (sf) significant figures --- -- Dependencies: none --- local roundto = function(x, sf) if x == 0 then return 0 end local s = 1 if x < 0 then x = -x s = -1 end if sf < 1 then sf = 1 end local p = 10 ^ (math.floor(math.log10(x)) - sf + 1) x = math.floor(x / p + 0.5) * p * s	-- if it's integral, cast to an integer: if x == math.floor(x) then x = math.floor(x) end return x end

--- -- decimalToDMS takes a decimal degrees (x) with precision (p) -- and returns degrees/minutes/seconds according to the precision --- -- Dependencies: none --- local decimalToDMS = function(x, p)	-- if p is not supplied, use a precision around 0.1 seconds if not tonumber(p) then p = 1e-4 end local d = math.floor(x) local ms = (x - d) * 60 if p > 0.5 then -- precision is > 1/2 a degree if ms > 30 then d = d + 1 end ms = 0 end local m = math.floor(ms) local s = (ms - m) * 60 if p > 0.008 then -- precision is > 1/2 a minute if s > 30 then m = m +1 end s = 0 elseif p > 0.00014 then -- precision is > 1/2 a second s = math.floor(s + 0.5) elseif p > 0.000014 then -- precision is > 1/20 second s = math.floor(10 * s + 0.5) / 10 elseif p > 0.0000014 then -- precision is > 1/200 second s = math.floor(100 * s + 0.5) / 100 else -- cap it at 3 dec places for now s = math.floor(1000 * s + 0.5) / 1000 end return d, m, s end

--- -- decimalPrecision takes a decimal (x) with precision (p) -- and returns x rounded approximately to the given precision -- precision should be between 1 and 1e-6, preferably a power of 10. --- -- Dependencies: none --- local decimalPrecision = function(x, p)	local s = 1 if x < 0 then x = -x s = -1 end -- if p is not supplied, pick an arbitrary precision if not tonumber(p) then p = 1e-4 elseif p > 1 then p = 1 elseif p < 1e-6 then p = 1e-6 else p = 10 ^ math.floor(math.log10(p)) end x = math.floor(x / p + 0.5) * p * s	-- if it's integral, cast to an integer: if x == math.floor(x) then x = math.floor(x) end -- if it's less than 1e-4, it will be in exponent form, so return a string with 6dp -- 9e-5 becomes 0.000090 if math.abs(x) < 1e-4 then x = string.format("%f", x) end return x end

--- -- formatDate takes a datetime of the usual format from mw.wikibase.entity:formatPropertyValues -- like "1 August 30 BCE" as parameter 1 -- and formats it according to the df (date format) and bc parameters -- df = ["dmy" / "mdy" / "y"] default will be "dmy" -- bc = ["BC" / "BCE"] default will be "BCE" --- -- Dependencies: none --- local format_Date = function(datetime, dateformat, bc) local datetime = datetime or "1 August 30 BCE" -- in case of nil value -- chop off multiple vales and/or any hours, mins, etc.	-- keep anything before punctuation - we just want a single date: local dateval = string.match( datetime, "[%w ]+")

local dateformat = string.lower(dateformat or "dmy") -- default to dmy

local bc = string.upper(bc or "") -- can't use nil for bc	-- we only want to accept two possibilities: BC or default to BCE if bc == "BC" then bc = " " .. i18n["BC"] -- prepend a non-breaking space. else bc = " " .. i18n["BCE"] end

local postchrist = true -- start by assuming no BCE local dateparts = {} for word in string.gmatch(dateval, "%w+") do		if word == "BCE" or word == "BC" then -- *** internationalise later *** postchrist = false else -- we'll keep the parts that are not 'BCE' in a table dateparts[#dateparts + 1] = word end end if postchrist then bc = "" end -- set AD dates to no suffix *** internationalise later ***

local sep = " " -- separator is nbsp local fdate = table.concat(dateparts, sep) -- set formatted date to same order as input

-- if we have day month year, check dateformat if #dateparts == 3 then if dateformat == "y" then fdate = dateparts[3] elseif dateformat == "mdy" then fdate = dateparts[2] .. sep .. dateparts[1] .. "," .. sep .. dateparts[3] end elseif #dateparts == 2 and dateformat == "y" then fdate = dateparts[2] end

return fdate .. bc end

--- -- dateFormat is the handler for properties that are of type "time" -- It takes timestamp, precision (6 to 11 per mediawiki), dateformat (y/dmy/mdy), BC format (BC/BCE), -- a plaindate switch (yes/no/adj) to en/disable "sourcing cirumstances"/use adjectival form, -- any qualifiers for the property, the language, and any adjective to use like 'before'. -- It passes the date through the "complex date" function -- and returns a string with the internatonalised date formatted according to preferences. --- -- Dependencies: findLang; cdate; dp[] --- local dateFormat = function(timestamp, dprec, df, bcf, pd, qualifiers, lang, adj) -- A year can be stored like this: "+1872-00-00T00:00:00Z", -- which is processed here as if it were the day before "+1872-01-01T00:00:00Z", -- and that's the last day of 1871, so the year is wrong. -- So fix the month 0, day 0 timestamp to become 1 January instead: timestamp = timestamp:gsub("%-00%-00T", "-01-01T") -- output formatting according to preferences (y/dmy/mdy) df = (df or ""):lower -- just in case date precision is missing dprec = dprec or 11 -- override more precise dates if required dateformat is year alone: if df == "y" and dprec > 9 then dprec = 9 end -- complex date only deals with precisions from 6 to 11, so clip range dprec = dprec>11 and 11 or dprec dprec = dprec<6 and 6 or dprec -- BC format is "BC" or "BCE" bcf = (bcf or ""):upper -- plaindate only needs the first letter (y/n/a) pd = (pd or ""):sub(1,1):lower if pd == "" or pd == "n" or pd == "f" or pd == "0" then pd = false end -- in case language isn't passed lang = lang or findLang.code -- set adj as empty if nil adj = adj or "" -- extract the day, month, year from the timestamp local bc = timestamp:sub(1, 1)=="-" and "BC" or "" local year, month, day = timestamp:match("[+-](%d*)-(%d*)-(%d*)T") local iso = tonumber(year) -- if year is missing, let it throw an error -- this will adjust the date format to be compatible with cdate -- possible formats are Y, YY, YYY0, YYYY, YYYY-MM, YYYY-MM-DD if dprec == 6 then iso = math.floor( (iso - 1) / 1000 ) + 1 end if dprec == 7 then iso = math.floor( (iso - 1) / 100 ) + 1 end if dprec == 8 then iso = math.floor( iso / 10 ) .. "0" end if dprec == 10 then iso = year .. "-" .. month end if dprec == 11 then iso = year .. "-" .. month .. "-" .. day end -- add "circa" (Q5727902) from "sourcing circumstances" (P1480) local sc = not pd and qualifiers and qualifiers.P1480 if sc then for k1, v1 in pairs(sc) do			if v1.datavalue and v1.datavalue.value.id == "Q5727902" then adj = "circa" break end end end local fdate = cdate("", adj, tostring(iso), dp[dprec], bc, "", "", "", "", lang, 1) -- this may have QuickStatements info appended to it in a div, so remove that fdate = fdate:gsub(' [^<]* ', '') -- it may also be returned wrapped in a microformat, so remove that fdate = fdate:gsub("<[^>]*>", "") -- if 'circa', use the abbreviated form *** internationalise later *** fdate = fdate:gsub('circa ', ' c. ') -- deal with BC/BCE if bcf == "BCE" then fdate = fdate:gsub('BC', 'BCE') end -- deal with mdy format if df == "mdy" then fdate = fdate:gsub("(%d+) (%w+) (%d+)", "%2 %1, %3") end -- deal with adjectival form *** internationalise later *** if pd == "a" then fdate = fdate:gsub(' century', '-century') end return fdate end

--- -- parseParam takes a (string) parameter, e.g. from the list of frame arguments, -- and makes "false", "no", and "0" into the (boolean) false -- it makes the empty string and nil into the (boolean) value passed as default -- allowing the parameter to be true or false by default. --- -- Dependencies: none --- local parseParam = function(param, default) if param and param ~= "" then param = param:lower if (param == "false") or (param:sub(1,1) == "n") or (param == "0") then return false else return true end else return default end end

--- -- _getSitelink takes the qid of a Wikidata entity passed as |qid= -- It takes an optional parameter |wiki= to determine which wiki is to be checked for a sitelink -- If the parameter is blank, then it uses the local wiki. -- If there is a sitelink to an article available, it returns the plain text link to the article -- If there is no sitelink, it returns nil. --- -- Dependencies: none --- local _getSitelink = function(qid, wiki) qid = (qid or ""):upper if qid == "" then return nil end wiki = wiki or "" local sitelink if wiki == "" then sitelink = mw.wikibase.sitelink(qid) else sitelink = mw.wikibase.sitelink(qid, wiki) end return sitelink end

--- -- _getCommonslink takes an optional qid of a Wikidata entity passed as |qid= -- It returns one of the following in order of preference: -- 	the Commons sitelink of the linked Wikidata item; -- 	the Commons sitelink of the topic's main category of the linked Wikidata item; -- 	the Commons category. -- If the optional parameter onlycat is true/yes/1 then only categories are returned -- defaults to false. --- -- Dependencies: _getSitelink; parseParam --- local _getCommonslink = function(qid, onlycat) qid = (qid or ""):upper if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return nil end onlycat = parseParam(onlycat, false) local sitelink = _getSitelink(qid, "commonswiki") if onlycat and sitelink and sitelink:sub(1,9) ~= "Category:" then sitelink = nil end if not sitelink then -- check for topic's main category local prop910 = mw.wikibase.getBestStatements(qid, "P910")[1] if prop910 then local tmcid = prop910.mainsnak.datavalue.value.id			sitelink = _getSitelink(tmcid, "commonswiki") end end if not sitelink then -- check for Commons category (string value) local prop373 = mw.wikibase.getBestStatements(qid, "P373")[1] if prop373 then sitelink = prop373.mainsnak.datavalue.value if sitelink then sitelink = "Category:" .. sitelink end end end return sitelink end

--- -- The label in a Wikidata item is subject to vulnerabilities -- that an attacker might try to exploit. -- It needs to be 'sanitised' by removing any wikitext before use. -- If it doesn't exist, return the id for the item -- a second (boolean) value is also returned, value is true when the label exists --- -- Dependencies: none --- local labelOrId = function(id) local label = mw.wikibase.label(id) if label then return mw.text.nowiki(label), true else return id, false end end

--- -- linkedItem takes an entity-id and returns a string, linked if possible. -- This is the handler for "wikibase-item". Preferences: -- 1. Display linked disambiguated sitelink if it exists -- 2. Display linked label if it is a redirect -- 3. TBA: Display an inter-language link for the label if it exists other than in default language -- 4. Display unlinked label if it exists -- 5. Display entity-id for now to indicate a label could be provided --- -- Dependencies: labelOrId --- local linkedItem = function(id, lprefix, lpostfix, prefix, postfix) lprefix = lprefix or "" -- toughen against nil values passed lpostfix = lpostfix or "" prefix = prefix or "" postfix = postfix or "" local disp local sitelink = mw.wikibase.sitelink(id) local label, islabel = labelOrId(id) if mw.site.siteName ~= "Wikimedia Commons" then if sitelink then -- strip any namespace or dab from the sitelink and use that as label local pos = sitelink:find(":") or 0 label = sitelink:sub(pos+1):gsub("%s%(.+%)$", ""):gsub(",.+$", "") disp = "" .. prefix .. label .. postfix .. "" elseif islabel then -- no sitelink, label exists, so check if a redirect with that title exists local artitle = mw.title.new(label, 0) if artitle and artitle.redirectTarget then -- there's a redirect with the same title as the label, so let's link to that disp = "" .. prefix .. label .. postfix .. "" else -- no sitelink, label exists, not a redirect, so output the plain label disp = prefix .. label .. postfix end -- test if article title exists as redirect on current Wiki else -- no sitelink and no label, so return whatever was returned from labelOrId for now -- add tracking category disp = prefix .. label .. postfix .. i18n.missinginfocat end else local ccat = mw.wikibase.getBestStatements(id, "P373")[1] if ccat then ccat = ccat.mainsnak.datavalue.value disp = "" .. prefix .. label .. postfix .. "" elseif sitelink then -- this asumes that if a sitelink exists, then a label also exists disp = "" .. prefix .. label .. postfix .. "" else -- no sitelink and no Commons cat, so return label from labelOrId for now disp = prefix .. label .. postfix end end return disp end

--- -- sourced takes a table representing a statement that may or may not have references -- it counts how many references are sourced to something not containing the word "wikipedia" -- it returns a boolean = true if there are any sourced references. --- -- Dependencies: none --- local sourced = function(claim) if claim.references then for kr, vr in pairs(claim.references) do			local ref = mw.wikibase.renderSnaks(vr.snaks) if not ref:find("Wikipedia") then return true end end end end

--- -- setRanks takes a flag (parameter passed) that requests the values to return -- "b[est]" returns preferred if available, otherwise normal -- "p[referred]" returns preferred -- "n[ormal]" returns normal -- "d[eprecated]" returns deprecated -- multiple values are allowed, e.g. "preferred normal" (which is the default) -- "best" will override the other flags, and set p and n --- -- Dependencies: none --- local setRanks = function(rank) rank = (rank or ""):lower -- if nothing passed, return preferred and normal -- if rank == "" then rank = "p n" end local ranks = {} for w in string.gmatch(rank, "%a+") do		w = w:sub(1,1) if w == "b" or w == "p" or w == "n" or w == "d" then ranks[w] = true end end -- check if "best" is requested or no ranks requested; and if so, set preferred and normal if ranks.b or not next(ranks) then ranks.p = true ranks.n = true end return ranks end

--- -- parseInput processes the Q-id, the blacklist and the whitelist -- if an input parameter is supplied, it returns that and ends the call. -- it returns (1) either the qid or nil indicating whether or not the call should continue -- and (2) a table containing all of the statements for the propertyID and relevant Qid -- if "best" ranks are requested, it returns those instead of all non-deprecated ranks --- -- Dependencies: none --- local parseInput = function(frame, input_parm, property_id) -- There may be a local parameter supplied, if it's blank, set it to nil input_parm = mw.text.trim(input_parm or "") if input_parm == "" then input_parm = nil end

local args = frame.args

-- can take a named parameter |qid which is the Wikidata ID for the article. -- if it's not supplied, use the id for the current page local qid = args.qid or "" if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end -- if there's no Wikidata item for the current page return nil if not qid then return false, input_parm end

-- The blacklist is passed in named parameter |suppressfields local blacklist = args.suppressfields or args.spf

-- The whitelist is passed in named parameter |fetchwikidata local whitelist = args.fetchwikidata or args.fwd if not whitelist or whitelist == "" then whitelist = "NONE" end

-- The name of the field that this function is called from is passed in named parameter |name local fieldname = args.name or ""

if blacklist then -- The name is compulsory when blacklist is used, so return nil if it is not supplied if not fieldname or fieldname == "" then return false, nil end -- If this field is on the blacklist, then return nil if blacklist:find(fieldname) then return false, nil end end

-- If we got this far then we're not on the blacklist -- The blacklist overrides any locally supplied parameter as well -- If a non-blank input parameter was supplied return it	if input_parm then return false, input_parm end

-- Otherwise see if this field is on the whitelist: -- needs a bit more logic because find will return its second value = 0 if fieldname is "" -- but nil if fieldname not found on whitelist local _, found = whitelist:find(fieldname) found = ((found or 0) > 0) if whitelist ~= 'ALL' and (whitelist:upper == "NONE" or not found) then return false, nil end

-- See what's on Wikidata (the call always returns a table, but it may be empty): local props = {} if args.reqranks.b then props = mw.wikibase.getBestStatements(qid, property_id) else props = mw.wikibase.getAllStatements(qid, property_id) end if props[1] then return qid, props end -- no property on Wikidata return false, nil end

--- -- createicon assembles the "Edit at Wikidata" pen icon. -- It returns a wikitext string. --- -- Dependencies: i18n[]; --- local createicon = function(langcode, entityID, propertyID) local icon = " frameless |text-top |10px |alt="	icon = icon .. i18n["editonwikidata"]	icon = icon .. "|link=https://www.wikidata.org/wiki/" .. entityID	icon = icon .. "?uselang=" .. langcode	if propertyID then icon = icon .. "#" .. propertyID end	icon = icon .. "|" .. i18n["editonwikidata"] .. "" return icon end

--- -- assembleoutput takes the sequence table containing the property values -- and formats it according to switches given. It returns a string or nil. -- It needs the entityID and propertyID to create a link in the pen icon. --- -- Dependencies: parseParam; --- local assembleoutput = function(out, args, entityID, propertyID)

-- sorted is a boolean passed to enable sorting of the values returned -- if nothing or an empty string is passed set it false -- if "false" or "no" or "0" is passed set it false local sorted = parseParam(args.sorted, false)

-- noicon is a boolean passed to suppress the trailing "edit at Wikidata" icon -- for use when the value is processed further by the infobox -- if nothing or an empty string is passed set it false -- if "false" or "no" or "0" is passed set it false local noic = parseParam(args.noicon, false)

-- list is the name of a template that a list of multiple values is passed through -- examples include "hlist" and "ubl" -- setting it to "prose" produces something like "1, 2, 3, and 4" local list = args.list or ""

-- sep is a string that is used to separate multiple returned values -- if nothing or an empty string is passed set it to the default -- any double-quotes " are stripped out, so that spaces may be passed	-- e.g. |sep=" - "	local sepdefault = i18n["list separator"]	local separator = args.sep or ""	separator = string.gsub(separator, '"', '') if separator == "" then separator = sepdefault end

-- collapse is a number that determines the maximum number of returned values -- before the output is collapsed. -- Zero or not a number result in no collapsing (default becomes 0). local collapse = tonumber(args.collapse) or 0

-- if there's anything to return, then return a list -- comma-separated by default, but may be specified by the sep parameter -- optionally specify a hlist or ubl or a prose list, etc.	local strout if #out > 0 then if sorted then table.sort(out) end -- if a pen icon is wanted add it the end of the last value if not noic then out[#out] = out[#out] .. createicon(args.langobj.code, entityID, propertyID) end if list == "" then strout = table.concat(out, separator) elseif list:lower == "prose" then strout = mw.text.listToText( out ) else strout = mw.getCurrentFrame:expandTemplate{title = list, args = out} end if collapse >0 and #out > collapse then strout = collapsediv .. strout .. " "		end else strout = nil -- no items had valid reference end return strout end

--- -- rendersnak takes a table (propval) containing the information stored on one property value -- and returns the value as a string and its language if monolingual text. -- It handles data of type: --		wikibase-item --		time --		string, url, commonsMedia, external-id --		quantity --		globe-coordinate --		monolingualtext -- It also requires linked, the link/pre/postfixes, uabbr, and the arguments passed from frame. -- The optional filter parameter allows quantities to be be filtered by unit. --- -- Dependencies: parseParam; labelOrId; i18n[]; dateFormat; -- roundto; decimalPrecision; decimalToDMS; --- local rendersnak = function(propval, args, linked, lpre, lpost, pre, post, uabbr, filter) lpre = lpre or "" lpost = lpost or "" pre = pre or "" post = post or "" local snak = propval.mainsnak or propval local dv = snak.datavalue local dtype = propval.datatype or propval.mainsnak.datatype dv = dv and dv.value -- value and monolingual text language code returned local val, mlt if propval.rank and not args.reqranks[propval.rank:sub(1, 1)] then -- val is nil: value has a rank that isn't requested elseif snak.snaktype == "somevalue" then -- value is unknown val = i18n["Unknown"] elseif snak.snaktype == "novalue" then -- value is none -- val = "No value" -- don't return anything elseif dtype == "wikibase-item" then -- data type is a wikibase item: -- it's wiki-linked value, so output as link if enabled and possible local qnumber = dv.id		if linked then val = linkedItem(qnumber, lpre, lpost, pre, post) else -- no link wanted so test for lang code local label, islabel = labelOrId(qnumber) local langlabel = mw.wikibase.getLabelByLang(qnumber, args.lang) if langlabel then label = mw.text.nowiki( langlabel ) end val = pre .. label .. post end -- test for link required elseif dtype == "time" then -- data type is time: -- time is in timestamp format -- date precision is integer per mediawiki -- output formatting according to preferences (y/dmy/mdy) -- BC format as BC or BCE -- plaindate is passed to disable looking for "sourcing cirumstances" -- or to set the adjectival form -- qualifiers (if any) is a nested table or nil -- lang is given, or user language, or site language val = dateFormat(dv.time, dv.precision, args.df, args.bc, args.pd, propval.qualifiers, args.lang) -- data types which are strings: elseif dtype == "commonsMedia" or dtype == "external-id" or dtype == "string" or dtype == "url" then -- commonsMedia or external-id or string or url -- all have mainsnak.datavalue.value as string if (lpre == "" or lpre == ":") and lpost == "" then -- don't link if no linkpre/postfix or linkprefix is just ":" val = pre .. dv .. post else val = "" .. pre .. dv .. post .. "" end -- check for link requested (i.e. either linkprefix or linkpostfix exists) -- data types which are quantities: elseif dtype == "quantity" then -- quantities have mainsnak.datavalue.value.amount and mainsnak.datavalue.value.unit -- the unit is of the form http://www.wikidata.org/entity/Q829073 --		-- implement a switch to turn on/off numerical formatting later local fnum = true --		-- a switch to turn on/off conversions - only for en-wiki local conv = parseParam(args.conv or args.convert, false) -- if we have conversions, we won't have formatted numbers or scales if conv then fnum = false args.scale = "0" end --		-- convert amount to a number local amount = tonumber(dv.amount) or i18n["NaN"] --		-- scale factor for millions, billions, etc.		local sc = tostring(args.scale or ""):sub(1,1):lower local scale if sc == "a" then -- automatic scaling if amount > 1e15 then scale = 12 elseif amount > 1e12 then scale = 9 elseif amount > 1e9 then scale = 6 elseif amount > 1e6 then scale = 3 else scale = 0 end else scale = tonumber(args.scale) or 0 if scale 12 then scale = 0 end scale = math.floor(scale/3) * 3 end local factor = 10^scale amount = amount / factor -- ranges: local range = "" -- check if upper and/or lower bounds are given and significant local upb = tonumber(dv.upperBound) local lowb = tonumber(dv.lowerBound) if upb and lowb then -- differences rounded to 2 sig fig: local posdif = roundto(upb - amount, 2) / factor local negdif = roundto(amount - lowb, 2) / factor upb, lowb = amount + posdif, amount - negdif -- round scaled numbers to integers or 4 sig fig if (scale > 0 or sc == "a") then if amount < 1e4 then amount = roundto(amount, 4) else amount = math.floor(amount + 0.5) end end if fnum then amount = args.langobj:formatNum( amount ) end if posdif ~= negdif then -- non-symmetrical range = " +" .. posdif .. " -" .. negdif elseif posdif ~= 0 then -- symmetrical and non-zero range = " ±" .. posdif else -- otherwise range is zero, so leave it as "" end else -- round scaled numbers to integers or 4 sig fig if (scale > 0 or sc == "a") then if amount < 1e4 then amount = roundto(amount, 4) else amount = math.floor(amount + 0.5) end end if fnum then amount = args.langobj:formatNum( amount ) end end -- unit names and symbols: -- extract the qid in the form 'Qnnn' from the value.unit url -- and then fetch the label from that - or symbol if unitabbr is true local unit = "" local usep = "" local usym = "" local unitqid = string.match( dv.unit, "(Q%d+)" ) if filter and unitqid ~= filter then return nil end if unitqid then local uname = mw.wikibase.getLabelByLang(unitqid, args.lang) or "" if uname ~= "" then usep, unit = " ", uname end if uabbr then -- see if there's a unit symbol (P5061) local unitsymbols = mw.wikibase.getAllStatements(unitqid, "P5061") -- construct fallback table local fbtbl = mw.language.getFallbacksFor( args.lang ) table.insert( fbtbl, 1, args.lang ) local found = false for idx1, us in ipairs(unitsymbols) do					for idx2, fblang in ipairs(fbtbl) do						if us.mainsnak.datavalue.value.language == fblang then usym = us.mainsnak.datavalue.value.text found = true break end if found then break end end -- loop through fallback table end -- loop through values of P5061 if found then usep, unit = " ", usym end end end -- format display: if conv and usym ~= "" then if range == "" then val = mw.getCurrentFrame:expandTemplate{title = "cvt", args = {amount, usym}} else val = mw.getCurrentFrame:expandTemplate{title = "cvt", args = {lowb, "to", upb, usym}} end elseif unit == "$" or unit == "£" then val = unit .. amount .. range .. i18n.multipliers[scale] else val = amount .. range .. i18n.multipliers[scale] .. usep .. unit end -- datatypes which are global coordinates: elseif dtype == "globe-coordinate" then -- 'display' parameter defaults to "inline, title" *** unused for now *** -- local disp = args.display or "" -- if disp == "" then disp = "inline, title" end --		-- format parameter switches from deg/min/sec to decimal degrees -- default is deg/min/sec -- decimal degrees needs |format = dec local form = (args.format or ""):lower:sub(1,3) if form ~= "dec" then form = "dms" end --		-- show parameter allows just the latitude or longitude to be shown local show = (args.show or ""):lower if show ~= "longlat" then show = show:sub(1,3) end --		local lat, long, prec = dv.latitude, dv.longitude, dv.precision if show == "lat" then val = decimalPrecision(lat, prec) elseif show == "lon" then val = decimalPrecision(long, prec) elseif show == "longlat" then val = decimalPrecision(long, prec) .. ", " .. decimalPrecision(lat, prec) else local ns = "N" local ew = "E" if lat < 0 then ns = "S" lat = - lat end if long < 0 then ew = "W" long = - long end if form == "dec" then lat = decimalPrecision(lat, prec) long = decimalPrecision(long, prec) val = lat .. "°" .. ns .. " " .. long .. "°" .. ew			else local latdeg, latmin, latsec = decimalToDMS(lat, prec) local longdeg, longmin, longsec = decimalToDMS(long, prec)

if latsec == 0 and longsec == 0 then if latmin == 0 and longmin == 0 then val = latdeg .. "°" .. ns .. " " .. longdeg .. "°" .. ew					else val = latdeg .. "°" .. latmin .. "′" .. ns .. " "						val = val .. longdeg .. "°".. longmin .. "′" .. ew					end else val = latdeg .. "°" .. latmin .. "′" .. latsec .. "″" .. ns .. " "					val = val .. longdeg .. "°" .. longmin .. "′" .. longsec .. "″" .. ew				end end end elseif dtype == "monolingualtext" then -- data type is Monolingual text: -- has mainsnak.datavalue.value as a table containing language/text pairs -- collect all the values in 'out' and languages in 'mlt' and process them later val = pre .. dv.text .. post mlt = dv.language else -- some other data type so write a specific handler val = "unknown data type: " .. dtype end -- of datatype/unknown value/sourced check return val, mlt end

--- -- propertyvalueandquals takes a property object, the arguments passed from frame, -- and a qualifier propertyID. -- It returns a sequence (table) of values representing the values of that property -- and qualifiers that match the qualifierID if supplied. --- -- Dependencies: parseParam; sourced; labelOrId; i18n.latestdatequalifier; format_Date; -- makeOrdinal; roundto; decimalPrecision; decimalToDMS; assembleoutput; --- local function propertyvalueandquals(objproperty, args, qualID) -- needs this style of declaration because it's re-entrant

-- onlysourced is a boolean passed to return only values sourced to other than Wikipedia -- if nothing or an empty string is passed set it true local onlysrc = parseParam(args.onlysourced or args.osd, true)

-- linked is a a boolean that enables the link to a local page via sitelink -- if nothing or an empty string is passed set it true local linked = parseParam(args.linked, true)

-- prefix is a string that may be nil, empty (""), or a string of characters -- this is prefixed to each value -- useful when when multiple values are returned -- any double-quotes " are stripped out, so that spaces may be passed	local prefix = (args.prefix or ""):gsub('"', '')

-- postfix is a string that may be nil, empty (""), or a string of characters -- this is postfixed to each value -- useful when when multiple values are returned -- any double-quotes " are stripped out, so that spaces may be passed	local postfix = (args.postfix or ""):gsub('"', '')

-- linkprefix is a string that may be nil, empty (""), or a string of characters -- this creates a link and is then prefixed to each value -- useful when when multiple values are returned and indirect links are needed -- any double-quotes " are stripped out, so that spaces may be passed	local lprefix = (args.linkprefix or args.lp or ""):gsub('"', '')

-- linkpostfix is a string that may be nil, empty (""), or a string of characters -- this is postfixed to each value when linking is enabled with lprefix -- useful when when multiple values are returned -- any double-quotes " are stripped out, so that spaces may be passed	local lpostfix = (args.linkpostfix or ""):gsub('"', '')

-- wdlinks is a boolean passed to enable links to Wikidata when no article exists -- if nothing or an empty string is passed set it false local wdl = parseParam(args.wdlinks or args.wdl, false)

-- unitabbr is a boolean passed to enable unit abbreviations for common units -- if nothing or an empty string is passed set it false local uabbr = parseParam(args.unitabbr or args.uabbr, false)

-- qualsonly is a boolean passed to return just the qualifiers -- if nothing or an empty string is passed set it false local qualsonly = parseParam(args.qualsonly or args.qo, false)

-- maxvals is a string that may be nil, empty (""), or a number -- this determines how many items may be returned when multiple values are available -- setting it = 1 is useful where the returned string is used within another call, e.g. image local maxvals = tonumber(args.maxvals) or 0

-- pd (plain date) is a string: yes/true/1 | no/false/0 | adj -- to disable/enable "sourcing cirumstances" or use adjectival form for the plain date local pd = args.plaindate or args.pd or "no" args.pd = pd

local lang = args.lang -- all proper values of a Wikidata property will be the same type as the first -- qualifiers don't have a mainsnak, properties do	local datatype = objproperty[1].datatype or objproperty[1].mainsnak.datatype -- out holds the values for this property -- mlt holds the language code if the datatype is monolingual text local out = {} local mlt = {} for k, v in pairs(objproperty) do		if (onlysrc and not sourced(v)) or (qualsonly and qualID) then -- no value: it isn't sourced when onlysourced=true -- or only qualifiers are requested else out[#out+1], mlt[#out+1] = rendersnak(v, args, linked, lprefix, lpostfix, prefix, postfix, uabbr) end

-- See if qualifiers are to be returned: local snak = v.mainsnak or v		if v.qualifiers and qualID and snak.snaktype=="value" then local qsep = (args.qsep or ""):gsub('"', '')			local qargs = {				["osd"]        = "false",				["linked"]      = tostring(linked),				["prefix"]      = args.qprefix,				["postfix"]     = args.qpostfix,				["linkprefix"]  = args.qlinkprefix or args.qlp,				["linkpostfix"] = args.qlinkpostfix,				["wdl"]         = "false",				["unitabbr"]    = tostring(uabbr),				["maxvals"]     = 0,				["sorted"]      = args.qsorted,				["noicon"]      = "true",				["list"]        = args.qlist,				["sep"]         = qsep,				["langobj"]     = args.langobj,				["lang"]        = args.langobj.code,			}			local qlist = {}			local t1, t2 = "", ""			-- see if we want all qualifiers			if qualID == "ALL" then				if v["qualifiers-order"] then					-- the values in the order table are the keys for the qualifiers table:					for k1, v1 in ipairs(v["qualifiers-order"]) do						if v1 == "P1326" then							local ts = v.qualifiers[v1][1].datavalue.value.time local dp = v.qualifiers[v1][1].datavalue.value.precision qlist[#qlist + 1] = dateFormat(ts, dp, args.df, args.bc, pd, "", lang, "before") else qlist[#qlist + 1] = assembleoutput(propertyvalueandquals(v.qualifiers[v1], qargs), qargs) end end else local ql = propertyvalueandquals(v.qualifiers, qargs) for k1, v1 in ipairs(ql) do						if k1 == "P1326" then local ts = v1[1].datavalue.value.time local dp = v1[1].datavalue.value.precision qlist[#qlist + 1] = dateFormat(ts, dp, args.df, args.bc, pd, "", lang, "before") else qlist[#qlist + 1] = v1						end end end -- see if we want date/range elseif qualID == "DATES" then qargs.maxvals = 1 for k1, v1 in pairs(v.qualifiers) do					if k1 == "P580" then -- P580 is "start time" t1 = propertyvalueandquals(v1, qargs)[1] or "" elseif k1 == "P582" then -- P582 is "end time" t2 = propertyvalueandquals(v1, qargs)[1] or "" end end -- otherwise process qualID as a list of qualifiers else for q in mw.text.gsplit(qualID, "%p") do -- split at punctuation and iterate q = mw.text.trim(q):upper -- remove whitespace and capitalise if q == "P1326" then -- latest date, so supply 'before' as well. Assume one date value. for k1, v1 in pairs(v.qualifiers) do							if k1 == "P1326" then local ts = v1[1].datavalue.value.time local dp = v1[1].datavalue.value.precision qlist[#qlist + 1] = dateFormat(ts, dp, args.df, args.bc, pd, "", lang, "before") end end else for k1, v1 in pairs(v.qualifiers) do							if k1 == q then local ql = propertyvalueandquals(v1, qargs) for k2, v2 in ipairs(ql) do									qlist[#qlist + 1] = v2								end end end end end -- of loop through list of qualifiers in qualID end -- of testing for what qualID is local t = t1 .. t2			-- *** internationalise date separators later *** local dsep = "–" if t:find("%s") or t:find(" ") then dsep = " – " end if #qlist > 0 then local qstr = assembleoutput(qlist, qargs) if qualsonly then out[#out+1] = qstr else out[#out] = out[#out] .. " (" .. qstr .. ")" end elseif t > "" then if qualsonly then out[#out+1] = t1 .. dsep .. t2				else out[#out] = out[#out] .. " (" .. t1 .. dsep .. t2 .. ")" end end end -- of test for qualifiers wanted

if maxvals > 0 and #out >= maxvals then break end end -- of for each value loop

-- we need to pick one value to return if the datatype was "monolingualtext" -- if there's only one value, use that -- otherwise look through the fallback languages for a match if datatype == "monolingualtext" and #out >1 then local langcode = args.langobj.code langcode = mw.text.split( langcode, '-', true )[1] local fbtbl = mw.language.getFallbacksFor( langcode ) table.insert( fbtbl, 1, langcode ) local bestval = "" local found = false for idx1, lang1 in ipairs(fbtbl) do			for idx2, lang2 in ipairs(mlt) do				if (lang1 == lang2) and not found then bestval = out[idx2] found = true break end end -- loop through values of property end -- loop through fallback languages if found then -- replace output table with a table containing the best value out = { bestval } else -- more than one value and none of them on the list of fallback languages -- sod it, just give them the first one out = { out[1] } end end return out end

--- -- Common code for p.getValueByQual and p.getValueByLang --- -- Dependencies: parseParam; setRanks; parseInput; sourced; assembleoutput; --- local _getvaluebyqual = function(frame, qualID, checkvalue)

-- The property ID that will have a qualifier is the first unnamed parameter local propertyID = mw.text.trim(frame.args[1] or "") if propertyID == "" then return "no property supplied" end

if qualID == "" then return "no qualifier supplied" end

-- onlysourced is a boolean passed to return property values -- only when property values are sourced to something other than Wikipedia -- if nothing or an empty string is passed set it true -- if "false" or "no" or 0 is passed set it false local onlysrc = parseParam(frame.args.onlysourced or frame.args.osd, true)

-- set the requested ranks flags frame.args.reqranks = setRanks(frame.args.rank)

-- set a language object and code in the frame.args table frame.args.langobj = findLang(frame.args.lang) frame.args.lang = frame.args.langobj.code

-- check for locally supplied parameter in second unnamed parameter -- success means no local parameter and the property exists local qid, props = parseInput(frame, frame.args[2], propertyID)

if qid then local out = {} -- Scan through the values of the property -- we want something like property is "pronunciation audio (P443)" in propertyID -- with a qualifier like "language of work or name (P407)" in qualID -- whose value has the required ID, like "British English (Q7979)", in qval for k1, v1 in pairs(props) do			if v1.mainsnak.snaktype == "value" and v1.mainsnak.datavalue.type == "string" then -- We'll only deal with returning strings for now -- so check if it has the right qualifier local v1q = v1.qualifiers if v1q and v1q[qualID] then if onlysrc == false or sourced(v1) then -- if we've got this far, we have a (sourced) claim with qualifiers -- so see if matches the required value -- We'll only deal with wikibase-items for now if v1q[qualID][1].datatype == "wikibase-item" then if checkvalue(v1q[qualID][1].datavalue.value.id) then out[#out + 1] = v1.mainsnak.datavalue.value end end end -- of check for sourced end -- of check for matching required value and has qualifiers else return "not string" end -- of check for string end -- of loop through values of propertyID return assembleoutput(out, frame.args, qid, propertyID) else return props -- either local parameter or nothing end -- of test for success return nil end

--- -- _location takes Q-id and follows P131 (located in the administrative territorial entity) -- or P276 (location) or P706 (located on terrain feature) -- from the initial item to higher level territories/locations until it reaches the highest. -- An optional boolean, 'first', determines whether the first item is returned (default: false). -- An optional boolean 'skip' toggles the display to skip to the last item (default: false). -- It returns a table containing the locations - linked where possible, except for the highest. --- -- Dependencies: findLang; labelOrId; linkedItem --- local _location = function(qid, first, skip) first = parseParam(first, false) skip = parseParam(skip, false) local out = {} local langcode = findLang:getCode local finished = false local count = 0 repeat local prop = mw.wikibase.getBestStatements(qid, "P131")[1] or mw.wikibase.getBestStatements(qid, "P276")[1] or mw.wikibase.getBestStatements(qid, "P706")[1] -- check if it's an instance of (P31) a country (Q6256) and terminate the chain if it is		local inst = mw.wikibase.getAllStatements(qid, "P31") if #inst > 0 then for k, v in ipairs(inst) do				local instid = v.mainsnak.datavalue.value.id				-- stop if it's a country (or a country within the United Kingdom if skip is true) if instid == "Q6256" or (skip and instid == "Q3336843") then prop = nil -- this will ensure this is treated as top-level location break end end end if prop and prop.mainsnak.datavalue then if not skip or count == 0 then out[#out+1] = linkedItem(qid, ":", "", "", "") -- get a linked value if we can end qid = prop.mainsnak.datavalue.value.id		else -- This is top-level location, so get short name except when this is the first item -- Use full label if there's no short name or this is the first item local prop1813 = mw.wikibase.getAllStatements(qid, "P1813") -- if there's a short name and this isn't the only item if prop1813[1] and (#out > 0)then local shortname -- short name is monolingual text, so look for match to the local language -- choose the shortest 'short name' in that language for k, v in pairs(prop1813) do					if v.mainsnak.datavalue.value.language == langcode then local name = v.mainsnak.datavalue.value.text if (not shortname) or (#name < #shortname) then shortname = name end end end -- add the shortname if one is found, fallback to the label -- but skip it if it's "USA" if shortname ~= "USA" then out[#out+1] = shortname or labelOrId(qid) else if skip then out[#out+1] = "US" end end else -- no shortname, so just add the label local loc = labelOrId(qid) -- exceptions go here: if loc == "United States of America" then out[#out+1] = "United States" else out[#out+1] = loc end end finished = true end count = count + 1 until finished or count >= 10 -- limit to 10 levels to avoid infinite loops if not first then table.remove(out, 1) end return out end

--- --- -- Public functions --- --- -- getValue is used to get the value(s) of a property -- The property ID is passed as the first unnamed parameter and is required. -- A locally supplied parameter may optionaly be supplied as the second unnamed parameter. -- The function will now also return qualifiers if parameter qual is supplied --- -- Dependencies: setRanks; parseInput; propertyvalueandquals; assembleoutput; parseParam; sourced; -- labelOrId; i18n.latestdatequalifier; format_Date; makeOrdinal; roundto; decimalPrecision; decimalToDMS; --- p.getValue = function(frame) if not frame.args[1] then frame.args = frame:getParent.args if not frame.args[1] then return i18n.errors["No property supplied"] end end

-- parameter sets for commonly used groups of parameters local paraset = tonumber(frame.args.ps or frame.args.parameterset or 0) if paraset == 1 then frame.args.rank = "best" frame.args.fetchwikidata = "ALL" frame.args.onlysourced = "no" frame.args.noicon = "true" elseif paraset == 2 then -- second set goes here end

local propertyID = mw.text.trim(frame.args[1] or "")

frame.args.reqranks = setRanks(frame.args.rank)

local entityid, props = parseInput(frame, frame.args[2], propertyID)

if not entityid then return props -- either the input parameter or nothing end -- qual is a string containing the property ID of the qualifier(s) to be returned -- if qual == "ALL" then all qualifiers returned -- if qual == "DATES" then qualifiers P580 (start time) and P582 (end time) returned -- if nothing or an empty string is passed set it nil -> no qualifiers returned local qualID = mw.text.trim(frame.args.qual or ""):upper if qualID == "" then qualID = nil end

-- set a language object and code in the frame.args table frame.args.langobj = findLang(frame.args.lang) frame.args.lang = frame.args.langobj.code

-- table 'out' stores the return value(s): local out = propertyvalueandquals(props, frame.args, qualID)

-- format the table of values and return it as a string: return assembleoutput(out, frame.args, entityid, propertyID) end

--- -- getPreferredValue is used to get a value, -- (or a comma separated list of them if multiple values exist). -- If preferred ranks are set, it will return those values, otherwise values with normal ranks -- now redundant to getValue with |rank=best --- -- Dependencies: p.getValue; setRanks; parseInput; propertyvalueandquals; assembleoutput; -- parseParam; sourced; labelOrId; i18n.latestdatequalifier; format_Date; -- makeOrdinal; roundto; decimalPrecision; decimalToDMS; --- p.getPreferredValue = function(frame) frame.args.rank = "best" return p.getValue(frame) end

--- -- getCoords is used to get coordinates for display in an infobox -- whitelist and blacklist are implemented -- optional 'display' parameter is allowed, defaults to "inline, title" --- -- Dependencies: setRanks; parseInput; decimalPrecision; --- p.getCoords = function(frame) local propertyID = "P625"

-- if there is a 'display' parameter supplied, use it	-- otherwise default to "inline, title" local disp = frame.args.display or "" if disp == "" then disp = "inline, title" end

-- there may be a format parameter to switch from deg/min/sec to decimal degrees -- default is deg/min/sec -- decimal degrees needs |format = dec local form = (frame.args.format or ""):lower:sub(1,3) if form ~= "dec" then form = "dms" end

-- just deal with best values frame.args.reqranks = setRanks("best")

local qid, props = parseInput(frame, frame.args[1], propertyID) if not qid then return props -- either local parameter or nothing else local dv = props[1].mainsnak.datavalue.value local lat, long, prec = dv.latitude, dv.longitude, dv.precision lat = decimalPrecision(lat, prec) long = decimalPrecision(long, prec) local lat_long = { lat, long } lat_long["display"] = disp lat_long["format"] = form -- invoke template Coord with the values stored in the table return frame:expandTemplate{title = 'coord', args = lat_long} end end

--- -- getQualifierValue is used to get a formatted value of a qualifier -- -- The call needs:	a property (the unnamed parameter or 1=) -- 					a target value for that property (pval=) --					a qualifier for that target value (qual=) -- The usual whitelisting and blacklisting of the property is implemented -- The boolean onlysourced= parameter can be set to return nothing -- when the property is unsourced (or only sourced to Wikipedia) --- -- Dependencies: parseParam; setRanks; parseInput; sourced; -- propertyvalueandquals; assembleoutput; -- labelOrId; i18n.latestdatequalifier; format_Date; -- findLang; makeOrdinal; roundto; decimalPrecision; decimalToDMS; --- p.getQualifierValue = function(frame)

-- The property ID that will have a qualifier is the first unnamed parameter local propertyID = mw.text.trim(frame.args[1] or "")

-- The value of the property we want to match whose qualifier value is to be returned -- is passed in named parameter |pval= local propvalue = frame.args.pval

-- The property ID of the qualifier -- whose value is to be returned is passed in named parameter |qual= local qualifierID = frame.args.qual

-- onlysourced is a boolean passed to return qualifiers -- only when property values are sourced to something other than Wikipedia -- if nothing or an empty string is passed set it true -- if "false" or "no" or 0 is passed set it false local onlysrc = parseParam(frame.args.onlysourced or frame.args.osd, true)

-- set a language object in the frame.args table frame.args.langobj = findLang(frame.args.lang)

-- set the requested ranks flags frame.args.reqranks = setRanks(frame.args.rank)

-- check for locally supplied parameter in second unnamed parameter -- success means no local parameter and the property exists local qid, props = parseInput(frame, frame.args[2], propertyID) if qid then local out = {} -- Scan through the values of the property -- we want something like property is P793, significant event (in propertyID) -- whose value is something like Q385378, construction (in propvalue) -- then we can return the value(s) of a qualifier such as P580, start time (in qualifierID) for k1, v1 in pairs(props) do			if v1.mainsnak.snaktype == "value" and v1.mainsnak.datavalue.type == "wikibase-entityid" then -- It's a wiki-linked value, so check if it's the target (in propvalue) -- and if it has qualifiers if v1.mainsnak.datavalue.value.id == propvalue and v1.qualifiers then if onlysrc == false or sourced(v1) then -- if we've got this far, we have a (sourced) claim with qualifiers -- which matches the target, so find the value(s) of the qualifier we want local quals = v1.qualifiers[qualifierID] if quals then -- can't reference qualifer, so set onlysourced = "no" (not boolean) local qargs = frame.args qargs.onlysourced = "no" local vals = propertyvalueandquals(quals, qargs, qid) for k, v in pairs(vals) do								out[#out + 1] = v							end end end -- of check for sourced end -- of check for matching required value and has qualifiers end -- of check for wikibase entity end -- of loop through values of propertyID return assembleoutput(out, frame.args, qid, propertyID) else return props -- either local parameter or nothing end -- of test for success return nil end

--- -- getValueByQual gets the value of a property which has a qualifier with a given entity value -- The call needs: --					a property ID (the unnamed parameter or 1=Pxxx) --					the ID of a qualifier for that property (qualID=Pyyy) --					the Wikibase-entity ID of a value for that qualifier (qvalue=Qzzz) -- The usual whitelisting, blacklisting, onlysourced, etc. are implemented --- -- Dependencies: _getvaluebyqual; parseParam; setRanks; parseInput; sourced; -- assembleoutput; --- p.getValueByQual = function(frame) local qualID = frame.args.qualID -- The Q-id of the value for the qualifier we want to match is in named parameter |qvalue= local qval = frame.args.qvalue or "" if qval == "" then return "no qualifier value supplied" end local function checkQID(id) return id == qval end return _getvaluebyqual(frame, qualID, checkQID) end

--- -- getValueByLang gets the value of a property which has a qualifier P407 -- ("language of work or name") whose value has the given language code -- The call needs: --					a property ID (the unnamed parameter or 1=Pxxx) --					the MediaWiki language code to match the language (lang=xx[-yy]) --					(if no code is supplied, it uses the default language) -- The usual whitelisting, blacklisting, onlysourced, etc. are implemented --- -- Dependencies: _getvaluebyqual; parseParam; setRanks; parseInput; sourced; assembleoutput; --- p.getValueByLang = function(frame)

-- The language code for the qualifier we want to match is in named parameter |lang= local langcode = frame.args.lang or "" if langcode == "" then langcode = frame:callParserFunction{ name = "int", args = "lang" } end local function checkLanguage(id) -- id should represent a language like "British English (Q7979)" -- it should have string property "Wikimedia language code (P424)" -- qlcode will be a table: local qlcode = mw.wikibase.getBestStatements(id, "P424") if (#qlcode > 0) and (qlcode[1].mainsnak.datavalue.value == langcode) then return true end end return _getvaluebyqual(frame, "P407", checkLanguage) end

--- -- getValueByRefSource gets the value of a property which has a reference "stated in" (P248) -- whose value has the given entity code. -- The call needs: --					a property ID (the unnamed parameter or 1=Pxxx) --					the entity ID of a value to match where the reference is stated in (match=Qzzz) -- The usual whitelisting, blacklisting, onlysourced, etc. are implemented --- -- Dependencies: parseParam; setRanks; parseInput; sourced; propertyvalueandquals assembleoutput; --- p.getValueByRefSource = function(frame) -- The property ID that we want to check is the first unnamed parameter local propertyID = mw.text.trim(frame.args[1] or ""):upper if propertyID == "" then return "no property supplied" end

-- The Q-id of the value we want to match is in named parameter |qvalue= local qval = (frame.args.match or ""):upper if qval == "" then qval = "Q21540096" end

local unit = (frame.args.unit or ""):upper if unit == "" then unit = "Q4917" end

local onlysrc = parseParam(frame.args.onlysourced or frame.args.osd, true)

-- set the requested ranks flags frame.args.reqranks = setRanks(frame.args.rank)

-- set a language object and code in the frame.args table frame.args.langobj = findLang(frame.args.lang) frame.args.lang = frame.args.langobj.code

local linked = parseParam(frame.args.linked, true)

local uabbr = parseParam(frame.args.uabbr or frame.args.unitabbr, false)

-- qid not nil means no local parameter and the property exists local qid, props = parseInput(frame, frame.args[2], propertyID)

if qid then local out = {} local mlt= {} for k1, v1 in pairs(props) do			if onlysrc == false or sourced(v1) then if v1.references then for k2, v2 in pairs(v1.references) do						if v2.snaks.P248 then for k3, v3 in pairs(v2.snaks.P248) do								if v3.datavalue.value.id == qval then out[#out+1], mlt[#out+1] = rendersnak(v1, frame.args, linked, "", "", "", "", uabbr, unit) if not mlt[#out] then -- we only need one match per property value -- unless datatype was monolingual text break end end -- of test for match end -- of loop through values "stated in" end -- of test that "stated in" exists end -- of loop through references end -- of test that references exist end -- of test for sourced end -- of loop through values of propertyID if #mlt > 0 then local langcode = frame.args.lang langcode = mw.text.split( langcode, '-', true )[1] local fbtbl = mw.language.getFallbacksFor( langcode ) table.insert( fbtbl, 1, langcode ) local bestval = "" local found = false for idx1, lang1 in ipairs(fbtbl) do				for idx2, lang2 in ipairs(mlt) do					if (lang1 == lang2) and not found then bestval = out[idx2] found = true break end end -- loop through values of property end -- loop through fallback languages if found then -- replace output table with a table containing the best value out = { bestval } else -- more than one value and none of them on the list of fallback languages -- sod it, just give them the first one out = { out[1] } end end return assembleoutput(out, frame.args, qid, propertyID) else return props -- no property or local parameter supplied end -- of test for success end

--- -- getCommonsLink takes an optional qid of a Wikidata entity passed as |qid= -- It returns one of the following in order of preference: -- the Commons sitelink of the linked Wikidata item; -- the Commons sitelink of the topic's main category of the linked Wikidata item; --- -- Dependencies: _getCommonslink; _getSitelink --- p.getCommonsLink = function(frame) return _getCommonslink(frame.args.qid, frame.args.onlycat or frame.args.onlycategories) end

--- -- getSitelink takes the qid of a Wikidata entity passed as |qid= -- It takes an optional parameter |wiki= to determine which wiki is to be checked for a sitelink -- If the parameter is blank, then it uses the local wiki. -- If there is a sitelink to an article available, it returns the plain text link to the article -- If there is no sitelink, it returns nil. --- -- Dependencies: none --- p.getSiteLink = function(frame) return _getSitelink(frame.args.qid, frame.args.wiki or mw.text.trim(frame.args[1] or "")) end

--- -- getLink has the qid of a Wikidata entity passed as the first unnamed parameter or as |qid= -- If there is a sitelink to an article on the local Wiki, it returns a link to the article -- with the Wikidata label as the displayed text. -- If there is no sitelink, it returns the label as plain text. -- If there is no label in the local language, it displays the qid instead. --- -- Dependencies: none --- p.getLink = function(frame) local itemID = mw.text.trim(frame.args[1] or frame.args.qid or "") if itemID == "" then return end local sitelink = mw.wikibase.sitelink(itemID) local label = labelOrId(itemID) if sitelink then return "" .. label .. "" else return label end end

--- -- getLabel has the qid of a Wikidata entity passed as the first unnamed parameter or as |qid= -- It returns the Wikidata label for the local language as plain text. -- If there is no label in the local language, it displays the qid instead. --- -- Dependencies: none --- p.getLabel = function(frame) local itemID = mw.text.trim(frame.args[1] or frame.args.qid or "") if itemID == "" then return end local label = labelOrId(itemID) return label end

--- -- getAT has the qid of a Wikidata entity passed as the first unnamed parameter or as |qid= -- If there is a sitelink to an article on the local Wiki, it returns the sitelink as plain text. -- If there is no sitelink, it returns nothing. --- -- Dependencies: none --- p.getAT = function(frame) local itemID = mw.text.trim(frame.args[1] or frame.args.qid or "") if itemID == "" then return end return mw.wikibase.sitelink(itemID) end

--- -- getDescription has the qid of a Wikidata entity passed as |qid= -- (it defaults to the associated qid of the current article if omitted) -- and a local parameter passed as the first unnamed parameter. -- Any local parameter passed (other than "Wikidata" or "none") becomes the return value. -- It returns the article description for the Wikidata entity if the local parameter is "Wikidata". -- Nothing is returned if the description doesn't exist or "none" is passed as the local parameter. --- -- Dependencies: none --- p.getDescription = function(frame) local desc = mw.text.trim(frame.args[1] or "") local itemID = mw.text.trim(frame.args.qid or "") if itemID == "" then itemID = nil end if desc:lower == 'wikidata' then return mw.wikibase.description(itemID) elseif desc:lower == 'none' then return nil else return desc end end

--- -- getAliases has the qid of a Wikidata entity passed as |qid= -- (it defaults to the associated qid of the current article if omitted) -- and a local parameter passed as the first unnamed parameter. -- It implements blacklisting and whitelisting with a field name of "alias" by default. -- Any local parameter passed becomes the return value. -- Otherwise it returns the aliases for the Wikidata entity with the usual list options. -- Nothing is returned if the aliases do not exist. --- -- Dependencies: findLang; assembleoutput --- p.getAliases = function(frame) local args = frame.args

local fieldname = args.name or "" if fieldname == "" then fieldname = "alias" end

local blacklist = args.suppressfields or args.spf or "" if blacklist:find(fieldname) then return nil end

local localval = mw.text.trim(args[1] or "") if localval ~= "" then return localval end

local whitelist = args.fetchwikidata or args.fwd or "" if whitelist == "" then whitelist = "NONE" end if not (whitelist == 'ALL' or whitelist:find(fieldname)) then return nil end

local qid = mw.text.trim(args.qid or "") if qid == "" then qid = nil end

local entity = mw.wikibase.getEntity(qid) if not entity then return nil end local aliases = entity.aliases if not aliases then return nil end if not qid then qid= mw.wikibase.getEntityIdForCurrentPage end

args.langobj = findLang(args.lang) local langcode = args.langobj.code args.lang = langcode

local out = {} for k1, v1 in pairs(aliases) do		if v1[1].language == langcode then for k1, v2 in ipairs(v1) do				out[#out+1] = v2.value end break end end

return assembleoutput(out, args, qid) end

--- -- pageId returns the page id (entity ID, Qnnn) of the current page -- returns nothing if the page is not connected to Wikidata --- -- Dependencies: none --- p.pageId = function(frame) return mw.wikibase.getEntityIdForCurrentPage end

--- -- formatDate is a wrapper to export the private function format_Date --- -- Dependencies: format_Date; --- p.formatDate = function(frame) return format_Date(frame.args[1], frame.args.df, frame.args.bc) end

--- -- location is a wrapper to export the private function _location -- it takes the entity-id as qid or the first unnamed parameter -- optional boolean parameter first toggles the display of the first item -- optional boolean parameter skip toggles the display to skip to the last item --- -- Dependencies: _location; --- p.location = function(frame) local qid = mw.text.trim(frame.args.qid or frame.args[1] or ""):upper if qid == "" then return "No id supplied" end local first = mw.text.trim(frame.args.first or "") local skip = mw.text.trim(frame.args.skip or "") return table.concat( _location(qid, first, skip), ", " ) end

--- -- checkBlacklist implements a test to check whether a named field is allowed -- returns true if the field is not blacklisted (i.e. allowed) -- returns false if the field is blacklisted (i.e. disallowed) -- -- displays "blacklisted" -- -- displays "not blacklisted" --- -- Dependencies: none --- p.checkBlacklist = function(frame) local blacklist = frame.args.suppressfields or frame.args.spf or "" local fieldname = frame.args.name or "" if blacklist ~= "" and fieldname ~= "" then if blacklist:find(fieldname) then return false else return true end else -- one of the fields is missing: let's call that "not on the list" return true end end

--- -- emptyor returns nil if its first unnamed argument is just punctuation, whitespace or html tags -- otherwise it returns the argument unchanged (including leading/trailing space). -- If the argument may contain "=", then it must be called explicitly: -- |1=arg -- (In that case, leading and trailing spaces are trimmed) -- It finds use in infoboxes where it can replace tests like: -- -- with a form that uses just a single call to Wikidata: -- --- -- Dependencies: none --- p.emptyor = function(frame) local s = frame.args[1] or "" if s == "" then return nil end local sx = s:gsub("%s", ""):gsub("<[^>]*>", ""):gsub("%p", "") if sx == "" then return nil else return s	end end

--- -- labelorid is a public function to expose the output of labelOrId -- Pass the Q-number as |qid= or as an unnamed parameter. -- It returns the Wikidata label for that entity or the qid if no label exists. --- -- Dependencies: labelOrId --- p.labelorid = function(frame) local label = labelOrId( frame.args.qid or frame.args[1] ) return label end

--- -- getLang returns the MediaWiki language code of the current content. -- If optional parameter |style=full, it returns the language name. --- -- Dependencies: none --- p.getLang = function(frame) local style = (frame.args.style or ""):lower local langcode = mw.language.getContentLanguage.code if style == "full" then return mw.language.fetchLanguageName( langcode ) end return langcode end

--- -- findLanguage exports the local findLang function -- It takes an optional language code and returns, in order of preference: -- the code if a known language; -- the user's language, if set; -- the server's content language. --- -- Dependencies: findLang --- p.findLanguage = function(frame) return findLang(frame.args.lang or frame.args[1]).code end

--- -- getQid returns the qid, if supplied -- failing that, the Wikidata entity ID of the "category's main topic (P301)", if it exists -- failing that, the Wikidata entity ID asociated with the curent page, if it exists -- otherwise, nothing --- -- Dependencies: none --- p.getQid = function(frame) local qid = (frame.args.qid or ""):upper -- check if a qid was passed; if so, return it: if qid ~= "" then return qid end -- check if there's a "category's main topic (P301)": qid = mw.wikibase.getEntityIdForCurrentPage if qid then local prop301 = mw.wikibase.getBestStatements(qid, "P301") if prop301[1] then local mctid = prop301[1].mainsnak.datavalue.value.id			if mctid then return mctid end end end -- otherwise return the page qid (if any) return qid end

--- -- followQid takes two optional parameters: qid and props -- If qid is not given, it uses the qid for the connected page -- or returns nil if there isn't one. -- props is a list of properties, separated by punctuation. -- If props is given, the Wikidata item for the qid is examined for each property in turn. -- If that property contains a value that is another Wikibase-item, that item's qid is returned, -- and the search terminates. -- If props is not given, the qid is returned. --- -- Dependencies: none --- p.followQid = function(frame) local qid = (frame.args.qid or ""):upper if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return nil end

local props = (frame.args.props or ""):upper if props ~= "" then for p in mw.text.gsplit(props, "%p") do -- split at punctuation and iterate p = mw.text.trim(p) for i, v in ipairs( mw.wikibase.getBestStatements(qid, p) ) do				local linkedid = v.mainsnak.datavalue and v.mainsnak.datavalue.value.id				if linkedid then return linkedid end end -- loop through values of property to follow end -- loop through list of properties to follow end

return qid end

--- -- siteID returns the root of the globalSiteID -- e.g. "en" for "enwiki", "enwikisource", etc. -- treats "en-gb" as "en", etc. --- -- Dependencies: none --- p.siteID = function(frame) local txtlang = frame:preprocess( "" ) or "" -- This deals with specific exceptions: be-tarask -> be-x-old if txtlang == "be-tarask" then return "be_x_old" end local pos = txtlang:find("-") local ret = "" if pos then ret = txtlang:sub(1, pos-1) else ret = txtlang end return ret end

--- -- projID returns the code used to link to the reader's language's project -- e.g "en" for en:WikidataIB -- treats "en-gb" as "en", etc. --- -- Dependencies: none --- p.projID = function(frame) local txtlang = frame:preprocess( "" ) or "" -- This deals with specific exceptions: be-tarask -> be-x-old if txtlang == "be-tarask" then return "be-x-old" end local pos = txtlang:find("-") local ret = "" if pos then ret = txtlang:sub(1, pos-1) else ret = txtlang end return ret end

--- -- formatNumber formats a number according to the the supplied language code ("|lang=") -- or the default language if not supplied. -- The number is the first unnamed parameter or "|num=" --- -- Dependencies: findLang --- p.formatNumber = function(frame) local lang local num = tonumber(frame.args[1] or frame.args.num) or 0 lang = findLang(frame.args.lang) return lang:formatNum( num ) end

--- -- examine dumps the property (the unnamed parameter or pid) -- from the item given by the parameter 'qid' (or the other unnamed parameter) -- or from the item corresponding to the current page if qid is not supplied. -- e.g. -- or or any combination of these -- or for the current page. --- -- Dependencies: none --- p.examine = function( frame ) local args if frame.args[1] or frame.args.pid or frame.args.qid then args = frame.args else args = frame:getParent.args end local par = {} local pid = (args.pid or ""):upper local qid = (args.qid or ""):upper par[1] = mw.text.trim( args[1] or "" ):upper par[2] = mw.text.trim( args[2] or "" ):upper table.sort(par) if par[2]:sub(1,1) == "P" then par[1], par[2] = par[2], par[1] end if pid == "" then pid = par[1] end if qid == "" then qid = par[2] end if pid:sub(1,1) ~= "P" then return "No property supplied" end if qid:sub(1,1) ~= "Q" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return "No item for this page" end return mw.dumpObject( mw.wikibase.getAllStatements( qid, pid ) ) end

--- -- checkvalue looks for 'val' as a wikibase-item value of a property (the unnamed parameter or pid) -- from the item given by the parameter 'qid' -- or from the Wikidata item associated with the current page if qid is not supplied. -- If property is not supplied, then P31 (instance of) is assumed. -- It returns val if found or nothing if not found. -- e.g. -- or -- or -- or for the current page. --- -- Dependencies: none --- p.checkvalue = function( frame ) local args if frame.args.val then args = frame.args else args = frame:getParent.args end local val = args.val if not val then return nil end local pid = mw.text.trim(args.pid or args[1] or "P31"):upper local qid = (args.qid or ""):upper if pid:sub(1,1) ~= "P" then return nil end if qid:sub(1,1) ~= "Q" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return nil end local stats = mw.wikibase.getAllStatements( qid, pid ) if not stats[1] then return nil end if stats[1].mainsnak.datatype == "wikibase-item" then for k, v in pairs( stats ) do			if v.mainsnak.snaktype == "value" and v.mainsnak.datavalue.value.id == val then return val end end end return nil end

--- -- url2 takes a parameter url= that is a proper url and formats it for use in an infobox. -- If no parameter is supplied, it returns nothing. -- This is the equivalent of Template:URL -- but it keeps the "edit at Wikidata" pen icon out of the microformat. -- Usually it will take its url parameter directly from a Wikidata call: -- e.g. {{#invoke:WikidataIB |url2 |url={{wdib |P856 |qid=Q23317 |fwd=ALL |osd=no}} --- -- Dependencies: none --- p.url2 = function(frame) local txt = frame.args.url or "" if txt == "" then return nil end local url, icon = txt:match("(.+) (.+)") url = url or txt icon = icon or "" local prot, addr = url:match("(http[s]*://)(.+)") prot = prot or url addr = addr or "" local disp, n = addr:gsub("%.", " %.") return ' [' .. prot .. addr .. " " .. disp .. "] " .. icon end

--- -- getWebsite fetches the Official website (P856) and formats it for use in an infobox. -- This is similar to Template:Official website but with a url displayed, -- and it adds the "edit at Wikidata" pen icon beyond the microformat if enabled. -- A local value will override the Wikidata value. "NONE" returns nothing. -- e.g. {{#invoke:WikidataIB |getWebsite |qid= |noicon= |lang= |url= }} --- -- Dependencies: findLang; parseParam; --- p.getWebsite = function(frame) local url = frame.args.url or "" if url:upper == "NONE" then return nil end

local qid = frame.args.qid or "" if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return nil end

local urls = {} local quals = {} if url == "" then local prop856 = mw.wikibase.getBestStatements(qid, "P856") for k, v in pairs(prop856) do			if v.mainsnak.snaktype == "value" then urls[#urls+1] = v.mainsnak.datavalue.value if v.qualifiers and v.qualifiers["P1065"] then -- just take the first archive url (P1065) local au = v.qualifiers["P1065"][1] if au.snaktype == "value" then quals[#urls] = au.datavalue.value end -- test for archive url having a value end -- test for qualifers end -- test for website having a value end -- loop through website(s) else urls[1] = url end if #urls == 0 then return nil end

local out = {} for i, u in ipairs(urls) do		local link = quals[i] or u		local prot, addr = u:match("(http[s]*://)(.+)") addr = addr or u		local disp, n = addr:gsub("%.", " %.") out[#out+1] = ' [' .. link .. " " .. disp .. "] "	end

local langcode = findLang(frame.args.lang).code local noicon = parseParam(frame.args.noicon, false) if url == "" and not noicon then out[#out] = out[#out] .. createicon(langcode, qid, "P856") end

local ret = "" if #out > 1 then ret = mw.getCurrentFrame:expandTemplate{title = "ubl", args = out} else ret = out[1] end

return ret end

--- -- getAllLabels fetches the set of labels and formats it for display as wikitext. -- It takes a parameter 'qid' for arbitrary access, otherwise it uses the current page. --- -- Dependencies: none --- p.getAllLabels = function(frame) local args = frame.args or frame:getParent.args or {}

local qid = args.qid if qid == "" then qid = nil end

local entity = mw.wikibase.getEntity(qid) if not entity then return i18n["entity-not-found"] end

local labels = entity.labels if not labels then return i18n["labels-not-found"] end

local out = {} for k, v in pairs(labels) do out[#out+1] = v.value .. " (" .. v.language .. ")" end

return table.concat(out, "; ") end

--- -- getAllDescriptions fetches the set of descriptions and formats it for display as wikitext. -- It takes a parameter 'qid' for arbitrary access, otherwise it uses the current page. --- -- Dependencies: none --- p.getAllDescriptions = function(frame) local args = frame.args or frame:getParent.args or {}

local qid = args.qid if qid == "" then qid = nil end

local entity = mw.wikibase.getEntity(qid) if not entity then return i18n["entity-not-found"] end

local descriptions = entity.descriptions if not descriptions then return i18n["descriptions-not-found"] end

local out = {} for k, v in pairs(descriptions) do out[#out+1] = v.value .. " (" .. v.language .. ")" end

return table.concat(out, "; ") end

--- -- getAllAliases fetches the set of aliases and formats it for display as wikitext. -- It takes a parameter 'qid' for arbitrary access, otherwise it uses the current page. --- -- Dependencies: none --- p.getAllAliases = function(frame) local args = frame.args or frame:getParent.args or {}

local qid = args.qid if qid == "" then qid = nil end

local entity = mw.wikibase.getEntity(qid) if not entity then return i18n["entity-not-found"] end

local aliases = entity.aliases if not aliases then return i18n["aliases-not-found"] end

local out = {} for k1, v1 in pairs(aliases) do		local lang = v1[1].language local val = {} for k1, v2 in ipairs(v1) do			val[#val+1] = v2.value end out[#out+1] = table.concat(val, ", ") .. " (" .. lang .. ")" end

return table.concat(out, "; ") end

return p

--- -- List of exported functions --- -- getValue -- getPreferredValue -- getCoords -- getQualifierValue -- getValueByQual -- getValueByLang -- getLink -- getLabel -- getAT -- getDescription -- getAliases -- pageId -- formatDate -- location -- checkBlacklist -- emptyor -- labelorid -- getLang -- findLanguage -- getQID -- followQid -- siteID -- projID -- formatNumber -- examine -- checkvalue -- url2 -- getWebsite -- getAllLabels -- getAllDescriptions -- getAllAliases ---