Module:Convert/wikidata

-- Functions to access Wikidata for Module:Convert.

local function collection -- Return a table to hold items. return { n = 0, add = function (self, item) self.n = self.n + 1 self[self.n] = item end, join = function (self, sep) return table.concat(self, sep) end, } end

local function strip_to_nil(text) -- If text is a non-empty string, return its trimmed content, -- otherwise return nothing (empty string or not a string). if type(text) == 'string' then return text:match('(%S.-)%s*$') end end

local function make_unit(units, parms, uid) -- Return a unit code for convert or nil if unit unknown. -- If necessary, add a dummy unit to parms so convert will use it	-- for the input without attempting a conversion since nothing -- useful is available (for example, with unit volt). local unit = units[uid] if type(unit) ~= 'table' then return nil end local ucode = unit.ucode if ucode and not unit.si then return ucode               -- a unit known to convert end parms.opt_ignore_error = true ucode = ucode or unit._ucode   -- must be a non-empty string local ukey, utable if unit.si then local base = units[unit.si] ukey = base.symbol         -- must be a non-empty string local n1 = base.name1 local n2 = base.name2 if not n1 then n1 = ukey n2 = n2 or n1          -- do not append 's'		end utable = { _symbol = ukey, _name1 = n1, _name2 = n2, link = unit.link or base.link, utype = n1, prefixes = 1, }	else ukey = ucode utable = { symbol = ucode,        -- must be a non-empty string name1 = unit.name1,    -- if nil, uses symbol name2 = unit.name2,    -- if nil, uses name1..'s'			link = unit.link,       -- if nil, uses name1 utype = unit.name1 or ucode, }	end utable.scale = 1 utable.default = '' utable.defkey = '' utable.linkey = '' utable.bad_mcode = '' parms.unittable = { [ukey] = utable } return ucode end

local function get_statements(qid, pid) -- Get item for qid and return a list of statements for property pid. -- Statements are in Wikidata's order except that those with preferred -- rank are first, then normal rank. Any other rank is ignored. -- qid is nil for the current page's item, or is an item id (expensive). local result, n = {}, 0 local entity = mw.wikibase.getEntity(qid) if type(entity) == 'table' then local statements = (entity.claims or {})[pid] if type(statements) == 'table' then for _, rank in ipairs({ 'preferred', 'normal' }) do				for _, statement in ipairs(statements) do					if type(statement) == 'table' and rank == statement.rank then n = n + 1 result[n] = statement end end end end end return result end

local function input_from_property(tdata, parms, qid, pid) -- Given that pid is a Wikidata property identifier like 'P123', -- return amount, ucode (two strings) for the item/property, -- or return nothing. for _, statement in ipairs(get_statements(qid, pid)) do		if statement.mainsnak and statement.mainsnak.datatype == 'quantity' then local value = (statement.mainsnak.datavalue or {}).value if value then local amount = value.amount if amount then amount = tostring(amount) -- in case amount is ever a number if amount:sub(1, 1) == '+' then amount = amount:sub(2) end local unit = value.unit if type(unit) == 'string' then unit = unit:match('Q%d+$') -- unit qid is at end of URL local ucode = make_unit(tdata.wikidata_units, parms, unit) if ucode then return amount, ucode end end end end end end end

local function input_from_text(tdata, parms, text) -- Given string should be of form "  ". -- Return value, ucode (two strings), or return nothing. text = text:gsub(' ', ' '):gsub(' +', ' ') local pos = text:find(' ', 1, true) if pos then -- Leave checking of value to convert which can handle fractions. local value = text:sub(1, pos - 1) local uid = text:sub(pos + 1) local ucode = make_unit(tdata.wikidata_units, parms, uid) return value, ucode or uid end end

local function adjustparameters(tdata, parms, index) -- For Module:Convert, adjust parms (a table of undefined undefined parameters). -- Return true if successful or return false, t where t is an error message table. -- This is intended mainly for use in infoboxes where the input might be	--        or	--      (uses an optional qid item id) -- If successful, insert value and unit in parms, before given index. local amount, ucode local text = parms.input -- should be a trimmed, non-empty string local qid = strip_to_nil(parms.qid) local pid = text:match('^P%d+$') if pid then parms.input_text = '' -- output an empty string if an error occurs amount, ucode = input_from_property(tdata, parms, qid, pid) else amount, ucode = input_from_text(tdata, parms, text) end if amount and ucode then table.insert(parms, index, ucode) table.insert(parms, index, amount) return true end return false, pid and { 'cvt_no_output' } or { 'cvt_bad_input', text } end

--- List units and check syntax of definitions -

local specifications = { -- seq = sequence in which fields are displayed base = { title = 'SI base units', fields = { symbol = { seq = 2, mandatory = true }, name1 = { seq = 3, mandatory = true }, name2 = { seq = 4 }, link  = { seq = 5 }, },		noteseq = 6, header = '{| class="wikitable"\n!si !!symbol !!name1 !!name2 !!link !!note', item = '|-\n|%s ||%s ||%s ||%s ||%s ||%s', footer = '|}', },	alias = { title = 'Aliases for convert', fields = { ucode = { seq = 2, mandatory = true }, si    = { seq = 3 }, },		noteseq = 4, header = '{| class="wikitable"\n!alias !!ucode !!base !!note', item = '|-\n|%s ||%s ||%s ||%s', footer = '|}', },	known = { title = 'Units known to convert', fields = { ucode = { seq = 2, mandatory = true }, label = { seq = 3, mandatory = true }, },		noteseq = 4, header = '{| class="wikitable"\n!qid !!ucode !!label !!note', item = '|-\n|%s ||%s ||%s ||%s', footer = '|}', },	unknown = { title = 'Units not known to convert', fields = { _ucode = { seq = 2, mandatory = true }, si    = { seq = 3 }, name1 = { seq = 4 }, name2 = { seq = 5 }, link  = { seq = 6 }, label = { seq = 7, mandatory = true }, },		noteseq = 8, header = '{| class="wikitable"\n!qid !!_ucode !!base !!name1 !!name2 !!link !!label !!note', item = '|-\n|%s ||%s ||%s ||%s ||%s ||%s ||%s ||%s', footer = '|}', }, }

local function listunits(tdata, ulookup) -- For Module:Convert, make wikitext to list the built-in Wikidata units. -- Return true, wikitext if successful or return false, t where t is an -- error message table. Currently, an error return never occurs. -- The syntax of each unit definition is checked and a note is added if -- a problem is detected. local function safe_cells(t) -- This is not currently needed, but in case definitions ever use wikitext -- like 'kg', escape the text so it works in a table cell. local result = {} for i, v in ipairs(t) do			if v:find('|', 1, true) then v = v:gsub('(%[%^%[%-)|(.-%]%])', '%1\0%2') -- replace pipe in piped link with a zero byte v = v:gsub('|', '&#124;')                       -- escape '|' v = v:gsub('%z', '|')                           -- restore pipe in piped link end result[i] = v:gsub('{', '&#123;')                   -- escape '{' end return unpack(result) end local wdunits = tdata.wikidata_units local speckeys = { 'base', 'alias', 'unknown', 'known' } for _, sid in ipairs(speckeys) do		specifications[sid].units = collection end local keys, n = {}, 0 for k, v in pairs(wdunits) do		n = n + 1 keys[n] = k	end table.sort(keys) local note_count = 0 for _, key in ipairs(keys) do		local unit = wdunits[key] local ktext, sid if key:match('^Q%d+$') then ktext =  .. key ..  if unit.ucode then sid = 'known' else sid = 'unknown' end elseif unit.ucode then ktext = key sid = 'alias' else ktext = key sid = 'base' end local result = { ktext } local spec = specifications[sid] local fields = spec.fields local note = collection for k, v in pairs(unit) do			if fields[k] then local seq = fields[k].seq if result[seq] then note:add('duplicate ' .. k) -- cannot happen since keys are unique else result[seq] = v				end else note:add('invalid ' .. k)			end end for k, v in pairs(fields) do			local value = result[v.seq] if value then if k == 'si' and not wdunits[value] then note:add('need si ' .. value) end if k == 'label' then local wdl = mw.wikibase.label(key) if wdl ~= value then note:add('label changed to ' .. tostring(wdl)) end end else result[v.seq] = '' if v.mandatory then note:add('missing ' .. k)				end end end local text if note.n > 0 then note_count = note_count + 1 text = '*' .. note:join(' ') end result[spec.noteseq] = text or '' spec.units:add(result) end local results = collection if note_count > 0 then local text = note_count .. (note_count == 1 and ' note' or ' notes') results:add("Search for * to see " .. text .. "\n") end for _, sid in ipairs(speckeys) do		local spec = specifications[sid] results:add("" .. spec.title .. "") results:add(spec.header) local fmt = spec.item for _, unit in ipairs(spec.units) do			results:add(string.format(fmt, safe_cells(unit))) end results:add(spec.footer) end return true, results:join('\n') end

return { _adjustparameters = adjustparameters, _listunits = listunits }