https://de.wikipedia.org/w/index.php?action=history&feed=atom&title=Module%3AConvert%2FwikidataModul:Convert/wikidata - Versionsgeschichte2025-06-09T06:39:40ZVersionsgeschichte dieser Seite in WikipediaMediaWiki 1.45.0-wmf.4https://de.wikipedia.org/w/index.php?title=Modul:Convert/wikidata&diff=178363191&oldid=prevNov3rd17: aus engl.Wikipedia2018-06-16T08:48:42Z<p>aus engl.Wikipedia</p>
<p><b>Neue Seite</b></p><div>-- Functions to access Wikidata for Module:Convert.<br />
<br />
local Collection = {}<br />
Collection.__index = Collection<br />
do<br />
function Collection:add(item)<br />
if item ~= nil then<br />
self.n = self.n + 1<br />
self[self.n] = item<br />
end<br />
end<br />
function Collection:join(sep)<br />
return table.concat(self, sep)<br />
end<br />
function Collection:remove(pos)<br />
if self.n > 0 and (pos == nil or (0 < pos and pos <= self.n)) then<br />
self.n = self.n - 1<br />
return table.remove(self, pos)<br />
end<br />
end<br />
function Collection:sort(comp)<br />
table.sort(self, comp)<br />
end<br />
function Collection.new()<br />
return setmetatable({n = 0}, Collection)<br />
end<br />
end<br />
<br />
local function strip_to_nil(text)<br />
-- If text is a non-empty string, return its trimmed content,<br />
-- otherwise return nothing (empty string or not a string).<br />
if type(text) == 'string' then<br />
return text:match('(%S.-)%s*$')<br />
end<br />
end<br />
<br />
local function frequency_unit(value, unit_table)<br />
-- For use when converting m to Hz.<br />
-- Return true, s where s = name of unit's default output unit,<br />
-- or return false, t where t is an error message table.<br />
-- However, for simplicity a valid result is always returned.<br />
local unit<br />
if unit_table._symbol == 'm' then<br />
-- c = speed of light in a vacuum = 299792458 m/s<br />
-- frequency = c / wavelength<br />
local w = value * (unit_table.scale or 1)<br />
local f = 299792458 / w -- if w == 0, f = math.huge which works here<br />
if f >= 1e12 then<br />
unit = 'THz'<br />
elseif f >= 1e9 then<br />
unit = 'GHz'<br />
elseif f >= 1e6 then<br />
unit = 'MHz'<br />
elseif f >= 1e3 then<br />
unit = 'kHz'<br />
else<br />
unit = 'Hz'<br />
end<br />
end<br />
return true, unit or 'Hz'<br />
end<br />
<br />
local function wavelength_unit(value, unit_table)<br />
-- Like frequency_unit but for use when converting Hz to m.<br />
local unit<br />
if unit_table._symbol == 'Hz' then<br />
-- Using 0.9993 rather than 1 avoids rounding which would give results<br />
-- like converting 300 MHz to 100 cm instead of 1 m.<br />
local w = 1 / (value * (unit_table.scale or 1)) -- Hz scale is inverted<br />
if w >= 0.9993e6 then<br />
unit = 'Mm'<br />
elseif w >= 0.9993e3 then<br />
unit = 'km'<br />
elseif w >= 0.9993 then<br />
unit = 'm'<br />
elseif w >= 0.9993e-2 then<br />
unit = 'cm'<br />
elseif w >= 0.9993e-3 then<br />
unit = 'mm'<br />
else<br />
unit = 'um'<br />
end<br />
end<br />
return true, unit or 'm'<br />
end<br />
<br />
local specials = {<br />
frequency = { frequency_unit },<br />
wavelength = { wavelength_unit },<br />
--------------------------------------------------------------------------------<br />
-- Following is a removed experiment to show two values as a range<br />
-- using '-' as the separator.<br />
-- frequencyrange = { frequency_unit, '-' },<br />
-- wavelengthrange = { wavelength_unit, '-' },<br />
}<br />
<br />
local function make_unit(units, parms, uid)<br />
-- Return a unit code for convert or nil if unit unknown.<br />
-- If necessary, add a dummy unit to parms so convert will use it<br />
-- for the input without attempting a conversion since nothing<br />
-- useful is available (for example, with unit volt).<br />
local unit = units[uid]<br />
if type(unit) ~= 'table' then<br />
return nil<br />
end<br />
local ucode = unit.ucode<br />
if ucode and not unit.si then<br />
return ucode -- a unit known to convert<br />
end<br />
parms.opt_ignore_error = true<br />
ucode = ucode or unit._ucode -- must be a non-empty string<br />
local ukey, utable<br />
if unit.si then<br />
local base = units[unit.si]<br />
ukey = base.symbol -- must be a non-empty string<br />
local n1 = base.name1<br />
local n2 = base.name2<br />
if not n1 then<br />
n1 = ukey<br />
n2 = n2 or n1 -- do not append 's'<br />
end<br />
utable = {<br />
_symbol = ukey,<br />
_name1 = n1,<br />
_name2 = n2,<br />
link = unit.link or base.link,<br />
utype = n1,<br />
prefixes = 1,<br />
}<br />
else<br />
ukey = ucode<br />
utable = {<br />
symbol = ucode, -- must be a non-empty string<br />
name1 = unit.name1, -- if nil, uses symbol<br />
name2 = unit.name2, -- if nil, uses name1..'s'<br />
link = unit.link, -- if nil, uses name1<br />
utype = unit.name1 or ucode,<br />
}<br />
end<br />
utable.scale = 1<br />
utable.default = ''<br />
utable.defkey = ''<br />
utable.linkey = ''<br />
utable.bad_mcode = ''<br />
parms.unittable = { [ukey] = utable }<br />
return ucode<br />
end<br />
<br />
local function matches_qualifier(statement, qual)<br />
-- Return:<br />
-- false, nil : if statement does not match specification<br />
-- true, nil : if matches, and statement has no qualifier<br />
-- true, sq : if matches, where sq is the statement's qualifier<br />
-- A match means that no qualifier was specified (qual == nil), or that<br />
-- the statement has a qualifier matching the specification.<br />
-- If a match occurs, the caller needs the statement's qualifier (if any)<br />
-- so statements that duplicate the qualifier are not used, after the first.<br />
-- Then, if convert is showing all values for a property such as the diameter<br />
-- of a telescope's mirror (diameters of primary and secondary mirrors), it<br />
-- will not show alternative values that could in principle be present for the<br />
-- same item (telescope) and property (diameter) and qualifier (primary/secondary).<br />
local target = (statement.qualifiers or {}).P518 -- P518 is "applies to part"<br />
if type(target) == 'table' then<br />
for _, q in ipairs(target) do<br />
if type(q) == 'table' then<br />
local value = (q.datavalue or {}).value<br />
if value then<br />
if qual == nil or qual == value.id then<br />
return true, value.id<br />
end<br />
end<br />
end<br />
end<br />
end<br />
if qual == nil then<br />
return true, nil -- only occurs if statement has no qualifier<br />
end<br />
return false, nil -- statement's qualifier is not relevant because statement will be skipped<br />
end<br />
<br />
local function get_statements(parms, pid)<br />
-- Get specified item and return a list of tables with each statement for property pid.<br />
-- Each table is of form {statqual=sq, stmt=statement} where sq = statement qualifier (nil if none).<br />
-- Statements are in Wikidata's order except that those with preferred rank<br />
-- are first, then normal rank. Any other rank is ignored.<br />
local stored = {} -- qualifiers of statements that are first for the qualifier, and will be returned<br />
local qid = strip_to_nil(parms.qid) -- nil for current page's item, or an item id (expensive)<br />
local qual = strip_to_nil(parms.qual) -- nil or id of wanted P518 (applies to part) item in qualifiers<br />
local result = Collection.new()<br />
local entity = mw.wikibase.getEntity(qid)<br />
if type(entity) == 'table' then<br />
local statements = (entity.claims or {})[pid]<br />
if type(statements) == 'table' then<br />
for _, rank in ipairs({ 'preferred', 'normal' }) do<br />
for _, statement in ipairs(statements) do<br />
if type(statement) == 'table' and rank == statement.rank then<br />
local is_match, statqual = matches_qualifier(statement, qual)<br />
if is_match then<br />
result:add({ statqual = statqual, stmt = statement })<br />
end<br />
end<br />
end<br />
end<br />
end<br />
end<br />
return result<br />
end<br />
<br />
local function input_from_property(tdata, parms, pid)<br />
-- Given that pid is a Wikidata property identifier like 'P123',<br />
-- return a collection of {amount, ucode} pairs (two strings)<br />
-- for each matching item/property, or return nothing.<br />
--------------------------------------------------------------------------------<br />
-- There appear to be few restrictions on how Wikidata is organized so it is<br />
-- very likely that any decision a module makes about how to handle data<br />
-- will be wrong for some cases at some time. This meets current requirements.<br />
-- For each qualifier (or if no qualitifer), if there are any preferred<br />
-- statements, use them and ignore any normal statements.<br />
-- For each qualifier, for the preferred statements if any, or for<br />
-- the normal statements (but not both):<br />
-- * Accept each statement if it has no qualifier (this will not occur<br />
-- if qual=x is specified because other code already ensures that in that<br />
-- case, only statements with a qualifier matching x are considered).<br />
-- * Ignore any statements after the first if it has a qualifier.<br />
-- The rationale is that for the diameter at [[South Pole Telescope]], want<br />
-- convert to show the diameters for both the primary and secondary mirrors<br />
-- if the convert does not specify which diameter is wanted.<br />
-- However, if convert is given the wanted qualifier, only one value<br />
-- (_the_ diameter) is wanted. For simplicity/consistency, that is also done<br />
-- even if no qual=x is specified. Unclear what should happen.<br />
-- For the wavelength at [[Nançay Radio Telescope]], want to show all three<br />
-- values, and the values have no qualifiers.<br />
--------------------------------------------------------------------------------<br />
local result = Collection.new()<br />
local done = {}<br />
local skip_normal<br />
for _, t in ipairs(get_statements(parms, pid)) do<br />
local statement = t.stmt<br />
if statement.mainsnak and statement.mainsnak.datatype == 'quantity' then<br />
local value = (statement.mainsnak.datavalue or {}).value<br />
if value then<br />
local amount = value.amount<br />
if amount then<br />
amount = tostring(amount) -- in case amount is ever a number<br />
if amount:sub(1, 1) == '+' then<br />
amount = amount:sub(2)<br />
end<br />
local unit = value.unit<br />
if type(unit) == 'string' then<br />
unit = unit:match('Q%d+$') -- unit item id is at end of URL<br />
local ucode = make_unit(tdata.wikidata_units, parms, unit)<br />
if ucode then<br />
local skip<br />
if t.statqual then<br />
if done[t.statqual] then<br />
skip = true<br />
else<br />
done[t.statqual] = true<br />
end<br />
else<br />
if statement.rank == 'preferred' then<br />
skip_normal = true<br />
elseif skip_normal then<br />
skip = true<br />
end<br />
end<br />
if not skip then<br />
result:add({ amount, ucode })<br />
end<br />
end<br />
end<br />
end<br />
end<br />
end<br />
end<br />
return result<br />
end<br />
<br />
local function input_from_text(tdata, parms, text, insert2)<br />
-- Given string should be of form "<value><space><unit>" or<br />
-- "<value1><space>ft<space><value2><space>in" for a special case (feet and inches).<br />
-- Return true if values/units were extracted and inserted, or return nothing.<br />
text = text:gsub('&nbsp;', ' '):gsub('%s+', ' ')<br />
local pos = text:find(' ', 1, true)<br />
if pos then<br />
-- Leave checking of value to convert which can handle fractions.<br />
local value = text:sub(1, pos - 1)<br />
local uid = text:sub(pos + 1)<br />
if uid:sub(1, 3) == 'ft ' and uid:sub(-3) == ' in' then<br />
-- Special case for enwiki to allow {{convert|input=5 ft 10+1/2 in}}<br />
insert2(uid:sub(4, -4), 'in')<br />
insert2(value, 'ft')<br />
else<br />
insert2(value, make_unit(tdata.wikidata_units, parms, uid) or uid)<br />
end<br />
return true<br />
end<br />
end<br />
<br />
local function adjustparameters(tdata, parms, index)<br />
-- For Module:Convert, adjust parms (a table of {{convert}} parameters).<br />
-- Return true if successful or return false, t where t is an error message table.<br />
-- This is intended mainly for use in infoboxes where the input might be<br />
-- <value><space><unit> or<br />
-- <wikidata-property-id><br />
-- If successful, insert values and units in parms, before given index.<br />
local text = parms.input -- should be a trimmed, non-empty string<br />
local pid = text:match('^P%d+$')<br />
local sep = ','<br />
local special = specials[parms[index]]<br />
if special then<br />
parms.out_unit = special[1]<br />
sep = special[2] or sep<br />
table.remove(parms, index)<br />
end<br />
local function quit()<br />
return false, pid and { 'cvt_no_output' } or { 'cvt_bad_input', text }<br />
end<br />
local function insert2(first, second)<br />
table.insert(parms, index, second)<br />
table.insert(parms, index, first)<br />
end<br />
if pid then<br />
parms.input_text = '' -- output an empty string if an error occurs<br />
local result = input_from_property(tdata, parms, pid)<br />
if result.n == 0 then<br />
return quit()<br />
end<br />
local ucode<br />
for i, t in ipairs(result) do<br />
-- Convert requires each input unit to be identical.<br />
if i == 1 then<br />
ucode = t[2]<br />
elseif ucode ~= t[2] then<br />
return quit()<br />
end<br />
end<br />
local item = ucode<br />
if item == parms[index] then<br />
-- Remove specified output unit if it is the same as the Wikidata unit.<br />
-- For example, {{convert|input=P2044|km}} with property "12 km".<br />
table.remove(parms, index)<br />
end<br />
for i = result.n, 1, -1 do<br />
insert2(result[i][1], item)<br />
item = sep<br />
end<br />
return true<br />
else<br />
if input_from_text(tdata, parms, text, insert2) then<br />
return true<br />
end<br />
end<br />
return quit()<br />
end<br />
<br />
--------------------------------------------------------------------------------<br />
--- List units and check syntax of definitions ---------------------------------<br />
--------------------------------------------------------------------------------<br />
local specifications = {<br />
-- seq = sequence in which fields are displayed<br />
base = {<br />
title = 'SI base units',<br />
fields = {<br />
symbol = { seq = 2, mandatory = true },<br />
name1 = { seq = 3, mandatory = true },<br />
name2 = { seq = 4 },<br />
link = { seq = 5 },<br />
},<br />
noteseq = 6,<br />
header = '{| class="wikitable"\n!si !!symbol !!name1 !!name2 !!link !!note',<br />
item = '|-\n|%s ||%s ||%s ||%s ||%s ||%s',<br />
footer = '|}',<br />
},<br />
alias = {<br />
title = 'Aliases for convert',<br />
fields = {<br />
ucode = { seq = 2, mandatory = true },<br />
si = { seq = 3 },<br />
},<br />
noteseq = 4,<br />
header = '{| class="wikitable"\n!alias !!ucode !!base !!note',<br />
item = '|-\n|%s ||%s ||%s ||%s',<br />
footer = '|}',<br />
},<br />
known = {<br />
title = 'Units known to convert',<br />
fields = {<br />
ucode = { seq = 2, mandatory = true },<br />
label = { seq = 3, mandatory = true },<br />
},<br />
noteseq = 4,<br />
header = '{| class="wikitable"\n!qid !!ucode !!label !!note',<br />
item = '|-\n|%s ||%s ||%s ||%s',<br />
footer = '|}',<br />
},<br />
unknown = {<br />
title = 'Units not known to convert',<br />
fields = {<br />
_ucode = { seq = 2, mandatory = true },<br />
si = { seq = 3 },<br />
name1 = { seq = 4 },<br />
name2 = { seq = 5 },<br />
link = { seq = 6 },<br />
label = { seq = 7, mandatory = true },<br />
},<br />
noteseq = 8,<br />
header = '{| class="wikitable"\n!qid !!_ucode !!base !!name1 !!name2 !!link !!label !!note',<br />
item = '|-\n|%s ||%s ||%s ||%s ||%s ||%s ||%s ||%s',<br />
footer = '|}',<br />
},<br />
}<br />
<br />
local function listunits(tdata, ulookup)<br />
-- For Module:Convert, make wikitext to list the built-in Wikidata units.<br />
-- Return true, wikitext if successful or return false, t where t is an<br />
-- error message table. Currently, an error return never occurs.<br />
-- The syntax of each unit definition is checked and a note is added if<br />
-- a problem is detected.<br />
local function safe_cells(t)<br />
-- This is not currently needed, but in case definitions ever use wikitext<br />
-- like '[[kilogram|kg]]', escape the text so it works in a table cell.<br />
local result = {}<br />
for i, v in ipairs(t) do<br />
if v:find('|', 1, true) then<br />
v = v:gsub('(%[%[[^%[%]]-)|(.-%]%])', '%1\0%2') -- replace pipe in piped link with a zero byte<br />
v = v:gsub('|', '&#124;') -- escape '|'<br />
v = v:gsub('%z', '|') -- restore pipe in piped link<br />
end<br />
result[i] = v:gsub('{', '&#123;') -- escape '{'<br />
end<br />
return unpack(result)<br />
end<br />
local wdunits = tdata.wikidata_units<br />
local speckeys = { 'base', 'alias', 'unknown', 'known' }<br />
for _, sid in ipairs(speckeys) do<br />
specifications[sid].units = Collection.new()<br />
end<br />
local keys = Collection.new()<br />
for k, v in pairs(wdunits) do<br />
keys:add(k)<br />
end<br />
table.sort(keys)<br />
local note_count = 0<br />
for _, key in ipairs(keys) do<br />
local unit = wdunits[key]<br />
local ktext, sid<br />
if key:match('^Q%d+$') then<br />
ktext = '[[d:' .. key .. '|' .. key .. ']]'<br />
if unit.ucode then<br />
sid = 'known'<br />
else<br />
sid = 'unknown'<br />
end<br />
elseif unit.ucode then<br />
ktext = key<br />
sid = 'alias'<br />
else<br />
ktext = key<br />
sid = 'base'<br />
end<br />
local result = { ktext }<br />
local spec = specifications[sid]<br />
local fields = spec.fields<br />
local note = Collection.new()<br />
for k, v in pairs(unit) do<br />
if fields[k] then<br />
local seq = fields[k].seq<br />
if result[seq] then<br />
note:add('duplicate ' .. k) -- cannot happen since keys are unique<br />
else<br />
result[seq] = v<br />
end<br />
else<br />
note:add('invalid ' .. k)<br />
end<br />
end<br />
for k, v in pairs(fields) do<br />
local value = result[v.seq]<br />
if value then<br />
if k == 'si' and not wdunits[value] then<br />
note:add('need si ' .. value)<br />
end<br />
if k == 'label' then<br />
local wdl = mw.wikibase.label(key)<br />
if wdl ~= value then<br />
note:add('label changed to ' .. tostring(wdl))<br />
end<br />
end<br />
else<br />
result[v.seq] = ''<br />
if v.mandatory then<br />
note:add('missing ' .. k)<br />
end<br />
end<br />
end<br />
local text<br />
if note.n > 0 then<br />
note_count = note_count + 1<br />
text = '*' .. note:join('<br />')<br />
end<br />
result[spec.noteseq] = text or ''<br />
spec.units:add(result)<br />
end<br />
local results = Collection.new()<br />
if note_count > 0 then<br />
local text = note_count .. (note_count == 1 and ' note' or ' notes')<br />
results:add("'''Search for * to see " .. text .. "'''\n")<br />
end<br />
for _, sid in ipairs(speckeys) do<br />
local spec = specifications[sid]<br />
results:add("'''" .. spec.title .. "'''")<br />
results:add(spec.header)<br />
local fmt = spec.item<br />
for _, unit in ipairs(spec.units) do<br />
results:add(string.format(fmt, safe_cells(unit)))<br />
end<br />
results:add(spec.footer)<br />
end<br />
return true, results:join('\n')<br />
end<br />
<br />
return { _adjustparameters = adjustparameters, _listunits = listunits }</div>Nov3rd17