Module:Convert: Difference between revisions
Jump to navigation
Jump to search
Content added Content deleted
(update from sandbox per Template talk:Convert#Module version 4) |
(update from sandbox per Template talk:Convert#Module version 5) |
||
Line 35: | Line 35: | ||
local extra_module -- name of module with extra units |
local extra_module -- name of module with extra units |
||
local extra_units -- nil or table of extra units from extra_module |
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) |
local function from_en(text) |
||
Line 289: | Line 290: | ||
end |
end |
||
-- Escape user input so it does not break the message. |
-- Escape user input so it does not break the message. |
||
-- To avoid |
-- To avoid tags (like {{convert|1<math>23</math>|m}}) breaking |
||
-- |
-- the mouseover title, any strip marker starting with char(127) is |
||
-- replaced with |
-- replaced with '...' (text not needing i18n). |
||
local append |
local append |
||
local pos = s:find(string.char(127), 1, true) |
local pos = s:find(string.char(127), 1, true) |
||
if pos then |
if pos then |
||
append = '...' |
|||
if s:find('-ref-', 1, true) then |
|||
append = '<ref>...</ref>' |
|||
else |
|||
append = '...' |
|||
end |
|||
s = s:sub(1, pos - 1) |
s = s:sub(1, pos - 1) |
||
end |
end |
||
if limit and ulen(s) > limit then |
if limit and ulen(s) > limit then |
||
s = usub(s, 1, limit) |
s = usub(s, 1, limit) |
||
append = '...' |
|||
append = '...' |
|||
end |
|||
end |
end |
||
s = nowiki(s) .. append |
s = nowiki(s) .. (append or '') |
||
else |
else |
||
s = '?' |
s = '?' |
||
Line 778: | Line 773: | ||
end |
end |
||
end |
end |
||
if not get_range(unitcode) then |
if not ignore_extra_units and not get_range(unitcode) then |
||
-- 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 |
if not extra_units then |
||
local success, extra = pcall(function () return require(extra_module).extra_units end) |
local success, extra = pcall(function () return require(extra_module).extra_units end) |
||
Line 1,239: | Line 1,237: | ||
local function extract_fraction(parms, text, negative) |
local function extract_fraction(parms, text, negative) |
||
-- If text represents a fraction, return |
-- If text represents a fraction, return |
||
-- value, altvalue, show |
-- value, altvalue, show, denominator |
||
-- where |
-- where |
||
-- value is a number (value of the fraction in argument text) |
-- value is a number (value of the fraction in argument text) |
||
-- altvalue is an alternate interpretation of any fraction for the hands |
-- altvalue is an alternate interpretation of any fraction for the hands |
||
-- unit where " |
-- unit where "12.1+3/4" means 12 hands 1.75 inches |
||
-- show is a string (formatted text for display of an input value, |
-- show is a string (formatted text for display of an input value, |
||
-- and is spelled if wanted and possible) |
-- and is spelled if wanted and possible) |
||
-- spelled is true if show was spelled |
|||
-- denominator is value of the denominator in the fraction |
-- denominator is value of the denominator in the fraction |
||
-- Otherwise, return nil. |
-- Otherwise, return nil. |
||
-- Input uses en digits and '.' decimal mark (input has been translated). |
-- Input uses en digits and '.' decimal mark (input has been translated). |
||
-- Output uses digits in local language and |
-- Output uses digits in local language and local 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 number can start with '+' or '-' (so '12+3/4' and '+12+3/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 |
|||
-- |
-- negative = false false true (there was a leading '-') |
||
-- |
-- text = '2/3' '+2/3' '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) |
local denominator = tonumber(denstr) |
||
if |
if numerator == nil or denominator == nil or (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 |
|||
if prefix == '' then |
|||
wholestr = '' |
|||
whole = 0 |
whole = 0 |
||
numstr = lhs |
|||
else |
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) |
whole = tonumber(wholestr) |
||
if whole == nil then return nil end |
if whole == nil then return nil end |
||
numstr = rhs |
|||
end |
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 |
if style > 2 then style = 2 end |
||
local wikitext = format_fraction(parms, 'in', negative, wholestr, numstr, denstr, |
local wikitext = format_fraction(parms, 'in', negative, leading_plus .. wholestr, numstr, denstr, parms.opt_spell_in, style) |
||
return value, altvalue, wikitext |
return value, altvalue, wikitext, denominator |
||
end |
end |
||
Line 1,314: | Line 1,314: | ||
-- where info is a table with the result, |
-- where info is a table with the result, |
||
-- or return false, t where t is an error message table. |
-- or return false, t where t is an error message table. |
||
-- Input can use en digits or digits in local language |
-- 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. |
-- Parameter another = true if the expected value is not the first. |
||
-- Before processing, the input text is cleaned: |
-- Before processing, the input text is cleaned: |
||
Line 1,329: | Line 1,331: | ||
-- clean = cleaned text with any separators and sign removed |
-- clean = cleaned text with any separators and sign removed |
||
-- (en digits and '.' decimal mark) |
-- (en digits and '.' decimal mark) |
||
-- show = text formatted for output |
-- show = text formatted for output, possibly with ref strip marker |
||
-- (digits in local language and custom decimal mark) |
-- (digits in local language and custom decimal mark) |
||
-- The resulting show: |
-- The resulting show: |
||
Line 1,338: | Line 1,340: | ||
-- '+' (if the input text used '+'), or is '' (if no sign in input). |
-- '+' (if the input text used '+'), or is '' (if no sign in input). |
||
text = strip(text or '') |
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) |
local clean = to_en(text, parms) |
||
if clean == '' then |
if clean == '' then |
||
Line 1,374: | Line 1,380: | ||
end |
end |
||
if value == nil then |
if value == nil then |
||
local spelled |
|||
if not no_fraction then |
if not no_fraction then |
||
value, altvalue, show |
value, altvalue, show, denominator = extract_fraction(parms, clean, isnegative) |
||
end |
end |
||
if value == nil then |
if value == nil then |
||
Line 1,413: | Line 1,418: | ||
singular = singular, |
singular = singular, |
||
clean = clean, |
clean = clean, |
||
show = show, |
show = show .. (reference or ''), |
||
denominator = denominator, |
denominator = denominator, |
||
} |
} |
||
Line 1,580: | Line 1,585: | ||
end |
end |
||
local function get_composite(parms, iparm |
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) |
||
-- |
-- in_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 |
-- Return true, iparm, unit where |
||
-- iparm = index just after the composite units (7 in above example) |
-- iparm = index just after the composite units (7 in above example) |
||
Line 1,594: | Line 1,598: | ||
local composite_units, count = { in_unit_table }, 1 |
local composite_units, count = { in_unit_table }, 1 |
||
local fixups = {} |
local fixups = {} |
||
local total = in_unit_table.valinfo[1].value |
|||
local subunit = in_unit_table |
local subunit = in_unit_table |
||
while subunit.subdivs do -- subdivs is nil or a table of allowed subdivisions |
while subunit.subdivs do -- subdivs is nil or a table of allowed subdivisions |
||
Line 1,825: | Line 1,830: | ||
-- If the parameter is not a value, try unpacking it as a range ("1-23" for "1 to 23"). |
-- 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. |
-- 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. |
-- 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 valstr = strip(parms[i]) -- trim so any '-' as a negative sign will be at start |
||
local success, result = extract_number(parms, valstr, i > 1) |
local success, result = extract_number(parms, valstr, i > 1) |
||
if not success and valstr and i < 20 then -- check i to limit abuse |
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 |
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 '-' |
local start, stop = valstr:find(sep, 2, true) -- start at 2 to skip any negative sign for range '-' |
||
Line 1,852: | Line 1,859: | ||
end |
end |
||
valinfo:add(info) |
valinfo:add(info) |
||
local |
local range_item = get_range(strip(parms[i])) |
||
local range_item = get_range(next) |
|||
if not range_item then |
if not range_item then |
||
break |
break |
||
Line 1,877: | Line 1,883: | ||
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, 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). |
-- 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 |
||
Line 1,900: | Line 1,905: | ||
local success, in_unit_table = lookup(in_unit, parms.opt_sp_us, 'no_combination') |
local success, in_unit_table = lookup(in_unit, parms.opt_sp_us, 'no_combination') |
||
if not success then return end |
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 |
end |
||
Line 1,926: | Line 1,932: | ||
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 |
||
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 |
if not success then |
||
local valinfo |
|||
success, valinfo, i = get_values(parms) |
success, valinfo, i = get_values(parms) |
||
if not success then return false, valinfo end |
if not success then return false, valinfo end |
||
Line 1,944: | Line 1,952: | ||
utype = "length", scale = 1, bad_mcode = in_unit_table }, unit_mt) |
utype = "length", scale = 1, bad_mcode = in_unit_table }, unit_mt) |
||
end |
end |
||
in_unit_table.valinfo = valinfo |
|||
end |
end |
||
if parms.test == 'msg' then |
if parms.test == 'msg' then |
||
Line 1,956: | Line 1,965: | ||
end |
end |
||
end |
end |
||
in_unit_table.valinfo = valinfo |
|||
in_unit_table.inout = 'in' -- this is an input unit |
in_unit_table.inout = 'in' -- this is an input unit |
||
if not parms.range then |
if not parms.range then |
||
local success, inext, composite_unit = get_composite(parms, i |
local success, inext, composite_unit = get_composite(parms, i, in_unit_table) |
||
if not success then return false, inext end |
if not success then return false, inext end |
||
if composite_unit then |
if composite_unit then |