Module:Convert: Difference between revisions

(update from sandbox per Template talk:Convert#Module v2 soon)
(update from sandbox per Template talk:Convert:Module version 3)
Line 1:
-- Convert a value from one unit of measurement to another.
-- Example: {{convert|123|lb|kg}} --> 123 pounds (56 kg)
-- See [[:en:Template:Convert/Transwiki guide]] if copying to another wiki.
 
local MINUS = '−' -- Unicode U+2212 MINUS SIGN (UTF-8: e2 88 92)
Line 14 ⟶ 15:
-- Conversion data and message text are defined in separate modules.
local config, maxsigfig
local numdot, numsep -- each must be '.' or ',' or a singlecharacter bytewhich forworks simplein a regex search/replaceas used here
local numsep, numsep_remove
local default_exceptions, link_exceptions, all_units
local text_code
Line 22 ⟶ 24:
-- Use translation_table in convert/text to change the following.
local group_method = 3 -- code for how many digits are in a group
local per_word = 'per' -- for units like "milesliters per gallonkilometer"
local plural_suffix = 's' -- only other useful value is probably '' to disable plural unit names
 
Line 31 ⟶ 33:
local extra_module -- name of module with extra units
local extra_units -- nil or table of extra units from extra_module
 
local function boolean(text)
-- Return true if text represents a "true" option value.
if text then
text = text:lower()
if text == 'on' or text == 'yes' then
return true
end
end
return false
end
 
local function from_en(text)
Line 62 ⟶ 53:
-- and no separators (they have to be removed here to handle cases like
-- numsep = '.' and numdot = ',' with input "1.234.567,8").
if numsepnumsep_remove ~= '' then
text = text:gsub('[' .. numsep .. ']'numsep_remove, '') -- use '[x]' in case x is '.'
end
if numdot ~= '.' then
text = text:gsub('[' .. numdot .. ']', '.')
end
if to_en_table then
Line 80 ⟶ 71:
-- Set configuration options from template #invoke or defaults.
config = frame.args
numdot = config.numdot or '.' -- decimal mark before fractional digits
numsep = config.numsep or ',' -- group separator for numbers (',', '.', '')
maxsigfig = config.maxsigfig or 14 -- maximum number of significant figures
-- Scribunto sets the global variable 'mw'.
Line 93 ⟶ 82:
spell_module = "ConvertNumeric"
else
local sandbox = boolean(config.sandbox) and ('/sandbox' .. config.sandbox) or ''
data_module = "Module:Convert/data" .. sandbox
text_module = "Module:Convert/text" .. sandbox
Line 106 ⟶ 95:
local translation = text_code.translation_table
if translation then
numdot = translation.numdot
numsep = translation.numsep
if translation.group then
group_method = translation.group
Line 133 ⟶ 124:
end
end
numdot = config.numdot or numdot or '.' -- decimal mark before fractional digits
numsep = config.numsep or numsep or ',' -- group separator for numbers
-- numsep should be ',' or '.' or '' or ' ' or a Unicode character.
-- numsep_remove must work in a regex to identify separators to be removed.
numsep_remove = (numsep == '.') and '%.' or numsep
end
 
Line 331 ⟶ 327:
-- END: Code required only for built-in units.
------------------------------------------------------------------------
 
local function get_range(word)
-- Return a range (string or table) corresponding to word (like "to"),
-- or return nil if not a range word.
local ranges = text_code.ranges
return ranges.types[word] or ranges.types[ranges.aliases[word]]
end
 
local function check_mismatch(unit1, unit2)
Line 369 ⟶ 372:
 
local unit_mt = {
-- Metatable to get missing values for a unit that does not accept SI prefixes,.
-- or for a unit that accepts prefixes but where no prefix was used.
-- In the latter case, and before use, fields symbol, name1, name1_us
-- must be set from _symbol, _name1, _name1_us respectively.
-- Warning: The boolean value 'false' is returned for any missing field
-- so __index is not called twice for the same field in a given unit.
Line 403:
end
}
 
local function prefixed_name(unit, name, index)
-- Return unit name with SI prefix inserted at correct position.
-- index = 1 (name1), 2 (name2), 3 (name1_us), 4 (name2_us).
-- The position is a byte (not character) index, so use Lua's sub().
local pos = rawget(unit, 'prefix_position')
if type(pos) == 'string' then
pos = tonumber(split(pos, ',')[index])
end
if pos then
return name:sub(1, pos - 1) .. unit.si_name .. name:sub(pos)
end
return unit.si_name .. name
end
 
local unit_prefixed_mt = {
-- Metatable to get missing values for a unit that accepts SI prefixes,.
-- and where a prefix has been used.
-- Before use, fields si_name, si_prefix must be defined.
-- The unit must define _symbol, _name1 and
-- may define _sym_us, _name1_us, _name2_us
-- (_sym_us, _name2_us may be defined for a language using sp=us
-- to refer to a variant unrelated to U.S. units).
__index = function (self, key)
local value
Line 413 ⟶ 430:
value = self.si_prefix .. self._symbol
elseif key == 'sym_us' then
value = rawget(self.symbol, -- always the same as sym_us for prefixed units'_sym_us')
if value then
value = self.si_prefix .. value
else
value = self.symbol
end
elseif key == 'name1' then
value = prefixed_name(self, self._name1, 1)
-- prefix_position is a byte (not character) position, so use Lua's sub().
local pos = rawget(self, 'prefix_position') or 1
value = self._name1
value = value:sub(1, pos - 1) .. self.si_name .. value:sub(pos)
elseif key == 'name2' then
value = rawget(self.name1, .. plural_suffix'_name2')
if value then
value = prefixed_name(self, value, 2)
else
value = self.name1 .. plural_suffix
end
elseif key == 'name1_us' then
value = rawget(self, '_name1_us')
if value then
local posvalue = rawgetprefixed_name(self, 'prefix_position'value, 3) or 1
value = value:sub(1, pos - 1) .. self.si_name .. value:sub(pos)
else
value = self.name1
end
elseif key == 'name2_us' then
ifvalue = rawget(self, '_name1_us_name2_us') then
if value then
value = prefixed_name(self, value, 4)
elseif rawget(self, '_name1_us') then
value = self.name1_us .. plural_suffix
else
Line 575 ⟶ 601:
result.sp_us = force_sp_us
if result.prefixes then
result.symbolsi_name = result._symbol''
result.name1si_prefix = result._name1''
return true, setmetatable(result, unit_prefixed_mt)
result.name1_us = result._name1_us
end
return true, setmetatable(result, unit_mt)
Line 673 ⟶ 699:
end
end
if not get_range(unitcode) then -- do not require extra if looking up a range word which cannot be a unit
if not extra_units then
if not extra_units then
local success, extra = pcall(function () return require(extra_module).extra_units end)
local success, extra = pcall(function () return require(extra_module).extra_units end)
if success and type(extra) == 'table' then
if success and type(extra) == 'table' then
extra_units = extra
extra_units = extra
end
end
if extra_units then
end
-- A unit in one data table might refer to a unit in the other table, so
if extra_units then
-- switch between them, relying on fails or depth to terminate loops.
-- A unit in one data table might refer to a unit in the other table, so
if not fails[unitcode] then
-- switch between them, relying on fails or depth to terminate loops.
if not fails[unitcode] then= true
local other = (utable == all_units) and extra_units or all_units
fails[unitcode] = true
local success, result = lookup(unitcode, opt_sp_us, what, other, fails, depth)
local other = (utable == all_units) and extra_units or all_units
if success then
local success, result = lookup(unitcode, opt_sp_us, what, other, fails, depth)
return true, result
if success then
end
return true, result
end
end
end
if to_en_table then
-- At fawiki it is common to translate all digits so a unit like "km2" becomes "km۲".
local en_code = ustring.gsub(unitcode, '%d', to_en_table)
if en_code ~= unitcode then
return lookup(en_code, opt_sp_us, what, utable, fails, depth)
end
end
Line 806 ⟶ 841:
return ''
end
local mid = (inout == (parms.opt_flip and 'out' or 'in')) and parms.mid or ''
local mid
if parms.opt_adjectivalwant_name then
if inout == (parms.opt_flip and 'out' or 'in')opt_adjectival then
return '-' .. hyphenated(id) .. mid, true
mid = parms.mid
end
if want_nameparms.opt_add_s and id:sub(-1) ~= 's' then
returnid '-' ..= hyphenated(id) .. (mid's' or ''),-- for truenowiki
end
end
return sep .. id .. (mid or '')
end
 
Line 1,538 ⟶ 1,573:
-- Return true if successful or return false, t where t is an error message table.
if kv_pairs.adj and kv_pairs.sing then
-- For en.wikienwiki (before translation), warn if attempt to use adj and sing
-- as the latter is a deprecated alias for the former.
if kv_pairs.adj ~= kv_pairs.sing and kv_pairs.sing ~= '' then
Line 1,605 ⟶ 1,640:
local cfg_abbr = config.abbr
if cfg_abbr then
-- Don't warn if invalid because every convert would show that warning.
if cfg_abbr == 'on always' then
parms.abbr = 'on'
elseif cfg_abbr == 'onoff defaultalways' then
if parms.abbr == nil then'off'
elseif parms.abbr == nil then
if cfg_abbr == 'on default' then
parms.abbr = 'on'
elseif cfg_abbr == 'off default' then
parms.abbr = 'off'
end
end
Line 1,660 ⟶ 1,700:
-- i = index to next entry in parms after those processed here
-- or return false, t where t is an error message table.
local ranges = text_code.ranges
local valinfo = collection() -- numbered table of input values
local range = collection() -- numbered table of range items (having, for example, 2 range items requires 3 input values)
Line 1,677 ⟶ 1,716:
local success, result = extract_number(parms, valstr, i > 1)
if not success and valstr and i < 20 then -- check i to limit abuse
for _, sep in ipairs(text_code.ranges.words) do
local start, stop = valstr:find(sep, 2, true) -- start at 2 to skip any negative sign for range '-'
if start then
Line 1,701 ⟶ 1,740:
valinfo:add(info)
local next = strip(parms[i])
local range_item = ranges.types[get_range(next] or ranges.types[ranges.aliases[next]])
if not range_item then
break
Line 1,786 ⟶ 1,825:
end
if parms.opt_ignore_error then -- display given unit code with no error (for use with {{val}})
in_unit_table = nil'' -- suppress error message and prevent processing of output unit
end
in_unit_table = setmetatable({ symbol = in_unit, name2 = in_unit, utype = "length", scale = 1, bad_mcode = in_unit_table, default = "m" }, unit_mt)
default = "m", defkey = "m", linkey = "m",
utype = "length", scale = 1, bad_mcode = in_unit_table }, unit_mt)
end
end
Line 1,845 ⟶ 1,886:
end
if parms.opt_adj_mid then
parms.opt_adjectival = true
next = parms[i]
i = i + 1
Line 2,369 ⟶ 2,409:
 
local function variable_name(clean, unit_table)
-- For sl.wikislwiki (Slovenian Wikipedia), a unit name depends on the value.
-- Parameter clean is the unsigned rounded value in en digits, as a string.
-- Value Source Example for "m"
Line 2,757 ⟶ 2,797:
if composite then
-- Simplify: assume there is no range, and no decoration.
local mid = (not parms.opt_flip) and parms.mid or ''
local sep1 = '&nbsp;'
local sep2 = ' '
if parms.opt_adjectival and want_name then
sep1 = '-'
if not parms.opt_flip then
midsep2 = parms.mid or '-'
end
if want_name then
sep1 = '-'
sep2 = '-'
end
end
local parts = { first_unit.valinfo[1].show .. sep1 .. id1 }
Line 2,936 ⟶ 2,971:
not (abbr == 'on' or abbr == 'out' or abbr == 'mos')
local want_link = (parms.lk == 'on' or parms.lk == 'out')
local mid = parms.opt_flip and parms.mid or ''
local sep1 = '&nbsp;'
local sep2 = ' '
if parms.opt_adjectival and want_name then
sep1 = '-'
if parms.opt_flip then
midsep2 = parms.mid or '-'
end
if want_name then
sep1 = '-'
sep2 = '-'
end
end
local do_spell = parms.opt_spell_out
Line 2,974 ⟶ 3,004:
decimals = ''
else
decimalslocal show = (outinfo.show):match('[' .. numdot-- ..number '](.*)')as or '' -- outinfo.showa isstring in local language
local p1, p2 = show:find(numdot, 1, true)
decimals = p1 and show:sub(p2 + 1) or '' -- text after numdot, if any
end
fmt = '%.' .. ulen(decimals) .. 'f' -- to reproduce precision
Line 3,003 ⟶ 3,035:
id = variable_name(clean, out_current)
else
idlocal = out_current[(thisvaluekey == 1) and 'name1' or 'name2']
if parms.opt_adjectival then
key = 'name1'
elseif tfrac then
if thisvalue == 0 then
key = 'name1'
end
elseif parms.opt_singular then
if 0 < thisvalue and thisvalue < 1.0001 then
key = 'name1'
end
else
if thisvalue == 1 then
key = 'name1'
end
end
id = out_current[key]
end
else
Line 3,162 ⟶ 3,210:
local wikitext
if bad_input_mcode then
if bad_input_mcode == '' then
wikitext = parts[1] .. message(bad_input_mcode)
wikitext = parts[1]
else
wikitext = parts[1] .. message(bad_input_mcode)
end
elseif parms.table_joins then
wikitext = parms.table_joins[1] .. parts[1] .. parms.table_joins[2] .. parts[2]
Anonymous user