update from sandbox per Template talk:Convert#Module version 27
細 (1版) |
bsd>Johnuniq (update from sandbox per Template talk:Convert#Module version 27) |
||
136行目: | 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 | local speller -- function from that module to handle spelling (set if needed) | ||
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) | ||
147行目: | 149行目: | ||
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) | ||
255行目: | 259行目: | ||
end | end | ||
local function wanted_category( | local function wanted_category(catkey, catsort, want_warning) | ||
-- Return | -- Return message category if it is wanted in current namespace, | ||
-- otherwise return ''. | |||
local cat | |||
local title = mw.title.getCurrentTitle() | local title = mw.title.getCurrentTitle() | ||
if title then | if title then | ||
264行目: | 269行目: | ||
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] | |||
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(mcode) | local function message(parms, mcode, is_warning) | ||
-- 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 | -- mcode[2] = 'parm1' (string to replace '$1' if any in message) | ||
-- mcode[3] = 'parm2' (string to replace | -- mcode[3] = 'parm2' (string to replace '$2' if any in message) | ||
-- mcode[4] = 'parm3' (string to replace | -- mcode[4] = 'parm3' (string to replace '$3' if any in message) | ||
local msg = text_code.all_messages[mcode[1]] | local msg | ||
local | if type(mcode) == 'table' then | ||
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 = {} | ||
305行目: | 330行目: | ||
append = '...' | append = '...' | ||
end | end | ||
s = nowiki(s) .. (append or '') | s = mw.text.nowiki(s) .. (append or '') | ||
else | else | ||
s = '?' | s = '?' | ||
end | end | ||
parts[i] = s | parts['$' .. i] = s | ||
end | end | ||
local title = | local function ispreview() | ||
local text = msg[2] or 'Missing message' | -- Return true if a prominent message should be shown. | ||
local cat = wanted_category( | if parms.test == 'preview' or parms.test == 'nopreview' then | ||
-- 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 | local fmtkey = ispreview() and 'cvt_format_preview' or | ||
(want_warning and 'cvt_format2' or msg.format or 'cvt_format') | |||
local fmt = text_code.all_messages[fmtkey] or 'convert: bug' | |||
return subparm(fmt, title:gsub('"', '"'), text, cat, anchor) | |||
end | end | ||
return 'Convert internal error: unknown message' | return 'Convert internal error: unknown message' | ||
324行目: | 364行目: | ||
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 parms.warnings == nil then | |||
parms.warnings = message(parms, { key, text1, text2 }, true) | |||
end | end | ||
end | end | ||
350行目: | 389行目: | ||
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') | add_warning(parms, 1, 'cvt_no_spell', 'spell') | ||
return nil | return nil | ||
end | end | ||
372行目: | 411行目: | ||
-- If no altitude given, use default (zero altitude = sea level). | -- If no altitude given, use default (zero altitude = sea level). | ||
-- Table gives speed of sound in miles per hour at various altitudes: | -- Table gives speed of sound in miles per hour at various altitudes: | ||
-- altitude = -17,499 to | -- altitude = -17,499 to 402,499 feet | ||
-- mach_table[a + 4] = s where | -- mach_table[a + 4] = s where | ||
-- a = (altitude / 5000) rounded to nearest integer (-3 to | -- a = (altitude / 5000) rounded to nearest integer (-3 to 80) | ||
-- s = speed of sound (mph) at that altitude | -- s = speed of sound (mph) at that altitude | ||
-- LATER: Should calculate result from an interpolation between the next | -- LATER: Should calculate result from an interpolation between the next | ||
384行目: | 423行目: | ||
660.1, 660.1, 660.1, 662.0, 664.3, 666.5, 668.9, 671.1, 673.4, 675.6, -- 11 to 20 | 660.1, 660.1, 660.1, 662.0, 664.3, 666.5, 668.9, 671.1, 673.4, 675.6, -- 11 to 20 | ||
677.9, 683.7, 689.9, 696.0, 702.1, 708.1, 714.0, 719.9, 725.8, 731.6, -- 21 to 30 | 677.9, 683.7, 689.9, 696.0, 702.1, 708.1, 714.0, 719.9, 725.8, 731.6, -- 21 to 30 | ||
737.3, 737.7, 737.7, 736.2, 730.5, 724.6, 718.8, 712.9, 707.0, 701. | 737.3, 737.7, 737.7, 736.2, 730.5, 724.6, 718.8, 712.9, 707.0, 701.0, -- 31 to 40 | ||
695.0, 688.9, 682.8, 676.6, 670.4, 664.1, 657.8, 652.9, 648.3, 643.7, -- 41 to 50 | 695.0, 688.9, 682.8, 676.6, 670.4, 664.1, 657.8, 652.9, 648.3, 643.7, -- 41 to 50 | ||
639.1, 634.4, 629.6, 624.8, 620.0, 615.2, 613.2, 613.2, 613.2, 613.5, -- 51 to 60 | 639.1, 634.4, 629.6, 624.8, 620.0, 615.2, 613.2, 613.2, 613.2, 613.5, -- 51 to 60 | ||
614.4, 615.3, 616.7, 619.8, 623.4, 629.7, 635.0, 641.1, 650.6, 660.0, -- 61 to 70 | |||
672.5, 674.3, 676.1, 677.9, 679.7, 681.5, 683.3, 685.1, 686.8, 688.6, -- 71 to 80 | |||
} | } | ||
altitude = altitude or 0 | altitude = altitude or 0 | ||
396行目: | 437行目: | ||
if a < -3 then | if a < -3 then | ||
a = -3 | a = -3 | ||
elseif a > | elseif a > 80 then | ||
a = | a = 80 | ||
end | end | ||
return mach_table[a + 4] * 0.44704 -- mph converted to m/s | return mach_table[a + 4] * 0.44704 -- mph converted to m/s | ||
403行目: | 444行目: | ||
-- END: Code required only for built-in units. | -- END: Code required only for built-in units. | ||
------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ||
local function add_style(parms, class) | |||
-- Add selected template style to parms if not already present. | |||
parms.templatestyles = parms.templatestyles or {} | |||
if not parms.templatestyles[class] then | |||
parms.templatestyles[class] = parms.frame:extensionTag({ | |||
name = 'templatestyles', args = { src = text_code.titles[class] } | |||
}) | |||
end | |||
end | |||
local function get_styles(parms) | |||
-- Return string of required template styles, empty if none. | |||
if parms.templatestyles then | |||
local t = {} | |||
for _, v in pairs(parms.templatestyles) do | |||
table.insert(t, v) | |||
end | |||
return table.concat(t) | |||
end | |||
return '' | |||
end | |||
local function get_range(word) | local function get_range(word) | ||
551行目: | 614行目: | ||
-- 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. | -- Similarly, the default output is handled elsewhere, and for a symbol | ||
-- this is only called from get_default() for default_exceptions. | |||
__index = function (self, key) | __index = function (self, key) | ||
local value | local value | ||
576行目: | 640行目: | ||
} | } | ||
local function make_per(unit_table, ulookup) | local function make_per(unitcode, 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 = { utype = unit_table.utype, per = {} } | local result = { | ||
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 | ||
632行目: | 700行目: | ||
-- Wikignomes may also put two spaces or " " in combinations, so | -- Wikignomes may also put two spaces or " " in combinations, so | ||
-- replace underscore, " ", and multiple spaces with a single space. | -- replace underscore, " ", and multiple spaces with a single space. | ||
utable = utable or all_units | utable = utable or parms.unittable or all_units | ||
fails = fails or {} | fails = fails or {} | ||
depth = depth and depth + 1 or 1 | depth = depth and depth + 1 or 1 | ||
645行目: | 713行目: | ||
end | end | ||
unitcode = unitcode:gsub('_', ' '):gsub(' ', ' '):gsub(' +', ' ') | unitcode = unitcode:gsub('_', ' '):gsub(' ', ' '):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 | ||
666行目: | 739行目: | ||
end | end | ||
if t.per then | if t.per then | ||
return | return call_make_per(t) | ||
end | end | ||
local combo = t.combination -- nil or a table of unitcodes | local combo = t.combination -- nil or a table of unitcodes | ||
686行目: | 759行目: | ||
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 = '' | ||
704行目: | 778行目: | ||
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 | end | ||
738行目: | 792行目: | ||
local err_is_fatal | local err_is_fatal | ||
local combo = collection() | local combo = collection() | ||
if | if unitcode:find('+', 1, true) 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 | ||
777行目: | 831行目: | ||
end | end | ||
end | end | ||
-- Look for x/y; split on right-most slash to get scale correct (x/y/z is x/y per z). | -- Accept any unit with an engineering notation prefix like "e6cuft" | ||
local top, bottom = unitcode:match('^(.-)/([^/]+)$') | -- (million cubic feet), but not chained prefixes like "e3e6cuft", | ||
if top and not unitcode:find('e%d') then | -- 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 | |||
-- Look for x/y; split on right-most slash to get scale correct (x/y/z is x/y per z). | |||
local top, bottom = unitcode:match('^(.-)/([^/]+)$') | |||
if top and not unitcode:find('e%d') then | |||
-- If valid, create an automatic per unit for an "x/y" unit code. | -- If valid, create an automatic per unit for an "x/y" unit code. | ||
-- The unitcode must not include extraneous spaces. | -- The unitcode must not include extraneous spaces. | ||
-- 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 = | local success, result = call_make_per({ per = {top, bottom} }) | ||
if success then | if success then | ||
return true, result | return true, result | ||
911行目: | 984行目: | ||
end | end | ||
return sep .. id .. mid | return sep .. id .. mid | ||
end | end | ||
1,002行目: | 1,067行目: | ||
-- 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) | ||
1,069行目: | 1,141行目: | ||
local fracfmt = { | local fracfmt = { | ||
{ -- Like {{frac}} (fraction slash). | { -- Like {{frac}} (fraction slash). | ||
'<span class="frac" role="math">{SIGN}<span class="num">{NUM}</span>⁄<span class="den">{DEN}</span></span>', -- 1/2 | |||
'<span class="frac" role="math">{SIGN}{WHOLE}<span class="sr-only">+</span><span class="num">{NUM}</span>⁄<span class="den">{DEN}</span></span>', -- 1+2/3 | |||
'<span class="frac | style = 'frac', | ||
'<span class="frac | |||
}, | }, | ||
{ -- Like {{sfrac}} (fraction horizontal bar). | { -- Like {{sfrac}} (stacked fraction, that is, horizontal bar). | ||
'<span class="sfrac tion" role="math">{SIGN}<span class="num">{NUM}</span><span class="sr-only">/</span><span class="den">{DEN}</span></span>', -- 1//2 | |||
'<span class="sfrac" role="math">{SIGN}{WHOLE}<span class="sr-only">+</span><span class="tion"><span class="num">{NUM}</span><span class="sr-only">/</span><span class="den">{DEN}</span></span></span>', -- 1+2//3 | |||
'<span class="sfrac | style = 'sfrac', | ||
'<span class="sfrac | |||
}, | }, | ||
} | } | ||
1,092行目: | 1,162行目: | ||
wholestr = nil | wholestr = nil | ||
end | end | ||
local substitute = { | |||
SIGN = negative and MINUS or '', | |||
WHOLE = wholestr and with_separator(parms, wholestr), | |||
NUM = from_en(numstr), | |||
DEN = from_en(denstr), | |||
} | |||
wikitext = fracfmt[style][wholestr and 2 or 1]:gsub('{(%u+)}', substitute) | |||
if do_spell then | if do_spell then | ||
if negative then | if negative then | ||
1,111行目: | 1,177行目: | ||
end | end | ||
end | end | ||
local s = spell_number(parms, inout, wholestr, numstr, denstr) | |||
if s then | |||
return s | |||
end | |||
end | end | ||
add_style(parms, fracfmt[style].style) | |||
return wikitext | return wikitext | ||
end | end | ||
1,279行目: | 1,349行目: | ||
-- with the hands unit (not worth adding code to enforce that). | -- with the hands unit (not worth adding code to enforce that). | ||
------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ||
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*$') | ||
1,293行目: | 1,362行目: | ||
return nil | return nil | ||
end | end | ||
local wholestr | local whole, wholestr | ||
if prefix == '' then | if prefix == '' then | ||
wholestr = '' | wholestr = '' | ||
1,332行目: | 1,401行目: | ||
-- 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 | -- * Any sign 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: | ||
1,359行目: | 1,427行目: | ||
while #remainder > 0 do | while #remainder > 0 do | ||
local ref, spaces | local ref, spaces | ||
ref, spaces, remainder = remainder:match('^(\ | ref, spaces, remainder = remainder:match('^(\127[^\127]*UNIQ[^\127]*%-ref[^\127]*\127)(%s*)(.*)') | ||
if ref then | if ref then | ||
table.insert(refs, ref) | table.insert(refs, ref) | ||
1,393行目: | 1,461行目: | ||
local valstr | local valstr | ||
for _, prefix in ipairs({ '-', MINUS, '−' }) do | for _, prefix in ipairs({ '-', MINUS, '−' }) do | ||
-- Including '-' | -- Including '-' 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 | ||
1,460行目: | 1,530行目: | ||
end | end | ||
end | end | ||
if isnegative and (value ~= 0) then | if isnegative and (value ~= 0) then | ||
value = -value | value = -value | ||
altvalue = -altvalue | altvalue = -(altvalue or value) | ||
end | end | ||
return true, { | return true, { | ||
value = value, | value = value, | ||
altvalue = altvalue, | altvalue = altvalue or value, | ||
singular = singular, | singular = singular, | ||
clean = clean, | clean = clean, | ||
1,481行目: | 1,550行目: | ||
-- v = value of text (text is a number) | -- v = value of text (text is a number) | ||
-- f = true if value is an integer | -- f = true if value is an integer | ||
-- Input can use en digits or digits in local language, | -- Input can use en digits or digits in local language or separators, | ||
-- but | -- but no Unicode minus, and no fraction. | ||
if text then | if text then | ||
local number = tonumber(to_en(text)) | local number = tonumber(to_en(text)) | ||
if number then | if number then | ||
local | local _, fracpart = math.modf(number) | ||
return number, (fracpart == 0) | return number, (fracpart == 0) | ||
end | end | ||
1,564行目: | 1,633行目: | ||
-- 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 | -- Using '+' gives output like "5+ feet" (no space before, but space after). | ||
local function withspace(text, | local function withspace(text, wantboth) | ||
-- | -- Return text with space before and, if wantboth, after. | ||
-- However, no space is | -- However, no space is added if there is a space or ' ' or '-' | ||
-- | -- at that position ('-' is for adjectival text). | ||
local | -- There is also no space if text starts with '&' | ||
if | -- (e.g. '°' would display a degree symbol with no preceding space). | ||
return text | local char = text:sub(1, 1) | ||
if char == '&' then | |||
return text -- an html entity can be used to specify the exact display | |||
end | end | ||
if | if not (char == ' ' or char == '-' or char == '+') then | ||
text = ' ' .. text | |||
end | end | ||
if | if wantboth then | ||
char = text:sub(-1, -1) | |||
if not (char == ' ' or char == '-' or text:sub(-6, -1) == ' ') then | |||
text = text .. ' ' | |||
end | |||
end | end | ||
return text | |||
end | end | ||
local PLUS = '+ ' | |||
preunit1 = preunit1 or '' | preunit1 = preunit1 or '' | ||
local trim1 = strip(preunit1) | local trim1 = strip(preunit1) | ||
1,592行目: | 1,662行目: | ||
return nil | return nil | ||
end | end | ||
return | if trim1 == '+' then | ||
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 == '' | if trim1 == '+' then | ||
if trim2 == '' or trim2 == '+' then | |||
return PLUS, PLUS | |||
end | |||
preunit1 = PLUS | |||
end | end | ||
if trim1 | if trim2 == '' then | ||
if trim1 == '' then | |||
return nil, nil | |||
end | |||
preunit2 = preunit1 | |||
elseif trim2 == '+' then | |||
preunit2 = PLUS | |||
elseif trim2 == ' ' then -- trick to make preunit2 empty | |||
preunit2 = nil | preunit2 = nil | ||
else | |||
preunit2 = withspace(preunit2) | |||
preunit2 = withspace(preunit2 | |||
end | end | ||
return preunit1, preunit2 | return preunit1, preunit2 | ||
end | end | ||
local function range_text(range, want_name, parms, before, after, inout) | local function range_text(range, want_name, parms, before, after, inout, options) | ||
-- Return before .. rtext .. after | -- Return before .. rtext .. after | ||
-- where rtext is the text that separates two values in a range. | -- where rtext is the text that separates two values in a range. | ||
local rtext, adj_text, exception | local rtext, adj_text, exception | ||
options = options or {} | |||
if type(range) == 'table' then | if type(range) == 'table' then | ||
-- Table must specify range text for ('off' and 'on') or ('input' and 'output'), | -- Table must specify range text for ('off' and 'on') or ('input' and 'output'), | ||
1,632行目: | 1,712行目: | ||
end | end | ||
end | end | ||
if rtext == '–' and after:sub(1, #MINUS) == MINUS then | if rtext == '–' and (options.spaced or after:sub(1, #MINUS) == MINUS) then | ||
rtext = ' – ' | rtext = ' – ' | ||
end | end | ||
1,639行目: | 1,719行目: | ||
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, | -- 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) | ||
1,718行目: | 1,798行目: | ||
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] | ||
if en_name then | if en_name then | ||
local en_value | local en_value = text_code.en_option_value[en_name] | ||
if | if en_value == 'INTEGER' then -- altitude_ft, altitude_m, frac, sigfig | ||
en_value = nil | |||
if loc_value == '' then | if loc_value == '' then | ||
add_warning(parms, 2, 'cvt_empty_option', loc_name) | add_warning(parms, 2, 'cvt_empty_option', loc_name) | ||
else | else | ||
local minimum | local minimum | ||
local number, is_integer = get_number(loc_value) | local number, is_integer = get_number(loc_value) | ||
if en_name == 'frac' then | if en_name == 'sigfig' then | ||
minimum = 1 | |||
elseif en_name == 'frac' then | |||
minimum = 2 | minimum = 2 | ||
if number and number < 0 then | if number and number < 0 then | ||
1,738行目: | 1,819行目: | ||
end | end | ||
else | else | ||
minimum = | minimum = -1e6 | ||
end | end | ||
if number and is_integer and number >= minimum then | if number and is_integer and number >= minimum then | ||
en_value = number | en_value = number | ||
else | else | ||
local m | |||
if en_name == 'frac' then | |||
m = 'cvt_bad_frac' | |||
elseif en_name == 'sigfig' then | |||
m = 'cvt_bad_sigfig' | |||
else | |||
m = 'cvt_bad_altitude' | |||
end | |||
add_warning(parms, 1, m, loc_name .. '=' .. loc_value) | |||
end | end | ||
end | end | ||
elseif en_name == ' | elseif en_value == 'TEXT' then -- $, input, qid, qual, stylein, styleout, tracking | ||
en_value = loc_value ~= '' and loc_value or nil -- accept non-empty user text with no validation | |||
if not en_value and (en_name == '$' or en_name == 'qid' or en_name == 'qual') then | |||
add_warning(parms, 2, 'cvt_empty_option', loc_name) | |||
elseif en_name == '$' then | |||
-- Value should be a single character like "€" for the euro currency symbol, but anything is accepted. | |||
currency_text = (loc_value == 'euro') and '€' or loc_value | |||
elseif 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 = | en_value = en_value[loc_value] | ||
if en_value and en_value:sub(-1) == '?' then | if en_value and en_value:sub(-1) == '?' then | ||
en_value = en_value:sub(1, -2) | en_value = en_value:sub(1, -2) | ||
1,796行目: | 1,895行目: | ||
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 | ||
1,802行目: | 1,905行目: | ||
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 | ||
1,963行目: | 2,073行目: | ||
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, | -- return true, i, in_unit, in_unit_table | ||
-- | -- i = index in parms of what 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 | -- Otherwise, return nothing or return false, parm1 for caller to interpret. | ||
-- 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%. | ||
local clean = to_en(strip(parms[1] or ''), parms) | local clean = to_en(strip(parms[1] or ''), parms) | ||
if #clean > 10 or not clean:match('^[0-9.]+$') then return end | if parms.opt_ri or parms.opt_spell_in or #clean > 10 or not clean:match('^[0-9.]+$') then | ||
return false, clean | |||
end | |||
local value = tonumber(clean) | local value = tonumber(clean) | ||
if not value then return end | if not value then return end | ||
1,989行目: | 2,100行目: | ||
end | end | ||
local function get_parms(args) | local function wikidata_call(parms, operation, ...) | ||
-- If successful, return true | -- Return true, s where s is the result of a Wikidata operation, | ||
-- 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. | ||
2,001行目: | 2,133行目: | ||
-- 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 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 | ||
2,009行目: | 2,140行目: | ||
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) | ||
2,021行目: | 2,168行目: | ||
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 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({ symbol = in_unit, name2 = in_unit, | in_unit_table = setmetatable({ | ||
default = | symbol = in_unit, name2 = in_unit, utype = in_unit, | ||
scale = 1, default = '', defkey = '', linkey = '', | |||
bad_mcode = in_unit_table }, unit_mt) | |||
end | end | ||
in_unit_table.valinfo = valinfo | in_unit_table.valinfo = valinfo | ||
2,054行目: | 2,200行目: | ||
end | end | ||
if in_unit_table.builtin == 'mach' then | if in_unit_table.builtin == 'mach' then | ||
-- As with old template, a number following Mach as the input unit is the altitude | -- As with old template, a number following Mach as the input unit is the altitude. | ||
-- | -- That is deprecated: should use altitude_ft=NUMBER or altitude_m=NUMBER. | ||
-- | local success, info | ||
success = tonumber(parms[i]) -- this will often work and will give correct result for values like 2e4 without forcing output scientific notation | |||
if success then | |||
info = { value = success } | |||
else | |||
success, info = extract_number(parms, parms[i], false, true) | |||
end | |||
if success then | if success then | ||
i = i + 1 | i = i + 1 | ||
2,064行目: | 2,214行目: | ||
end | end | ||
end | end | ||
local | local word = strip(parms[i]) | ||
i = i + 1 | i = i + 1 | ||
local precision, is_bad_precision | local precision, is_bad_precision | ||
2,079行目: | 2,229行目: | ||
end | end | ||
end | end | ||
if not set_precision( | if word and not set_precision(word) then | ||
parms.out_unit = | parms.out_unit = parms.out_unit or word | ||
if set_precision(strip(parms[i])) then | if set_precision(strip(parms[i])) then | ||
i = i + 1 | i = i + 1 | ||
2,086行目: | 2,236行目: | ||
end | end | ||
if parms.opt_adj_mid then | if parms.opt_adj_mid then | ||
word = parms[i] | |||
i = i + 1 | i = i + 1 | ||
if | if word then -- mid-text words | ||
if | if word:sub(1, 1) == '-' then | ||
parms.mid = | parms.mid = word | ||
else | else | ||
parms.mid = ' ' .. | parms.mid = ' ' .. word | ||
end | end | ||
end | end | ||
2,132行目: | 2,282行目: | ||
parms.precision = precision | parms.precision = precision | ||
end | end | ||
return true | for j = i, i + 3 do | ||
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 | ||
2,168行目: | 2,325行目: | ||
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 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" | ||
2,280行目: | 2,436行目: | ||
end | end | ||
if in_builtin == 'mach' or out_builtin == 'mach' then | if in_builtin == 'mach' or out_builtin == 'mach' then | ||
local | -- Should check that only one altitude is given but am planning to remove | ||
-- in_current.altitude (which can only occur when Mach is the input unit), | |||
-- and out_current.altitude cannot occur. | |||
local alt = parms.altitude_ft or in_current.altitude | |||
if not alt and parms.altitude_m then | |||
alt = parms.altitude_m / 0.3048 -- 1 ft = 0.3048 m | |||
end | |||
local spd = speed_of_sound(alt) | |||
if in_builtin == 'mach' then | if in_builtin == 'mach' then | ||
inscale = | inscale = spd | ||
return invalue * (inscale / outscale) | |||
end | end | ||
outscale = spd | |||
local adjust = 0.1 / inscale | |||
return true, { | return true, { | ||
outvalue = invalue * (inscale / outscale), | outvalue = invalue * (inscale / outscale), | ||
2,406行目: | 2,568行目: | ||
end | end | ||
local sortspan | local sortspan | ||
if sortkey and | if sortkey and not parms.table_align then | ||
sortspan = parms.opt_sortable_debug and | sortspan = parms.opt_sortable_debug and | ||
'<span style="border:1px solid | '<span data-sort-value="' .. sortkey .. '♠"><span style="border:1px solid">' .. sortkey .. '♠</span></span>' or | ||
'<span | '<span data-sort-value="' .. 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 joins = {} | local joins = {} | ||
for i = 1, 2 do | for i = 1, 2 do | ||
2,433行目: | 2,603行目: | ||
-- 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 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. | ||
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 | ||
2,468行目: | 2,628行目: | ||
outvalue = -outvalue | outvalue = -outvalue | ||
end | end | ||
local | local precision, show, exponent | ||
local denominator = out_current.frac | local denominator = out_current.frac | ||
if denominator then | if denominator then | ||
2,489行目: | 2,649行目: | ||
show = format('%.0f', floor((outvalue / n) + 0.5) * n) | show = format('%.0f', floor((outvalue / n) + 0.5) * n) | ||
end | end | ||
elseif in_current.builtin == 'mach' then | |||
local sigfig = info.clean:gsub('^[0.]+', ''):gsub('%.', ''):len() + 1 | |||
show, exponent = make_sigfig(outvalue, sigfig) | |||
else | else | ||
local inclean = info.clean | local inclean = info.clean | ||
2,720行目: | 2,883行目: | ||
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 make_link(link, id, | local function unlink(unit_table) | ||
-- 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]] | ||
2,727行目: | 2,898行目: | ||
-- * 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 | |||
if unit_table then | |||
link_key = unit_table.unitcode or unit_table | |||
else | |||
link_key = link | |||
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 | ||
2,775行目: | 2,947行目: | ||
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] | ||
2,805行目: | 2,980行目: | ||
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 | ||
2,816行目: | 2,992行目: | ||
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 | ||
2,855行目: | 3,034行目: | ||
unit_table.sep = '' | unit_table.sep = '' | ||
end | end | ||
return result .. linked_id(parms, unit2, key_id2, want_link, '1') | return result .. paren1 .. linked_id(parms, unit2, key_id2, want_link, '1') .. paren2 | ||
end | end | ||
if multiplier then | if multiplier then | ||
2,936行目: | 3,115行目: | ||
local abbr_org = parms.abbr_org | local abbr_org = parms.abbr_org | ||
local adjectival = parms.opt_adjectival | local adjectival = parms.opt_adjectival | ||
local lk = parms.lk | local lk = parms.lk | ||
local want_link = (lk == 'on' or lk == inout) | local want_link = (lk == 'on' or lk == inout) | ||
3,021行目: | 3,199行目: | ||
local inout = unit_table.inout | local inout = unit_table.inout | ||
local abbr = parms.abbr | local abbr = parms.abbr | ||
if abbr == 'on' or abbr == inout then | if (abbr == 'on' or abbr == inout) and not parms.number_word then | ||
info.show = info.show .. | info.show = info.show .. | ||
'<span style="margin-left:0.2em">×<span style="margin-left:0.1em">' .. | |||
from_en('10') .. | |||
'</span></span><s style="display:none">^</s><sup>' .. | |||
from_en(tostring(engscale.exponent)) .. '</sup>' | |||
elseif number_word then | elseif number_word then | ||
local number_id | local number_id | ||
3,083行目: | 3,261行目: | ||
return preunit .. id1 | return preunit .. id1 | ||
end | end | ||
if parms.opt_also_symbol and not composite then | if parms.opt_also_symbol and not composite and not parms.opt_flip then | ||
local join1 = parms.joins[1] | local join1 = parms.joins[1] | ||
if join1 == ' (' or join1 == ' [' then | if join1 == ' (' or join1 == ' [' then | ||
3,097行目: | 3,275行目: | ||
-- For simplicity and because more not needed, handle one range item only. | -- For simplicity and because more not needed, handle one range item only. | ||
local prefix2 = make_id(parms, 2, first_unit) .. ' ' | local prefix2 = make_id(parms, 2, first_unit) .. ' ' | ||
result = range_text(range[1], want_name, parms, result, prefix2 .. valinfo[2].show, 'in') | result = range_text(range[1], want_name, parms, result, prefix2 .. valinfo[2].show, 'in', {spaced=true}) | ||
end | end | ||
return preunit .. result | return preunit .. result | ||
3,128行目: | 3,306行目: | ||
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) | |||
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 | ||
3,165行目: | 3,343行目: | ||
-- 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 | ||
3,186行目: | 3,365行目: | ||
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, | result = range_text(range[1], want_name, parms, result, prefix .. valinfo[2].show, inout, {spaced=true}) | ||
end | end | ||
return preunit .. result | return preunit .. result | ||
3,195行目: | 3,374行目: | ||
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) | |||
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, | local extra, was_hyphenated = hyphenated_maybe(parms, want_name, sep, id, inout) | ||
if was_hyphenated then | if was_hyphenated then | ||
add_unit = false | add_unit = false | ||
3,219行目: | 3,398行目: | ||
result = show | result = show | ||
else | else | ||
result = range_text(range[i], want_name, parms, result, show, | result = range_text(range[i], want_name, parms, result, show, inout) | ||
end | end | ||
end | end | ||
3,236行目: | 3,415行目: | ||
-- 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. | ||
out_unit_table.valinfo = collection() | if parms.opt_order_out and in_unit_table.unitcode == out_unit_table.unitcode then | ||
out_unit_table.valinfo = in_unit_table.valinfo | |||
else | |||
out_unit_table.valinfo = collection() | |||
for _, v in ipairs(in_unit_table.valinfo) do | |||
local success, info = cvtround(parms, v, in_unit_table, out_unit_table) | |||
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) | ||
3,250行目: | 3,432行目: | ||
-- 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) | ||
3,256行目: | 3,439行目: | ||
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 == | not (abbr == 'on' or abbr == inout or abbr == 'mos') | ||
local want_link = (parms.lk == 'on' or parms.lk == | local want_link = (parms.lk == 'on' or parms.lk == inout) | ||
local mid = parms.opt_flip and parms.mid or '' | local mid = parms.opt_flip and parms.mid or '' | ||
local sep1 = ' ' | local sep1 = ' ' | ||
3,273行目: | 3,456行目: | ||
local tfrac, thisvalue, strforce | local tfrac, thisvalue, strforce | ||
local out_current = combos[i] | local out_current = combos[i] | ||
out_current.inout = | out_current.inout = inout | ||
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') | ||
3,355行目: | 3,538行目: | ||
end | end | ||
local strval | local strval | ||
local | local spell_inout = (i == #combos or outvalue == 0) and inout 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 | ||
3,361行目: | 3,544行目: | ||
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, | strval = format_fraction(parms, spell_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, | strval = spell_number(parms, spell_inout, strval) or strval | ||
end | end | ||
end | end | ||
3,388行目: | 3,571行目: | ||
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, | result = range_text(range[i], want_name, parms, result, result2, inout, {spaced=true}) | ||
end | end | ||
end | end | ||
3,395行目: | 3,578行目: | ||
local function process(parms, in_unit_table, out_unit_table) | local function process(parms, in_unit_table, out_unit_table) | ||
-- Return true, s where s = final wikitext result, | -- Return true, s, outunit 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 -- | local bad_input_mcode = in_unit_table.bad_mcode -- nil if input unit is a valid convert unit | ||
local out_unit = parms.out_unit | local out_unit = parms.out_unit | ||
if out_unit == nil or out_unit == '' then | if out_unit == nil or out_unit == '' or type(out_unit) == 'function' 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 | ||
success, out_unit = | local getdef = type(out_unit) == 'function' and out_unit or get_default | ||
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 | ||
3,424行目: | 3,607行目: | ||
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 | |||
rhs = (bad_output == '') and '' or message(parms, bad_output) | |||
elseif parms.opt_input_unit_only then | |||
rhs = '' | |||
else | |||
local combos -- nil (for 'ft' or 'ftin'), or table of unit tables (for 'm ft') | |||
if not out_unit_table.multiple then -- nil/false ('ft' or 'm ft'), or table of factors ('ftin') | |||
combos = out_unit_table.combination | |||
end | |||
else | local frac = parms.frac -- nil or denominator of fraction for output values | ||
if frac then | |||
-- Apply fraction to the unit (if only one), or to non-SI units (if a combination), | |||
-- except that if a precision is also specified, the fraction only applies to | |||
-- the hand unit; that allows the following result: | |||
-- {{convert|156|cm|in hand|1|frac=2}} → 156 centimetres (61.4 in; 15.1½ hands) | |||
-- 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 | end | ||
else | |||
out_unit_table.frac = frac | |||
end | end | ||
end | |||
local outputs = {} | |||
for i = 1, imax do | local imax = combos and #combos or 1 -- 1 (single unit) or number of unit tables | ||
if imax == 1 then | |||
parms.opt_order_out = nil -- only useful with an output combination | |||
end | |||
if not flipped and not parms.opt_order_out then | |||
-- Process left side first so any duplicate links (from lk=on) are suppressed | |||
-- on right. Example: {{convert|28|e9pc|e9ly|abbr=off|lk=on}} | |||
lhs = process_input(parms, in_unit_table) | |||
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 | if parms.opt_order_out then | ||
out_current.inout = 'in' | |||
end | end | ||
end | end | ||
if | if out_current.multiple then | ||
success, item = make_output_multiple(parms, in_unit_table, out_current) | |||
else | else | ||
success, item = make_output_single(parms, in_unit_table, out_current) | |||
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 | |||
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 = | wikitext = lhs | ||
else | else | ||
wikitext = | wikitext = lhs .. message(parms, bad_input_mcode) | ||
end | end | ||
elseif parms.table_joins then | elseif parms.table_joins then | ||
wikitext = parms.table_joins[1] .. | wikitext = parms.table_joins[1] .. lhs .. parms.table_joins[2] .. rhs | ||
else | else | ||
wikitext = | wikitext = lhs .. parms.joins[1] .. rhs .. parms.joins[2] | ||
end | end | ||
if parms.warnings and not bad_input_mcode then | if parms.warnings and not bad_input_mcode then | ||
wikitext = wikitext .. parms.warnings | wikitext = wikitext .. parms.warnings | ||
end | end | ||
return true, wikitext, out_unit_table | return true, get_styles(parms) .. wikitext, out_unit_table | ||
end | end | ||
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 success, | |||
if success then | if success then | ||
for | if type(result) ~= 'table' then | ||
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 | ||
3,522行目: | 3,723行目: | ||
end | end | ||
end | end | ||
end | end | ||
if success then | -- If input=x gives a problem, the result should be just the user input | ||
return result | -- (if x is a property like P123 it has been replaced with ''). | ||
-- 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 message(result) | return success and result or message(parms, result) | ||
end | end | ||
3,569行目: | 3,783行目: | ||
opt_sortable_debug = options.sort == 'debug', | opt_sortable_debug = options.sort == 'debug', | ||
} | } | ||
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] = { | |||
_name1 = symbol, | _name1 = symbol, | ||
_name2 = symbol, | _name2 = symbol, | ||
3,585行目: | 3,798行目: | ||
}} | }} | ||
end | end | ||
local success, unit_table = lookup(parms, unitcode, 'no_combination' | local success, unit_table = lookup(parms, unitcode, 'no_combination') | ||
if not success then | if not success then | ||
unit_table = setmetatable({ | unit_table = setmetatable({ | ||
symbol = unitcode, name2 = unitcode, utype = unitcode, | |||
scale = 1, default = '', defkey = '', linkey = '' }, unit_mt) | |||
end | end | ||
local value = tonumber(options.value) or 1 | local value = tonumber(options.value) or 1 |