Module:Wikidata table

--	version dated 2021-02-22	Module:Wikidata table --

local p = {}

local debugging = false

local sep = ", " -- separator for multiple values of same property, changed from " " local sep2 = " " -- separator for values of different properties

-- Internationalisation

-- takes optional string language code (e.g. "en") and returns a language object 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 local currentlang = findLang.code

local i18n = { filespace = "File", editonwikidata = "Edit this on Wikidata", ordinal = { [1] = "st", [2] = "nd", [3] = "rd", ["default"] = "th" }, }

local months = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" } local mnths = {} for idx, val in ipairs(months) do	mnths[idx] = val:sub(1,3) end

-- makeOrdinal needs to be internationalised along with the above i18n -- takes cardinal number as a numeric and returns the ordinal as a string -- we need three exceptions in English for 1st, 2nd, 3rd, 21st, .. 31st, etc. --- p.makeOrdinal = function(cardinal) local card = tonumber(cardinal) if not card then return cardinal end local ordsuffix = i18n.ordinal.default if card % 10 == 1 then ordsuffix = i18n.ordinal[1] elseif card % 10 == 2 then ordsuffix = i18n.ordinal[2] elseif card % 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 (card % 100 == 11) or (card % 100 == 12) or (card % 100 == 13) then ordsuffix = i18n.ordinal.default end return card .. ordsuffix end

local unitsymbol = { inch = "in", foot = "ft", yard = "yd", mile = "mi", ["nautical mile"] = "nmi", metre = "m", centimetre = "cm", millimetre = "mm", kilometre = "km", acre = "acres", hectare = "ha" }

-- prefixes for particular qualifiers local prefix = { P580 = "from ", P582 = "until ", }

-- external ids which have a formatter url (P1630) local formaturl = { P3563 = "https://wikidata-externalid-url.toolforge.org/?url=https%3A%2F%2Fmsi.nga.mil%2FqueryResults%3Fpublications%2Fngalol%2Flights-buoys%3Fvolume%3D%251%26featureNumber%3D%252%26includeRemovals%3Dfalse%26output%3Dhtml&exp=(%5Cd%7B3%7D)-(.*)&id=$1", -- NGA number P613 = "http://nearby.org.uk/coord.cgi?p=$1", -- OS grid reference P373 = "https://commons.wikimedia.org/wiki/Category:$1", -- Commons category }

-- date format default to dmy local df = "dmy"

-- fallbacks for common properties: -- property-to-fallback-from = "property-to-fallback-to" local fallback = { P276 = "P131", P571 = "P1619", P729 = "P571", P4755 = "P296" }

-- error messages local function errmsg(txt) if debugging then return "Error: " .. txt else return nil end end

-- formats the first character of linked item to uppercase local function ucf(lnk) local tbl = mw.text.split( lnk, "|", true ) local ret if tbl[2] then -- piped link tbl[2] = tbl[2]:gsub("^(%l)", mw.ustring.upper) ret = table.concat(tbl, "|") elseif lnk:sub(1,2) == "[[" then -- unpiped link		ret = lnk:gsub("^(%[%[%l)", mw.ustring.upper)	else -- unlinked		ret = lnk:gsub("^(%l)", mw.ustring.upper)	end	return ret end -- entrypoint for invoke function p.ucf(frame)	return ucf(frame.args.text or "") end

-- return a number rounded to a precision local function decimalprecision(x, prec) local s = 1 if x < 0 then x = -x s = -1 end -- if prec is not suplied, pick an arbitrary precision if not tonumber(prec) then prec = 1e-4 elseif prec > 1 then prec = 1 elseif prec < 1e-6 then prec = 1e-6 else prec = 10 ^ math.floor(math.log10(prec)) end x = math.floor(x / prec + 0.5) * prec * 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

-- creates an icon that links to the relevant Wikidata entity local function createicon(entityID, propertyID, langcode) 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 icon end

-- takes a statement tuple supplied from Wikidata and returns any references function p._getrefs(statement) if not statement.references then return nil end local rtbl = {} local frm = mw.getCurrentFrame for idx, ref in ipairs(statement.references) do		if ref.snaks.P854 then -- reference url local url = ref.snaks.P854[1].datavalue.value if ref.snaks.P1476 then -- title (monolingual text) local title for idx1, titles in ipairs(ref.snaks.P1476) do					if titles.datavalue.value.language == currentlang then title = titles.datavalue.value.text local citeweb = frm:expandTemplate{ title = "Cite web", args = { url=url, title=title } } local hash = "WD" .. mw.hash.hashValue("crc32", citeweb) rtbl[#rtbl+1] = frm:callParserFunction{ name = "#tag:ref", args = { citeweb, name=hash } } break end end else local hash = "WD" .. mw.hash.hashValue("crc32", url) rtbl[#rtbl+1] = frm:callParserFunction{ name = "#tag:ref", args = { url, name=hash } } end end if ref.snaks.P248 then -- stated in			local rqid = ref.snaks.P248[1].datavalue.value.id			local citeq = frm:expandTemplate{ title = "Cite Q", args = { rqid } } rtbl[#rtbl+1] = frm:callParserFunction{ name = "#tag:ref", args = { citeq, name=rqid } } end end return table.concat(rtbl) end

-- takes a qid and attempts to return a linked name for it -- otherwise an unlinked name; otherwise the qid function p._getLink(qid) local lbl = "" local slink = mw.wikibase.sitelink(qid) local label = mw.wikibase.getLabel(qid) if slink and label then if slink:lower == label:lower then if label:find("^%u") then -- match label's case lbl = "" .. slink:gsub("^(%l)", mw.ustring.upper) .. "" else lbl = "" .. slink:gsub("^(%u)", mw.ustring.lower) .. "" end else lbl = "" .. label .. "" end elseif slink then lbl = "" .. slink .. "" elseif label then lbl = label else lbl = qid end return lbl end -- entrypoint for #invoke getLink function p.getLink(frame) local qid = (frame.args.qid or ""):upper if qid == "" then return nil end return p._getLink(qid) end

-- takes a snak for a time property and returns the date local function _getDate(snak, prec) local retval retval = mw.wikibase.renderSnak(snak) if prec == 7 then -- century local num, txt = retval:match("^(%d+)%.(.+)$") retval = p.makeOrdinal(num) .. txt elseif prec == 10 then local m, y, e = retval:match("^(%a+) (%d+)(.*)") retval = m:sub(1,3) .. " " .. y .. e	elseif prec == 11 then local d, m, y, e = retval:match("^(%d+) (%a+) (%d+)(.*)") retval = d .. " " .. m:sub(1,3) .. " " .. y .. e	end return retval end

-- takes a qid and a property id and returns the corresponding values or nil -- maxvals greater than zero sets the maximum number of values to return -- the string quals contains property ids of qualifiers to be returned ('-' is the separator) function p._getWD(qid, pid, maxvals, quals) maxvals = maxvals or 0 local ret = {} for idx, prop in ipairs(mw.wikibase.getBestStatements(qid, pid)) do		local retval if prop.mainsnak.snaktype ~= "value" then break end local dtype = prop.mainsnak.datatype local dval = prop.mainsnak.datavalue.value if dtype == "wikibase-item" then retval = p._getLink(dval.id) elseif dtype == "monlingualtext" then for idx1, vals in ipairs(dval) do				if vals.language == currentlang then retval = vals.text break end end elseif dtype == "commonsMedia" or dtype == "url" then retval = dval elseif dtype == "external-id" or dtype == "string" then if formaturl[pid] then retval = "[" .. mw.ustring.gsub(formaturl[pid], "$1", dval) .. " " .. dval .. "]"			else retval = dval end elseif dtype == "time" then retval = _getDate(prop.mainsnak, dval.precision) if dval.precision == 11 and df == "mdy" then local d, m, y, e = retval:match("^(%d+) (%a+) (%d+)(.*)") retval = m .. " " .. d .. ", " .. y .. e			end elseif dtype == "quantity" then local amount = tonumber(dval.amount) local unitqid = string.match( dval.unit, "(Q%d+)" ) if unitqid then local unit = mw.wikibase.getLabel(unitqid) local symbol = unitsymbol[unit] if symbol then retval = mw.getCurrentFrame:expandTemplate{ title = "cvt", args = {amount, symbol} } else retval = amount .. " " .. unit end else retval = amount end elseif dtype == "globe-coordinate" then local lat = decimalprecision(dval.latitude, dval.precision) local long = decimalprecision(dval.longitude, dval.precision) retval = " " .. mw.wikibase.formatValue(prop.mainsnak) .. " "		else retval = dval end -- get references retval = retval .. (p._getrefs(prop) or "") -- get qualifiers if quals and prop.qualifiers and retval then local qtbl = {} for qpid in quals:gmatch("P%d+") do				if prop.qualifiers[qpid] then for i, qv in ipairs(prop.qualifiers[qpid]) do						local fqv if qv.datatype == "globe-coordinate" then fqv = mw.wikibase.formatValue(qv) -- linked elseif qv.datatype == "time" then fqv = _getDate(qv, qv.datavalue.value.precision) if qv.datavalue.value.precision == 11 then -- trim to month fqv = fqv:match("%d+ (.+)") end else fqv = mw.wikibase.renderSnak(qv) -- plaintext end if fqv and fqv ~= "" then qtbl[#qtbl+1] = (prefix[qpid] or "") .. fqv end end end end if #qtbl > 0 then retval = retval .. " (" .. table.concat(qtbl, " ") .. ") " end end ret[#ret+1] = retval if maxvals > 0 and #ret >= maxvals then break end end if #ret < 1 then return nil else return table.concat(ret, sep) end end -- entrypoint for #invoke getWD function p.getWD(frame) local qid = (frame.args.qid or ""):upper if qid == "" then return nil end local pid = (frame.args.pid or ""):upper if pid == "" then return nil end local maxvals = tonumber(frame.args.maxvals) or 0 local quals = (frame.args.quals or ""):upper if quals == "" then quals = nil end return p._getWD(qid, pid, maxvals, quals) end

-- make multiple table rows, one for each qid in args.qids, -- with one table cell for each pid in args.pids function p._makerows(args) args.qids = (args.qids or ""):upper if args.qids == "" then return errmsg("missing qids") end args.pids = (args.pids or ""):upper if args.pids == "" then return errmsg("missing pids") end local qids, pids = {}, {} for qid in args.qids:gmatch("Q%d+") do		qids[#qids+1] = qid end for pid in args.pids:gmatch("P%d+") do		pids[#pids+1] = pid end args.df = args.df or "" if args.df ~= "" then df = args.df end local out = "" for r, qid in ipairs(qids) do out = out .. " "		out = out .. "" .. p._getLink(qid) .. " "		for c, pid in ipairs(pids) do			if pid == "P18" then -- image local img = p._getWD(qid, pid, 1) if img then out = out .. " "				end else out = out .. " " .. (p._getWD(qid, pid, 0) or " ") .. " "			end end out = out .. " "	end return out end -- entry point for #invoke makerows function p.makerows(frame) local args = {} for key, value in pairs(frame:getParent.args) do		args[key] = value end for key, value in pairs(frame.args) do		args[key] = value end return p._makerows(args) end

-- make a single table row, one cell per value passed in args.pids -- each value may be a combination of properties and qualifiers function p._makerow(args) local qid = (args.qid or ""):upper:match("Q%d+") -- qid can be nil if we want a row without wikidata -- remove whitespace, uppercase, trap nil args.pids = (args.pids or ""):upper:gsub("%s", "") if args.pids == "" then return errmsg("missing pids") end -- collect any parameters c1, c2, etc. as cell replacements; c1+, c2+, etc. as addenda local cellrep, celladd = {}, {} for key, value in pairs(args) do		local col = (type(key) == "string") and tonumber(key:match("^[Cc](%d+)$")) if col then cellrep[col] = value end end for key, value in pairs(args) do		local col = (type(key) == "string") and tonumber(key:match("^[Cc](%d+)%+$")) if col then celladd[col] = value end end -- set date format if passed args.df = args.df or "" if args.df ~= "" then df = args.df end -- create the html to return local out = " " if cellrep[1] and qid then out = out .. "" .. cellrep[1] .. createicon(qid) .. " "	elseif not qid then out = out .. " " .. (cellrep[1] or " ") .. " "	else out = out .. "" .. ucf(p._getLink(qid)) .. (celladd[1] or "") .. createicon(qid) .. " "	end -- split args.pids at comma separators into sequence of cellpids (each may be like P12+P34/P456-P789) local cellpids = mw.text.split(args.pids, ",+") for c, val in ipairs(cellpids) do		if cellrep[c+1] then out = out .. " " .. cellrep[c+1] .. " "		elseif not qid then out = out .. " "		else -- separate multiple properties in same cell, sep=+ local ptbl = {} -- sequence of values for one cell for propandquals in mw.text.gsplit(val, "+", true) do				-- for each property, split off property from qualifiers, sep=/ local pid = mw.text.split(propandquals, "/")[1]:match("P%d+") local quals = mw.text.split(propandquals, "/")[2] if pid == "P18" then -- image local img = p._getWD(qid, pid, 1) if img then ptbl[#ptbl+1] = "" end else local wdval = p._getWD(qid, pid, 0, quals) if not wdval and fallback[pid] then wdval = p._getWD(qid, fallback[pid], 0, quals) end ptbl[#ptbl+1] = wdval and ucf(wdval) end end -- of loop through multiple properties in same cell out = out .. " " .. table.concat(ptbl, sep2) .. (celladd[c+1] or "") .. " "		end end -- of loop through all of the cells in the row out = out .. " "	return out end -- entry point for #invoke makerow function p.makerow(frame) local args = {} for key, value in pairs(frame:getParent.args) do		args[key] = value end for key, value in pairs(frame.args) do		args[key] = value end return p._makerow(args) end

return p