Jump to content
 







Main menu
   


Navigation  



Main page
Contents
Current events
Random article
About Wikipedia
Contact us
Donate
 




Contribute  



Help
Learn to edit
Community portal
Recent changes
Upload file
 








Search  

































Create account

Log in
 









Create account
 Log in
 




Pages for logged out editors learn more  



Contributions
Talk
 

















Module:Convert/wikidata






Аԥсшәа
العربية
Asturianu
Azərbaycanca
تۆرکجه
Basa Bali

Banjar
Беларуская (тарашкевіца)

Bikol Central
Български
Brezhoneg
Català
Cymraeg
الدارجة
Deutsch
Ελληνικά
فارسی
Galego



Ilokano
Bahasa Indonesia
עברית
Jawa

 / کٲشُر
Қазақша
Kurdî
Latviešu
Lietuvių
Македонски

Malti

Bahasa Melayu
Minangkabau
Mirandés



Norsk bokmål
Oʻzbekcha / ўзбекча


Português

Scots
Shqip
Simple English
سنڌي
Slovenščina
Српски / srpski
Sunda
Taqbaylit

Тоҷикӣ
Türkçe
Українська
اردو
Tiếng Vit
Winaray

 

Edit links
 









Module
Talk
 

















Read
View source
View history
 








Tools
   


Actions  



Read
View source
View history
 




General  



What links here
Related changes
Upload file
Special pages
Permanent link
Page information
Get shortened URL
Download QR code
Wikidata item
 




Print/export  



Download as PDF
Printable version
 




In other projects  



Wikispecies
Wikibooks
 
















Appearance
   

 





Permanently protected module

From Wikipedia, the free encyclopedia
 

< Module:Convert

-- Functions to access Wikidata for Module:Convert.

local Collection = {}
Collection.__index = Collection
do
 function Collection:add(item)
  if item ~= nil then
   self.n = self.n + 1
   self[self.n] = item
  end
 end
 function Collection:join(sep)
  return table.concat(self, sep)
 end
 function Collection:remove(pos)
  if self.n > 0 and (pos == nil or (0 < pos and pos <= self.n)) then
   self.n = self.n - 1
   return table.remove(self, pos)
  end
 end
 function Collection:sort(comp)
  table.sort(self, comp)
 end
 function Collection.new()
  return setmetatable({n = 0}, Collection)
 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 frequency_unit(value, unit_table)
 -- For use when converting m to Hz.
 -- Return true, s where s = name of unit's default output unit,
 -- or return false, t where t is an error message table.
 -- However, for simplicity a valid result is always returned.
 local unit
 if unit_table._symbol == 'm' then
  -- c = speed of light in a vacuum = 299792458 m/s
  -- frequency = c / wavelength
  local w = value * (unit_table.scale or 1)
  local f = 299792458 / w  -- if w == 0, f = math.huge which works here
  if f >= 1e12 then
   unit = 'THz'
  elseif f >= 1e9 then
   unit = 'GHz'
  elseif f >= 1e6 then
   unit = 'MHz'
  elseif f >= 1e3 then
   unit = 'kHz'
  else
   unit = 'Hz'
  end
 end
 return true, unit or 'Hz'
end

local function wavelength_unit(value, unit_table)
 -- Like frequency_unit but for use when converting Hz to m.
 local unit
 if unit_table._symbol == 'Hz' then
  -- Using 0.9993 rather than 1 avoids rounding which would give results
  -- like converting 300 MHz to 100 cm instead of 1 m.
  local w = 1 / (value * (unit_table.scale or 1))  -- Hz scale is inverted
  if w >= 0.9993e6 then
   unit = 'Mm'
  elseif w >= 0.9993e3 then
   unit = 'km'
  elseif w >= 0.9993 then
   unit = 'm'
  elseif w >= 0.9993e-2 then
   unit = 'cm'
  elseif w >= 0.9993e-3 then
   unit = 'mm'
  else
   unit = 'um'
  end
 end
 return true, unit or 'm'
end

local specials = {
 frequency = { frequency_unit },
 wavelength = { wavelength_unit },
 --------------------------------------------------------------------------------
 -- Following is a removed experiment to show two values as a range
 -- using '-' as the separator.
 -- frequencyrange = { frequency_unit, '-' },
 -- wavelengthrange = { wavelength_unit, '-' },
}

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 matches_qualifier(statement, qual)
 -- Return:
 --   false, nil : if statement does not match specification
 --   true, nil  : if matches, and statement has no qualifier
 --   true, sq   : if matches, where sq is the statement's qualifier
 -- A match means that no qualifier was specified (qual == nil), or that
 -- the statement has a qualifier matching the specification.
 -- If a match occurs, the caller needs the statement's qualifier (if any)
 -- so statements that duplicate the qualifier are not used, after the first.
 -- Then, if convert is showing all values for a property such as the diameter
 -- of a telescope's mirror (diameters of primary and secondary mirrors), it
 -- will not show alternative values that could in principle be present for the
 -- same item (telescope) and property (diameter) and qualifier (primary/secondary).
 local target = (statement.qualifiers or {}).P518  -- P518 is "applies to part"
 if type(target) == 'table' then
  for _, q in ipairs(target) do
   if type(q) == 'table' then
    local value = (q.datavalue or {}).value
    if value then
     if qual == nil or qual == value.id then
      return true, value.id
     end
    end
   end
  end
 end
 if qual == nil then
  return true, nil  -- only occurs if statement has no qualifier
 end
 return false, nil  -- statement's qualifier is not relevant because statement will be skipped
end

local function get_statements(parms, pid)
 -- Get specified item and return a list of tables with each statement for property pid.
 -- Each table is of form {statqual=sq, stmt=statement} where sq = statement qualifier (nil if none).
 -- Statements are in Wikidata's order except that those with preferred rank
 -- are first, then normal rank. Any other rank is ignored.
 local stored = {}  -- qualifiers of statements that are first for the qualifier, and will be returned
 local qid = strip_to_nil(parms.qid)  -- nil for current page's item, or an item id (expensive)
 local qual = strip_to_nil(parms.qual)  -- nil or id of wanted P518 (applies to part) item in qualifiers
 local result = Collection.new()
 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
      local is_match, statqual = matches_qualifier(statement, qual)
      if is_match then
       result:add({ statqual = statqual, stmt = statement })
      end
     end
    end
   end
  end
 end
 return result
end

local function input_from_property(tdata, parms, pid)
 -- Given that pid is a Wikidata property identifier like 'P123',
 -- return a collection of {amount, ucode} pairs (two strings)
 -- for each matching item/property, or return nothing.
 --------------------------------------------------------------------------------
 -- There appear to be few restrictions on how Wikidata is organized so it is
 -- very likely that any decision a module makes about how to handle data
 -- will be wrong for some cases at some time. This meets current requirements.
 -- For each qualifier (or if no qualifier), if there are any preferred
 -- statements, use them and ignore any normal statements.
 -- For each qualifier, for the preferred statements if any, or for
 -- the normal statements (but not both):
 -- * Accept each statement if it has no qualifier (this will not occur
 --   if qual=x is specified because other code already ensures that in that
 --   case, only statements with a qualifier matching x are considered).
 -- * Ignore any statements after the first if it has a qualifier.
 -- The rationale is that for the diameter at [[South Pole Telescope]], want
 -- convert to show the diameters for both the primary and secondary mirrors
 -- if the convert does not specify which diameter is wanted.
 -- However, if convert is given the wanted qualifier, only one value
 -- (_the_ diameter) is wanted. For simplicity/consistency, that is also done
 -- even if no qual=x is specified. Unclear what should happen.
 -- For the wavelength at [[Nançay Radio Telescope]], want to show all three
 -- values, and the values have no qualifiers.
 --------------------------------------------------------------------------------
 local result = Collection.new()
 local done = {}
 local skip_normal
 for _, t in ipairs(get_statements(parms, pid)) do
  local statement = t.stmt
  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 item id is at end of URL
      local ucode = make_unit(tdata.wikidata_units, parms, unit)
      if ucode then
       local skip
       if t.statqual then
        if done[t.statqual] then
         skip = true
        else
         done[t.statqual] = true
        end
       else
        if statement.rank == 'preferred' then
         skip_normal = true
        elseif skip_normal then
         skip = true
        end
       end
       if not skip then
        result:add({ amount, ucode })
       end
      end
     end
    end
   end
  end
 end
 return result
end

local function input_from_text(tdata, parms, text, insert2)
 -- Given string should be of form "<value><space><unit>"or
 -- "<value1><space>ft<space><value2><space>in" for a special case (feet and inches).
 -- Return true if values/units were extracted and inserted, or return nothing.
 text = text:gsub('&nbsp;', ' '):gsub('%s+', ' ')
 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)
  if uid:sub(1, 3) == 'ft ' and uid:sub(-3) == ' in' then
   -- Special case for enwiki to allow {{convert|input=5 ft 10+1/2 in}}
   insert2(uid:sub(4, -4), 'in')
   insert2(value, 'ft')
  else
   insert2(value, make_unit(tdata.wikidata_units, parms, uid) or uid)
  end
  return true
 end
end

local function adjustparameters(tdata, parms, index)
 -- For Module:Convert, adjust parms (a table of {{convert}} 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
 --    <value><space><unit>or
 --    <wikidata-property-id>
 -- If successful, insert values and units in parms, before given index.
 local text = parms.input  -- should be a trimmed, non-empty string
 local pid = text:match('^P%d+$')
 local sep = ','
 local special = specials[parms[index]]
 if special then
  parms.out_unit = special[1]
  sep = special[2] or sep
  table.remove(parms, index)
 end
 local function quit()
  return false, pid and { 'cvt_no_output' } or { 'cvt_bad_input', text }
 end
 local function insert2(first, second)
  table.insert(parms, index, second)
  table.insert(parms, index, first)
 end
 if pid then
  parms.input_text = ''  -- output an empty string if an error occurs
  local result = input_from_property(tdata, parms, pid)
  if result.n == 0 then
   return quit()
  end
  local ucode
  for i, t in ipairs(result) do
   -- Convert requires each input unit to be identical.
   if i == 1 then
    ucode = t[2]
   elseif ucode ~= t[2] then
    return quit()
   end
  end
  local item = ucode
  if item == parms[index] then
   -- Remove specified output unit if it is the same as the Wikidata unit.
   -- For example, {{convert|input=P2044|km}} with property "12 km".
   table.remove(parms, index)
  end
  for i = result.n, 1, -1 do
   insert2(result[i][1], item)
   item = sep
  end
  return true
 else
  if input_from_text(tdata, parms, text, insert2) then
   return true
  end
 end
 return quit()
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 '[[kilogram|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.new()
 end
 local keys = Collection.new()
 for k, v in pairs(wdunits) do
  keys:add(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 = '[[d:' .. key .. '|' .. 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.new()
  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.getLabel(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('<br />')
  end
  result[spec.noteseq] = text or ''
  spec.units:add(result)
 end
 local results = Collection.new()
 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 }

Retrieved from "https://en.wikipedia.org/w/index.php?title=Module:Convert/wikidata&oldid=1021696366"

Hidden category: 
Wikipedia template-protected modules
 



This page was last edited on 6 May 2021, at 05:08 (UTC).

Text is available under the Creative Commons Attribution-ShareAlike License 4.0; additional terms may apply. By using this site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization.



Privacy policy

About Wikipedia

Disclaimers

Contact Wikipedia

Code of Conduct

Developers

Statistics

Cookie statement

Mobile view



Wikimedia Foundation
Powered by MediaWiki