Module:Convert: Difference between revisions

(update from sandbox per Template talk:Convert#Module version 4)
(update from sandbox per Template talk:Convert#Module version 5)
Line 35:
local extra_module -- name of module with extra units
local extra_units -- nil or table of extra units from extra_module
local ignore_extra_units -- if true, do not require the extra module
 
local function from_en(text)
Line 289 ⟶ 290:
end
-- Escape user input so it does not break the message.
-- To avoid reference tags (like {{convert|1<refmath>xyz23</refmath>|m}}) or other tagsbreaking
-- breaking the mouseover title, any strip marker starting with char(127) is
-- replaced with escaped '<ref>...</ref>' or '...' (text not needing i18n).
local append = ''
local pos = s:find(string.char(127), 1, true)
if pos then
append = '...'
if s:find('-ref-', 1, true) then
append = '&lt;ref&gt;...&lt;/ref&gt;'
else
append = '...'
end
s = s:sub(1, pos - 1)
end
if limit and ulen(s) > limit then
s = usub(s, 1, limit)
if append == '...' then
append = '...'
end
end
s = nowiki(s) .. (append or '')
else
s = '?'
Line 778 ⟶ 773:
end
end
if not ignore_extra_units and not get_range(unitcode) then -- do not require extra if looking up a range word which cannot be a unit
-- Want the "what links here" list for the extra_module to show only cases
-- where an extra unit is used, so do not require it if invoked from {{val}}
-- or if looking up a range word which cannot be a unit.
if not extra_units then
local success, extra = pcall(function () return require(extra_module).extra_units end)
Line 1,239 ⟶ 1,237:
local function extract_fraction(parms, text, negative)
-- If text represents a fraction, return
-- value, altvalue, show, spelled, denominator
-- where
-- value is a number (value of the fraction in argument text)
-- altvalue is an alternate interpretation of any fraction for the hands
-- unit where "1412.1+3/4" means 1412 hands 1.75 inches!
-- show is a string (formatted text for display of an input value,
-- and is spelled if wanted and possible)
-- spelled is true if show was spelled
-- denominator is value of the denominator in the fraction
-- Otherwise, return nil.
-- Input uses en digits and '.' decimal mark (input has been translated).
-- Output uses digits in local language and customlocal decimal mark, if any.
------------------------------------------------------------------------
--
-- Originally this function accepted x+y/z where x, y, z were any valid
-- In the following, '(3/8)' represents the wikitext required to
-- numbers, possibly with a sign. For example '1.23e+2+1.2/2.4' = 123.5,
-- display a fraction with numerator 3 and denominator 8.
-- and '2-3/8' = 1.625. However, such usages were found to be errors or
-- In the wikitext, Unicode minus is used for a negative value.
-- misunderstandings, so since August 2014 the following restrictions apply:
-- text value, show value, show
-- x (if present) is an integer or has a single digit after decimal mark
-- if not negative if negative
-- y and z are unsigned integers
-- 3 / 8 0.375, '(3/8)' -0.375, '−(3/8)'
-- e notation is not accepted
-- 2 + 3 / 8 2.375, '2(3/8)' -1.625, '−2(−3/8)'
-- The overall 2number -can 3start /with 8'+' or '-' 1.625,(so '2(−312+3/8)4' -2.375,and '−2(+12+3/8)4'
-- and '-12-3/4' are valid).
-- 1 + 20/8 3.5 , '1/(20/8)' 1.5 , '−1/(−20/8)'
-- Any leading negative sign is removed by the caller, so only inputs
-- 1 - 20/8 -1.5., '1(−20/8)' -3.5 , '−1(20/8)'
-- like the following are accepted here (may have whitespace):
-- Wherever an integer appears above, numbers like 1.25 or 12.5e-3
-- (which may be negative) are= alsofalse accepted false true (likethere was a oldleading template'-').
-- Old template interpretstext = '1.23e+2+12/243' as '123(12+2/24)3' = 123.5! '2/3'
-- text = '1+2/3' '+1+2/3' '1-2/3'
local numstr, whole, value, altvalue
-- text = '12.3+1/2' '+12.3+1/2' '12.3-1/2'
local lhs, slash, denstr = text:match('^%s*([^/]-)%s*(/+)%s*(.-)%s*$')
-- Values like '12.3+1/2' are accepted, but are intended only for use
-- with the hands unit (not worth adding code to enforce that).
------------------------------------------------------------------------
local numstr, whole
local leading_plus, prefix, numstr, slashes, denstr =
text:match('^%s*(%+?)%s*(.-)%s*(%d+)%s*(/+)%s*(%d+)%s*$')
if not leading_plus then
-- Accept a single U+2044 fraction slash because that may be pasted.
leading_plus, prefix, numstr, denstr =
text:match('^%s*(%+?)%s*(.-)%s*(%d+)%s*⁄%s*(%d+)%s*$')
slashes = '/'
end
local numerator = tonumber(numstr)
local denominator = tonumber(denstr)
if denominatornumerator == nil thenor returndenominator == nil endor (negative and leading_plus ~= '') then
return nil
local wholestr, negfrac, rhs = lhs:match('^%s*(.-[^eE])%s*([+-])%s*(.-)%s*$')
end
if wholestr == nil or wholestr == '' then
local wholestr = nil
if prefix == '' then
wholestr = ''
whole = 0
numstr = lhs
else
-- Any prefix must be like '12+' or '12-' (whole number and fraction sign);
-- '12.3+' and '12.3-' are also accepted (single digit after decimal point)
-- because '12.3+1/2 hands' is valid (12 hands 3½ inches).
local num1, num2, frac_sign = prefix:match('^(%d+)(%.?%d?)%s*([+-])$')
if num1 == nil then return nil end
if num2 == '' then -- num2 must be '' or like '.1' but not '.' or '.12'
wholestr = num1
else
if #num2 ~= 2 then return nil end
wholestr = num1 .. num2
end
if frac_sign ~= (negative and '-' or '+') then return nil end
whole = tonumber(wholestr)
if whole == nil then return nil end
numstr = rhs
end
local value = whole + numerator / denominator
negfrac = (negfrac == '-')
if not valid_number(value) then return nil end
local numerator = tonumber(numstr)
local altvalue = whole + numerator / (denominator * 10)
if numerator == nil then return nil end
local style = #slashes -- kludge: 1 or 2 slashes can be used to select style
-- Spelling of silly inputs like "-2+3/8" or "2+3/+8" (mixed or excess signs) is not supported.
local do_spell
if negative == negfrac or wholestr == nil then
value = whole + numerator / denominator
altvalue = whole + numerator / (denominator * 10)
do_spell = parms.opt_spell_in
if do_spell then
if not (numstr:match('^%d') and denstr:match('^%d')) then -- if either has a sign
do_spell = false
end
end
else
value = whole - numerator / denominator
altvalue = whole - numerator / (denominator * 10)
numstr = change_sign(numstr)
do_spell = false
end
if not valid_number(value) then
return nil -- overflow or similar
end
numstr = use_minus(numstr)
denstr = use_minus(denstr)
local style = #slash -- kludge: 1 or 2 slashes can be used to select style
if style > 2 then style = 2 end
local wikitext = format_fraction(parms, 'in', negative, leading_plus .. wholestr, numstr, denstr, do_spellparms.opt_spell_in, style)
return value, altvalue, wikitext, do_spell, denominator
end
 
Line 1,314:
-- where info is a table with the result,
-- or return false, t where t is an error message table.
-- Input can use en digits or digits in local language. and can
-- have one reference at the end. Accepting a reference is intended
-- for use in infoboxes with a field for a value passed to convert.
-- Parameter another = true if the expected value is not the first.
-- Before processing, the input text is cleaned:
Line 1,329 ⟶ 1,331:
-- clean = cleaned text with any separators and sign removed
-- (en digits and '.' decimal mark)
-- show = text formatted for output, possibly with ref strip marker
-- (digits in local language and custom decimal mark)
-- The resulting show:
Line 1,338 ⟶ 1,340:
-- '+' (if the input text used '+'), or is '' (if no sign in input).
text = strip(text or '')
local t, reference = text:match('^(.*)(\127UNIQ%x+%-ref%-%x+%-QINU\127)$')
if reference then -- found a single strip marker at end containing "-ref-"
text = strip(t)
end
local clean = to_en(text, parms)
if clean == '' then
Line 1,374 ⟶ 1,380:
end
if value == nil then
local spelled
if not no_fraction then
value, altvalue, show, spelled, denominator = extract_fraction(parms, clean, isnegative)
end
if value == nil then
Line 1,413 ⟶ 1,418:
singular = singular,
clean = clean,
show = show .. (reference or ''),
denominator = denominator,
}
Line 1,580 ⟶ 1,585:
end
 
local function get_composite(parms, iparm, total, in_unit_table)
-- Look for a composite input unit. For example, "{{convert|1|yd|2|ft|3|in}}"
-- would result in a call to this function with
-- iparm = 3 (parms[iparm] = "2", just after the first unit)
-- totalin_unit_table = (unit table for "yd"; contains value 1 (for number of yards)
-- in_unit_table = (unit table for "yd")
-- Return true, iparm, unit where
-- iparm = index just after the composite units (7 in above example)
Line 1,594 ⟶ 1,598:
local composite_units, count = { in_unit_table }, 1
local fixups = {}
local total = in_unit_table.valinfo[1].value
local subunit = in_unit_table
while subunit.subdivs do -- subdivs is nil or a table of allowed subdivisions
Line 1,825 ⟶ 1,830:
-- If the parameter is not a value, try unpacking it as a range ("1-23" for "1 to 23").
-- However, "-1-2/3" is a negative fraction (-1⅔), so it must be extracted first.
-- Do not unpack a parameter if it is like "3-1/2" which is sometimes incorrectly
-- used instead of "3+1/2" (and which should not be interpreted as "3 to ½").
-- Unpacked items are inserted into the parms table.
local valstr = strip(parms[i]) -- trim so any '-' as a negative sign will be at start
local success, result = extract_number(parms, valstr, i > 1)
if not success and valstr and not valstr:match('%-.*/') and i < 20 then -- check i to limit abuse
for _, sep in ipairs(text_code.ranges.words) do
local start, stop = valstr:find(sep, 2, true) -- start at 2 to skip any negative sign for range '-'
Line 1,852 ⟶ 1,859:
end
valinfo:add(info)
local nextrange_item = get_range(strip(parms[i]))
local range_item = get_range(next)
if not range_item then
break
Line 1,877 ⟶ 1,883:
local function simple_get_values(parms)
-- If input is like "{{convert|valid_value|valid_unit|...}}",
-- return true, v, 3, in_unit, in_unit_table
-- (as for get_values(), but with a unit name and table for a valid unit;
-- 3 = index in parms of whatever follows valid_unit, if anything).
-- The valid_value is not negative and does not use a fraction, and
Line 1,900 ⟶ 1,905:
local success, in_unit_table = lookup(in_unit, parms.opt_sp_us, 'no_combination')
if not success then return end
in_unit_table.valinfo = { info }
return true, { info }, 3, in_unit, in_unit_table
return true, 3, in_unit, in_unit_table
end
 
Line 1,926 ⟶ 1,932:
local success, msg = translate_parms(parms, kv_pairs)
if not success then return false, msg end
ignore_extra_units = parms.opt_ignore_error
local success, valinfo, 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
local valinfo
success, valinfo, i = get_values(parms)
if not success then return false, valinfo end
Line 1,944 ⟶ 1,952:
utype = "length", scale = 1, bad_mcode = in_unit_table }, unit_mt)
end
in_unit_table.valinfo = valinfo
end
if parms.test == 'msg' then
Line 1,956 ⟶ 1,965:
end
end
in_unit_table.valinfo = valinfo
in_unit_table.inout = 'in' -- this is an input unit
if not parms.range then
local success, inext, composite_unit = get_composite(parms, i, valinfo[1].value, in_unit_table)
if not success then return false, inext end
if composite_unit then
Anonymous user