-- vim: set noexpandtab ft=lua ts=4 sw=4:require('strict')
local p = local debug = false
-------------------------------------------------------------------------------- module local variables and functions
local wiki =
-- internationalisationlocal i18n =
if wiki.langcode ~= "en" then --require("Module:i18n").loadI18n("Module:Wikidata/i18n", i18n) -- got idea from local module_title; if ...
-- this function 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.local function makeOrdinal (cardinal) local ordsuffix = i18n.ordinal.default if cardinal % 10
2 then ordsuffix = i18n.ordinal[2] elseif cardinal % 10
11) or (cardinal % 100
13) then ordsuffix = i18n.ordinal.default end return tostring(cardinal) .. ordsuffixend
local function printError(code) return '
' .. (i18n.errors[code] or code) .. ''endlocal function parseDateFormat(f, timestamp, addon, prefix_addon, addon_sep) local year_suffix local tstr = "" local lang_obj = mw.language.new(wiki.langcode) local f_parts = mw.text.split(f, 'Y', true) for idx, f_part in pairs(f_parts) do year_suffix = if string.match(f_part, "x[mijkot]$") then -- for non-Gregorian year f_part = f_part .. 'Y' elseif idx < #f_parts then -- supress leading zeros in year year_suffix = lang_obj:formatDate('Y', timestamp) year_suffix = string.gsub(year_suffix, '^0+', , 1) end tstr = tstr .. lang_obj:formatDate(f_part, timestamp) .. year_suffix end if addon ~= "" and prefix_addon then return addon .. addon_sep .. tstr elseif addon ~= "" then return tstr .. addon_sep .. addon else return tstr endendlocal function parseDateValue(timestamp, date_format, date_addon) local prefix_addon = i18n["datetime"]["prefix-addon"] local addon_sep = i18n["datetime"]["addon-sep"] local addon = ""-- check for negative date if string.sub(timestamp, 1, 1)
-- This local function combines the year/month/day/BC/BCE handling of parseDateValue-- with the millennium/century/decade handling of formatDatelocal function parseDateFull(timestamp, precision, date_format, date_addon) local prefix_addon = i18n["datetime"]["prefix-addon"] local addon_sep = i18n["datetime"]["addon-sep"] local addon = ""
-- check for negative date if string.sub(timestamp, 1, 1)
-- get the next four characters after the + (should be the year now in all cases) -- ok, so this is dirty, but let's get it working first local intyear = tonumber(string.sub(timestamp, 2, 5)) if intyear
-- precision is 10000 years or more if precision <= 5 then local factor = 10 ^ ((5 - precision) + 4) local y2 = math.ceil(math.abs(intyear) / factor) local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2)) if addon ~= "" then -- negative date relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative) else relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative) end return relative end
-- precision is decades (8), centuries (7) and millennia (6) local era, card if precision
7 then card = math.floor((intyear - 1) / 100) + 1 era = mw.ustring.gsub(i18n.datetime[7], "$1", makeOrdinal(card)) end if precision
local _date_format = i18n["datetime"]["format"][date_format] if _date_format ~= nil then -- check for precision is year and override supplied date_format if precision
-- the "qualifiers" and "snaks" field have a respective "qualifiers-order" and "snaks-order" field-- use these as the second parameter and this function instead of the built-in "pairs" function-- to iterate over all qualifiers and snaks in the intended order.local function orderedpairs(array, order) if not order then return pairs(array) end
-- return iterator function local i = 0 return function i = i + 1 if order[i] then return order[i], array[order[i]] end endend
-- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millennia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - secondlocal function normalizeDate(date) date = mw.text.trim(date, "+") -- extract year local yearstr = mw.ustring.match(date, "^\-?%d+") local year = tonumber(yearstr) -- remove leading zeros of year return year .. mw.ustring.sub(date, #yearstr + 1), yearend
local function formatDate(date, precision, timezone) precision = precision or 11 local date, year = normalizeDate(date) if year
-- precision is 10000 years or more if precision <= 5 then local factor = 10 ^ ((5 - precision) + 4) local y2 = math.ceil(math.abs(year) / factor) local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2)) if year < 0 then relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative) else relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative) end return relative end
-- precision is decades, centuries and millennia local era if precision
7 then era = mw.ustring.gsub(i18n.datetime[7], "$1", tostring(math.floor((math.abs(year) - 1) / 100) + 1)) end if precision
-- precision is year if precision
-- precision is less than years if precision > 9 then --the following code replaces the UTC suffix with the given negated timezone to convert the global time to the given local time timezone = tonumber(timezone) if timezone and timezone ~= 0 then timezone = -timezone timezone = string.format("%.2d%.2d", timezone / 60, timezone % 60) if timezone[1] ~= '-' then timezone = "+" .. timezone end date = mw.text.trim(date, "Z") .. " " .. timezone end --
local formatstr = i18n.datetime[precision] if year
local function printDatavalueEntity(data, parameter) -- data fields: entity-type [string], numeric-id [int, Wikidata id] local id
if data["entity-type"]
"property" then id = "P" .. data["numeric-id"] else return printError("unknown-entity-type") end
if parameter then if parameter
local function printDatavalueTime(data, parameter) -- data fields: time [ISO 8601 time], timezone [int in minutes], before [int], after [int], precision [int], calendarmodel [wikidata URI] -- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millennia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second -- calendarmodel: e.g. http://www.wikidata.org/entity/Q1985727 for the proleptic Gregorian calendar or http://www.wikidata.org/wiki/Q11184 for the Julian calendar] if parameter then if parameter
"time" then data.time = normalizeDate(data.time) end return data[parameter] else return formatDate(data.time, data.precision, data.timezone) endend
local function printDatavalueMonolingualText(data, parameter) -- data fields: language [string], text [string] if parameter then return data[parameter] else local result = mw.ustring.gsub(mw.ustring.gsub(i18n.monolingualtext, "%%language", data["language"]), "%%text", data["text"]) return result endend
local function findClaims(entity, property) if not property or not entity or not mw.wikibase.getBestStatements(entity, property)[1] then return end
if mw.ustring.match(property, "^P%d+$") then -- if the property is given by an id (P..) access the claim list by this id return mw.wikibase.getBestStatements(entity, property) else property = mw.wikibase.resolvePropertyId(property) if not property then return end
return mw.wikibase.getBestStatements(entity, property) endend
local function getSnakValue(snak, parameter) if snak.snaktype
"string" then return snak.datavalue.value elseif snak.datavalue.type
"quantity" then return printDatavalueQuantity(snak.datavalue.value, parameter) elseif snak.datavalue.type
"wikibase-entityid" then return printDatavalueEntity(snak.datavalue.value, parameter) elseif snak.datavalue.type
local function getQualifierSnak(claim, qualifierId) -- a "snak" is Wikidata terminology for a typed key/value pair -- a claim consists of a main snak holding the main information of this claim, -- as well as a list of attribute snaks and a list of references snaks if qualifierId then -- search the attribute snak with the given qualifier as key if claim.qualifiers then local qualifier = claim.qualifiers[qualifierId] if qualifier then return qualifier[1] end end return nil, printError("qualifier-not-found") else -- otherwise return the main snak return claim[1].mainsnak endend
local function getValueOfClaim(claim, qualifierId, parameter) local error local snak snak, error = getQualifierSnak(claim, qualifierId) if snak then return getSnakValue(snak, parameter) else return nil, error endend
local function getReferences(frame, claim) local result = "" -- traverse through all references for ref in pairs(claim.references or) do local refparts -- traverse through all parts of the current reference for snakkey, snakval in orderedpairs(claim.references[ref].snaks or, claim.references[ref]["snaks-order"]) do if refparts then refparts = refparts .. ", " else refparts = "" end -- output the label of the property of the reference part, e.g. "imported from" for P143 refparts = refparts .. tostring(mw.wikibase.getLabel(snakkey)) .. ": " -- output all values of this reference part, e.g. "German Wikipedia" and "English Wikipedia" if the referenced claim was imported from both sites for snakidx = 1, #snakval do if snakidx > 1 then refparts = refparts .. ", " end refparts = refparts .. getSnakValue(snakval[snakidx]) end end if refparts then result = result .. frame:extensionTag("ref", refparts) end end return resultend
local function parseInput(frame) local qid = frame.args.qid or mw.wikibase.getEntityIdForCurrentPage or nil if qid and (#qid
"value" and claims.mainsnak.datavalue.type
nil then labelHook = function (qnumber) return nil; end end local out = if claims[1] then local i = 1 while claims[i] ~= nil do if isType(claims[i], "wikibase-entityid") then local qnumber = "Q" .. claims[i].mainsnak.datavalue.value["numeric-id"] local sitelink = mw.wikibase.getSitelink(qnumber) local label = labelHook(qnumber) or mw.wikibase.getLabel(qnumber) or qnumber if sitelink then out[#out + 1] = "" .. label .. "" else out[#out + 1] = "" .. label .. "[*]" end else out[#out + 1] = mw.wikibase.renderSnak(claims[i].mainsnak) end i = i + 1 end end return table.concat(out, delim)end
-------------------------------------------------------------------------------- module global functions
if debug then function p.inspectI18n(frame) local val = i18n for _, key in pairs(frame.args) do key = mw.text.trim(key) val = val[key] end return val endend
function p.descriptionIn(frame) local langcode = frame.args[1] local id = frame.args[2] -- return description of a Wikidata entity in the given language or the default language of this Wikipedia site return mw.wikibase.getEntity(id):getDescription(langcode or wiki.langcode)end
function p.labelIn(frame) local langcode = frame.args[1] local id = frame.args[2] -- return label of a Wikidata entity in the given language or the default language of this Wikipedia site return mw.wikibase.getEntity(id):getLabel(langcode or wiki.langcode)end
-- This is used to get a value, or a comma separated list of them if multiple values existp.getValue = function(frame) local delimdefault = ", " -- **internationalise later** local delim = frame.args.delimiter or "" delim = string.gsub(delim, '"', ) if #delim
-- Same as above, but uses the short name property for label if available.p.getValueShortName = function(frame) local go, claims = parseInput(frame) if not go then return claims end -- if wiki-linked value output as link if possible local function labelHook (qnumber) local label local i = 1 if mw.wikibase.entityExists(qnumber) then if mw.wikibase.getBestStatements(qnumber, "P1813")[1] then while mw.wikibase.getBestStatements(qnumber, "P1813")[i] ~= nil do if mw.wikibase.getBestStatements(qnumber, "P1813")[i].mainsnak.datavalue.value.language
nil or label
-- This is used to get a value, or a comma separated list of them if multiple values exist-- from an arbitrary entry by using its QID.-- Use : -- E.g.: - to fetch value of 'spouse' (P26) from 'Richard Burton' (Q151973)-- Please use sparingly - this is an *expensive call*.p.getValueFromID = function(frame) local itemID = mw.text.trim(frame.args[1] or "") local propertyID = mw.text.trim(frame.args[2] or "") local input_parm = mw.text.trim(frame.args[3] or "") if input_parm
"FETCH_WIKIDATA" then local entityid = mw.wikibase.getEntityIdForCurrentPage if mw.wikibase.entityExists(entityid) ~= nil and mw.wikibase.getBestStatements(entityid, propertyID)[1] ~= nil then local out = local i = 1 while mw.wikibase.getBestStatements(entityid, propertyID)[i] ~= nil do for k2, v2 in pairs(mw.wikibase.getBestStatements(entityid, propertyID)[i].qualifiers[qualifierID]) do if v2.snaktype
-- This is used to get a value like 'male' (for property p21) which won't be linked and numbers without the thousand separatorsp.getRawValue = function(frame) local go, claims = parseInput(frame) if not go then return claims end local result = local i = 1 while claims[i] ~= nil do -- if number type: remove thousand separators, bounds and units if isType(claims[i], "quantity") then result[#result +1] = mw.ustring.gsub(mw.wikibase.renderSnak(claims[i].mainsnak), "(%d),(%d)", "%1%2") result[#result +1] = mw.ustring.gsub(mw.wikibase.renderSnak(claims[i].mainsnak), "(%d)±.*", "%1") else result[#result + 1] = mw.wikibase.renderSnak(claims[i].mainsnak) end i = i + 1 end return table.concat(result, ', ')end
-- This is used to get the unit name for the numeric value returned by getRawValuep.getUnits = function(frame) local go, claims = parseInput(frame) if not go then return claims end local result = local i = 1 while claims[i] ~= nil do local i = 1 if isType(claims[i], "quantity") then result[#result +1] = mw.ustring.sub(mw.renderSnak(claims[i].mainsnak), mw.ustring.find(result, " ")+1, -1) else result[#result + 1] = mw.renderSnak(claims[i].mainsnak) end i = i + 1 end return resultend
-- This is used to get the unit's QID to use with the numeric value returned by getRawValuep.getUnitID = function(frame) local go, claims = parseInput(frame) if not go then return claims end local result if isType(claims[1], "quantity") then -- get the url for the unit entry on Wikidata: result = claims[1].mainsnak.datavalue.value.unit -- and just reurn the last bit from "Q" to the end (which is the QID): result = mw.ustring.sub(result, mw.ustring.find(result, "Q"), -1) end return resultend
p.getRawQualifierValue = function(frame) local function outputHook(value) if value.datavalue.value["numeric-id"] then return mw.wikibase.getLabel("Q" .. value.datavalue.value["numeric-id"]) else return value.datavalue.value end end local ret, gotData = getQualifier(frame, outputHook) if gotData then ret = string.upper(string.sub(ret, 1, 1)) .. string.sub(ret, 2) end return retend
-- This is used to get a date value for date_of_birth (P569), etc. which won't be linked-- Dates and times are stored in ISO 8601 format (sort of).-- At present the local formatDate(date, precision, timezone) function doesn't handle timezone-- So I'll just supply "Z" in the call to formatDate below:p.getDateValue = function(frame) local date_format = mw.text.trim(frame.args[3] or i18n["datetime"]["default-format"]) local date_addon = mw.text.trim(frame.args[4] or i18n["datetime"]["default-addon"]) local go, claims = parseInput(frame) if not go then return claims end local out = local i = 1 while claims[i] ~= nil do if claims[i].mainsnak.datavalue.type
-- This is used to fetch all of the images with a particular property, e.g. image (P18), Gene Atlas Image (P692), etc.-- Parameters are | propertyID | value / FETCH_WIKIDATA / nil | separator (default=space) | size (default=frameless)-- It will return a standard wiki-markup for each image with a selectable size and separator (which may be html)-- e.g. -- e.g. -- If a property is chosen that is not of type "commonsMedia", it will return empty text.p.getImages = function(frame) local sep = mw.text.trim(frame.args[3] or " ") local imgsize = mw.text.trim(frame.args[4] or "frameless") local go, claims = parseInput(frame) if not go then return claims end if (claims[1] and claims[1].mainsnak.datatype
-- This is used to get the TA98 (Terminologia Anatomica first edition 1998) values like 'A01.1.00.005' (property P1323)-- which are then linked to https://ifaa.unifr.ch/Public/EntryPage/TA98%20Tree/Entity%20TA98%20EN/01.1.00.005%20Entity%20TA98%20EN.htm-- uses the newer mw.wikibase calls instead of directly using the snaks-- formatPropertyValues returns a table with the P1323 values concatenated with ", " so we have to split them out into a table in order to construct the return stringp.getTAValue = function(frame) local entid = mw.wikibase.getEntityIdForCurrentPage local props = mw.wikibase.getEntity(entid):formatPropertyValues('P1323') local out = local t = for k, v in pairs(props) do if k
0 then ret = "Invalid TA" end return retend
--getImageLegend | Ranks are: 'preferred' > 'normal'This returns the label from the first image with 'preferred' rankOr the label from the first image with 'normal' rank if preferred returns nothingRanks: https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua
p.getImageLegend = function(frame) -- look for named parameter id; if it's blank make it nil local id = frame.args.id if id and (#id
-- look for named parameter lang -- it should contain a two-character ISO-639 language code -- if it's blank fetch the language of the local wiki local lang = frame.args.lang if (not lang) or (#lang < 2) then lang = mw.language.getContentLanguage.code end
-- first unnamed parameter is the local parameter, if supplied local input_parm = mw.text.trim(frame.args[1] or "") if input_parm
"preferred" then -- in getBestStatements if there is an preferred value, it is ordered first. imglbl = mw.wikibase.getBestStatements(id, "P18")[1].mainsnak.datavalue.value elseif id and mw.wikibase.entityExists(id) and mw.wikibase.getBestStatements(id, "P18")[1] and mw.wikibase.getBestStatements(id, "P18")[1].mainsnak.datavalue then -- only normal and preferred rank values outputted in beststatements imglbl = mw.wikibase.getBestStatements(id, "P18")[1].mainsnak.datavalue.value end return imglbl else return input_parm endend
-- This is used to get the QIDs of all of the values of a property, as a comma separated list if multiple values exist-- Usage: -- Usage:
p.getPropertyIDs = function(frame) local go, propclaims = parseInput(frame) if not go then return propclaims end -- if wiki-linked value collect the QID in a table if (propclaims[1] and propclaims[1].mainsnak.snaktype
"wikibase-entityid") then local out = local i = 1 while claims[i] ~= nil do out[#out + 1] = "Q" .. claims[i].mainsnak.datavalue.value["numeric-id"] i = i + 1 end return table.concat(out, ", ") else -- not a wikibase-entityid, so return empty return "" endend
-- returns the page id (Q...) of the current page or nothing of the page is not connected to Wikidatafunction p.pageId(frame) return mw.wikibase.getEntityIdForCurrentPageend
function p.claim(frame) local property = frame.args[1] or "" local id = frame.args["id"] or mw.wikibase.getEntityIdForCurrentPage local qualifierId = frame.args["qualifier"] local parameter = frame.args["parameter"] local list = frame.args["list"] local references = frame.args["references"] local showerrors = frame.args["showerrors"] local default = frame.args["default"] if default then showerrors = nil end
-- check wikidata entity if not mw.wikibase.entityExists(id) then if showerrors then return printError("entity-not-found") else return default end end -- fetch the first claim of satisfying the given property local claims = mw.wikibase.getBestStatements(id, property) or nil if not claims or not claims[1] then if showerrors then return printError("property-not-found") else return default end end
-- no need to sort, bestStatements orders preferred rank first
local result local error local i = 1 if list then local value -- iterate over all elements and return their value (if existing) result = for idx in pairs(claims) do local claim = claims[i] value, error = getValueOfClaim(claim, qualifierId, parameter) if not value and showerrors then value = error end if value and references then value = value .. getReferences(frame, claim) end result[#result + 1] = value i = i + 1 end result = table.concat(result, list) else -- return first element local claim = claims[i] result, error = getValueOfClaim(claim, qualifierId, parameter) if result and references then result = result .. getReferences(frame, claim) end end
if result then return result else if showerrors then return error else return default end endend
-- look into entity objectfunction p.ViewSomething(frame) local f = (frame.args[1] or frame.args.id) and frame or frame:getParent local id = f.args.id if id and (#id
local i = 1 while true do local index = f.args[i] if not index then if type(data)
data = data[index] or data[tonumber(index)] if not data then return end
i = i + 1 endend
-- getting sitelink of a given wiki-- get sitelink of current item if qid not suppliedfunction p.getSiteLink(frame) local qid = frame.args.qid if qid
function p.Dump(frame) local f = (frame.args[1] or frame.args.id) and frame or frame:getParent local data = mw.wikibase.getEntity(f.args.id) if not data then return i18n.warnDump end
local i = 1 while true do local index = f.args[i] if not index then return "
"..mw.dumpObject(data).."".. i18n.warnDump end
data = data[index] or data[tonumber(index)] if not data then return i18n.warnDump end
i = i + 1 endend
return p