Editing Module:Convert

Jump to: navigation, search

Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.

The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then save the changes below to finish undoing the edit.
Latest revision Your text
Line 136: Line 136:
  
 
local spell_module  -- name of module that can spell numbers
 
local spell_module  -- name of module that can spell numbers
local speller      -- function from that module to handle spelling (set if needed)
+
local speller      -- function from that module to handle spelling (set if spelling is wanted)
local wikidata_module, wikidata_data_module  -- names of Wikidata modules
 
local wikidata_code, wikidata_data  -- exported tables from those modules (set if needed)
 
  
 
local function set_config(args)
 
local function set_config(args)
Line 149: Line 147:
 
text_module = "Module:Convert/text" .. sandbox
 
text_module = "Module:Convert/text" .. sandbox
 
extra_module = "Module:Convert/extra" .. sandbox
 
extra_module = "Module:Convert/extra" .. sandbox
wikidata_module = "Module:Convert/wikidata" .. sandbox
 
wikidata_data_module = "Module:Convert/wikidata/data" .. sandbox
 
 
spell_module = "Module:ConvertNumeric"
 
spell_module = "Module:ConvertNumeric"
 
data_code = mw.loadData(data_module)
 
data_code = mw.loadData(data_module)
Line 259: Line 255:
 
end
 
end
  
local function wanted_category(catkey, catsort, want_warning)
+
local function wanted_category(cat)
-- Return message category if it is wanted in current namespace,
+
-- Return cat if it is wanted in current namespace, otherwise return nil.
-- otherwise return ''.
+
-- This is so tracking categories only include pages that need correction.
local cat
 
 
local title = mw.title.getCurrentTitle()
 
local title = mw.title.getCurrentTitle()
 
if title then
 
if title then
Line 269: Line 264:
 
for _, v in ipairs(split(config.nscat or nsdefault, ',')) do
 
for _, v in ipairs(split(config.nscat or nsdefault, ',')) do
 
if namespace == tonumber(v) then
 
if namespace == tonumber(v) then
cat = text_code.all_categories[want_warning and 'warning' or catkey]
+
return cat
if catsort and catsort ~= '' and cat:sub(-2) == ']]' then
 
cat = cat:sub(1, -3) .. '|' .. mw.text.nowiki(usub(catsort, 1, 20)) .. ']]'
 
end
 
break
 
 
end
 
end
 
end
 
end
 
end
 
end
return cat or ''
 
 
end
 
end
  
local function message(parms, mcode, is_warning)
+
local function message(mcode)
 
-- Return wikitext for an error message, including category if specified
 
-- Return wikitext for an error message, including category if specified
 
-- for the message type.
 
-- for the message type.
 
-- mcode = numbered table specifying the message:
 
-- mcode = numbered table specifying the message:
 
--    mcode[1] = 'cvt_xxx' (string used as a key to get message info)
 
--    mcode[1] = 'cvt_xxx' (string used as a key to get message info)
--    mcode[2] = 'parm1' (string to replace '$1' if any in message)
+
--    mcode[2] = 'parm1' (string to replace first %s if any in message)
--    mcode[3] = 'parm2' (string to replace '$2' if any in message)
+
--    mcode[3] = 'parm2' (string to replace second %s if any in message)
--    mcode[4] = 'parm3' (string to replace '$3' if any in message)
+
--    mcode[4] = 'parm3' (string to replace third %s if any in message)
local msg
+
local msg = text_code.all_messages[mcode[1]]
if type(mcode) == 'table' then
+
local nowiki = mw.text.nowiki
if mcode[1] == 'cvt_no_output' then
 
-- Some errors should cause convert to output an empty string,
 
-- for example, for an optional field in an infobox.
 
return ''
 
end
 
msg = text_code.all_messages[mcode[1]]
 
end
 
parms.have_problem = true
 
local function subparm(fmt, ...)
 
local rep = {}
 
for i, v in ipairs({...}) do
 
rep['$' .. i] = v
 
end
 
return (fmt:gsub('$%d+', rep))
 
end
 
 
if msg then
 
if msg then
 
local parts = {}
 
local parts = {}
Line 330: Line 305:
 
append = '...'
 
append = '...'
 
end
 
end
s = mw.text.nowiki(s) .. (append or '')
+
s = nowiki(s) .. (append or '')
 
else
 
else
 
s = '?'
 
s = '?'
 
end
 
end
parts['$' .. i] = s
+
parts[i] = s
 
end
 
end
local function ispreview()
+
local title = format(msg[1] or 'Missing message', parts[1], parts[2], parts[3])
-- Return true if a prominent message should be shown.
+
local text = msg[2] or 'Missing message'
if parms.test == 'preview' or parms.test == 'nopreview' then
+
local cat = wanted_category(text_code.all_categories[msg[3]]) or ''
-- For testing, can preview a real message or simulate a preview
 
-- when running automated tests.
 
return parms.test == 'preview'
 
end
 
local success, revid = pcall(function ()
 
return (parms.frame):preprocess('{{REVISIONID}}') end)
 
return success and (revid == '')
 
end
 
local want_warning = is_warning and
 
not config.warnings and  -- show unobtrusive warnings if config.warnings not configured
 
not msg.nowarn          -- but use msg settings, not standard warning, if specified
 
local title = string.gsub(msg[1] or 'Missing message', '$%d+', parts)
 
local text = want_warning and '*' or msg[2] or 'Missing message'
 
local cat = wanted_category(msg[3], mcode[2], want_warning)
 
 
local anchor = msg[4] or ''
 
local anchor = msg[4] or ''
local fmtkey = ispreview() and 'cvt_format_preview' or
+
local fmt = text_code.all_messages[msg.format or 'cvt_format'] or 'convert: bug'
(want_warning and 'cvt_format2' or msg.format or 'cvt_format')
+
title = title:gsub('"', '"')
local fmt = text_code.all_messages[fmtkey] or 'convert: bug'
+
return format(fmt, anchor, title, text, cat)
return subparm(fmt, title:gsub('"', '"'), text, cat, anchor)
 
 
end
 
end
 
return 'Convert internal error: unknown message'
 
return 'Convert internal error: unknown message'
Line 364: Line 324:
 
function add_warning(parms, level, key, text1, text2)  -- for forward declaration above
 
function add_warning(parms, level, key, text1, text2)  -- for forward declaration above
 
-- If enabled, add a warning that will be displayed after the convert result.
 
-- If enabled, add a warning that will be displayed after the convert result.
-- A higher level is more verbose: more kinds of warnings are displayed.
 
 
-- To reduce output noise, only the first warning is displayed.
 
-- To reduce output noise, only the first warning is displayed.
if level <= (tonumber(config.warnings) or 1) then
+
if config.warnings or level < 0 then
if parms.warnings == nil then
+
if level <= (tonumber(config.warnings) or 1) then
parms.warnings = message(parms, { key, text1, text2 }, true)
+
if parms.warnings == nil then
 +
parms.warnings = message({ key, text1, text2 })
 +
end
 
end
 
end
 
end
 
end
Line 389: Line 350:
 
success, speller = pcall(get_speller, spell_module)
 
success, speller = pcall(get_speller, spell_module)
 
if not success or type(speller) ~= 'function' then
 
if not success or type(speller) ~= 'function' then
add_warning(parms, 1, 'cvt_no_spell', 'spell')
+
add_warning(parms, 1, 'cvt_no_spell')
 
return nil
 
return nil
 
end
 
end
Line 590: Line 551:
 
-- This is never called to determine a unit name or link because per units
 
-- This is never called to determine a unit name or link because per units
 
-- are handled as a special case.
 
-- are handled as a special case.
-- Similarly, the default output is handled elsewhere, and for a symbol
+
-- Similarly, the default output is handled elsewhere.
-- this is only called from get_default() for default_exceptions.
 
 
__index = function (self, key)
 
__index = function (self, key)
 
local value
 
local value
Line 616: Line 576:
 
}
 
}
  
local function make_per(unitcode, unit_table, ulookup)
+
local function make_per(unit_table, ulookup)
 
-- Return true, t where t is a per unit with unit codes expanded to unit tables,
 
-- Return true, t where t is a per unit with unit codes expanded to unit tables,
 
-- or return false, t where t is an error message table.
 
-- or return false, t where t is an error message table.
local result = {
+
local result = { utype = unit_table.utype, per = {} }
unitcode = unitcode,
 
utype = unit_table.utype,
 
per = {}
 
}
 
 
override_from(result, unit_table, { 'invert', 'iscomplex', 'default', 'link', 'symbol', 'symlink' })
 
override_from(result, unit_table, { 'invert', 'iscomplex', 'default', 'link', 'symbol', 'symlink' })
 
result.symbol_raw = (result.symbol or false)  -- to distinguish between a defined exception and a metatable calculation
 
result.symbol_raw = (result.symbol or false)  -- to distinguish between a defined exception and a metatable calculation
Line 676: Line 632:
 
-- Wikignomes may also put two spaces or "&nbsp;" in combinations, so
 
-- Wikignomes may also put two spaces or "&nbsp;" in combinations, so
 
-- replace underscore, "&nbsp;", and multiple spaces with a single space.
 
-- replace underscore, "&nbsp;", and multiple spaces with a single space.
utable = utable or parms.unittable or all_units
+
utable = utable or all_units
 
fails = fails or {}
 
fails = fails or {}
 
depth = depth and depth + 1 or 1
 
depth = depth and depth + 1 or 1
Line 689: Line 645:
 
end
 
end
 
unitcode = unitcode:gsub('_', ' '):gsub('&nbsp;', ' '):gsub('  +', ' ')
 
unitcode = unitcode:gsub('_', ' '):gsub('&nbsp;', ' '):gsub('  +', ' ')
local function call_make_per(t)
 
return make_per(unitcode, t,
 
function (ucode) return lookup(parms, ucode, 'no_combination', utable, fails, depth) end
 
)
 
end
 
 
local t = utable[unitcode]
 
local t = utable[unitcode]
 
if t then
 
if t then
Line 715: Line 666:
 
end
 
end
 
if t.per then
 
if t.per then
return call_make_per(t)
+
return make_per(t, function (ucode) return lookup(parms, ucode, 'no_combination', utable, fails, depth) end)
 
end
 
end
 
local combo = t.combination  -- nil or a table of unitcodes
 
local combo = t.combination  -- nil or a table of unitcodes
Line 735: Line 686:
 
end
 
end
 
local result = shallow_copy(t)
 
local result = shallow_copy(t)
result.unitcode = unitcode
 
 
if result.prefixes then
 
if result.prefixes then
 
result.si_name = ''
 
result.si_name = ''
Line 754: Line 704:
 
if t and t.prefixes then
 
if t and t.prefixes then
 
local result = shallow_copy(t)
 
local result = shallow_copy(t)
result.unitcode = unitcode
 
 
result.si_name = parms.opt_sp_us and si.name_us or si.name
 
result.si_name = parms.opt_sp_us and si.name_us or si.name
 
result.si_prefix = si.prefix or prefix
 
result.si_prefix = si.prefix or prefix
 
result.scale = t.scale * 10 ^ (si.exponent * t.prefixes)
 
result.scale = t.scale * 10 ^ (si.exponent * t.prefixes)
 
return true, setmetatable(result, unit_prefixed_mt)
 
return true, setmetatable(result, unit_prefixed_mt)
 +
end
 +
end
 +
end
 +
-- Accept any unit with an engineering notation prefix like "e6cuft"
 +
-- (million cubic feet), but not chained prefixes like "e3e6cuft",
 +
-- and not if the unit is a combination or multiple,
 +
-- and not if the unit has an offset or is a built-in.
 +
-- Only en digits are accepted.
 +
local has_plus = unitcode:find('+', 1, true)
 +
if not has_plus then
 +
local exponent, baseunit = unitcode:match('^e(%d+)(.*)')
 +
if exponent then
 +
local engscale = text_code.eng_scales[exponent]
 +
if engscale then
 +
local success, result = lookup(parms, baseunit, 'no_combination', utable, fails, depth)
 +
if success and not (result.offset or result.builtin or result.engscale) then
 +
result.defkey = unitcode  -- key to lookup default exception
 +
result.engscale = engscale
 +
result.scale = result.scale * 10 ^ tonumber(exponent)
 +
return true, result
 +
end
 
end
 
end
 
end
 
end
Line 768: Line 738:
 
local err_is_fatal
 
local err_is_fatal
 
local combo = collection()
 
local combo = collection()
if unitcode:find('+', 1, true) then
+
if has_plus then
 
err_is_fatal = true
 
err_is_fatal = true
 
for item in (unitcode .. '+'):gmatch('%s*(.-)%s*%+') do
 
for item in (unitcode .. '+'):gmatch('%s*(.-)%s*%+') do
Line 805: Line 775:
 
if success or err_is_fatal then
 
if success or err_is_fatal then
 
return success, result
 
return success, result
end
 
end
 
-- Accept any unit with an engineering notation prefix like "e6cuft"
 
-- (million cubic feet), but not chained prefixes like "e3e6cuft",
 
-- and not if the unit is a combination or multiple,
 
-- and not if the unit has an offset or is a built-in.
 
-- Only en digits are accepted.
 
local exponent, baseunit = unitcode:match('^e(%d+)(.*)')
 
if exponent then
 
local engscale = text_code.eng_scales[exponent]
 
if engscale then
 
local success, result = lookup(parms, baseunit, 'no_combination', utable, fails, depth)
 
if success and not (result.offset or result.builtin or result.engscale) then
 
result.unitcode = unitcode  -- 'e6cuft' not 'cuft'
 
result.defkey = unitcode  -- key to lookup default exception
 
result.engscale = engscale
 
result.scale = result.scale * 10 ^ tonumber(exponent)
 
return true, result
 
end
 
 
end
 
end
 
end
 
end
Line 833: Line 784:
 
-- Engineering notation (apart from at start and which has been stripped before here),
 
-- Engineering notation (apart from at start and which has been stripped before here),
 
-- is not supported so do not make a per unit if find text like 'e3' in unitcode.
 
-- is not supported so do not make a per unit if find text like 'e3' in unitcode.
local success, result = call_make_per({ per = {top, bottom} })
+
local success, result = make_per({ per = {top, bottom} }, function (ucode) return lookup(parms, ucode, 'no_combination', utable, fails, depth) end)
 
if success then
 
if success then
 
return true, result
 
return true, result
Line 960: Line 911:
 
end
 
end
 
return sep .. id .. mid
 
return sep .. id .. mid
 +
end
 +
 +
local function change_sign(text)
 +
-- Change sign of text for correct appearance because it is negated.
 +
if text:sub(1, 1) == '-' then
 +
return text:sub(2)
 +
end
 +
return '-' .. text
 
end
 
end
  
Line 1,043: Line 1,002:
 
-- When using gaps, they are inserted before and after the decimal mark.
 
-- When using gaps, they are inserted before and after the decimal mark.
 
-- Separators are inserted only before the decimal mark.
 
-- Separators are inserted only before the decimal mark.
-- A trailing dot (as in '123.') is removed because their use appears to
 
-- be accidental, and such a number should be shown as '123' or '123.0'.
 
-- It is useful for convert to suppress the dot so, for example, '4000.'
 
-- is a simple way of indicating that all the digits are significant.
 
if text:sub(-1) == '.' then
 
text = text:sub(1, -2)
 
end
 
 
if #text < 4 or parms.opt_nocomma or numsep == '' then
 
if #text < 4 or parms.opt_nocomma or numsep == '' then
 
return from_en(text)
 
return from_en(text)
Line 1,327: Line 1,279:
 
-- with the hands unit (not worth adding code to enforce that).
 
-- with the hands unit (not worth adding code to enforce that).
 
------------------------------------------------------------------------
 
------------------------------------------------------------------------
 +
local numstr, whole
 
local leading_plus, prefix, numstr, slashes, denstr =
 
local leading_plus, prefix, numstr, slashes, denstr =
 
text:match('^%s*(%+?)%s*(.-)%s*(%d+)%s*(/+)%s*(%d+)%s*$')
 
text:match('^%s*(%+?)%s*(.-)%s*(%d+)%s*(/+)%s*(%d+)%s*$')
Line 1,340: Line 1,293:
 
return nil
 
return nil
 
end
 
end
local whole, wholestr
+
local wholestr
 
if prefix == '' then
 
if prefix == '' then
 
wholestr = ''
 
wholestr = ''
Line 1,379: Line 1,332:
 
-- Before processing, the input text is cleaned:
 
-- Before processing, the input text is cleaned:
 
-- * Any thousand separators (valid or not) are removed.
 
-- * Any thousand separators (valid or not) are removed.
-- * Any sign is replaced with '-' (if negative) or '' (otherwise).
+
-- * Any sign (and optional following whitespace) is replaced with
 +
--  '-' (if negative) or '' (otherwise).
 
--  That replaces Unicode minus with '-'.
 
--  That replaces Unicode minus with '-'.
 
-- If successful, the returned info table contains named fields:
 
-- If successful, the returned info table contains named fields:
Line 1,405: Line 1,359:
 
while #remainder > 0 do
 
while #remainder > 0 do
 
local ref, spaces
 
local ref, spaces
ref, spaces, remainder = remainder:match('^(\127[^\127]*UNIQ[^\127]*%-ref[^\127]*\127)(%s*)(.*)')
+
ref, spaces, remainder = remainder:match('^(\127UNIQ[^\127]*%-ref%-%x+%-QINU\127)(%s*)(.*)')
 
if ref then
 
if ref then
 
table.insert(refs, ref)
 
table.insert(refs, ref)
Line 1,439: Line 1,393:
 
local valstr
 
local valstr
 
for _, prefix in ipairs({ '-', MINUS, '&minus;' }) do
 
for _, prefix in ipairs({ '-', MINUS, '&minus;' }) do
-- Including '-' sets isnegative in case input is a fraction like '-2-3/4'.
+
-- Including '-' means inputs like '- 2' (with space) are accepted as -2.
 +
-- It also sets isnegative in case input is a fraction like '-2-3/4'.
 
local plen = #prefix
 
local plen = #prefix
 
if clean:sub(1, plen) == prefix then
 
if clean:sub(1, plen) == prefix then
 
valstr = clean:sub(plen + 1)
 
valstr = clean:sub(plen + 1)
if valstr:match('^%s') then  -- "- 1" is invalid but "-1 - 1/2" is ok
 
return false, { 'cvt_bad_num', text }
 
end
 
 
break
 
break
 
end
 
end
Line 1,508: Line 1,460:
 
end
 
end
 
end
 
end
 +
local altvalue = altvalue or value
 
if isnegative and (value ~= 0) then
 
if isnegative and (value ~= 0) then
 
value = -value
 
value = -value
altvalue = -(altvalue or value)
+
altvalue = -altvalue
 
end
 
end
 
return true, {
 
return true, {
 
value = value,
 
value = value,
altvalue = altvalue or value,
+
altvalue = altvalue,
 
singular = singular,
 
singular = singular,
 
clean = clean,
 
clean = clean,
Line 1,533: Line 1,486:
 
local number = tonumber(to_en(text))
 
local number = tonumber(to_en(text))
 
if number then
 
if number then
local _, fracpart = math.modf(number)
+
local integer, fracpart = math.modf(number)
 
return number, (fracpart == 0)
 
return number, (fracpart == 0)
 
end
 
end
Line 1,611: Line 1,564:
 
--    p2 is text to insert before the output unit
 
--    p2 is text to insert before the output unit
 
--    p1 or p2 may be nil to mean "no preunit"
 
--    p1 or p2 may be nil to mean "no preunit"
-- Using '+' gives output like "5+ feet" (no space before, but space after).
+
-- Using '+ ' gives output like "5+ feet" (no preceding space).
local function withspace(text, wantboth)
+
local function withspace(text, i)
-- Return text with space before and, if wantboth, after.
+
-- Insert space at beginning if i == 1, or at end if i == -1.
-- However, no space is added if there is a space or '&nbsp;' or '-'
+
-- However, no space is inserted if there is a space or '&nbsp;'
-- at that position ('-' is for adjectival text).
+
-- or '-' at that position ('-' is for adjectival text).
-- There is also no space if text starts with '&'
+
local current = text:sub(i, i)
-- (e.g. '&deg;' would display a degree symbol with no preceding space).
+
if current == ' ' or current == '-' then
local char = text:sub(1, 1)
+
return text
if char == '&' then
 
return text -- an html entity can be used to specify the exact display
 
 
end
 
end
if not (char == ' ' or char == '-' or char == '+') then
+
if i == 1 then
text = ' ' .. text
+
current = text:sub(1, 6)
 +
else
 +
current = text:sub(-6, -1)
 
end
 
end
if wantboth then
+
if current == '&nbsp;' then
char = text:sub(-1, -1)
+
return text
if not (char == ' ' or char == '-' or text:sub(-6, -1) == '&nbsp;') then
+
end
text = text .. ' '
+
if i == 1 then
end
+
return ' ' .. text
 
end
 
end
return text
+
return text .. ' '
 
end
 
end
local PLUS = '+ '
 
 
preunit1 = preunit1 or ''
 
preunit1 = preunit1 or ''
 
local trim1 = strip(preunit1)
 
local trim1 = strip(preunit1)
Line 1,640: Line 1,592:
 
return nil
 
return nil
 
end
 
end
if trim1 == '+' then
+
return withspace(withspace(preunit1, 1), -1)
return PLUS
 
end
 
return withspace(preunit1, true)
 
 
end
 
end
preunit1 = withspace(preunit1)
 
 
preunit2 = preunit2 or ''
 
preunit2 = preunit2 or ''
 
local trim2 = strip(preunit2)
 
local trim2 = strip(preunit2)
if trim1 == '+' then
+
if trim1 == '' and trim2 == '' then
if trim2 == '' or trim2 == '+' then
+
return nil, nil
return PLUS, PLUS
+
end
end
+
if trim1 ~= '+' then
preunit1 = PLUS
+
preunit1 = withspace(preunit1, 1)
 
end
 
end
if trim2 == '' then
+
if trim2 == '&#32;' then -- trick to make preunit2 empty
if trim1 == '' then
+
preunit2 = nil
return nil, nil
+
elseif trim2 == '' then
end
 
 
preunit2 = preunit1
 
preunit2 = preunit1
elseif trim2 == '+' then
+
elseif trim2 ~= '+' then
preunit2 = PLUS
+
preunit2 = withspace(preunit2, 1)
elseif trim2 == '&#32;' then  -- trick to make preunit2 empty
 
preunit2 = nil
 
else
 
preunit2 = withspace(preunit2)
 
 
end
 
end
 
return preunit1, preunit2
 
return preunit1, preunit2
Line 1,696: Line 1,639:
  
 
local function get_composite(parms, iparm, in_unit_table)
 
local function get_composite(parms, iparm, in_unit_table)
-- Look for a composite input unit. For example, {{convert|1|yd|2|ft|3|in}}
+
-- Look for a composite input unit. For example, "{{convert|1|yd|2|ft|3|in}}"
 
-- would result in a call to this function with
 
-- would result in a call to this function with
 
--  iparm = 3 (parms[iparm] = "2", just after the first unit)
 
--  iparm = 3 (parms[iparm] = "2", just after the first unit)
Line 1,767: Line 1,710:
 
-- Return true if successful or return false, t where t is an error message table.
 
-- Return true if successful or return false, t where t is an error message table.
 
currency_text = nil  -- local testing can hold module in memory; must clear globals
 
currency_text = nil  -- local testing can hold module in memory; must clear globals
local accept_any_text = {
 
input = true,
 
qid = true,
 
qual = true,
 
stylein = true,
 
styleout = true,
 
tracking = true,
 
}
 
 
if kv_pairs.adj and kv_pairs.sing then
 
if kv_pairs.adj and kv_pairs.sing then
 
-- For enwiki (before translation), warn if attempt to use adj and sing
 
-- For enwiki (before translation), warn if attempt to use adj and sing
Line 1,783: Line 1,718:
 
kv_pairs.sing = nil
 
kv_pairs.sing = nil
 
end
 
end
kv_pairs.comma = kv_pairs.comma or config.comma  -- for plwiki who want default comma=5
 
 
for loc_name, loc_value in pairs(kv_pairs) do
 
for loc_name, loc_value in pairs(kv_pairs) do
 
local en_name = text_code.en_option_name[loc_name]
 
local en_name = text_code.en_option_name[loc_name]
Line 1,809: Line 1,743:
 
en_value = number
 
en_value = number
 
else
 
else
add_warning(parms, 1, (en_name == 'frac' and 'cvt_bad_frac' or 'cvt_bad_sigfig'), loc_name .. '=' .. loc_value)
+
add_warning(parms, 1, (en_name == 'frac' and 'cvt_bad_frac' or 'cvt_bad_sigfig'), loc_value)
 
end
 
end
 
end
 
end
elseif accept_any_text[en_name] then
+
elseif en_name == 'stylein' or en_name == 'styleout' then
en_value = loc_value ~= '' and loc_value or nil  -- accept non-empty user text with no validation
+
en_value = loc_value  -- accept user text with no validation
if en_name == 'input' then
 
-- May have something like {{convert|input=}} (empty input) if source is an infobox
 
-- with optional fields. In that case, want to output nothing rather than an error.
 
parms.input_text = loc_value  -- keep input because parms.input is nil if loc_value == ''
 
end
 
 
else
 
else
 
en_value = text_code.en_option_value[en_name][loc_value]
 
en_value = text_code.en_option_value[en_name][loc_value]
Line 1,867: Line 1,796:
 
end
 
end
 
if parms.abbr then
 
if parms.abbr then
if parms.abbr == 'unit' then
 
parms.abbr = 'on'
 
parms.number_word = true
 
end
 
 
parms.abbr_org = parms.abbr  -- original abbr, before any flip
 
parms.abbr_org = parms.abbr  -- original abbr, before any flip
 
elseif parms.opt_hand_hh then
 
elseif parms.opt_hand_hh then
Line 1,877: Line 1,802:
 
else
 
else
 
parms.abbr = 'out'  -- default is to abbreviate output only (use symbol, not name)
 
parms.abbr = 'out'  -- default is to abbreviate output only (use symbol, not name)
end
 
if parms.opt_order_out then
 
-- Disable options that do not work in a useful way with order=out.
 
parms.opt_flip = nil  -- override adj=flip
 
parms.opt_spell_in = nil
 
parms.opt_spell_out = nil
 
parms.opt_spell_upper = nil
 
 
end
 
end
 
if parms.opt_spell_out and not abbr_entered then
 
if parms.opt_spell_out and not abbr_entered then
Line 2,045: Line 1,963:
 
local function simple_get_values(parms)
 
local function simple_get_values(parms)
 
-- If input is like "{{convert|valid_value|valid_unit|...}}",
 
-- If input is like "{{convert|valid_value|valid_unit|...}}",
-- return true, i, in_unit, in_unit_table
+
-- return true, 3, in_unit, in_unit_table
-- i = index in parms of what follows valid_unit, if anything.
+
-- 3 = index in parms of whatever follows valid_unit, if anything).
 
-- The valid_value is not negative and does not use a fraction, and
 
-- The valid_value is not negative and does not use a fraction, and
 
-- no options requiring further processing of the input are used.
 
-- no options requiring further processing of the input are used.
-- Otherwise, return nothing or return false, parm1 for caller to interpret.
+
-- Otherwise, return nothing and caller will reparse the input.
 
-- Testing shows this function is successful for 96% of converts in articles,
 
-- Testing shows this function is successful for 96% of converts in articles,
 
-- and that on average it speeds up converts by 8%.
 
-- and that on average it speeds up converts by 8%.
 +
if parms.opt_ri or parms.opt_spell_in then return end
 
local clean = to_en(strip(parms[1] or ''), parms)
 
local clean = to_en(strip(parms[1] or ''), parms)
if parms.opt_ri or parms.opt_spell_in or #clean > 10 or not clean:match('^[0-9.]+$') then
+
if #clean > 10 or not clean:match('^[0-9.]+$') then return end
return false, clean
 
end
 
 
local value = tonumber(clean)
 
local value = tonumber(clean)
 
if not value then return end
 
if not value then return end
Line 2,072: Line 1,989:
 
end
 
end
  
local function wikidata_call(parms, operation, ...)
+
local function get_parms(args)
-- Return true, s where s is the result of a Wikidata operation,
+
-- If successful, return true, parms, unit where
-- or return false, t where t is an error message table.
 
local function worker(...)
 
wikidata_code = wikidata_code or require(wikidata_module)
 
wikidata_data = wikidata_data or mw.loadData(wikidata_data_module)
 
return wikidata_code[operation](wikidata_data, ...)
 
end
 
local success, status, result = pcall(worker, ...)
 
if success then
 
return status, result
 
end
 
if parms.opt_sortable_debug then
 
-- Use debug=yes to crash if an error while accessing Wikidata.
 
error('Error accessing Wikidata: ' .. status, 0)
 
end
 
return false, { 'cvt_wd_fail' }
 
end
 
 
 
local function get_parms(parms, args)
 
-- If successful, update parms and return true, unit where
 
 
--  parms is a table of all arguments passed to the template
 
--  parms is a table of all arguments passed to the template
 
--        converted to named arguments, and
 
--        converted to named arguments, and
 
--  unit is the input unit table;
 
--  unit is the input unit table;
 
-- or return false, t where t is an error message table.
 
-- or return false, t where t is an error message table.
-- For special processing (not a convert), can also return
 
-- true, wikitext where wikitext is the final result.
 
 
-- The returned input unit table may be for a fake unit using the specified
 
-- The returned input unit table may be for a fake unit using the specified
 
-- unit code as the symbol and name, and with bad_mcode = message code table.
 
-- unit code as the symbol and name, and with bad_mcode = message code table.
Line 2,105: Line 2,001:
 
-- whitespace entered in the template, and whitespace is used by some
 
-- whitespace entered in the template, and whitespace is used by some
 
-- parameters (example: the numbered parameters associated with "disp=x").
 
-- parameters (example: the numbered parameters associated with "disp=x").
 +
local parms = {}  -- arguments passed to template, after translation
 
local kv_pairs = {}  -- table of input key:value pairs where key is a name; needed because cannot iterate parms and add new fields to it
 
local kv_pairs = {}  -- table of input key:value pairs where key is a name; needed because cannot iterate parms and add new fields to it
 
for k, v in pairs(args) do
 
for k, v in pairs(args) do
Line 2,112: Line 2,009:
 
kv_pairs[k] = v
 
kv_pairs[k] = v
 
end
 
end
end
 
if parms.test == 'wikidata' then
 
local ulookup = function (ucode)
 
-- Use empty table for parms so it does not accumulate results when used repeatedly.
 
return lookup({}, ucode, 'no_combination')
 
end
 
return wikidata_call(parms, '_listunits', ulookup)
 
 
end
 
end
 
local success, msg = translate_parms(parms, kv_pairs)
 
local success, msg = translate_parms(parms, kv_pairs)
 
if not success then return false, msg end
 
if not success then return false, msg end
if parms.input then
 
success, msg = wikidata_call(parms, '_adjustparameters', parms, 1)
 
if not success then return false, msg end
 
end
 
 
local success, i, in_unit, in_unit_table = simple_get_values(parms)
 
local success, i, in_unit, in_unit_table = simple_get_values(parms)
 
if not success then
 
if not success then
if type(i) == 'string' and i:match('^NNN+$') then
 
-- Some infoboxes have examples like {{convert|NNN|m}} (3 or more "N").
 
-- Output an empty string for these.
 
return false, { 'cvt_no_output' }
 
end
 
 
local valinfo
 
local valinfo
 
success, valinfo, i = get_values(parms)
 
success, valinfo, i = get_values(parms)
Line 2,140: Line 2,021:
 
success, in_unit_table = lookup(parms, in_unit, 'no_combination')
 
success, in_unit_table = lookup(parms, in_unit, 'no_combination')
 
if not success then
 
if not success then
in_unit = in_unit or ''
+
if in_unit == nil then
 +
in_unit = ''
 +
end
 
if parms.opt_ignore_error then  -- display given unit code with no error (for use with {{val}})
 
if parms.opt_ignore_error then  -- display given unit code with no error (for use with {{val}})
 
in_unit_table = ''  -- suppress error message and prevent processing of output unit
 
in_unit_table = ''  -- suppress error message and prevent processing of output unit
 
end
 
end
in_unit_table = setmetatable({
+
in_unit_table = setmetatable({ symbol = in_unit, name2 = in_unit,
symbol = in_unit, name2 = in_unit, utype = in_unit,
+
default = "m", defkey = "m", linkey = "m",
scale = 1, default = '', defkey = '', linkey = '',
+
utype = "length", scale = 1, bad_mcode = in_unit_table }, unit_mt)
bad_mcode = in_unit_table }, unit_mt)
 
 
end
 
end
 
in_unit_table.valinfo = valinfo
 
in_unit_table.valinfo = valinfo
Line 2,182: Line 2,064:
 
end
 
end
 
end
 
end
local word = strip(parms[i])
+
local next = strip(parms[i])
 
i = i + 1
 
i = i + 1
 
local precision, is_bad_precision
 
local precision, is_bad_precision
Line 2,197: Line 2,079:
 
end
 
end
 
end
 
end
if word and not set_precision(word) then
+
if not set_precision(next) then
parms.out_unit = parms.out_unit or word
+
parms.out_unit = next
 
if set_precision(strip(parms[i])) then
 
if set_precision(strip(parms[i])) then
 
i = i + 1
 
i = i + 1
Line 2,204: Line 2,086:
 
end
 
end
 
if parms.opt_adj_mid then
 
if parms.opt_adj_mid then
word = parms[i]
+
next = parms[i]
 
i = i + 1
 
i = i + 1
if word then  -- mid-text words
+
if next then  -- mid-text words
if word:sub(1, 1) == '-' then
+
if next:sub(1, 1) == '-' then
parms.mid = word
+
parms.mid = next
 
else
 
else
parms.mid = ' ' .. word
+
parms.mid = ' ' .. next
 
end
 
end
 
end
 
end
Line 2,250: Line 2,132:
 
parms.precision = precision
 
parms.precision = precision
 
end
 
end
for j = i, i + 3 do
+
return true, parms, in_unit_table
local parm = parms[j]  -- warn if find a non-empty extraneous parameter
 
if parm and parm:match('%S') then
 
add_warning(parms, 1, 'cvt_unknown_option', parm)
 
break
 
end
 
end
 
return true, in_unit_table
 
 
end
 
end
  
Line 2,293: Line 2,168:
 
local fudge = 1e-14  -- {{Order of magnitude}} adds this, so we do too
 
local fudge = 1e-14  -- {{Order of magnitude}} adds this, so we do too
 
local prec, minprec, adjust
 
local prec, minprec, adjust
 +
local utype = out_current.utype
 
local subunit_ignore_trailing_zero
 
local subunit_ignore_trailing_zero
 
local subunit_more_precision  -- kludge for "in" used in input like "|2|ft|6|in"
 
local subunit_more_precision  -- kludge for "in" used in input like "|2|ft|6|in"
Line 2,530: Line 2,406:
 
end
 
end
 
local sortspan
 
local sortspan
if sortkey and not parms.table_align then
+
if sortkey and (parms.opt_sortable_debug or not parms.table_align) then
 
sortspan = parms.opt_sortable_debug and
 
sortspan = parms.opt_sortable_debug and
'<span data-sort-value="' .. sortkey .. '♠"><span style="border:1px solid">' .. sortkey .. '♠</span></span>' or
+
'<span style="border:1px solid;display:inline;" class="sortkey">' .. sortkey .. '♠</span>' or
'<span data-sort-value="' .. sortkey .. '♠"></span>'
+
'<span style="display:none" class="sortkey">' .. sortkey .. '♠</span>'
 
parms.join_before = sortspan
 
parms.join_before = sortspan
 
end
 
end
 
if parms.table_align then
 
if parms.table_align then
local sort
 
if sortkey then
 
sort = ' data-sort-value="' .. sortkey .. '"'
 
if parms.opt_sortable_debug then
 
parms.join_before = '<span style="border:1px solid">' .. sortkey .. '</span>'
 
end
 
else
 
sort = ''
 
end
 
 
local style = 'style="text-align:' .. parms.table_align .. ';'
 
local style = 'style="text-align:' .. parms.table_align .. ';'
 +
local sort = sortkey and ' data-sort-value="' .. sortkey .. '"' or ''
 
local joins = {}
 
local joins = {}
 
for i = 1, 2 do
 
for i = 1, 2 do
Line 2,565: Line 2,433:
 
--      is "1", or like "1.00", or is a fraction with value < 1;
 
--      is "1", or like "1.00", or is a fraction with value < 1;
 
--  (and more fields shown below, and a calculated 'absvalue' field).
 
--  (and more fields shown below, and a calculated 'absvalue' field).
 +
-- or return true, nil if no value specified;
 
-- or return false, t where t is an error message table.
 
-- or return false, t where t is an error message table.
 
-- Input info.clean uses en digits (it has been translated, if necessary).
 
-- Input info.clean uses en digits (it has been translated, if necessary).
 
-- Output show uses en or non-en digits as appropriate, or can be spelled.
 
-- Output show uses en or non-en digits as appropriate, or can be spelled.
 +
local invalue
 +
if info then
 +
invalue = info.value
 +
if in_current.builtin == 'hand' then
 +
invalue = info.altvalue
 +
end
 +
end
 +
if invalue == nil or invalue == '' then
 +
return true, nil
 +
end
 
if out_current.builtin == 'hand' then
 
if out_current.builtin == 'hand' then
 
return cvt_to_hand(parms, info, in_current, out_current)
 
return cvt_to_hand(parms, info, in_current, out_current)
 
end
 
end
local invalue = in_current.builtin == 'hand' and info.altvalue or info.value
 
 
local outvalue, extra = convert(parms, invalue, info, in_current, out_current)
 
local outvalue, extra = convert(parms, invalue, info, in_current, out_current)
 
if parms.need_table_or_sort then
 
if parms.need_table_or_sort then
Line 2,590: Line 2,468:
 
outvalue = -outvalue
 
outvalue = -outvalue
 
end
 
end
local precision, show, exponent
+
local numerator, precision, success, show, exponent
 
local denominator = out_current.frac
 
local denominator = out_current.frac
 
if denominator then
 
if denominator then
Line 2,842: Line 2,720:
 
local linked_pages  -- to record linked pages so will not link to the same page more than once
 
local linked_pages  -- to record linked pages so will not link to the same page more than once
  
local function unlink(unit_table)
+
local function make_link(link, id, link_key)
-- Forget that the given unit has previously been linked (if it has).
 
-- That is needed when processing a range of inputs or outputs when an id
 
-- for the first range value may have been evaluated, but only an id for
 
-- the last value is displayed, and that id may need to be linked.
 
linked_pages[unit_table.unitcode or unit_table] = nil
 
end
 
 
 
local function make_link(link, id, unit_table)
 
 
-- Return wikilink "[[link|id]]", possibly abbreviated as in examples:
 
-- Return wikilink "[[link|id]]", possibly abbreviated as in examples:
 
--  [[Mile|mile]]  --> [[mile]]
 
--  [[Mile|mile]]  --> [[mile]]
Line 2,857: Line 2,727:
 
-- * no link given (so caller does not need to check if a link was defined); or
 
-- * no link given (so caller does not need to check if a link was defined); or
 
-- * link has previously been used during the current convert (to avoid overlinking).
 
-- * link has previously been used during the current convert (to avoid overlinking).
local link_key
+
-- Linking with a unit uses the unit table as the link key, which fails to detect
if unit_table then
+
-- overlinking for conversions like the following (each links "mile" twice):
link_key = unit_table.unitcode or unit_table
+
--  {{convert|1|impgal/mi|USgal/mi|lk=on}}
else
+
--  {{convert|1|l/km|impgal/mi USgal/mi|lk=on}}
link_key = link
+
link_key = link_key or link  -- use key if given (the key, but not the link, may be known when need to cancel a link record)
end
 
 
if not link or link == '' or linked_pages[link_key] then
 
if not link or link == '' or linked_pages[link_key] then
 
return id
 
return id
Line 2,906: Line 2,775:
 
else
 
else
 
i = 3
 
i = 3
end
 
if i > 1 and varname == 'pl' then
 
i = i - 1
 
 
end
 
end
 
vname = split(unit_table.varname, '!')[i]
 
vname = split(unit_table.varname, '!')[i]
Line 2,939: Line 2,805:
 
local per = unit_table.per
 
local per = unit_table.per
 
if per then
 
if per then
local paren1, paren2 = '', ''  -- possible parentheses around bottom unit
 
 
local unit1 = per[1]  -- top unit_table, or nil
 
local unit1 = per[1]  -- top unit_table, or nil
 
local unit2 = per[2]  -- bottom unit_table
 
local unit2 = per[2]  -- bottom unit_table
Line 2,951: Line 2,816:
 
return symbol  -- for exceptions that have the symbol built-in
 
return symbol  -- for exceptions that have the symbol built-in
 
end
 
end
end
 
if (unit2.symbol):find('⋅', 1, true) then
 
paren1, paren2 = '(', ')'
 
 
end
 
end
 
end
 
end
Line 2,993: Line 2,855:
 
unit_table.sep = ''
 
unit_table.sep = ''
 
end
 
end
return result .. paren1 .. linked_id(parms, unit2, key_id2, want_link, '1') .. paren2
+
return result .. linked_id(parms, unit2, key_id2, want_link, '1')
 
end
 
end
 
if multiplier then
 
if multiplier then
Line 3,074: Line 2,936:
 
local abbr_org = parms.abbr_org
 
local abbr_org = parms.abbr_org
 
local adjectival = parms.opt_adjectival
 
local adjectival = parms.opt_adjectival
 +
local disp = parms.disp
 
local lk = parms.lk
 
local lk = parms.lk
 
local want_link = (lk == 'on' or lk == inout)
 
local want_link = (lk == 'on' or lk == inout)
Line 3,158: Line 3,021:
 
local inout = unit_table.inout
 
local inout = unit_table.inout
 
local abbr = parms.abbr
 
local abbr = parms.abbr
if (abbr == 'on' or abbr == inout) and not parms.number_word then
+
if abbr == 'on' or abbr == inout then
 
info.show = info.show ..
 
info.show = info.show ..
'<span style="margin-left:0.2em">×<span style="margin-left:0.1em">' ..
+
'<span style="margin-left:0.2em">×<span style="margin-left:0.1em">' ..
from_en('10') ..
+
from_en('10') ..
'</span></span><s style="display:none">^</s><sup>' ..
+
'</span></span><s style="display:none">^</s><sup>' ..
from_en(tostring(engscale.exponent)) .. '</sup>'
+
from_en(tostring(engscale.exponent)) .. '</sup>'
 
elseif number_word then
 
elseif number_word then
 
local number_id
 
local number_id
Line 3,220: Line 3,083:
 
return  preunit .. id1
 
return  preunit .. id1
 
end
 
end
if parms.opt_also_symbol and not composite and not parms.opt_flip then
+
if parms.opt_also_symbol and not composite then
 
local join1 = parms.joins[1]
 
local join1 = parms.joins[1]
 
if join1 == ' (' or join1 == ' [' then
 
if join1 == ' (' or join1 == ' [' then
Line 3,265: Line 3,128:
 
local range = parms.range
 
local range = parms.range
 
if range and not add_unit then
 
if range and not add_unit then
unlink(first_unit)
+
linked_pages[first_unit] = nil  -- so the final and only id will be linked, if wanted
 
end
 
end
 
local id = range and make_id(parms, range.n + 1, first_unit) or id1
 
local id = range and make_id(parms, range.n + 1, first_unit) or id1
Line 3,302: Line 3,165:
 
-- Processing required for each output unit.
 
-- Processing required for each output unit.
 
-- Return block of text to represent output (value/unit).
 
-- Return block of text to represent output (value/unit).
local inout = out_current.inout  -- normally 'out' but can be 'in' for order=out
 
 
local id1, want_name = make_id(parms, 1, out_current)
 
local id1, want_name = make_id(parms, 1, out_current)
 
local sep = out_current.sep  -- set by make_id
 
local sep = out_current.sep  -- set by make_id
Line 3,324: Line 3,186:
 
if range then
 
if range then
 
-- For simplicity and because more not needed, handle one range item only.
 
-- For simplicity and because more not needed, handle one range item only.
result = range_text(range[1], want_name, parms, result, prefix .. valinfo[2].show, inout)
+
result = range_text(range[1], want_name, parms, result, prefix .. valinfo[2].show, 'out')
 
end
 
end
 
return preunit .. result
 
return preunit .. result
Line 3,333: Line 3,195:
 
local range = parms.range
 
local range = parms.range
 
if range and not add_unit then
 
if range and not add_unit then
unlink(out_current)
+
linked_pages[out_current] = nil  -- so the final and only id will be linked, if wanted
 
end
 
end
 
local id = range and make_id(parms, range.n + 1, out_current) or id1
 
local id = range and make_id(parms, range.n + 1, out_current) or id1
local extra, was_hyphenated = hyphenated_maybe(parms, want_name, sep, id, inout)
+
local extra, was_hyphenated = hyphenated_maybe(parms, want_name, sep, id, 'out')
 
if was_hyphenated then
 
if was_hyphenated then
 
add_unit = false
 
add_unit = false
Line 3,357: Line 3,219:
 
result = show
 
result = show
 
else
 
else
result = range_text(range[i], want_name, parms, result, show, inout)
+
result = range_text(range[i], want_name, parms, result, show, 'out')
 
end
 
end
 
end
 
end
Line 3,374: Line 3,236:
 
-- for a single output (which is not a combination or a multiple);
 
-- for a single output (which is not a combination or a multiple);
 
-- or return false, t where t is an error message table.
 
-- or return false, t where t is an error message table.
if parms.opt_order_out and in_unit_table.unitcode == out_unit_table.unitcode then
+
out_unit_table.valinfo = collection()
out_unit_table.valinfo = in_unit_table.valinfo
+
local range = parms.range
else
+
for i = 1, (range and (range.n + 1) or 1) do
out_unit_table.valinfo = collection()
+
local success, info = cvtround(parms, in_unit_table.valinfo[i], in_unit_table, out_unit_table)
for _, v in ipairs(in_unit_table.valinfo) do
+
if not success then return false, info end
local success, info = cvtround(parms, v, in_unit_table, out_unit_table)
+
out_unit_table.valinfo:add(info)
if not success then return false, info end
 
out_unit_table.valinfo:add(info)
 
end
 
 
end
 
end
 
return true, process_one_output(parms, out_unit_table)
 
return true, process_one_output(parms, out_unit_table)
Line 3,391: Line 3,250:
 
-- for an output which is a multiple (like 'ftin');
 
-- for an output which is a multiple (like 'ftin');
 
-- or return false, t where t is an error message table.
 
-- or return false, t where t is an error message table.
local inout = out_unit_table.inout  -- normally 'out' but can be 'in' for order=out
 
 
local multiple = out_unit_table.multiple  -- table of scaling factors (will not be nil)
 
local multiple = out_unit_table.multiple  -- table of scaling factors (will not be nil)
 
local combos = out_unit_table.combination  -- table of unit tables (will not be nil)
 
local combos = out_unit_table.combination  -- table of unit tables (will not be nil)
Line 3,398: Line 3,256:
 
local disp = parms.disp
 
local disp = parms.disp
 
local want_name = (abbr_org == nil and (disp == 'or' or disp == 'slash')) or
 
local want_name = (abbr_org == nil and (disp == 'or' or disp == 'slash')) or
not (abbr == 'on' or abbr == inout or abbr == 'mos')
+
not (abbr == 'on' or abbr == 'out' or abbr == 'mos')
local want_link = (parms.lk == 'on' or parms.lk == inout)
+
local want_link = (parms.lk == 'on' or parms.lk == 'out')
 
local mid = parms.opt_flip and parms.mid or ''
 
local mid = parms.opt_flip and parms.mid or ''
 
local sep1 = '&nbsp;'
 
local sep1 = '&nbsp;'
Line 3,415: Line 3,273:
 
local tfrac, thisvalue, strforce
 
local tfrac, thisvalue, strforce
 
local out_current = combos[i]
 
local out_current = combos[i]
out_current.inout = inout
+
out_current.inout = 'out'
 
local scale = multiple[i]
 
local scale = multiple[i]
 
if i == 1 then  -- least significant unit ('in' from 'ftin')
 
if i == 1 then  -- least significant unit ('in' from 'ftin')
Line 3,497: Line 3,355:
 
end
 
end
 
local strval
 
local strval
local spell_inout = (i == #combos or outvalue == 0) and inout or ''  -- trick so the last value processed (first displayed) has uppercase, if requested
+
local inout = (i == #combos or outvalue == 0) and 'out' or ''  -- trick so the last value processed (first displayed) has uppercase, if requested
 
if strforce and outvalue == 0 then
 
if strforce and outvalue == 0 then
 
sign = ''  -- any sign is in strforce
 
sign = ''  -- any sign is in strforce
Line 3,503: Line 3,361:
 
elseif tfrac then
 
elseif tfrac then
 
local wholestr = (thisvalue > 0) and tostring(thisvalue) or nil
 
local wholestr = (thisvalue > 0) and tostring(thisvalue) or nil
strval = format_fraction(parms, spell_inout, false, wholestr, tfrac.numstr, tfrac.denstr, do_spell)
+
strval = format_fraction(parms, inout, false, wholestr, tfrac.numstr, tfrac.denstr, do_spell)
 
else
 
else
 
strval = (thisvalue == 0) and from_en('0') or with_separator(parms, format(fmt, thisvalue))
 
strval = (thisvalue == 0) and from_en('0') or with_separator(parms, format(fmt, thisvalue))
 
if do_spell then
 
if do_spell then
strval = spell_number(parms, spell_inout, strval) or strval
+
strval = spell_number(parms, inout, strval) or strval
 
end
 
end
 
end
 
end
Line 3,530: Line 3,388:
 
local success, result2 = make_result(valinfo[i+1])
 
local success, result2 = make_result(valinfo[i+1])
 
if not success then return false, result2 end
 
if not success then return false, result2 end
result = range_text(range[i], want_name, parms, result, result2, inout)
+
result = range_text(range[i], want_name, parms, result, result2, 'out')
 
end
 
end
 
end
 
end
Line 3,537: Line 3,395:
  
 
local function process(parms, in_unit_table, out_unit_table)
 
local function process(parms, in_unit_table, out_unit_table)
-- Return true, s, outunit where s = final wikitext result,
+
-- Return true, s where s = final wikitext result,
 
-- or return false, t where t is an error message table.
 
-- or return false, t where t is an error message table.
 
linked_pages = {}
 
linked_pages = {}
 
local success, bad_output
 
local success, bad_output
local bad_input_mcode = in_unit_table.bad_mcode  -- nil if input unit is a valid convert unit
+
local bad_input_mcode = in_unit_table.bad_mcode  -- false if input unit is valid
 +
local invalue1 = in_unit_table.valinfo[1].value
 
local out_unit = parms.out_unit
 
local out_unit = parms.out_unit
if out_unit == nil or out_unit == '' or type(out_unit) == 'function' then
+
if out_unit == nil or out_unit == '' then
 
if bad_input_mcode or parms.opt_input_unit_only then
 
if bad_input_mcode or parms.opt_input_unit_only then
 
bad_output = ''
 
bad_output = ''
 
else
 
else
local getdef = type(out_unit) == 'function' and out_unit or get_default
+
success, out_unit = get_default(invalue1, in_unit_table)
success, out_unit = getdef(in_unit_table.valinfo[1].value, in_unit_table)
 
 
parms.out_unit = out_unit
 
parms.out_unit = out_unit
 
if not success then
 
if not success then
Line 3,566: Line 3,424:
 
end
 
end
 
end
 
end
local lhs, rhs
 
 
local flipped = parms.opt_flip and not bad_input_mcode
 
local flipped = parms.opt_flip and not bad_input_mcode
if bad_output then
+
local parts = {}
rhs = (bad_output == '') and '' or message(parms, bad_output)
+
for part = 1, 2 do
elseif parms.opt_input_unit_only then
+
-- The LHS (parts[1]) is normally the input, but is the output if flipped.
rhs = ''
+
-- Process LHS first so it will be linked, if wanted.
else
+
-- Linking to the same item is suppressed in the RHS to avoid overlinking.
local combos  -- nil (for 'ft' or 'ftin'), or table of unit tables (for 'm ft')
+
if (part == 1 and not flipped) or (part == 2 and flipped) then
if not out_unit_table.multiple then  -- nil/false ('ft' or 'm ft'), or table of factors ('ftin')
+
parts[part] = process_input(parms, in_unit_table)
combos = out_unit_table.combination
+
elseif bad_output then
end
+
parts[part] = (bad_output == '') and '' or message(bad_output)
local frac = parms.frac  -- nil or denominator of fraction for output values
+
else
if frac then
+
local outputs = {}
-- Apply fraction to the unit (if only one), or to non-SI units (if a combination),
+
local combos  -- nil (for 'ft' or 'ftin'), or table of unit tables (for 'm ft')
-- except that if a precision is also specified, the fraction only applies to
+
if not out_unit_table.multiple then  -- nil/false ('ft' or 'm ft'), or table of factors ('ftin')
-- the hand unit; that allows the following result:
+
combos = out_unit_table.combination
-- {{convert|156|cm|in hand|1|frac=2}} → 156 centimetres (61.4 in; 15.1½ hands)
+
end
-- However, the following is handled elsewhere as a special case:
+
local frac = parms.frac  -- nil or denominator of fraction for output values
-- {{convert|156|cm|hand in|1|frac=2}} → 156 centimetres (15.1½ hands; 61½ in)
+
if frac then
if combos then
+
-- Apply fraction to the unit (if only one), or to non-SI units (if a combination),
local precision = parms.precision
+
-- except that if a precision is also specified, the fraction only applies to
for _, unit in ipairs(combos) do
+
-- the hand unit; that allows the following result:
if unit.builtin == 'hand' or (not precision and not unit.prefixes) then
+
-- {{convert|156|cm|in hand|1|frac=2}} → 156 centimetres (61.4 in; 15.1½ hands)
unit.frac = frac
+
-- However, the following is handled elsewhere as a special case:
 +
-- {{convert|156|cm|hand in|1|frac=2}} → 156 centimetres (15.1½ hands; 61½ in)
 +
if combos then
 +
local precision = parms.precision
 +
for _, unit in ipairs(combos) do
 +
if unit.builtin == 'hand' or (not precision and not unit.prefixes) then
 +
unit.frac = frac
 +
end
 
end
 
end
 +
else
 +
out_unit_table.frac = frac
 
end
 
end
else
 
out_unit_table.frac = frac
 
 
end
 
end
end
+
local out_first
local outputs = {}
+
local imax = combos and #combos or 1  -- 1 (single unit) or number of unit tables
local imax = combos and #combos or 1  -- 1 (single unit) or number of unit tables
+
for i = 1, imax do
if imax == 1 then
+
local success, item
parms.opt_order_out = nil  -- only useful with an output combination
+
local out_current = combos and combos[i] or out_unit_table
end
+
out_current.inout = 'out'
if not flipped and not parms.opt_order_out then
+
if i == 1 then
-- Process left side first so any duplicate links (from lk=on) are suppressed
+
out_first = out_current
-- on right. Example: {{convert|28|e9pc|e9ly|abbr=off|lk=on}}
+
if imax > 1 and out_current.builtin == 'hand' then
lhs = process_input(parms, in_unit_table)
+
out_current.out_next = combos[2]  -- built-in hand can influence next unit in a combination
end
+
end
for i = 1, imax do
 
local success, item
 
local out_current = combos and combos[i] or out_unit_table
 
out_current.inout = 'out'
 
if i == 1 then
 
if imax > 1 and out_current.builtin == 'hand' then
 
out_current.out_next = combos[2]  -- built-in hand can influence next unit in a combination
 
 
end
 
end
if parms.opt_order_out then
+
if out_current.multiple then
out_current.inout = 'in'
+
success, item = make_output_multiple(parms, in_unit_table, out_current)
 +
else
 +
success, item = make_output_single(parms, in_unit_table, out_current)
 
end
 
end
 +
if not success then return false, item end
 +
table.insert(outputs, item)
 
end
 
end
if out_current.multiple then
+
if parms.opt_input_unit_only then
success, item = make_output_multiple(parms, in_unit_table, out_current)
+
parts[part] = ''
 
else
 
else
success, item = make_output_single(parms, in_unit_table, out_current)
+
local sep = parms.table_joins and parms.table_joins[2] or parms.join_between
 +
parts[part] = table.concat(outputs, sep)
 
end
 
end
if not success then return false, item end
 
outputs[i] = item
 
end
 
if parms.opt_order_out then
 
lhs = outputs[1]
 
table.remove(outputs, 1)
 
end
 
local sep = parms.table_joins and parms.table_joins[2] or parms.join_between
 
rhs = table.concat(outputs, sep)
 
end
 
if flipped or not lhs then
 
local input = process_input(parms, in_unit_table)
 
if flipped then
 
lhs = rhs
 
rhs = input
 
else
 
lhs = input
 
 
end
 
end
 
end
 
end
 
if parms.join_before then
 
if parms.join_before then
lhs = parms.join_before .. lhs
+
parts[1] = parms.join_before .. parts[1]
 
end
 
end
 
local wikitext
 
local wikitext
 
if bad_input_mcode then
 
if bad_input_mcode then
 
if bad_input_mcode == '' then
 
if bad_input_mcode == '' then
wikitext = lhs
+
wikitext = parts[1]
 
else
 
else
wikitext = lhs .. message(parms, bad_input_mcode)
+
wikitext = parts[1] .. message(bad_input_mcode)
 
end
 
end
 
elseif parms.table_joins then
 
elseif parms.table_joins then
wikitext = parms.table_joins[1] .. lhs .. parms.table_joins[2] .. rhs
+
wikitext = parms.table_joins[1] .. parts[1] .. parms.table_joins[2] .. parts[2]
 
else
 
else
wikitext = lhs .. parms.joins[1] .. rhs .. parms.joins[2]
+
wikitext = parts[1] .. parms.joins[1] .. parts[2] .. parms.joins[2]
 
end
 
end
 
if parms.warnings and not bad_input_mcode then
 
if parms.warnings and not bad_input_mcode then
Line 3,665: Line 3,510:
 
local function main_convert(frame)
 
local function main_convert(frame)
 
-- Do convert, and if needed, do it again with higher default precision.
 
-- Do convert, and if needed, do it again with higher default precision.
local parms = { frame = frame }  -- will hold template arguments, after translation
 
 
set_config(frame.args)
 
set_config(frame.args)
local success, result = get_parms(parms, frame:getParent().args)
+
local result, out_unit_table
 +
local success, parms, in_unit_table = get_parms(frame:getParent().args)
 
if success then
 
if success then
if type(result) ~= 'table' then
+
for i = 1, 2 do  -- use counter so cannot get stuck repeating convert
return tostring(result)
 
end
 
local in_unit_table = result
 
local out_unit_table
 
for _ = 1, 2 do  -- use counter so cannot get stuck repeating convert
 
 
success, result, out_unit_table = process(parms, in_unit_table, out_unit_table)
 
success, result, out_unit_table = process(parms, in_unit_table, out_unit_table)
 
if success and parms.do_convert_again then
 
if success and parms.do_convert_again then
Line 3,682: Line 3,522:
 
end
 
end
 
end
 
end
 +
else
 +
result = parms
 
end
 
end
-- If input=x gives a problem, the result should be just the user input
+
if success then
-- (if x is a property like P123 it has been replaced with '').
+
return result
-- An unknown input unit would display the input and an error message
 
-- with success == true at this point.
 
-- Also, can have success == false with a message that outputs an empty string.
 
if parms.input_text then
 
if success and not parms.have_problem then
 
return result
 
end
 
local cat
 
if parms.tracking then
 
-- Add a tracking category using the given text as the category sort key.
 
-- There is currently only one type of tracking, but in principle multiple
 
-- items could be tracked, using different sort keys for convenience.
 
cat = wanted_category('tracking', parms.tracking)
 
end
 
return parms.input_text .. (cat or '')
 
 
end
 
end
return success and result or message(parms, result)
+
return message(result)
 
end
 
end
  
Line 3,742: Line 3,569:
 
opt_sortable_debug = options.sort == 'debug',
 
opt_sortable_debug = options.sort == 'debug',
 
}
 
}
 +
local utable
 
if options.si then
 
if options.si then
 
-- Make a dummy table of units (just one unit) for lookup to use.
 
-- Make a dummy table of units (just one unit) for lookup to use.
 
-- This makes lookup recognize any SI prefix in the unitcode.
 
-- This makes lookup recognize any SI prefix in the unitcode.
 
local symbol = options.si[1] or '?'
 
local symbol = options.si[1] or '?'
parms.unittable = { [symbol] = {
+
utable = { [symbol] = {
 
_name1 = symbol,
 
_name1 = symbol,
 
_name2 = symbol,
 
_name2 = symbol,
Line 3,757: Line 3,585:
 
}}
 
}}
 
end
 
end
local success, unit_table = lookup(parms, unitcode, 'no_combination')
+
local success, unit_table = lookup(parms, unitcode, 'no_combination', utable)
 
if not success then
 
if not success then
 
unit_table = setmetatable({
 
unit_table = setmetatable({
symbol = unitcode, name2 = unitcode, utype = unitcode,
+
symbol = unitcode, name2 = unitcode,
scale = 1, default = '', defkey = '', linkey = '' }, unit_mt)
+
default = "m", defkey = "m", linkey = "m",
 +
utype = "length", scale = 1 }, unit_mt)
 
end
 
end
 
local value = tonumber(options.value) or 1
 
local value = tonumber(options.value) or 1

Please note that all contributions to All About Ayrshire may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see All About Ayrshire:Copyrights for details). Do not submit copyrighted work without permission!

Cancel Editing help (opens in new window)

Template used on this page: