Module:WikidataIB/lite explained

-- Version: 2021-01-03-- Module to implement getValue from Module:WikidataIB attempting to minimise resource use

local p =

local cdate -- initialise as nil and only load _complex_date function if needed-- is loaded lazily and has the following dependencies:-- Module:I18n/complex date, Module:ISOdate, Module:DateI18n (alternative for 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 =

local i18n =-- This allows an internationisation module to override the above tableif 'en' ~= mw.getContentLanguage:getCode then require("Module:i18n").loadI18n("Module:WikidataIB/i18n", i18n)end

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

'

-- Some items should not be linked.-- Each wiki can create a list of those in Module:WikidataIB/nolinks-- It should return a table called itemsindex, containing true for each item not to be linkedlocal donotlink = local nolinks_exists, nolinks = pcall(mw.loadData, "Module:WikidataIB/nolinks")if nolinks_exists then donotlink = nolinks.itemsindexend

--------------------------------------------------------------------------------- Private functions------------------------------------------------------------------------------------------------------------------------------------------------------------------ findLang takes a "langcode" parameter 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:callParserFunction('int',) if mw.language.isKnownLanguageTag(langcode) then langobj = mw.language.new(langcode) else langobj = mw.language.getContentLanguage end end return langobjend

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

--------------------------------------------------------------------------------- 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, send

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

--------------------------------------------------------------------------------- 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 circumstances"/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, model) -- output formatting according to preferences (y/dmy/mdy/ymd) df = (df or ""):lower -- if ymd is required, return the part of the timestamp in YYYY-MM-DD form -- but apply Year zero#Astronomers fix: 1 BC = 0000; 2 BC = -0001; etc. if df

"ymd" then if timestamp:sub(1,1)

"+" then return timestamp:sub(2,11) else local yr = tonumber(timestamp:sub(2,5)) - 1 yr = ("000" .. yr):sub(-4) if yr ~= "0000" then yr = "-" .. yr end return yr .. timestamp:sub(6,11) end end -- 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") -- 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 -- deal with Julian dates: -- no point in saying that dates before 1582 are Julian - they are by default -- doesn't make sense for dates less precise than year -- we can suppress it by setting |plaindate, e.g. for use in constructing categories. local calendarmodel = "" if tonumber(year) > 1582 and dprec > 8 and not pd and model

"http://www.wikidata.org/entity/Q1985786" then calendarmodel = "julian" end if not cdate then cdate = require("Module:Complex date")._complex_date end local fdate = cdate(calendarmodel, 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("<[^>]*>", "") -- there may be leading zeros that we should remove fdate = fdate:gsub("^0*", "") -- if a plain date is required, then remove any links (like BC linked) if pd then fdate = fdate:gsub("%[%[.*|", ""):gsub("]]", "") end -- 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 fdateend

--------------------------------------------------------------------------------- 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.-- It returns a boolean.--------------------------------------------------------------------------------- Dependencies: none-------------------------------------------------------------------------------local parseParam = function(param, default) if type(param)

"boolean" then param = tostring(param) end 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 endend

--------------------------------------------------------------------------------- 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, lang) if lang

"default" then lang = findLang.code end local label if lang then label = mw.wikibase.getLabelByLang(id, lang) else label = mw.wikibase.getLabel(id) end if label then return mw.text.nowiki(label), true else return id, false endend

--------------------------------------------------------------------------------- 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-- dtxt is text to be used instead of label, or nil.-- lang is the current language code.-- uselbl is boolean switch to force display of the label instead of the sitelink (default: false)-- linkredir is boolean switch to allow linking to a redirect (default: false)-- formatvalue is boolean switch to allow formatting as italics or quoted (default: false)--------------------------------------------------------------------------------- Dependencies: labelOrId; donotlink[]-------------------------------------------------------------------------------local linkedItem = function(id, args) local lprefix = args.lprefix or "" -- toughen against nil values passed local lpostfix = args.lpostfix or "" local prefix = args.prefix or "" local postfix = args.postfix or "" local dtxt = args.dtxt local lang = args.lang or "en" -- fallback to default if missing local uselbl = args.uselabel or args.uselbl uselbl = parseParam(uselbl, false) local linkredir = args.linkredir linkredir = parseParam(linkredir, false) local disp local sitelink = mw.wikibase.getSitelink(id) local label, islabel if dtxt then label, islabel = dtxt, true else label, islabel = labelOrId(id) end if mw.site.siteName ~= "Wikimedia Commons" then if sitelink then if not dtxt then -- if sitelink and label are the same except for case, no need to process further if sitelink:lower ~= label:lower then -- strip any namespace or dab from the sitelink local pos = sitelink:find(":") or 0 local slink = sitelink if pos > 0 then local pfx = sitelink:sub(1,pos-1) if mw.site.namespaces[pfx] then -- that prefix is a valid namespace, so remove it slink = sitelink:sub(pos+1) end end -- remove stuff after commas or inside parentheses - ie. dabs slink = slink:gsub("%s%(.+%)$", ""):gsub(",.+$", "") -- if uselbl is false, use sitelink instead of label if not uselbl then -- use slink as display, preserving label case - find("^%u") is true for 1st char uppercase if label:find("^%u") then label = slink:gsub("^(%l)", string.upper) else label = slink:gsub("^(%u)", string.lower) end end end end if donotlink[label] then disp = prefix .. label .. postfix else disp = "" .. prefix .. label .. postfix .. "" end elseif islabel then -- no sitelink, label exists, so check if a redirect with that title exists, if linkredir is true -- display plain label by default disp = prefix .. label .. postfix if linkredir then local artitle = mw.title.new(label, 0) -- only nil if label has invalid chars if not donotlink[label] and 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 .. "" end 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 -- for enwiki, just return the tracking category if mw.wikibase.getGlobalSiteId

"enwiki" then disp = i18n.missinginfocat else disp = prefix .. label .. postfix .. i18n.missinginfocat end end else local ccat = mw.wikibase.getBestStatements(id, "P373")[1] if ccat and ccat.mainsnak.datavalue 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 dispend

--------------------------------------------------------------------------------- sourced takes a table representing a statement that may or may not have references-- it looks for a reference sourced to something not containing the word "wikipedia"-- it returns a boolean = true if it finds a sourced reference.--------------------------------------------------------------------------------- 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("Wiki") then return true end end endend

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

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

-- return nil if Wikidata is not available if not mw.wikibase then return false, input_parm 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 or ""

-- The whitelist is passed in named parameter |fetchwikidata local whitelist = args.fetchwikidata or args.fwd or "" if 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 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

-- We can filter out non-valid properties if property_id:sub(1,1):upper ~="P" or property_id

"P0" then return false, nil 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, nilend

--------------------------------------------------------------------------------- createicon assembles the "Edit at Wikidata" pen icon.-- It returns a wikitext string inside a span class="penicon"-- if entityID is nil or empty, the ID associated with current page is used-- langcode and propertyID may be nil or empty--------------------------------------------------------------------------------- Dependencies: i18n[];-------------------------------------------------------------------------------local createicon = function(langcode, entityID, propertyID) langcode = langcode or "" if not entityID or entityID

"" then entityID= mw.wikibase.getEntityIdForCurrentPage end propertyID = propertyID or "" local icon = " 

frameless |text-top |10px |alt=" .. i18n["editonwikidata"] .. "|link=https://www.wikidata.org/wiki/" .. entityID if langcode ~= "" then icon = icon .. "?uselang=" .. langcode end if propertyID ~= "" then icon = icon .. "#" .. propertyID end icon = icon .. "|" .. i18n["editonwikidata"] .. "]]" return iconend

--------------------------------------------------------------------------------- assembleoutput takes the sequence table containing the property values-- and formats it according to switches given. It returns a string or nil.-- It uses the entityID (and optionally 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

-- replacetext (rt) is a string that is returned instead of any non-empty Wikidata value -- this is useful for tracking and debugging local replacetext = mw.text.trim(args.rt or args.replacetext or "")

-- 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 there's something to display and a pen icon is wanted, add it the end of the last value local hasdisplay = false for i, v in ipairs(out) do if v ~= i18n.missinginfocat then hasdisplay = true break end end if not noic and hasdisplay 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 end if collapse >0 and #out > collapse then strout = collapsediv .. strout .. "

" end else strout = nil -- no items had valid reference end if replacetext ~= "" and strout then strout = replacetext end return stroutend

--------------------------------------------------------------------------------- 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 Qid.--------------------------------------------------------------------------------- Dependencies: parseParam; labelOrId; i18n[]; dateFormat;-- roundto; decimalPrecision; decimalToDMS; linkedItem;-------------------------------------------------------------------------------local rendersnak = function(propval, args, linked, lpre, lpost, pre, post, uabbr, filter) lpre = lpre or "" lpost = lpost or "" pre = pre or "" post = post or "" args.lang = args.lang or findLang.code -- allow values to display a fixed text instead of label local dtxt = args.displaytext or args.dt if dtxt

"" then dtxt = nil end -- switch to use display of short name (P1813) instead of label local snak = propval.mainsnak or propval local dtype = snak.datatype local dv = snak.datavalue 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, args) else -- no link wanted so check for display-text, otherwise test for lang code local label, islabel if dtxt then label = dtxt else label, islabel = labelOrId(qnumber) local langlabel = mw.wikibase.getLabelByLang(qnumber, args.lang) if langlabel then label = mw.text.nowiki(langlabel) end 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 -- -- Here we can check whether args.df has a value -- If not, use code from Module:Sandbox/RexxS/Getdateformat to set it from templates like val = dateFormat(dv.time, dv.precision, args.df, args.bc, args.pd, propval.qualifiers, args.lang, "", dv.calendarmodel) ------------------------------------ -- 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 elseif dtype

"external-id" then val = "[" .. lpre .. dv .. lpost .. " " .. 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 uabbr = true fnum = false args.scale = "0" end -- -- a switch to turn on/off showing units, default is true local showunits = parseParam(args.su or args.showunits, true) -- -- 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 < 0 or 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 and showunits 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.getBestStatements(unitqid, "P5061") -- construct fallback table, add local lang and multiple languages local fbtbl = mw.language.getFallbacksFor(args.lang) table.insert(fbtbl, 1, args.lang) table.insert(fbtbl, 1, "mul") 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 then if range

"" then val = mw.getCurrentFrame:expandTemplate else val = mw.getCurrentFrame:expandTemplate 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 -- not needed for now -- -- show parameter allows just the latitude, or just the longitude, or both -- to be returned as a signed decimal, ignoring the format parameter. 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, mltend

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

-- allow qualifiers to have a different date format; default to year args.qdf = args.qdf or args.qualifierdateformat or args.df or "y"

local lang = args.lang or findlang.code

-- qualID is a string list of wanted qualifiers or "ALL" qualID = qualID or "" -- capitalise list of wanted qualifiers and substitute "DATES" qualID = qualID:upper:gsub("DATES", "P580, P582") local allflag = (qualID

"ALL") -- create table of wanted qualifiers as key local qwanted = -- create sequence of wanted qualifiers local qorder = for q in mw.text.gsplit(qualID, "%p") do -- split at punctuation and iterate local qtrim = mw.text.trim(q) if qtrim ~= "" then qwanted[mw.text.trim(q)] = true qorder[#qorder+1] = qtrim end end -- qsep is the output separator for rendering qualifier list local qsep = (args.qsep or ""):gsub('"', ) -- qargs are the arguments to supply to assembleoutput local qargs =

-- 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 a list of returned values for this property -- mlt[] holds the language code if the datatype is monolingual text local out = local mlt =

for k, v in ipairs(objproperty) do local hasvalue = true if (onlysrc and not sourced(v)) then -- no value: it isn't sourced when onlysourced=true hasvalue = false else local val, lcode = rendersnak(v, args, linked, lprefix, lpostfix, prefix, postfix, uabbr) if not val then hasvalue = false -- rank doesn't match elseif qualsonly and qualID then -- suppress value returned: only qualifiers are requested else out[#out+1], mlt[#out+1] = val, lcode end end

-- See if qualifiers are to be returned: local snak = v.mainsnak or v if hasvalue and v.qualifiers and qualID ~= "" and snak.snaktype~="novalue" then -- collect all wanted qualifier values returned in qlist, indexed by propertyID local qlist = local timestart, timeend = "", "" -- loop through qualifiers for k1, v1 in pairs(v.qualifiers) do if allflag or qwanted[k1] then if k1

"P1326" then local ts = v1[1].datavalue.value.time local dp = v1[1].datavalue.value.precision qlist[k1] = dateFormat(ts, dp, args.qdf, args.bc, pd, "", lang, "before") elseif k1

"P1319" then local ts = v1[1].datavalue.value.time local dp = v1[1].datavalue.value.precision qlist[k1] = dateFormat(ts, dp, args.qdf, args.bc, pd, "", lang, "after") elseif k1

"P580" then timestart = propertyvalueandquals(v1, qargs)[1] or "" -- treat only one start time as valid elseif k1

"P582" then timeend = propertyvalueandquals(v1, qargs)[1] or "" -- treat only one end time as valid else local q = assembleoutput(propertyvalueandquals(v1, qargs), qargs) -- we already deal with circa via 'sourcing circumstances' if the datatype was time -- circa may be either linked or unlinked *** internationalise later *** if datatype ~= "time" or q ~= "circa" and not (type(q)

"string" and q:find("circa]]")) then qlist[k1] = q end end end -- of test for wanted end -- of loop through qualifiers -- set date separator local t = timestart .. timeend -- *** internationalise date separators later *** local dsep = " - " if t:find("%s") or t:find(" ") then dsep = "  - " end -- set the order for the list of qualifiers returned; start time and end time go last if next(qlist) then local qlistout = if allflag then for k2, v2 in pairs(qlist) do qlistout[#qlistout+1] = v2 end else for i2, v2 in ipairs(qorder) do qlistout[#qlistout+1] = qlist[v2] end end if t ~= "" then qlistout[#qlistout+1] = timestart .. dsep .. timeend end local qstr = assembleoutput(qlistout, qargs) if qualsonly then out[#out+1] = qstr else out[#out] = out[#out] .. " (" .. qstr .. ")" end elseif t ~= "" then if qualsonly then out[#out+1] = timestart .. dsep .. timeend else out[#out] = out[#out] .. " (" .. timestart .. dsep .. timeend .. ")" 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 lang = mw.text.split(lang, '-', true)[1] local fbtbl = mw.language.getFallbacksFor(lang) table.insert(fbtbl, 1, lang) 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 = else -- more than one value and none of them on the list of fallback languages -- sod it, just give them the first one out = end end return outend

---------------------------------------------------------------------------------------------------------------------------------------------------------------- Public functions---------------------------------------------------------------------------------------------------------------------------------------------------------------- _getValue makes the functionality of getValue available to other modules--------------------------------------------------------------------------------- Dependencies: setRanks; parseInput; propertyvalueandquals; assembleoutput; parseParam; sourced;-- labelOrId; i18n.latestdatequalifier; roundto; decimalPrecision; decimalToDMS;-------------------------------------------------------------------------------p._getValue = function(args) -- parameter sets for commonly used groups of parameters local paraset = tonumber(args.ps or args.parameterset or 0) if paraset

1 then -- a common setting args.rank = "best" args.fetchwikidata = "ALL" args.onlysourced = "no" args.noicon = "true" elseif paraset

2 then -- equivalent to raw args.rank = "best" args.fetchwikidata = "ALL" args.onlysourced = "no" args.noicon = "true" args.linked = "no" args.pd = "true" elseif paraset

3 then -- third set goes here end

-- implement eid parameter local eid = args.eid if eid

"" then return nil elseif eid then args.qid = eid end

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

args.reqranks = setRanks(args.rank)

-- replacetext (rt) is a string that is returned instead of any non-empty Wikidata value -- this is useful for tracking and debugging, so we set fetchwikidata=ALL to fill the whitelist local replacetext = mw.text.trim(args.rt or args.replacetext or "") if replacetext ~= "" then args.fetchwikidata = "ALL" end

local f = f.args = args local entityid, props = parseInput(f, f.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(args.qual or ""):upper if qualID

"" then qualID = nil end

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

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

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

--------------------------------------------------------------------------------- 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: _getValue; setRanks; parseInput; propertyvalueandquals; assembleoutput; parseParam; sourced;-- labelOrId; i18n.latestdatequalifier; roundto; decimalPrecision; decimalToDMS;-------------------------------------------------------------------------------p.getValue = function(frame) local args= frame.args if not args[1] then args = frame:getParent.args if not args[1] then return i18n.errors["No property supplied"] end end

return p._getValue(args)end

--------------------------------------------------------------------------------- getPropOfProp takes two propertyIDs: prop1 and prop2 (as well as the usual parameters)-- If the value(s) of prop1 are of type "wikibase-item" then it returns the value(s) of prop2-- of each of those wikibase-items.-- The usual whitelisting, blacklisting, onlysourced, etc. are implemented--------------------------------------------------------------------------------- Dependencies: parseParam; setRanks; parseInput; sourced; propertyvalueandquals assembleoutput;-------------------------------------------------------------------------------p._getPropOfProp = function(args) -- parameter sets for commonly used groups of parameters local paraset = tonumber(args.ps or args.parameterset or 0) if paraset

1 then -- a common setting args.rank = "best" args.fetchwikidata = "ALL" args.onlysourced = "no" args.noicon = "true" elseif paraset

2 then -- equivalent to raw args.rank = "best" args.fetchwikidata = "ALL" args.onlysourced = "no" args.noicon = "true" args.linked = "no" args.pd = "true" elseif paraset

3 then -- third set goes here end

args.reqranks = setRanks(args.rank) args.langobj = findLang(args.lang) args.lang = args.langobj.code local pid1 = args.prop1 or args.pid1 or "" local pid2 = args.prop2 or args.pid2 or "" local localval = mw.text.trim(args[1] or "") if pid1

"" or pid2

"" then return nil end

local f = f.args = args local qid1, statements1 = parseInput(f, localval, pid1) if not qid1 then return localval end

local onlysrc = parseParam(args.onlysourced or args.osd, true) local maxvals = tonumber(args.maxvals) or 0 local qualID = mw.text.trim(args.qual or ""):upper if qualID

"" then qualID = nil end local out = for k, v in ipairs(statements1) do if not onlysrc or sourced(v) then local snak = v.mainsnak if snak.datatype

"wikibase-item" and snak.snaktype

"value" then local qid2 = snak.datavalue.value.id local statements2 = if args.reqranks.b then statements2 = mw.wikibase.getBestStatements(qid2, pid2) else statements2 = mw.wikibase.getAllStatements(qid2, pid2) end if statements2[1] then local out2 = propertyvalueandquals(statements2, args, qualID) out[#out+1] = assembleoutput(out2, args, qid2, pid2) end end -- of test for valid property1 value end -- of test for sourced if maxvals > 0 and #out >= maxvals then break end end -- of loop through values of property1 return assembleoutput(out, args, qid1, pid1)end

p.getPropOfProp = function(frame) local args= frame.args if not args.prop1 and not args.pid1 then args = frame:getParent.args if not args.prop1 and not args.pid1 then return i18n.errors["No property supplied"] end end

return p._getPropOfProp(args)end

return p

--------------------------------------------------------------------------------- List of exported functions---------------------------------------------------------------------------------_getValuegetValue_getPropOfPropgetPropOfProp---------------------------------------------------------------------------------