Sửa đổi Mô đun:Convert

Chú ý: Bạn chưa đăng nhập và địa chỉ IP của bạn sẽ hiển thị công khai khi lưu các sửa đổi.

Bạn có thể tham gia như người biên soạn chuyên nghiệp và lâu dài ở Bách khoa Toàn thư Việt Nam, bằng cách đăng ký và đăng nhập - IP của bạn sẽ không bị công khai và có thêm nhiều lợi ích khác.

Các sửa đổi có thể được lùi lại. Xin hãy kiểm tra phần so sánh bên dưới để xác nhận lại những gì bạn muốn làm, sau đó lưu thay đổi ở dưới để hoàn tất việc lùi lại sửa đổi.

Bản hiện tại Nội dung bạn nhập
Dòng 1: Dòng 1:
 +
-- Convert a value from one unit of measurement to another.
 +
-- Example: {{convert|123|lb|kg}} --> 123 pounds (56 kg)
 +
 
local MINUS = '−'  -- Unicode U+2212 MINUS SIGN (UTF-8: e2 88 92)
 
local MINUS = '−'  -- Unicode U+2212 MINUS SIGN (UTF-8: e2 88 92)
 
local abs = math.abs
 
local abs = math.abs
Dòng 13: Dòng 16:
 
local numdot  -- must be '.' or ',' or a character which works in a regex
 
local numdot  -- must be '.' or ',' or a character which works in a regex
 
local numsep, numsep_remove, numsep_remove2
 
local numsep, numsep_remove, numsep_remove2
local default_exceptions, link_exceptions, all_units
+
local data_code, all_units
 
local text_code
 
local text_code
 
local varname        -- can be a code to use variable names that depend on value
 
local varname        -- can be a code to use variable names that depend on value
Dòng 31: Dòng 34:
 
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
 +
 +
-- Some options in the invoking template can set variables used later in the module.
 +
local currency_text  -- for a user-defined currency symbol: {{convert|12|$/ha|$=€}} (euro replaces dollar)
  
 
local function from_en(text)
 
local function from_en(text)
Dòng 81: Dòng 87:
 
end
 
end
  
local add_warning  -- forward declaration
+
local add_warning, with_separator -- forward declarations
 
local function to_en_with_check(text, parms)
 
local function to_en_with_check(text, parms)
-- Version of to_en() if using numdot = ',' and numsep = '.' to check
+
-- Version of to_en() for a wiki using numdot = ',' and numsep = '.' to check
-- text (an input number as a string)
+
-- text (an input number as a string) which might have been copied from enwiki.
 
-- For example, in '1.234' the '.' could be a decimal mark or a group separator.
 
-- For example, in '1.234' the '.' could be a decimal mark or a group separator.
 +
-- From viwiki.
 
if to_en_table then
 
if to_en_table then
 
text = ustring.gsub(text, '%d', to_en_table)
 
text = ustring.gsub(text, '%d', to_en_table)
Dòng 92: Dòng 99:
 
local original = text
 
local original = text
 
text = text:gsub(',', '')  -- for example, interpret "1,234.5" as an enwiki value
 
text = text:gsub(',', '')  -- for example, interpret "1,234.5" as an enwiki value
-- tạm thời không cần cảnh báo, vì hiển thị cho người đọc vẫn ổn
+
if parms then
-- if parms then
+
add_warning(parms, 0, 'cvt_enwiki_num', original, with_separator({}, text))
-- add_warning(parms, 0, 'cvt_enwiki_num', original, with_separator({}, text))
+
end
-- end
 
 
else
 
else
 
if numsep_remove then
 
if numsep_remove then
Dòng 110: Dòng 116:
 
end
 
end
  
local function want_separator(id)
+
local function omit_separator(id)
-- Return true if id (a unit symbole or name) should be proceeded by a separator.
+
-- Return true if there should be no separator before id (a unit symbol or name).
 
-- For zhwiki, there should be no separator if id uses local characters.
 
-- For zhwiki, there should be no separator if id uses local characters.
 
-- The following kludge should be a sufficient test.
 
-- The following kludge should be a sufficient test.
if id:sub(1, 2) == '-{' then  -- for "-{...}-" content language variant
+
if omitsep then
return false
+
if id:sub(1, 2) == '-{' then  -- for "-{...}-" content language variant
end
+
return true
if id:byte() > 127 then
+
end
local first = usub(id, 1, 1)
+
if id:byte() > 127 then
if first ~= 'Å' and first ~= '°' and first ~= 'µ' then
+
local first = usub(id, 1, 1)
return false
+
if first ~= 'Å' and first ~= '°' and first ~= 'µ' then
 +
return true
 +
end
 
end
 
end
 
end
 
end
return true
+
return id:sub(1, 1) == '/'  -- no separator before units like "/ha"
 
end
 
end
  
 
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 spelling is wanted)
+
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(frame)
+
local function set_config(args)
 
-- Set configuration options from template #invoke or defaults.
 
-- Set configuration options from template #invoke or defaults.
config = frame.args
+
config = args
 
maxsigfig = config.maxsigfig or 14  -- maximum number of significant figures
 
maxsigfig = config.maxsigfig or 14  -- maximum number of significant figures
-- Scribunto sets the global variable 'mw'.
+
local data_module, text_module
-- A testing program can set the global variable 'is_test_run'.
+
local sandbox = config.sandbox and ('/' .. config.sandbox) or ''
local data_module, text_module, data_code
+
data_module = "Module:Convert/data" .. sandbox
if is_test_run then
+
text_module = "Module:Convert/text" .. sandbox
local langcode = mw.language.getContentLanguage().code
+
extra_module = "Module:Convert/extra" .. sandbox
data_module = "convertdata-" .. langcode
+
wikidata_module = "Module:Convert/wikidata" .. sandbox
text_module = "converttext-" .. langcode
+
wikidata_data_module = "Module:Convert/wikidata/data" .. sandbox
extra_module = "convertextra-" .. langcode
+
spell_module = "Module:ConvertNumeric"
spell_module = "ConvertNumeric"
 
else
 
local sandbox = config.sandbox and ('/' .. config.sandbox) or ''
 
data_module = "Module:Convert/data" .. sandbox
 
text_module = "Module:Convert/text" .. sandbox
 
extra_module = "Module:Convert/extra" .. sandbox
 
spell_module = "Module:ConvertNumeric"
 
end
 
 
data_code = mw.loadData(data_module)
 
data_code = mw.loadData(data_module)
 
text_code = mw.loadData(text_module)
 
text_code = mw.loadData(text_module)
default_exceptions = data_code.default_exceptions
 
link_exceptions = data_code.link_exceptions
 
 
all_units = data_code.all_units
 
all_units = data_code.all_units
 
local translation = text_code.translation_table
 
local translation = text_code.translation_table
Dòng 248: Dòng 248:
 
end
 
end
  
local function wanted_category(cat)
+
local function table_len(t)
-- Return cat if it is wanted in current namespace, otherwise return nil.
+
-- Return length (<100) of a numbered table to replace #t which is
-- This is so tracking categories only include pages that need correction.
+
-- documented to not work if t is accessed via mw.loadData().
 +
for i = 1, 100 do
 +
if t[i] == nil then
 +
return i - 1
 +
end
 +
end
 +
end
 +
 
 +
local function wanted_category(catkey, catsort, want_warning)
 +
-- 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
Dòng 257: Dòng 268:
 
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
return cat
+
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 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 first %s if any in message)
+
--    mcode[2] = 'parm1' (string to replace '$1' if any in message)
--    mcode[3] = 'parm2' (string to replace second %s if any in message)
+
--    mcode[3] = 'parm2' (string to replace '$2' if any in message)
--    mcode[4] = 'parm3' (string to replace third %s if any in message)
+
--    mcode[4] = 'parm3' (string to replace '$3' if any in message)
local msg = text_code.all_messages[mcode[1]]
+
local msg
local nowiki = mw.text.nowiki
+
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 = {}
Dòng 285: Dòng 316:
 
end
 
end
 
-- Escape user input so it does not break the message.
 
-- Escape user input so it does not break the message.
-- To avoid reference tags (like {{convert|1<ref>xyz</ref>|m}}) or other tags
+
-- To avoid tags (like {{convert|1<math>23</math>|m}}) breaking
-- breaking the mouseover title, any strip marker starting with char(127) is
+
-- the mouseover title, any strip marker starting with char(127) is
-- replaced with escaped '<ref>...</ref>' or '...' (text not needing i18n).
+
-- 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
if s:find('-ref-', 1, true) then
+
append = '...'
append = '&lt;ref&gt;...&lt;/ref&gt;'
 
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)
if append == '' then
+
append = '...'
append = '...'
 
end
 
 
end
 
end
s = nowiki(s) .. append
+
s = mw.text.nowiki(s) .. (append or '')
 
else
 
else
 
s = '?'
 
s = '?'
 
end
 
end
parts[i] = s
+
parts['$' .. i] = s
 
end
 
end
local title = format(msg[1] or 'Missing message', parts[1], parts[2], parts[3])
+
local function ispreview()
local text = msg[2] or 'Missing message'
+
-- Return true if a prominent message should be shown.
local cat = wanted_category(text_code.all_categories[msg[3]]) or ''
+
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 fmt = text_code.all_messages['cvt_format'] or 'convert: bug'
+
local fmtkey = ispreview() and 'cvt_format_preview' or
title = title:gsub('"', '&quot;')
+
(want_warning and 'cvt_format2' or msg.format or 'cvt_format')
return format(fmt, anchor, title, text, cat)
+
local fmt = text_code.all_messages[fmtkey] or 'convert: bug'
 +
return subparm(fmt, title:gsub('"', '&quot;'), text, cat, anchor)
 
end
 
end
 
return 'Convert internal error: unknown message'
 
return 'Convert internal error: unknown message'
Dòng 323: Dòng 363:
 
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 config.warnings then
+
if level <= (tonumber(config.warnings) or 1) then
if level <= (tonumber(config.warnings) or 1) then
+
if parms.warnings == nil then
if parms.warnings == nil then
+
parms.warnings = message(parms, { key, text1, text2 }, true)
parms.warnings = message({ key, text1, text2 })
 
end
 
 
end
 
end
 
end
 
end
Dòng 349: Dòng 388:
 
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
Dòng 547: Dòng 586:
  
 
local unit_per_mt = {
 
local unit_per_mt = {
-- Metatable to get values for a "per" unit of form "x/y".
+
-- Metatable to get values for a per unit of form "x/y".
-- 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
 +
-- this is only called from get_default() for default_exceptions.
 
__index = function (self, key)
 
__index = function (self, key)
 
local value
 
local value
Dòng 574: Dòng 615:
 
}
 
}
  
local function lookup(unitcode, opt_sp_us, what, utable, fails, depth)
+
local function make_per(unitcode, unit_table, ulookup)
-- Return true, t where t is a copy of the unit's converter table,
+
-- 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.
-- Parameter opt_sp_us is true for US spelling of SI prefixes and
+
local result = {
-- the symbol and name of the unit. If true, the result includes field
+
unitcode = unitcode,
-- sp_us = true (that field may also have been in the unit definition).
+
utype = unit_table.utype,
-- Parameter 'what' determines whether combination units are accepted:
+
per = {}
--  'no_combination'  : single unit only
+
}
--  'any_combination' : single unit or combination or output multiple
+
override_from(result, unit_table, { 'invert', 'iscomplex', 'default', 'link', 'symbol', 'symlink' })
--  'only_multiple'  : single unit or output multiple only
+
result.symbol_raw = (result.symbol or false)  -- to distinguish between a defined exception and a metatable calculation
 +
local prefix
 +
for i, v in ipairs(unit_table.per) do
 +
if i == 1 and v == '' then
 +
-- First unit symbol can be empty; that gives a nil first unit table.
 +
elseif i == 1 and text_code.currency[v] then
 +
prefix = currency_text or v
 +
else
 +
local success, t = ulookup(v)
 +
if not success then return false, t end
 +
result.per[i] = t
 +
end
 +
end
 +
local multiplier = unit_table.multiplier
 +
if not result.utype then
 +
-- Creating an automatic per unit.
 +
local unit1 = result.per[1]
 +
local utype = (unit1 and unit1.utype or prefix or '') .. '/' .. result.per[2].utype
 +
local t = data_code.per_unit_fixups[utype]
 +
if t then
 +
if type(t) == 'table' then
 +
utype = t.utype or utype
 +
result.link = result.link or t.link
 +
multiplier = multiplier or t.multiplier
 +
else
 +
utype = t
 +
end
 +
end
 +
result.utype = utype
 +
end
 +
result.scalemultiplier = multiplier or 1
 +
result.vprefix = prefix or false  -- set to non-nil to avoid calling __index
 +
return true, setmetatable(result, unit_per_mt)
 +
end
 +
 
 +
local function lookup(parms, unitcode, what, utable, fails, depth)
 +
-- Return true, t where t is a copy of the unit's converter table,
 +
-- or return false, t where t is an error message table.
 +
-- Parameter 'what' determines whether combination units are accepted:
 +
--  'no_combination'  : single unit only
 +
--  'any_combination' : single unit or combination or output multiple
 +
--  'only_multiple'  : single unit or output multiple only
 
-- Parameter unitcode is a symbol (like 'g'), with an optional SI prefix (like 'kg').
 
-- Parameter unitcode is a symbol (like 'g'), with an optional SI prefix (like 'kg').
 
-- If, for example, 'kg' is in this table, that entry is used;
 
-- If, for example, 'kg' is in this table, that entry is used;
Dòng 593: Dòng 675:
 
-- 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 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
Dòng 606: Dòng 688:
 
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
Dòng 611: Dòng 698:
 
return false, { 'cvt_should_be', t.shouldbe }
 
return false, { 'cvt_should_be', t.shouldbe }
 
end
 
end
local force_sp_us = opt_sp_us
 
 
if t.sp_us then
 
if t.sp_us then
force_sp_us = true
+
parms.opt_sp_us = true
opt_sp_us = true
 
 
end
 
end
 
local target = t.target  -- nil, or unitcode is an alias for this target
 
local target = t.target  -- nil, or unitcode is an alias for this target
 
if target then
 
if target then
local success, result = lookup(target, opt_sp_us, what, utable, fails, depth)
+
local success, result = lookup(parms, target, what, utable, fails, depth)
 
if not success then return false, result end
 
if not success then return false, result end
 
override_from(result, t, { 'customary', 'default', 'link', 'symbol', 'symlink' })
 
override_from(result, t, { 'customary', 'default', 'link', 'symbol', 'symlink' })
Dòng 628: Dòng 713:
 
return true, result
 
return true, result
 
end
 
end
local per = t.per  -- nil/false, or a numbered table for "x/y" units
+
if t.per then
if per then
+
return call_make_per(t)
local result = { utype = t.utype, per = {} }
 
result.scalemultiplier = t.multiplier or 1
 
override_from(result, t, { 'invert', 'iscomplex', 'default', 'link', 'symbol', 'symlink' })
 
result.symbol_raw = (result.symbol or false)  -- to distinguish between a defined exception and a metatable calculation
 
local cvt = result.per
 
local prefix
 
for i, v in ipairs(per) do
 
if i == 1 and text_code.currency[v] then
 
prefix = v
 
else
 
local success, t = lookup(v, opt_sp_us, 'no_combination', utable, fails, depth)
 
if not success then return false, t end
 
cvt[i] = t
 
if t.sp_us then  -- if the top or bottom unit forces sp=us, set the per unit to use the correct name/symbol
 
force_sp_us = true
 
end
 
end
 
end
 
if prefix then
 
result.vprefix = prefix
 
else
 
result.vprefix = false  -- to avoid calling __index
 
end
 
result.sp_us = force_sp_us
 
return true, setmetatable(result, unit_per_mt)
 
 
end
 
end
 
local combo = t.combination  -- nil or a table of unitcodes
 
local combo = t.combination  -- nil or a table of unitcodes
Dòng 667: Dòng 727:
 
local cvt = result.combination
 
local cvt = result.combination
 
for i, v in ipairs(combo) do
 
for i, v in ipairs(combo) do
local success, t = lookup(v, opt_sp_us, multiple and 'no_combination' or 'only_multiple', utable, fails, depth)
+
local success, t = lookup(parms, v, multiple and 'no_combination' or 'only_multiple', utable, fails, depth)
 
if not success then return false, t end
 
if not success then return false, t end
 
cvt[i] = t
 
cvt[i] = t
Dòng 674: Dòng 734:
 
end
 
end
 
local result = shallow_copy(t)
 
local result = shallow_copy(t)
result.sp_us = force_sp_us
+
result.unitcode = unitcode
 
if result.prefixes then
 
if result.prefixes then
 
result.si_name = ''
 
result.si_name = ''
Dòng 693: Dòng 753:
 
if t and t.prefixes then
 
if t and t.prefixes then
 
local result = shallow_copy(t)
 
local result = shallow_copy(t)
if opt_sp_us then
+
result.unitcode = unitcode
result.sp_us = true
+
result.si_name = parms.opt_sp_us and si.name_us or si.name
end
 
if result.sp_us and si.name_us then
 
result.si_name = si.name_us
 
else
 
result.si_name = si.name
 
end
 
 
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(baseunit, opt_sp_us, 'no_combination', utable, fails, depth)
 
if not success then return false, result end
 
if 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
Dòng 735: Dòng 767:
 
local err_is_fatal
 
local err_is_fatal
 
local combo = collection()
 
local combo = collection()
if has_plus then
+
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
Dòng 755: Dòng 787:
 
local cvt = result.combination
 
local cvt = result.combination
 
for i, v in ipairs(combo) do
 
for i, v in ipairs(combo) do
local success, t = lookup(v, opt_sp_us, 'only_multiple', utable, fails, depth)
+
local success, t = lookup(parms, v, 'only_multiple', utable, fails, depth)
 
if not success then return false, t end
 
if not success then return false, t end
 
if i == 1 then
 
if i == 1 then
Dòng 774: Dòng 806:
 
end
 
end
 
end
 
end
if not get_range(unitcode) then -- do not require extra if looking up a range word which cannot be a unit
+
-- 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
 +
-- 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.
 +
-- The unitcode must not include extraneous spaces.
 +
-- 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.
 +
local success, result = call_make_per({ per = {top, bottom} })
 +
if success then
 +
return true, result
 +
end
 +
end
 +
if not parms.opt_ignore_error 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)
Dòng 787: Dòng 853:
 
fails[unitcode] = true
 
fails[unitcode] = true
 
local other = (utable == all_units) and extra_units or all_units
 
local other = (utable == all_units) and extra_units or all_units
local success, result = lookup(unitcode, opt_sp_us, what, other, fails, depth)
+
local success, result = lookup(parms, unitcode, what, other, fails, depth)
 
if success then
 
if success then
 
return true, result
 
return true, result
Dòng 798: Dòng 864:
 
local en_code = ustring.gsub(unitcode, '%d', to_en_table)
 
local en_code = ustring.gsub(unitcode, '%d', to_en_table)
 
if en_code ~= unitcode then
 
if en_code ~= unitcode then
return lookup(en_code, opt_sp_us, what, utable, fails, depth)
+
return lookup(parms, en_code, what, utable, fails, depth)
 
end
 
end
 
end
 
end
Dòng 812: Dòng 878:
 
return true
 
return true
 
end
 
end
end
 
 
local function ntsh(num, debug)
 
-- Return html text to be used for a hidden sort key so that
 
-- the given number will be sorted in numeric order.
 
-- If debug == true, output is in a box (not hidden).
 
-- This implements Template:Ntsh (number table sorting, hidden).
 
local result, style
 
if not valid_number(num) then
 
if num < 0 then
 
result = '1000000000000000000'
 
else
 
result = '9000000000000000000'
 
end
 
elseif num == 0 then
 
result = '5000000000000000000'
 
else
 
local mag = floor(log10(abs(num)) + 1e-14)
 
local prefix
 
if num > 0 then
 
prefix = 7000 + mag
 
else
 
prefix = 2999 - mag
 
num = num + 10^(mag+1)
 
end
 
result = format('%d', prefix) .. format('%015.0f', floor(num * 10^(14-mag)))
 
end
 
if debug then
 
style = 'border:1px solid'
 
else
 
style = 'display:none'
 
end
 
return '<span style="' .. style .. '">' .. result .. '</span>'
 
 
end
 
end
  
Dòng 875: Dòng 908:
 
return name:sub(1, pos+1) .. name:sub(pos+2):gsub(' ', '-')
 
return name:sub(1, pos+1) .. name:sub(pos+2):gsub(' ', '-')
 
end
 
end
elseif name:sub(-1, -1) == ')' then
+
elseif name:sub(-1) == ')' then
 
pos = name:find('(', 1, true)
 
pos = name:find('(', 1, true)
 
if pos then
 
if pos then
Dòng 926: Dòng 959:
 
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
  
Dòng 944: Dòng 969:
 
end
 
end
  
local function digit_grouper(method, gaps)
+
local function digit_groups(parms, text, method)
-- Return a table to hold groups of digits which can be joined with
+
-- Return a numbered table of groups of digits (left-to-right, in local language).
-- suitable separators (such as commas).
 
-- Each group is separately translated to the local language because
 
-- gap separators include digits which should not be translated.
 
 
-- Parameter method is a number or nil:
 
-- Parameter method is a number or nil:
--  3 for 3-digit grouping, or
+
--  3 for 3-digit grouping (default), or
--  2 for 3-then-2 grouping.
+
--  2 for 3-then-2 grouping (only for digits before decimal mark).
-- Parameter gaps is true to use <span> gaps (numsep ignored).
+
local len_right
return {
+
local len_left = text:find('.', 1, true)
n = 0,
+
if len_left then
add = function (self, digits)
+
len_right = #text - len_left
self.n = self.n + 1
+
len_left = len_left - 1
self[self.n] = from_en(digits)
+
else
end,
+
len_left = #text
join = function (self, rhs)
+
end
-- Concatenate in reverse order.
+
local twos = method == 2 and len_left > 5
if gaps then
+
local groups = collection()
local result = ''
+
local run = len_left
for i = 1, self.n - 1 do
+
local n
result = '<span style="margin-left: 0.25em">' .. self[i] .. '</span>' .. result
+
if run < 4 or (run == 4 and parms.opt_comma5) then
 +
if parms.opt_gaps then
 +
n = run
 +
else
 +
n = #text
 +
end
 +
elseif twos then
 +
n = run % 2 == 0 and 1 or 2
 +
else
 +
n = run % 3 == 0 and 3 or run % 3
 +
end
 +
while run > 0 do
 +
groups:add(n)
 +
run = run - n
 +
n = (twos and run > 3) and 2 or 3
 +
end
 +
if len_right then
 +
if groups.n == 0 then
 +
groups:add(0)
 +
end
 +
if parms.opt_gaps and len_right > 3 then
 +
local want4 = not parms.opt_gaps3  -- true gives no gap before trailing single digit
 +
local isfirst = true
 +
run = len_right
 +
while run > 0 do
 +
n = (want4 and run == 4) and 4 or (run > 3 and 3 or run)
 +
if isfirst then
 +
isfirst = false
 +
groups[groups.n] = groups[groups.n] + 1 + n
 +
else
 +
groups:add(n)
 
end
 
end
return '<span style="white-space: nowrap">' .. self[self.n] .. result .. from_en(rhs) .. '</span>'
+
run = run - n
else
 
local result = self[1]
 
for i = 2, self.n do
 
result = self[i] .. numsep .. result
 
end
 
return result .. from_en(rhs)
 
 
end
 
end
end,
+
else
step = 3,
+
groups[groups.n] = groups[groups.n] + 1 + len_right
next_position = function (self, previous)
+
end
-- Return position of digit just before next group.
+
end
-- Digits are grouped from right-to-left (least significant first).
+
local pos = 1
local result = previous - self.step
+
for i, length in ipairs(groups) do
if method == 2 then
+
groups[i] = from_en(text:sub(pos, pos + length - 1))
self.step = 2
+
pos = pos + length
end
+
end
return (result < 0) and 0 or result
+
return groups
end,
 
}
 
 
end
 
end
  
function with_separator(parms, text)
+
function with_separator(parms, text) -- for forward declaration above
-- Input text is a number in en digits and optional '.' decimal mark.
+
-- Input text is a number in en digits with optional '.' decimal mark.
-- Return an equivalent of text, formatted for display:
+
-- Return an equivalent, formatted for display:
 
--  with a custom decimal mark instead of '.', if wanted
 
--  with a custom decimal mark instead of '.', if wanted
 
--  with thousand separators inserted, if wanted
 
--  with thousand separators inserted, if wanted
 
--  digits in local language
 
--  digits in local language
-- The given text is like '123' or '12345.6789' or '1.23e45'
+
-- The given text is like '123' or '123.' or '12345.6789'.
-- (e notation can only occur when processing an input value).
 
 
-- The text has no sign (caller inserts that later, if necessary).
 
-- The text has no sign (caller inserts that later, if necessary).
-- Separator is inserted only in the integer part of the significand
+
-- When using gaps, they are inserted before and after the decimal mark.
-- (not after the decimal mark, and not after 'e' or 'E').
+
-- Separators are inserted only before the decimal mark.
if parms.opt_nocomma or numsep == '' then
+
-- A trailing dot (as in '123.') is removed because their use appears to
return from_en(text)
+
-- be accidental, and such a number should be shown as '123' or '123.0'.
end
+
-- It is useful for convert to suppress the dot so, for example, '4000.'
local last = text:match('()[.eE]')  -- () returns position
+
-- is a simple way of indicating that all the digits are significant.
if last == nil then
+
if text:sub(-1) == '.' then
last = #text
+
text = text:sub(1, -2)
else
 
last = last - 1 -- index of last character before dot/e/E
 
 
end
 
end
if last < 4 or (last == 4 and parms.opt_comma5) then
+
if #text < 4 or parms.opt_nocomma or numsep == '' then
 
return from_en(text)
 
return from_en(text)
 
end
 
end
local groups = digit_grouper(group_method, parms.opt_gaps)
+
local groups = digit_groups(parms, text, group_method)
local i = last
+
if parms.opt_gaps then
while i > 0 do
+
if groups.n <= 1 then
local position = groups:next_position(i)
+
return groups[1] or ''
groups:add(text:sub(position+1, i))
+
end
i = position
+
local nowrap = '<span style="white-space: nowrap">'
 +
local gap = '<span style="margin-left: 0.25em">'
 +
local close = '</span>'
 +
return nowrap .. groups[1] .. gap .. table.concat(groups, close .. gap, 2, groups.n) .. close .. close
 
end
 
end
return groups:join(text:sub(last+1))
+
return table.concat(groups, numsep)
 
end
 
end
  
-- Input values can use values like 1.23e12, but are never displayed
+
-- An input value like 1.23e12 is displayed using scientific notation (1.23×10¹²).
-- using scientific notation like 1.23×10¹².
+
-- That also makes the output use scientific notation, except for small values.
-- Very small or very large output values use scientific notation.
+
-- In addition, very small or very large output values use scientific notation.
-- Use format(fmtpower, significand, '10', exponent) where each arg is a string.
+
-- Use format(fmtpower, significand, '10', exponent) where each argument is a string.
 
local fmtpower = '%s<span style="margin:0 .15em 0 .25em">×</span>%s<sup>%s</sup>'
 
local fmtpower = '%s<span style="margin:0 .15em 0 .25em">×</span>%s<sup>%s</sup>'
  
local function with_exponent(show, exponent)
+
local function with_exponent(parms, show, exponent)
 
-- Return wikitext to display the implied value in scientific notation.
 
-- Return wikitext to display the implied value in scientific notation.
 
-- Input uses en digits; output uses digits in local language.
 
-- Input uses en digits; output uses digits in local language.
if #show > 1 then
+
return format(fmtpower, with_separator(parms, show), from_en('10'), use_minus(from_en(tostring(exponent))))
show = show:sub(1, 1) .. '.' .. show:sub(2)
 
end
 
return format(fmtpower, from_en(show), from_en('10'), use_minus(from_en(tostring(exponent))))
 
 
end
 
end
  
Dòng 1.154: Dòng 1.195:
 
-- * Uses a custom decimal mark, if wanted.
 
-- * Uses a custom decimal mark, if wanted.
 
-- * Has digits grouped where necessary, if wanted.
 
-- * Has digits grouped where necessary, if wanted.
-- * Uses scientific notation for very small or large values
+
-- * Uses scientific notation if requested, or for very small or large values
--  (which forces output to not be spelled).
+
--  (which forces result to not be spelled).
 
-- * Has no more than maxsigfig significant digits
 
-- * Has no more than maxsigfig significant digits
 
--  (same as old template and {{#expr}}).
 
--  (same as old template and {{#expr}}).
 +
local xhi, xlo  -- these control when scientific notation (exponent) is used
 +
if parms.opt_scientific then
 +
xhi, xlo = 4, 2  -- default for output if input uses e-notation
 +
elseif parms.opt_scientific_always then
 +
xhi, xlo = 0, 0  -- always use scientific notation (experimental)
 +
else
 +
xhi, xlo = 10, 4  -- default
 +
end
 
local sign = isnegative and MINUS or ''
 
local sign = isnegative and MINUS or ''
 
local maxlen = maxsigfig
 
local maxlen = maxsigfig
Dòng 1.168: Dòng 1.217:
 
if not tfrac and not exponent then
 
if not tfrac and not exponent then
 
local integer, dot, decimals = show:match('^(%d*)(%.?)(.*)')
 
local integer, dot, decimals = show:match('^(%d*)(%.?)(.*)')
if #integer >= 10 then
+
if integer == '0' or integer == '' then
show = integer .. decimals
 
exponent = #integer
 
elseif integer == '0' or integer == '' then
 
 
local zeros, figs = decimals:match('^(0*)([^0]?.*)')
 
local zeros, figs = decimals:match('^(0*)([^0]?.*)')
 
if #figs == 0 then
 
if #figs == 0 then
Dòng 1.177: Dòng 1.223:
 
show = '0.' .. zeros:sub(1, maxlen)
 
show = '0.' .. zeros:sub(1, maxlen)
 
end
 
end
elseif #zeros >= 4 then
+
elseif #zeros >= xlo then
 
show = figs
 
show = figs
 
exponent = -#zeros
 
exponent = -#zeros
Dòng 1.183: Dòng 1.229:
 
show = '0.' .. zeros .. figs:sub(1, maxlen)
 
show = '0.' .. zeros .. figs:sub(1, maxlen)
 
end
 
end
 +
elseif #integer >= xhi then
 +
show = integer .. decimals
 +
exponent = #integer
 
else
 
else
 
maxlen = maxlen + #dot
 
maxlen = maxlen + #dot
Dòng 1.191: Dòng 1.240:
 
end
 
end
 
if exponent then
 
if exponent then
 +
local function zeros(n)
 +
return string.rep('0', n)
 +
end
 
if #show > maxlen then
 
if #show > maxlen then
 
show = show:sub(1, maxlen)
 
show = show:sub(1, maxlen)
 
end
 
end
if exponent > 10 or exponent <= -4 or (exponent == 10 and show ~= '1000000000') then
+
if exponent > xhi or exponent <= -xlo or (exponent == xhi and show ~= '1' .. zeros(xhi - 1)) then
-- Rounded value satisfies: value >= 1e9 or value < 1e-4 (1e9 = 0.1e10).
+
-- When xhi, xlo = 10, 4 (the default), scientific notation is used if the
 +
-- rounded value satisfies: value >= 1e9 or value < 1e-4 (1e9 = 0.1e10),
 +
-- except if show is '1000000000' (1e9), for example:
 +
-- {{convert|1000000000|m|m|sigfig=10}} → 1,000,000,000 metres (1,000,000,000 m)
 +
local significand
 +
if #show > 1 then
 +
significand = show:sub(1, 1) .. '.' .. show:sub(2)
 +
else
 +
significand = show
 +
end
 
return {
 
return {
 
clean = '.' .. show,
 
clean = '.' .. show,
 
exponent = exponent,
 
exponent = exponent,
 
sign = sign,
 
sign = sign,
show = sign .. with_exponent(show, exponent-1),
+
show = sign .. with_exponent(parms, significand, exponent-1),
 
is_scientific = true,
 
is_scientific = true,
 
}
 
}
 
end
 
end
 
if exponent >= #show then
 
if exponent >= #show then
show = show .. string.rep('0', exponent - #show)  -- result has no dot
+
show = show .. zeros(exponent - #show)  -- result has no dot
 
elseif exponent <= 0 then
 
elseif exponent <= 0 then
show = '0.' .. string.rep('0', -exponent) .. show
+
show = '0.' .. zeros(-exponent) .. show
 
else
 
else
 
show = show:sub(1, exponent) .. '.' .. show:sub(exponent+1)
 
show = show:sub(1, exponent) .. '.' .. show:sub(exponent+1)
Dòng 1.235: Dòng 1.296:
 
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, spelled, denominator
+
--  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 "14.1+3/4" means 14 hands 1.75 inches!
+
--        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 custom decimal mark, if any.
+
-- Output uses digits in local language and local decimal mark, if any.
--
+
------------------------------------------------------------------------
-- In the following, '(3/8)' represents the wikitext required to
+
-- Originally this function accepted x+y/z where x, y, z were any valid
-- display a fraction with numerator 3 and denominator 8.
+
-- numbers, possibly with a sign. For example '1.23e+2+1.2/2.4' = 123.5,
-- In the wikitext, Unicode minus is used for a negative value.
+
-- and '2-3/8' = 1.625. However, such usages were found to be errors or
--  text          value, show            value, show
+
-- misunderstandings, so since August 2014 the following restrictions apply:
--                 if not negative      if negative
+
--  x (if present) is an integer or has a single digit after decimal mark
--   3 / 8        0.375, '(3/8)'       -0.375, '−(3/8)'
+
--  y and z are unsigned integers
--   2 + 3 / 8    2.375, '2(3/8)'       -1.625, '−2(−3/8)'
+
--   e-notation is not accepted
--  2 - 3 / 8    1.625, '2(−3/8)'     -2.375, '−2(3/8)'
+
-- The overall number can start with '+' or '-' (so '12+3/4' and '+12+3/4'
--  1 + 20/8      3.5  , '1/(20/8)'    1.5  , '−1/(−20/8)'
+
-- and '-12-3/4' are valid).
--  1 - 20/8      -1.5., '1(−20/8)'     -3.5 , '−1(20/8)'
+
-- Any leading negative sign is removed by the caller, so only inputs
-- Wherever an integer appears above, numbers like 1.25 or 12.5e-3
+
-- like the following are accepted here (may have whitespace):
-- (which may be negative) are also accepted (like old template).
+
--  negative = false      false        true (there was a leading '-')
-- Old template interprets '1.23e+2+12/24' as '123(12/24)' = 123.5!
+
--  text    = '2/3'       '+2/3'      '2/3'
local numstr, whole, value, altvalue
+
--  text    = '1+2/3'     '+1+2/3'    '1-2/3'
local lhs, slash, denstr = text:match('^%s*([^/]-)%s*(/+)%s*(.-)%s*$')
+
--  text    = '12.3+1/2'  '+12.3+1/2'  '12.3-1/2'
 +
-- 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 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 denominator == nil then return nil end
+
if numerator == nil or denominator == nil or (negative and leading_plus ~= '') then
local wholestr, negfrac, rhs = lhs:match('^%s*(.-[^eE])%s*([+-])%s*(.-)%s*$')
+
return nil
if wholestr == nil or wholestr == '' then
+
end
wholestr = nil
+
local whole, 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
negfrac = (negfrac == '-')
+
local value = whole + numerator / denominator
local numerator = tonumber(numstr)
+
if not valid_number(value) then return nil end
if numerator == nil then return nil end
+
local altvalue = whole + numerator / (denominator * 10)
-- Spelling of silly inputs like "-2+3/8" or "2+3/+8" (mixed or excess signs) is not supported.
+
local style = #slashes -- kludge: 1 or 2 slashes can be used to select style
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, do_spell, style)
+
local wikitext = format_fraction(parms, 'in', negative, leading_plus .. wholestr, numstr, denstr, parms.opt_spell_in, style)
return value, altvalue, wikitext, do_spell, denominator
+
return value, altvalue, wikitext, denominator
 
end
 
end
  
Dòng 1.310: Dòng 1.372:
 
-- 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 references at the end. Accepting references 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:
 
-- * Any thousand separators (valid or not) are removed.
 
-- * Any thousand separators (valid or not) are removed.
-- * Any sign (and optional following whitespace) is replaced with
+
-- * Any sign is replaced with '-' (if negative) or '' (otherwise).
--  '-' (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:
Dòng 1.321: Dòng 1.384:
 
--  altvalue = a valid number, usually same as value but different
 
--  altvalue = a valid number, usually same as value but different
 
--              if fraction used (for hands unit)
 
--              if fraction used (for hands unit)
--  singular = true if value is 1 (to use singular form of units)
+
--  singular = true if value is 1 or -1 (to use singular form of units)
--            = false if value is -1 (like old template)
 
 
--  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 markers
 
--              (digits in local language and custom decimal mark)
 
--              (digits in local language and custom decimal mark)
 
-- The resulting show:
 
-- The resulting show:
Dòng 1.334: Dòng 1.396:
 
--  '+' (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 reference
 +
local pos = text:find('\127', 1, true)
 +
if pos then
 +
local before = text:sub(1, pos - 1)
 +
local remainder = text:sub(pos)
 +
local refs = {}
 +
while #remainder > 0 do
 +
local ref, spaces
 +
ref, spaces, remainder = remainder:match('^(\127[^\127]*UNIQ[^\127]*%-ref[^\127]*\127)(%s*)(.*)')
 +
if ref then
 +
table.insert(refs, ref)
 +
else
 +
refs = {}
 +
break
 +
end
 +
end
 +
if #refs > 0 then
 +
text = strip(before)
 +
reference = table.concat(refs)
 +
end
 +
end
 
local clean = to_en(text, parms)
 
local clean = to_en(text, parms)
 
if clean == '' then
 
if clean == '' then
Dòng 1.355: Dòng 1.438:
 
local valstr
 
local valstr
 
for _, prefix in ipairs({ '-', MINUS, '&minus;' }) do
 
for _, prefix in ipairs({ '-', MINUS, '&minus;' }) do
-- Including '-' means inputs like '- 2' (with space) are accepted as -2.
+
-- Including '-' sets isnegative in case input is a fraction like '-2-3/4'.
-- 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
Dòng 1.370: Dòng 1.455:
 
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, spelled, denominator = extract_fraction(parms, clean, isnegative)
+
value, altvalue, show, denominator = extract_fraction(parms, clean, isnegative)
 
end
 
end
 
if value == nil then
 
if value == nil then
Dòng 1.386: Dòng 1.470:
 
end
 
end
 
if show == nil then
 
if show == nil then
singular = (value == 1 and not isnegative)
+
-- clean is a non-empty string with no spaces, and does not represent a fraction,
local precision = parms.input_precision
+
-- and value = tonumber(clean) is a number >= 0.
if precision and 0 <= precision and precision <= 8 then
+
-- If the input uses e-notation, show will be displayed using a power of ten, but
local fmt = '%.' .. format('%d', precision) .. 'f'
+
-- we use the number as given so it might not be normalized scientific notation.
show = fmt:format(value + 2e-14)  -- fudge for some common cases of bad rounding
+
-- The input value is spelled if specified so any e-notation is ignored;
 +
-- that allows input like 2e6 to be spelled as "two million" which works
 +
-- because the spell module converts '2e6' to '2000000' before spelling.
 +
local function rounded(value, default, exponent)
 +
local precision = parms.opt_ri
 +
if precision then
 +
local fmt = '%.' .. format('%d', precision) .. 'f'
 +
local result = fmt:format(tonumber(value) + 2e-14)  -- fudge for some common cases of bad rounding
 +
if not exponent then
 +
singular = (tonumber(result) == 1)
 +
end
 +
return result
 +
end
 +
return default
 +
end
 +
singular = (value == 1)
 +
local scientific
 +
local significand, exponent = clean:match('^([%d.]+)[Ee]([+%-]?%d+)')
 +
if significand then
 +
show = with_exponent(parms, rounded(significand, significand, exponent), exponent)
 +
scientific = true
 
else
 
else
show = clean
+
show = with_separator(parms, rounded(value, clean))
 
end
 
end
show = propersign .. with_separator(parms, show)
+
show = propersign .. show
 
if parms.opt_spell_in then
 
if parms.opt_spell_in then
show = spell_number(parms, 'in', propersign .. clean) or show
+
show = spell_number(parms, 'in', propersign .. rounded(value, clean)) or show
 +
scientific = false
 +
end
 +
if scientific then
 +
parms.opt_scientific = true
 
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
+
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,
show = show,
+
show = show .. (reference or ''),
 
denominator = denominator,
 
denominator = denominator,
 
}
 
}
Dòng 1.425: Dòng 1.532:
 
local number = tonumber(to_en(text))
 
local number = tonumber(to_en(text))
 
if number then
 
if number then
local integer, fracpart = math.modf(number)
+
local _, fracpart = math.modf(number)
 
return number, (fracpart == 0)
 
return number, (fracpart == 0)
 
end
 
end
Dòng 1.503: Dòng 1.610:
 
--    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 preceding space).
+
-- Using '+' gives output like "5+ feet" (no space before, but space after).
local function withspace(text, i)
+
local function withspace(text, wantboth)
-- Insert space at beginning if i == 1, or at end if i == -1.
+
-- Return text with space before and, if wantboth, after.
-- However, no space is inserted if there is a space or '&nbsp;'
+
-- However, no space is added if there is a space or '&nbsp;' or '-'
-- or '-' at that position ('-' is for adjectival text).
+
-- at that position ('-' is for adjectival text).
local current = text:sub(i, i)
+
-- There is also no space if text starts with '&'
if current == ' ' or current == '-' then
+
-- (e.g. '&deg;' 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 i == 1 then
+
if not (char == ' ' or char == '-' or char == '+') then
current = text:sub(1, 6)
+
text = ' ' .. text
else
 
current = text:sub(-6, -1)
 
 
end
 
end
if current == '&nbsp;' then
+
if wantboth then
return text
+
char = text:sub(-1, -1)
 +
if not (char == ' ' or char == '-' or text:sub(-6, -1) == '&nbsp;') then
 +
text = text .. ' '
 +
end
 
end
 
end
if i == 1 then
+
return text
return ' ' .. text
 
end
 
return text .. ' '
 
 
end
 
end
 +
local PLUS = '+ '
 
preunit1 = preunit1 or ''
 
preunit1 = preunit1 or ''
 
local trim1 = strip(preunit1)
 
local trim1 = strip(preunit1)
Dòng 1.531: Dòng 1.639:
 
return nil
 
return nil
 
end
 
end
return withspace(withspace(preunit1, 1), -1)
+
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 == '' and trim2 == '' then
+
if trim1 == '+' then
return nil, nil
+
if trim2 == '' or trim2 == '+' then
 +
return PLUS, PLUS
 +
end
 +
preunit1 = PLUS
 
end
 
end
if trim1 ~= '+' then
+
if trim2 == '' then
preunit1 = withspace(preunit1, 1)
+
if trim1 == '' then
end
+
return nil, nil
if trim2 == '&#32;' then  -- trick to make preunit2 empty
+
end
 +
preunit2 = preunit1
 +
elseif trim2 == '+' then
 +
preunit2 = PLUS
 +
elseif trim2 == '&#32;' then  -- trick to make preunit2 empty
 
preunit2 = nil
 
preunit2 = nil
elseif trim2 == '' then
+
else
preunit2 = preunit1
+
preunit2 = withspace(preunit2)
elseif trim2 ~= '+' then
 
preunit2 = withspace(preunit2, 1)
 
 
end
 
end
 
return preunit1, preunit2
 
return preunit1, preunit2
 
end
 
end
  
local function range_text(range, want_name, parms, before, after)
+
local function range_text(range, want_name, parms, before, after, inout)
 
-- 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
 
if type(range) == 'table' then
 
if type(range) == 'table' then
-- Table must specify range text for abbr=off and for abbr=on,
+
-- Table must specify range text for ('off' and 'on') or ('input' and 'output'),
 
-- and may specify range text for 'adj=on',
 
-- and may specify range text for 'adj=on',
 
-- and may specify exception = true.
 
-- and may specify exception = true.
rtext = range[want_name and 'off' or 'on']
+
rtext = range[want_name and 'off' or 'on'] or
 +
range[((inout == 'in') == (parms.opt_flip == true)) and 'output' or 'input']
 
adj_text = range['adj']
 
adj_text = range['adj']
 
exception = range['exception']
 
exception = range['exception']
Dòng 1.576: Dòng 1.694:
 
end
 
end
  
local function get_composite(parms, iparm, total, 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)
--  total = 1 (number of yards)
+
--  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)
Dòng 1.590: Dòng 1.707:
 
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
 
local subcode = strip(parms[iparm+1])
 
local subcode = strip(parms[iparm+1])
local subdiv = subunit.subdivs[subcode]
+
local subdiv = subunit.subdivs[subcode] or subunit.subdivs[(all_units[subcode] or {}).target]
 
if not subdiv then
 
if not subdiv then
 
break
 
break
 
end
 
end
 
local success
 
local success
success, subunit = lookup(subcode, parms.opt_sp_us, 'no_combination')
+
success, subunit = lookup(parms, subcode, 'no_combination')
 
if not success then return false, subunit end  -- should never occur
 
if not success then return false, subunit end  -- should never occur
 
success, subinfo = extract_number(parms, parms[iparm])
 
success, subinfo = extract_number(parms, parms[iparm])
Dòng 1.626: Dòng 1.744:
 
composite_units[i].fixed_name = name
 
composite_units[i].fixed_name = name
 
else
 
else
local success, alternate = lookup(unit, parms.opt_sp_us, 'no_combination')
+
local success, alternate = lookup(parms, unit, 'no_combination')
 
if not success then return false, alternate end  -- should never occur
 
if not success then return false, alternate end  -- should never occur
 
alternate.inout = 'in'
 
alternate.inout = 'in'
Dòng 1.647: Dòng 1.765:
 
-- Also, checks are performed which may display warnings, if enabled.
 
-- Also, checks are performed which may display warnings, if enabled.
 
-- 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
 +
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
Dòng 1.655: Dòng 1.782:
 
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
if en_name == 'frac' or en_name == 'sigfig' then
+
if en_name == '$' or en_name == 'frac' or en_name == 'sigfig' then
 
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)
 +
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
 
else
 
else
 
local minimum
 
local minimum
Dòng 1.677: Dòng 1.808:
 
en_value = number
 
en_value = number
 
else
 
else
add_warning(parms, 1, (en_name == 'frac' and 'cvt_bad_frac' or 'cvt_bad_sigfig'), loc_value)
+
add_warning(parms, 1, (en_name == 'frac' and 'cvt_bad_frac' or 'cvt_bad_sigfig'), loc_name .. '=' .. loc_value)
 
end
 
end
 +
end
 +
elseif accept_any_text[en_name] then
 +
en_value = loc_value ~= '' and loc_value or nil  -- accept non-empty 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
 
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]
 +
if en_value and en_value:sub(-1) == '?' then
 +
en_value = en_value:sub(1, -2)
 +
add_warning(parms, -1, 'cvt_deprecated', loc_name .. '=' .. loc_value)
 +
end
 
if en_value == nil then
 
if en_value == nil then
 
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
-- loc_value can no longer be nil here (at one time, that could occur
+
add_warning(parms, 1, 'cvt_unknown_option', loc_name .. '=' .. loc_value)
-- with aliases like |sing=off|adj=on), but am retaining safety check.
 
local text = loc_value and (loc_name .. '=' .. loc_value) or loc_name
 
add_warning(parms, 1, 'cvt_unknown_option', text)
 
 
end
 
end
 
elseif en_value == '' then
 
elseif en_value == '' then
Dòng 1.695: Dòng 1.834:
 
elseif type(en_value) == 'string' and en_value:sub(1, 4) == 'opt_' then
 
elseif type(en_value) == 'string' and en_value:sub(1, 4) == 'opt_' then
 
for _, v in ipairs(split(en_value, ',')) do
 
for _, v in ipairs(split(en_value, ',')) do
parms[v] = true
+
local lhs, rhs = v:match('^(.-)=(.+)$')
 +
if rhs then
 +
parms[lhs] = tonumber(rhs) or rhs
 +
else
 +
parms[v] = true
 +
end
 
end
 
end
 
en_value = nil
 
en_value = nil
Dòng 1.705: Dòng 1.849:
 
end
 
end
 
end
 
end
if parms.adj then
+
local abbr_entered = parms.abbr
if parms.adj:sub(1, 2) == 'ri' then
 
-- It is known that adj is 'riN' where N is a single digit, so precision is valid.
 
-- Only a single en digit is accepted.
 
parms.input_precision = tonumber(parms.adj:sub(-1))
 
parms.adj = nil
 
end
 
end
 
 
local cfg_abbr = config.abbr
 
local cfg_abbr = config.abbr
 
if cfg_abbr then
 
if cfg_abbr then
Dòng 1.729: Dòng 1.866:
 
end
 
end
 
if parms.abbr then
 
if parms.abbr then
parms.abbr_org = parms.abbr  -- original abbr that was set, before any flip
+
if parms.abbr == 'unit' then
 +
parms.abbr = 'on'
 +
parms.number_word = true
 +
end
 +
parms.abbr_org = parms.abbr  -- original abbr, before any flip
 
elseif parms.opt_hand_hh then
 
elseif parms.opt_hand_hh then
 
parms.abbr_org = 'on'
 
parms.abbr_org = 'on'
Dòng 1.735: Dòng 1.876:
 
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
 +
if parms.opt_spell_out and not abbr_entered then
 +
parms.abbr = 'off'  -- should show unit name when spelling the output value
 
end
 
end
 
if parms.opt_flip then
 
if parms.opt_flip then
Dòng 1.758: Dòng 1.909:
 
end
 
end
 
if parms.opt_table or parms.opt_tablecen then
 
if parms.opt_table or parms.opt_tablecen then
if parms.abbr_org == nil and parms.lk == nil then
+
if abbr_entered == nil and parms.lk == nil then
 
parms.opt_values = true
 
parms.opt_values = true
 
end
 
end
local align = format('align="%s"', parms.opt_table and 'right' or 'center')
+
parms.table_align = parms.opt_table and 'right' or 'center'
parms.table_joins = { align .. '|', '\n|' .. align .. '|' }
+
end
 +
if parms.table_align or parms.opt_sortable_on then
 +
parms.need_table_or_sort = true
 
end
 
end
 
local disp_joins = text_code.disp_joins
 
local disp_joins = text_code.disp_joins
Dòng 1.776: Dòng 1.929:
 
local abbr = parms.abbr
 
local abbr = parms.abbr
 
if disp == 'slash' then
 
if disp == 'slash' then
if parms.abbr_org == nil then
+
if abbr_entered == nil then
 
disp = 'slash-nbsp'
 
disp = 'slash-nbsp'
 
elseif abbr == 'in' or abbr == 'out' then
 
elseif abbr == 'in' or abbr == 'out' then
Dòng 1.792: Dòng 1.945:
 
parms.joins = disp_joins[disp] or default_joins
 
parms.joins = disp_joins[disp] or default_joins
 
parms.join_between = parms.joins[3] or parms.join_between
 
parms.join_between = parms.joins[3] or parms.join_between
 +
parms.wantname = parms.joins.wantname
 
end
 
end
 
if (en_default and not parms.opt_lang_local and (parms[1] or ''):find('%d')) or parms.opt_lang_en then
 
if (en_default and not parms.opt_lang_local and (parms[1] or ''):find('%d')) or parms.opt_lang_en then
Dòng 1.821: Dòng 1.975:
 
-- 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.
 +
-- The tail recursion allows combinations like "1x2 to 3x4".
 
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 i < 20 then  -- check i to limit abuse
for _, sep in ipairs(text_code.ranges.words) do
+
local lhs, sep, rhs = valstr:match('^(%S+)%s+(%S+)%s+(%S.*)')
local start, stop = valstr:find(sep, 2, true)  -- start at 2 to skip any negative sign for range '-'
+
if lhs and not (sep == '-' and rhs:match('/')) then
if start then
+
if sep:find('%d') then
parms[i] = valstr:sub(stop + 1)
+
return success, result  -- to reject {{convert|1 234 567|m}} with a decent message (en only)
table.insert(parms, i, sep)
+
end
table.insert(parms, i, valstr:sub(1, start - 1))
+
parms[i] = rhs
return extractor(i) -- this allows combinations like "1 x 2 to 3 x 4"
+
table.insert(parms, i, sep)
 +
table.insert(parms, i, lhs)
 +
return extractor(i)
 +
end
 +
if not valstr:match('%-.*/') then
 +
for _, sep in ipairs(text_code.ranges.words) do
 +
local start, stop = valstr:find(sep, 2, true)  -- start at 2 to skip any negative sign for range '-'
 +
if start then
 +
parms[i] = valstr:sub(stop + 1)
 +
table.insert(parms, i, sep)
 +
table.insert(parms, i, valstr:sub(1, start - 1))
 +
return extractor(i)
 +
end
 
end
 
end
 
end
 
end
Dòng 1.848: Dòng 2.017:
 
end
 
end
 
valinfo:add(info)
 
valinfo:add(info)
local next = strip(parms[i])
+
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
Dòng 1.856: Dòng 2.024:
 
range:add(range_item)
 
range:add(range_item)
 
if type(range_item) == 'table' then
 
if type(range_item) == 'table' then
parms.is_range_x = range_item.is_range_x
+
-- For range "x", if append unit to some values, append it to all.
 +
parms.in_range_x = parms.in_range_x or range_item.in_range_x
 +
parms.out_range_x = parms.out_range_x or range_item.out_range_x
 +
parms.abbr_range_x = parms.abbr_range_x or range_item.abbr_range_x
 
is_change = range_item.is_range_change
 
is_change = range_item.is_range_change
 
end
 
end
Dòng 1.873: Dòng 2.044:
 
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, v, 3, in_unit, in_unit_table
+
-- return true, i, in_unit, in_unit_table
-- (as for get_values(), but with a unit name and table for a valid unit;
+
-- 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 and caller will reparse the input.
+
-- 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%.
if parms.input_precision 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 #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
Dòng 1.894: Dòng 2.065:
 
}
 
}
 
local in_unit = strip(parms[2])
 
local in_unit = strip(parms[2])
local success, in_unit_table = lookup(in_unit, parms.opt_sp_us, 'no_combination')
+
local success, in_unit_table = lookup(parms, in_unit, 'no_combination')
 
if not success then return end
 
if not success then return end
return true, { info }, 3, in_unit, in_unit_table
+
in_unit_table.valinfo = { info }
 +
return true, 3, in_unit, in_unit_table
 
end
 
end
  
local function get_parms(pframe)
+
local function wikidata_call(parms, operation, ...)
-- If successful, return true, parms, unit where
+
-- 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.
Dòng 1.911: Dòng 2.104:
 
-- 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(pframe.args) do
+
for k, v in pairs(args) do
 
if type(k) == 'number' or k == 'test' then  -- parameter "test" is reserved for testing and is not translated
 
if type(k) == 'number' or k == 'test' then  -- parameter "test" is reserved for testing and is not translated
 
parms[k] = v
 
parms[k] = v
Dòng 1.919: Dòng 2.111:
 
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
local success, valinfo, i, in_unit, in_unit_table = simple_get_values(parms)
+
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)
 
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
 
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
 
in_unit = strip(parms[i])
 
in_unit = strip(parms[i])
 
i = i + 1
 
i = i + 1
success, in_unit_table = lookup(in_unit, parms.opt_sp_us, 'no_combination')
+
success, in_unit_table = lookup(parms, in_unit, 'no_combination')
 
if not success then
 
if not success then
if in_unit == nil then
+
in_unit = in_unit or ''
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({ symbol = in_unit, name2 = in_unit,
+
in_unit_table = setmetatable({
default = "m", defkey = "m", linkey = "m",
+
symbol = in_unit, name2 = in_unit, utype = in_unit,
utype = "length", scale = 1, bad_mcode = in_unit_table }, unit_mt)
+
scale = 1, default = '', defkey = '', linkey = '',
 +
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
Dòng 1.952: Dòng 2.161:
 
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, valinfo[1].value, in_unit_table)
+
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
Dòng 1.973: Dòng 2.181:
 
end
 
end
 
end
 
end
local next = strip(parms[i])
+
local word = strip(parms[i])
 
i = i + 1
 
i = i + 1
 
local precision, is_bad_precision
 
local precision, is_bad_precision
Dòng 1.988: Dòng 2.196:
 
end
 
end
 
end
 
end
if not set_precision(next) then
+
if word and not set_precision(word) then
parms.out_unit = next
+
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
Dòng 1.995: Dòng 2.203:
 
end
 
end
 
if parms.opt_adj_mid then
 
if parms.opt_adj_mid then
next = parms[i]
+
word = parms[i]
 
i = i + 1
 
i = i + 1
if next then  -- mid-text words
+
if word then  -- mid-text words
if next:sub(1, 1) == '-' then
+
if word:sub(1, 1) == '-' then
parms.mid = next
+
parms.mid = word
 
else
 
else
parms.mid = ' ' .. next
+
parms.mid = ' ' .. word
 
end
 
end
 
end
 
end
Dòng 2.041: Dòng 2.249:
 
parms.precision = precision
 
parms.precision = precision
 
end
 
end
return true, parms, in_unit_table
+
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
  
Dòng 2.077: Dòng 2.292:
 
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"
Dòng 2.108: Dòng 2.322:
 
end
 
end
 
if in_current.istemperature and out_current.istemperature then
 
if in_current.istemperature and out_current.istemperature then
-- Converting between common temperatures (°C, °F, °R, K); not keVT, MK.
+
-- Converting between common temperatures (°C, °F, °R, K); not keVT.
 
-- Kelvin value can be almost zero, or small but negative due to precision problems.
 
-- Kelvin value can be almost zero, or small but negative due to precision problems.
 
-- Also, an input value like -300 C (below absolute zero) gives negative kelvins.
 
-- Also, an input value like -300 C (below absolute zero) gives negative kelvins.
Dòng 2.245: Dòng 2.459:
 
end
 
end
  
local cvt_to_hand
+
local function user_style(parms, i)
 +
-- Return text for a user-specified style for a table cell, or '' if none,
 +
-- given i = 1 (input style) or 2 (output style).
 +
local style = parms[(i == 1) and 'stylein' or 'styleout']
 +
if style then
 +
style = style:gsub('"', '')
 +
if style ~= '' then
 +
if style:sub(-1) ~= ';' then
 +
style = style .. ';'
 +
end
 +
return style
 +
end
 +
end
 +
return ''
 +
end
  
local function cvtround(parms, info, in_current, out_current)
+
local function make_table_or_sort(parms, invalue, info, in_current, scaled_top)
 +
-- Set options to handle output for a table or a sort key, or both.
 +
-- The text sort key is based on the value resulting from converting
 +
-- the input to a fake base unit with scale = 1, and other properties
 +
-- required for a conversion derived from the input unit.
 +
-- For other modules, return the sort key in a hidden span element, and
 +
-- the scaled value used to generate the sort key.
 +
-- If scaled_top is set, it is the scaled value of the numerator of a per unit
 +
-- to be combined with this unit (the denominator) to make the sort key.
 +
-- Scaling only works with units that convert with a factor (not temperature).
 +
local sortkey, scaled_value
 +
if parms.opt_sortable_on then
 +
local base = {  -- a fake unit with enough fields for a valid convert
 +
scale = 1,
 +
invert = in_current.invert and 1,
 +
iscomplex = in_current.iscomplex,
 +
offset = in_current.offset and 0,
 +
}
 +
local outvalue, extra = convert(parms, invalue, info, in_current, base)
 +
if extra then
 +
outvalue = extra.outvalue
 +
end
 +
if in_current.istemperature then
 +
-- Have converted to kelvin; assume numbers close to zero have a
 +
-- rounding error and should be zero.
 +
if abs(outvalue) < 1e-12 then
 +
outvalue = 0
 +
end
 +
end
 +
if scaled_top and outvalue ~= 0 then
 +
outvalue = scaled_top / outvalue
 +
end
 +
scaled_value = outvalue
 +
if not valid_number(outvalue) then
 +
if outvalue < 0 then
 +
sortkey = '1000000000000000000'
 +
else
 +
sortkey = '9000000000000000000'
 +
end
 +
elseif outvalue == 0 then
 +
sortkey = '5000000000000000000'
 +
else
 +
local mag = floor(log10(abs(outvalue)) + 1e-14)
 +
local prefix
 +
if outvalue > 0 then
 +
prefix = 7000 + mag
 +
else
 +
prefix = 2999 - mag
 +
outvalue = outvalue + 10^(mag+1)
 +
end
 +
sortkey = format('%d', prefix) .. format('%015.0f', floor(outvalue * 10^(14-mag)))
 +
end
 +
end
 +
local sortspan
 +
if sortkey and not parms.table_align then
 +
sortspan = parms.opt_sortable_debug and
 +
'<span data-sort-value="' .. sortkey .. '♠"><span style="border:1px solid">' .. sortkey .. '♠</span></span>' or
 +
'<span data-sort-value="' .. sortkey .. '♠"></span>'
 +
parms.join_before = sortspan
 +
end
 +
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 joins = {}
 +
for i = 1, 2 do
 +
joins[i] = (i == 1 and '' or '\n|') .. style .. user_style(parms, i) .. '"' .. sort .. '|'
 +
end
 +
parms.table_joins = joins
 +
end
 +
return sortspan, scaled_value
 +
end
 +
 
 +
local cvt_to_hand
 +
 
 +
local function cvtround(parms, info, in_current, out_current)
 
-- Return true, t where t is a table with the conversion results; fields:
 
-- Return true, t where t is a table with the conversion results; fields:
 
--  show = rounded, formatted string with the result of converting value in info,
 
--  show = rounded, formatted string with the result of converting value in info,
 
--      using the rounding specified in parms.
 
--      using the rounding specified in parms.
--  singular = true if result is positive, and (after rounding)
+
--  singular = true if result (after rounding and ignoring any negative sign)
--      is "1", or like "1.00";
+
--      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
 +
parms.need_table_or_sort = nil  -- process using first input value only
 +
make_table_or_sort(parms, invalue, info, in_current)
 +
end
 
if extra then
 
if extra then
 
if not outvalue then return false, extra end
 
if not outvalue then return false, extra end
Dòng 2.285: Dòng 2.589:
 
outvalue = -outvalue
 
outvalue = -outvalue
 
end
 
end
local numerator, precision, success, show, exponent
+
local precision, show, exponent
 
local denominator = out_current.frac
 
local denominator = out_current.frac
 
if denominator then
 
if denominator then
Dòng 2.292: Dòng 2.596:
 
precision = parms.precision
 
precision = parms.precision
 
if not precision then
 
if not precision then
local sigfig = parms.sigfig
+
if parms.sigfig then
if sigfig then
+
show, exponent = make_sigfig(outvalue, parms.sigfig)
show, exponent = make_sigfig(outvalue, sigfig)
+
elseif parms.opt_round then
elseif parms.opt_round5 or parms.opt_round25  then
+
local n = parms.opt_round
local n = parms.opt_round5 and 5 or 25
+
if n == 0.5 then
show = format('%.0f', floor((outvalue / n) + 0.5) * n)
+
local integer, fracpart = math.modf(floor(2 * outvalue + 0.5) / 2)
 +
if fracpart == 0 then
 +
show = format('%.0f', integer)
 +
else
 +
show = format('%.1f', integer + fracpart)
 +
end
 +
else
 +
show = format('%.0f', floor((outvalue / n) + 0.5) * n)
 +
end
 
else
 
else
 
local inclean = info.clean
 
local inclean = info.clean
Dòng 2.339: Dòng 2.651:
 
end
 
end
 
local t = format_number(parms, show, exponent, isnegative)
 
local t = format_number(parms, show, exponent, isnegative)
-- Set singular using match because on some systems 0.99999999999999999 is 1.0.
+
if type(show) == 'string' then
t.singular = (type(show) == 'string' and (show == '1' or show:match('^1%.0*$') ~= nil) and not isnegative)
+
-- Set singular using match because on some systems 0.99999999999999999 is 1.0.
t.fraction_table = (type(show) == 'table') and show or nil
+
if exponent then
 +
t.singular = (exponent == 1 and show:match('^10*$'))
 +
else
 +
t.singular = (show == '1' or show:match('^1%.0*$'))
 +
end
 +
else
 +
t.fraction_table = show
 +
t.singular = (outvalue <= 1) -- cannot have 'fraction == 1', but if it were possible it would be singular
 +
end
 
t.raw_absvalue = outvalue  -- absolute value before rounding
 
t.raw_absvalue = outvalue  -- absolute value before rounding
 
return true, setmetatable(t, {
 
return true, setmetatable(t, {
Dòng 2.466: Dòng 2.786:
 
-- 'smallsuffix' if (value < 120), or 'bigsuffix' otherwise.
 
-- 'smallsuffix' if (value < 120), or 'bigsuffix' otherwise.
 
-- Input must use en digits and '.' decimal mark.
 
-- Input must use en digits and '.' decimal mark.
local default = default_exceptions[unit_table.defkey or unit_table.symbol] or unit_table.default
+
local default = data_code.default_exceptions[unit_table.defkey or unit_table.symbol] or unit_table.default
 
if not default then
 
if not default then
 +
local per = unit_table.per
 +
if per then
 +
local function a_default(v, u)
 +
local success, ucode = get_default(v, u)
 +
if not success then
 +
return '?'  -- an unlikely error has occurred; will cause lookup of default to fail
 +
end
 +
-- Attempt to use only the first unit if a combination or output multiple.
 +
-- This is not bulletproof but should work for most cases.
 +
-- Where it does not work, the convert will need to specify the wanted output unit.
 +
local t = all_units[ucode]
 +
if t then
 +
local combo = t.combination
 +
if combo then
 +
-- For a multiple like ftin, the "first" unit (ft) is last in the combination.
 +
local i = t.multiple and table_len(combo) or 1
 +
ucode = combo[i]
 +
end
 +
else
 +
-- Try for an automatically generated combination.
 +
local item = ucode:match('^(.-)%+') or ucode:match('^(%S+)%s')
 +
if all_units[item] then
 +
return item
 +
end
 +
end
 +
return ucode
 +
end
 +
local unit1, unit2 = per[1], per[2]
 +
local def1 = (unit1 and a_default(value, unit1) or unit_table.vprefix or '')
 +
local def2 = a_default(1, unit2)  -- 1 because per unit of denominator
 +
return true, def1 .. '/' .. def2
 +
end
 
return false, { 'cvt_no_default', unit_table.symbol }
 
return false, { 'cvt_no_default', unit_table.symbol }
 
end
 
end
Dòng 2.489: Dòng 2.841:
 
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, link_key)
+
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]]
Dòng 2.496: Dòng 2.856:
 
-- * 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).
-- Linking with a unit uses the unit table as the link key, which fails to detect
+
local link_key
-- overlinking for conversions like the following (each links "mile" twice):
+
if unit_table then
--  {{convert|1|impgal/mi|USgal/mi|lk=on}}
+
link_key = unit_table.unitcode or unit_table
--  {{convert|1|l/km|impgal/mi USgal/mi|lk=on}}
+
else
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)
+
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
Dòng 2.544: Dòng 2.905:
 
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]
Dòng 2.560: Dòng 2.924:
 
end
 
end
  
local function linked_id(unit_table, key_id, want_link, clean)
+
local function linked_id(parms, unit_table, key_id, want_link, clean)
 
-- Return final unit id (symbol or name), optionally with a wikilink,
 
-- Return final unit id (symbol or name), optionally with a wikilink,
 
-- and update unit_table.sep if required.
 
-- and update unit_table.sep if required.
Dòng 2.574: Dòng 2.938:
 
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
Dòng 2.585: Dòng 2.950:
 
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
Dòng 2.607: Dòng 2.975:
 
if want_link and unit_table.link then
 
if want_link and unit_table.link then
 
if abbr_on or not varname then
 
if abbr_on or not varname then
result = (unit1 and unit1[key_id] or '') .. result .. unit2[key_id2]
+
result = (unit1 and linked_id(parms, unit1, key_id, false, clean) or '') .. result .. linked_id(parms, unit2, key_id2, false, '1')
 
else
 
else
 
result = (unit1 and variable_name(clean, unit1) or '') .. result .. variable_name('1', unit2)
 
result = (unit1 and variable_name(clean, unit1) or '') .. result .. variable_name('1', unit2)
 
end
 
end
if omitsep and not want_separator(result) then
+
if omit_separator(result) then
 
unit_table.sep = ''
 
unit_table.sep = ''
 
end
 
end
Dòng 2.617: Dòng 2.985:
 
end
 
end
 
if unit1 then
 
if unit1 then
result = linked_id(unit1, key_id, want_link, clean) .. result
+
result = linked_id(parms, unit1, key_id, want_link, clean) .. result
 
if unit1.sep then
 
if unit1.sep then
 
unit_table.sep = unit1.sep
 
unit_table.sep = unit1.sep
Dòng 2.624: Dòng 2.992:
 
unit_table.sep = ''
 
unit_table.sep = ''
 
end
 
end
return result .. linked_id(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
Dòng 2.643: Dòng 3.011:
 
end
 
end
 
local id = unit_table.fixed_name or ((varname and not abbr_on) and variable_name(clean, unit_table) or unit_table[key_id])
 
local id = unit_table.fixed_name or ((varname and not abbr_on) and variable_name(clean, unit_table) or unit_table[key_id])
if omitsep and not want_separator(id) then
+
if omit_separator(id) then
 
unit_table.sep = ''
 
unit_table.sep = ''
 
end
 
end
 
if want_link then
 
if want_link then
local link = link_exceptions[unit_table.linkey or unit_table.symbol] or unit_table.link
+
local link = data_code.link_exceptions[unit_table.linkey or unit_table.symbol] or unit_table.link
 
if link then
 
if link then
 
local before = ''
 
local before = ''
 
local i = unit_table.customary
 
local i = unit_table.customary
if i == 1 and unit_table.sp_us then
+
if i == 1 and parms.opt_sp_us then
 
i = 2  -- show "U.S." not "US"
 
i = 2  -- show "U.S." not "US"
 
end
 
end
Dòng 2.673: Dòng 3.041:
 
end
 
end
 
-- Omit any "US"/"U.S."/"imp"/"imperial" from start of id since that will be inserted.
 
-- Omit any "US"/"U.S."/"imp"/"imperial" from start of id since that will be inserted.
local removes = (i < 3) and { '&nbsp;Mỹ', ' Mỹ' } or { '&nbsp;Anh', ' Anh' }
+
local removes = (i < 3) and { 'US&nbsp;', 'US ', 'U.S.&nbsp;', 'U.S. ' } or { 'imp&nbsp;', 'imp ', 'imperial ' }
 
for _, prefix in ipairs(removes) do
 
for _, prefix in ipairs(removes) do
id = mw.ustring.gsub(prefix + "$", "")
+
local plen = #prefix
 +
if id:sub(1, plen) == prefix then
 +
id = id:sub(plen + 1)
 +
break
 +
end
 
end
 
end
 
before = pertext .. make_link(customary.link, customary[1]) .. ' '
 
before = pertext .. make_link(customary.link, customary[1]) .. ' '
Dòng 2.689: Dòng 3.061:
 
--  id = unit name or symbol, possibly modified
 
--  id = unit name or symbol, possibly modified
 
--  f = true if id is a name, or false if id is a symbol
 
--  f = true if id is a name, or false if id is a symbol
-- using 1st or 2nd values (which), and for 'in' or 'out' (unit_table.inout).
+
-- using the value for index 'which', and for 'in' or 'out' (unit_table.inout).
 
-- Result is '' if no symbol/name is to be used.
 
-- Result is '' if no symbol/name is to be used.
 
-- In addition, set unit_table.sep = ' ' or '&nbsp;' or ''
 
-- In addition, set unit_table.sep = ' ' or '&nbsp;' or ''
Dòng 2.701: Dòng 3.073:
 
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)
 
local usename = unit_table.usename
 
local usename = unit_table.usename
 
local singular = info.singular
 
local singular = info.singular
if usename then
 
-- Old template does something like this.
 
if want_link then
 
-- A linked unit uses the standard singular.
 
else
 
-- Set non-standard singular.
 
local flipped = parms.opt_flip
 
if inout == 'in' then
 
if not adjectival and (abbr_org == 'out' or flipped) then
 
local value = info.value
 
singular = (0 < value and value < 1.0001)
 
end
 
else
 
if (abbr_org == 'on') or
 
(not flipped and (abbr_org == nil or abbr_org == 'out')) or
 
(flipped and abbr_org == 'in') then
 
singular = (info.absvalue < 1.0001 and
 
not info.is_scientific)
 
end
 
end
 
end
 
end
 
 
local want_name
 
local want_name
 
if usename then
 
if usename then
Dòng 2.733: Dòng 3.082:
 
else
 
else
 
if abbr_org == nil then
 
if abbr_org == nil then
if disp == 'br' or disp == 'or' or disp == 'slash' then
+
if parms.wantname then
 
want_name = true
 
want_name = true
 
end
 
end
Dòng 2.771: Dòng 3.120:
 
end
 
end
 
end
 
end
if unit_table.engscale or parms.is_range_x then
+
if unit_table.engscale then
 
-- engscale: so "|1|e3kg" gives "1 thousand kilograms" (plural)
 
-- engscale: so "|1|e3kg" gives "1 thousand kilograms" (plural)
-- is_range_x: so "|0.5|x|0.9|mi" gives "0.5 by 0.9 miles" (plural)
 
 
singular = false
 
singular = false
 
end
 
end
 
key = (adjectival or singular) and 'name1' or 'name2'
 
key = (adjectival or singular) and 'name1' or 'name2'
if unit_table.sp_us then
+
if parms.opt_sp_us then
 
key = key .. '_us'
 
key = key .. '_us'
 
end
 
end
Dòng 2.787: Dòng 3.135:
 
end
 
end
 
unit_table.sep = '&nbsp;'
 
unit_table.sep = '&nbsp;'
key = unit_table.sp_us and 'sym_us' or 'symbol'
+
key = parms.opt_sp_us and 'sym_us' or 'symbol'
 
end
 
end
return linked_id(unit_table, key, want_link, info.clean), want_name
+
return linked_id(parms, unit_table, key, want_link, info.clean), want_name
 
end
 
end
  
local function decorate_value(parms, unit_table, which)
+
local function decorate_value(parms, unit_table, which, number_word)
 
-- If needed, update unit_table so values will be shown with extra information.
 
-- If needed, update unit_table so values will be shown with extra information.
 
-- For consistency with the old template (but different from fmtpower),
 
-- For consistency with the old template (but different from fmtpower),
Dòng 2.806: Dòng 3.154:
 
end
 
end
 
info.decorated = true
 
info.decorated = true
end
+
if engscale then
if engscale then
+
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
else
+
local number_id
local number_id
+
local lk = parms.lk
local lk = parms.lk
+
if lk == 'on' or lk == inout then
if lk == 'on' or lk == inout then
+
number_id = make_link(engscale.link, engscale[1])
number_id = make_link(engscale.link, engscale[1])
+
else
else
+
number_id = engscale[1]
number_id = engscale[1]
+
end
 +
-- WP:NUMERAL recommends "&nbsp;" in values like "12 million".
 +
info.show = info.show .. (parms.opt_adjectival and '-' or '&nbsp;') .. number_id
 
end
 
end
-- WP:NUMERAL recommends "&nbsp;" in values like "12 million".
 
info.show = info.show .. (parms.opt_adjectival and '-' or '&nbsp;') .. number_id
 
 
end
 
end
end
+
if prefix then
if prefix then
+
info.show = prefix .. info.show
info.show = prefix .. info.show
+
end
 
end
 
end
 
end
 
end
Dòng 2.871: Dòng 3.219:
 
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
parms.joins = { join1 .. first_unit[first_unit.sp_us and 'sym_us' or 'symbol'] .. ', ', parms.joins[2] }
+
parms.joins = { ' [' .. first_unit[parms.opt_sp_us and 'sym_us' or 'symbol'] .. ']' .. join1 , parms.joins[2] }
 
end
 
end
 
end
 
end
Dòng 2.885: Dòng 3.233:
 
-- 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) .. '&nbsp;'
 
local prefix2 = make_id(parms, 2, first_unit) .. '&nbsp;'
result = range_text(range[1], want_name, parms, result, prefix2 .. valinfo[2].show)
+
result = range_text(range[1], want_name, parms, result, prefix2 .. valinfo[2].show, 'in')
 
end
 
end
 
return preunit .. result
 
return preunit .. result
Dòng 2.911: Dòng 3.259:
 
return table.concat(parts, sep2) .. mid
 
return table.concat(parts, sep2) .. mid
 
end
 
end
local result, mos
+
local add_unit = (parms.abbr == 'mos') or
local abbr = parms.abbr
+
parms[parms.opt_flip and 'out_range_x' or 'in_range_x'] or
 +
(not want_name and parms.abbr_range_x)
 
local range = parms.range
 
local range = parms.range
if range then
+
if range and not add_unit then
mos = (abbr == 'mos')
+
unlink(first_unit)
if not (mos or (parms.is_range_x and not want_name)) then
 
linked_pages[first_unit] = nil  -- so the second and only id will be linked, if wanted
 
end
 
 
end
 
end
local id = (range == nil) and id1 or make_id(parms, 2, first_unit)
+
local id = range and make_id(parms, range.n + 1, first_unit) or id1
 
local extra, was_hyphenated = hyphenated_maybe(parms, want_name, sep, id, 'in')
 
local extra, was_hyphenated = hyphenated_maybe(parms, want_name, sep, id, 'in')
if mos and was_hyphenated then
+
if was_hyphenated then
mos = false -- suppress repeat of unit in a range
+
add_unit = false
if linked_pages[first_unit] then
 
linked_pages[first_unit] = nil
 
id = make_id(parms, 2, first_unit)
 
extra = hyphenated_maybe(parms, want_name, sep, id, 'in')
 
end
 
 
end
 
end
 +
local result
 
local valinfo = first_unit.valinfo
 
local valinfo = first_unit.valinfo
 
if range then
 
if range then
if range.n == 1 then
+
for i = 0, range.n do
-- Like {{convert|1|x|2|ft}} (one range item; two values).
+
local number_word
-- Do what old template did.
+
if i == range.n then
local sep1 = first_unit.sep
+
add_unit = false
if mos then
+
number_word = true
decorate_value(parms, in_current, 1)
+
end
decorate_value(parms, in_current, 2)
+
decorate_value(parms, first_unit, i+1, number_word)
result = valinfo[1].show .. sep1 .. id1
+
local show = valinfo[i+1].show
elseif parms.is_range_x and not want_name then
+
if add_unit then
if abbr == 'in' or abbr == 'on' then
+
show = show .. first_unit.sep .. (i == 0 and id1 or make_id(parms, i+1, first_unit))
decorate_value(parms, in_current, 1)
+
end
end
+
if i == 0 then
decorate_value(parms, in_current, 2)
+
result = show
result = valinfo[1].show .. sep1 .. id1
 
 
else
 
else
if abbr == 'in' or abbr == 'on' then
+
result = range_text(range[i], want_name, parms, result, show, 'in')
decorate_value(parms, in_current, 1)
 
end
 
decorate_value(parms, in_current, 2)
 
result = valinfo[1].show
 
end
 
result = range_text(range[1], want_name, parms, result, valinfo[2].show)
 
else
 
-- Like {{convert|1|x|2|x|3|ft}} (two or more range items): simplify.
 
decorate_value(parms, in_current, 1)
 
result = valinfo[1].show
 
for i = 1, range.n do
 
decorate_value(parms, in_current, i+1)
 
result = range_text(range[i], want_name, parms, result, valinfo[i+1].show)
 
 
end
 
end
 
end
 
end
 
else
 
else
decorate_value(parms, first_unit, 1)
+
decorate_value(parms, first_unit, 1, true)
 
result = valinfo[1].show
 
result = valinfo[1].show
 
end
 
end
Dòng 2.973: Dòng 3.301:
 
-- 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
Dòng 2.994: Dòng 3.323:
 
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)
 
end
 
end
 
return preunit .. result
 
return preunit .. result
 
end
 
end
local result
+
local add_unit = (parms[parms.opt_flip and 'in_range_x' or 'out_range_x'] or
 +
(not want_name and parms.abbr_range_x)) and
 +
not parms.opt_output_number_only
 
local range = parms.range
 
local range = parms.range
if range then
+
if range and not add_unit then
if not (parms.is_range_x and not want_name) then
+
unlink(out_current)
linked_pages[out_current] = nil  -- so the second 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 extra, was_hyphenated = hyphenated_maybe(parms, want_name, sep, id, inout)
 +
if was_hyphenated then
 +
add_unit = false
 
end
 
end
local id = (range == nil) and id1 or make_id(parms, 2, out_current)
+
local result
local extra = hyphenated_maybe(parms, want_name, sep, id, 'out')
 
 
local valinfo = out_current.valinfo
 
local valinfo = out_current.valinfo
 
if range then
 
if range then
if range.n == 1 then
+
for i = 0, range.n do
local sep1 = out_current.sep
+
local number_word
local abbr = parms.abbr
+
if i == range.n then
if parms.is_range_x and not want_name then
+
add_unit = false
if abbr == 'out' or abbr == 'on' then
+
number_word = true
decorate_value(parms, out_current, 1)
+
end
end
+
decorate_value(parms, out_current, i+1, number_word)
decorate_value(parms, out_current, 2)
+
local show = valinfo[i+1].show
result = valinfo[1].show .. sep1 .. id1
+
if add_unit then
 +
show = show .. out_current.sep .. (i == 0 and id1 or make_id(parms, i+1, out_current))
 +
end
 +
if i == 0 then
 +
result = show
 
else
 
else
if abbr == 'out' or abbr == 'on' then
+
result = range_text(range[i], want_name, parms, result, show, inout)
decorate_value(parms, out_current, 1)
 
end
 
decorate_value(parms, out_current, 2)
 
result = valinfo[1].show
 
end
 
result = range_text(range[1], want_name, parms, result, valinfo[2].show)
 
else
 
-- Like {{convert|1|x|2|x|3|ft}} (two or more range items): simplify.
 
decorate_value(parms, out_current, 1)
 
result = valinfo[1].show
 
for i = 1, range.n do
 
decorate_value(parms, out_current, i+1)
 
result = range_text(range[i], want_name, parms, result, valinfo[i+1].show)
 
 
end
 
end
 
end
 
end
 
else
 
else
decorate_value(parms, out_current, 1)
+
decorate_value(parms, out_current, 1, true)
 
result = valinfo[1].show
 
result = valinfo[1].show
 
end
 
end
Dòng 3.049: Dòng 3.373:
 
-- 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
local range = parms.range
+
out_unit_table.valinfo = in_unit_table.valinfo
for i = 1, (range and (range.n + 1) or 1) do
+
else
local success, info = cvtround(parms, in_unit_table.valinfo[i], in_unit_table, out_unit_table)
+
out_unit_table.valinfo = collection()
if not success then return false, info end
+
for _, v in ipairs(in_unit_table.valinfo) do
out_unit_table.valinfo:add(info)
+
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)
Dòng 3.063: Dòng 3.390:
 
-- 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)
Dòng 3.069: Dòng 3.397:
 
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 == 'out' or abbr == 'mos')
+
not (abbr == 'on' or abbr == inout or abbr == 'mos')
local want_link = (parms.lk == 'on' or parms.lk == 'out')
+
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 = '&nbsp;'
 
local sep1 = '&nbsp;'
Dòng 3.086: Dòng 3.414:
 
local tfrac, thisvalue, strforce
 
local tfrac, thisvalue, strforce
 
local out_current = combos[i]
 
local out_current = combos[i]
out_current.inout = 'out'
+
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')
Dòng 3.156: Dòng 3.484:
 
id = out_current['symbol']
 
id = out_current['symbol']
 
end
 
end
if omitsep and i == 1 and not want_separator(id) then
+
if i == 1 and omit_separator(id) then
 
-- Testing the id of the least significant unit should be sufficient.
 
-- Testing the id of the least significant unit should be sufficient.
 
sep1 = ''
 
sep1 = ''
Dòng 3.168: Dòng 3.496:
 
end
 
end
 
local strval
 
local strval
local inout = (i == #combos or outvalue == 0) and 'out' or ''  -- trick so the last value processed (first displayed) has uppercase, if requested
+
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
Dòng 3.174: Dòng 3.502:
 
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, inout, false, wholestr, tfrac.numstr, tfrac.denstr, do_spell)
+
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, inout, strval) or strval
+
strval = spell_number(parms, spell_inout, strval) or strval
 
end
 
end
 
end
 
end
Dòng 3.201: Dòng 3.529:
 
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)
 
end
 
end
 
end
 
end
Dòng 3.208: Dòng 3.536:
  
 
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, out_first
+
local success, bad_output
local bad_input_mcode = in_unit_table.bad_mcode  -- false if input unit is valid
+
local bad_input_mcode = in_unit_table.bad_mcode  -- nil if input unit is a valid convert unit
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 == '' then
+
if out_unit == nil or out_unit == '' or type(out_unit) == 'function' then
if bad_input_mcode then
+
if bad_input_mcode or parms.opt_input_unit_only then
 
bad_output = ''
 
bad_output = ''
 
else
 
else
success, out_unit = get_default(invalue1, in_unit_table)
+
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
Dòng 3.227: Dòng 3.555:
 
end
 
end
 
if not bad_output and not out_unit_table then
 
if not bad_output and not out_unit_table then
success, out_unit_table = lookup(out_unit, parms.opt_sp_us, 'any_combination')
+
success, out_unit_table = lookup(parms, out_unit, 'any_combination')
 
if success then
 
if success then
 
local mismatch = check_mismatch(in_unit_table, out_unit_table)
 
local mismatch = check_mismatch(in_unit_table, out_unit_table)
Dòng 3.237: Dòng 3.565:
 
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
local parts = {}
+
if bad_output then
for part = 1, 2 do
+
rhs = (bad_output == '') and '' or message(parms, bad_output)
-- The LHS (parts[1]) is normally the input, but is the output if flipped.
+
elseif parms.opt_input_unit_only then
-- Process LHS first so it will be linked, if wanted.
+
rhs = ''
-- Linking to the same item is suppressed in the RHS to avoid overlinking.
+
else
if (part == 1 and not flipped) or (part == 2 and flipped) then
+
local combos  -- nil (for 'ft' or 'ftin'), or table of unit tables (for 'm ft')
parts[part] = process_input(parms, in_unit_table)
+
if not out_unit_table.multiple then  -- nil/false ('ft' or 'm ft'), or table of factors ('ftin')
elseif bad_output then
+
combos = out_unit_table.combination
if bad_output ~= '' then
+
end
parts[part] = message(bad_output)
+
local frac = parms.frac  -- nil or denominator of fraction for output values
end
+
if frac then
else
+
-- Apply fraction to the unit (if only one), or to non-SI units (if a combination),
local outputs = {}
+
-- except that if a precision is also specified, the fraction only applies to
local combos  -- nil (for 'ft' or 'ftin'), or table of unit tables (for 'm ft')
+
-- the hand unit; that allows the following result:
if not out_unit_table.multiple then  -- nil/false ('ft' or 'm ft'), or table of factors ('ftin')
+
-- {{convert|156|cm|in hand|1|frac=2}} → 156 centimetres (61.4 in; 15.1½ hands)
combos = out_unit_table.combination
+
-- However, the following is handled elsewhere as a special case:
end
+
-- {{convert|156|cm|hand in|1|frac=2}} → 156 centimetres (15.1½ hands; 61½ in)
local frac = parms.frac  -- nil or denominator of fraction for output values
+
if combos then
if frac then
+
local precision = parms.precision
-- Apply fraction to the unit (if only one), or to non-SI units (if a combination),
+
for _, unit in ipairs(combos) do
-- except that if a precision is also specified, the fraction only applies to
+
if unit.builtin == 'hand' or (not precision and not unit.prefixes) then
-- the hand unit; that allows the following result:
+
unit.frac = frac
-- {{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
else
 
out_unit_table.frac = frac
 
 
end
 
end
 +
else
 +
out_unit_table.frac = frac
 
end
 
end
local imax = combos and #combos or 1  -- 1 (single unit) or number of unit tables
+
end
for i = 1, imax do
+
local outputs = {}
local success, item
+
local imax = combos and #combos or 1  -- 1 (single unit) or number of unit tables
local out_current = combos and combos[i] or out_unit_table
+
if imax == 1 then
out_current.inout = 'out'
+
parms.opt_order_out = nil  -- only useful with an output combination
if i == 1 then
+
end
out_first = out_current
+
if not flipped and not parms.opt_order_out then
if imax > 1 and out_current.builtin == 'hand' then
+
-- Process left side first so any duplicate links (from lk=on) are suppressed
out_current.out_next = combos[2]  -- built-in hand can influence next unit in a combination
+
-- on right. Example: {{convert|28|e9pc|e9ly|abbr=off|lk=on}}
end
+
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 out_current.multiple then
+
if parms.opt_order_out then
success, item = make_output_multiple(parms, in_unit_table, out_current)
+
out_current.inout = 'in'
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
local sep = parms.table_joins and parms.table_joins[2] or parms.join_between
+
if out_current.multiple then
parts[part] = parms.opt_input_unit_only and '' or table.concat(outputs, sep)
+
success, item = make_output_multiple(parms, in_unit_table, out_current)
 +
else
 +
success, item = make_output_single(parms, in_unit_table, out_current)
 +
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
 
end
 +
local sep = parms.table_joins and parms.table_joins[2] or parms.join_between
 +
rhs = table.concat(outputs, sep)
 
end
 
end
if parms.opt_sortable_in or parms.opt_sortable_out then
+
if flipped or not lhs then
local value
+
local input = process_input(parms, in_unit_table)
if parms.opt_sortable_in then
+
if flipped then
value = invalue1
+
lhs = rhs
 +
rhs = input
 
else
 
else
local info = out_first and out_first.valinfo
+
lhs = input
if info then
 
info = info[1]
 
value = info.raw_absvalue
 
if value and info.sign == MINUS then
 
value = -value
 
end
 
end
 
 
end
 
end
parts[1] = ntsh((value or 0), parms.opt_sortable_debug) .. parts[1]
+
end
 +
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 = parts[1]
+
wikitext = lhs
 
else
 
else
wikitext = parts[1] .. message(bad_input_mcode)
+
wikitext = lhs .. message(parms, bad_input_mcode)
 
end
 
end
 
elseif parms.table_joins then
 
elseif parms.table_joins then
wikitext = parms.table_joins[1] .. parts[1] .. parms.table_joins[2] .. parts[2]
+
wikitext = parms.table_joins[1] .. lhs .. parms.table_joins[2] .. rhs
 
else
 
else
wikitext = parts[1] .. parms.joins[1] .. parts[2] .. parms.joins[2]
+
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
Dòng 3.333: Dòng 3.664:
 
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.
set_config(frame)
+
local parms = { frame = frame }  -- will hold template arguments, after translation
local result, out_unit_table
+
set_config(frame.args)
local success, parms, in_unit_table = get_parms(frame:getParent())
+
local success, result = get_parms(parms, frame:getParent().args)
 
if success then
 
if success then
for i = 1, 2 do  -- use counter so cannot get stuck repeating convert
+
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
Dòng 3.345: Dòng 3.681:
 
end
 
end
 
end
 
end
else
 
result = parms
 
 
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
 +
return success and result or message(parms, result)
 +
end
 +
 
 +
local function _unit(unitcode, options)
 +
-- Helper function for Module:Val to look up a unit.
 +
-- Parameter unitcode must be a string to identify the wanted unit.
 +
-- Parameter options must be nil or a table with optional fields:
 +
--  value = number (for sort key; default value is 1)
 +
--  scaled_top = nil for a normal unit, or a number for a unit which is
 +
--                the denominator of a per unit (for sort key)
 +
--  si = { 'symbol', 'link' }
 +
--                (a table with two strings) to make an SI unit
 +
--                that will be used for the look up
 +
--  link = true if result should be [[linked]]
 +
--  sort = 'on' or 'debug' if result should include a sort key in a
 +
--                span element ('debug' makes the key visible)
 +
--  name = true for the name of the unit instead of the symbol
 +
--  us = true for the US spelling of the unit, if any
 +
-- Return nil if unitcode is not a non-empty string.
 +
-- Otherwise return a table with fields:
 +
--  text = requested symbol or name of unit, optionally linked
 +
--  scaled_value = input value adjusted by unit scale; used for sort key
 +
--  sortspan = span element with sort key like that provided by {{ntsh}},
 +
--    calculated from the result of converting value
 +
--    to a base unit with scale 1.
 +
--  unknown = true if the unitcode was not known
 +
unitcode = strip(unitcode)
 +
if unitcode == nil or unitcode == '' then
 +
return nil
 +
end
 +
set_config({})
 +
linked_pages = {}
 +
options = options or {}
 +
local parms = {
 +
abbr = options.name and 'off' or 'on',
 +
lk = options.link and 'on' or nil,
 +
opt_sp_us = options.us and true or nil,
 +
opt_ignore_error = true,  -- do not add pages using this function to 'what links here' for Module:Convert/extra
 +
opt_sortable_on = options.sort == 'on' or options.sort == 'debug',
 +
opt_sortable_debug = options.sort == 'debug',
 +
}
 +
if options.si then
 +
-- Make a dummy table of units (just one unit) for lookup to use.
 +
-- This makes lookup recognize any SI prefix in the unitcode.
 +
local symbol = options.si[1] or '?'
 +
parms.unittable = { [symbol] = {
 +
_name1 = symbol,
 +
_name2 = symbol,
 +
_symbol = symbol,
 +
utype = symbol,
 +
scale = symbol == 'g' and 0.001 or 1,
 +
prefixes = 1,
 +
default = symbol,
 +
link = options.si[2],
 +
}}
 +
end
 +
local success, unit_table = lookup(parms, unitcode, 'no_combination')
 +
if not success then
 +
unit_table = setmetatable({
 +
symbol = unitcode, name2 = unitcode, utype = unitcode,
 +
scale = 1, default = '', defkey = '', linkey = '' }, unit_mt)
 +
end
 +
local value = tonumber(options.value) or 1
 +
local clean = tostring(abs(value))
 +
local info = {
 +
value = value,
 +
altvalue = value,
 +
singular = (clean == '1'),
 +
clean = clean,
 +
show = clean,
 +
}
 +
unit_table.inout = 'in'
 +
unit_table.valinfo = { info }
 +
local sortspan, scaled_value
 +
if options.sort then
 +
sortspan, scaled_value = make_table_or_sort(parms, value, info, unit_table, options.scaled_top)
 
end
 
end
return message(result)
+
return {
 +
text = make_id(parms, 1, unit_table),
 +
sortspan = sortspan,
 +
scaled_value = scaled_value,
 +
unknown = not success and true or nil,
 +
}
 
end
 
end
  
return { convert = main_convert }
+
return { convert = main_convert, _unit = _unit }

Lưu ý rằng tất cả các đóng góp của bạn tại Bách khoa Toàn thư Việt Nam sẽ được phát hành theo giấy phép Creative Commons Ghi công–Chia sẻ tương tự (xem thêm Bản quyền). Nếu bạn không muốn những gì mình viết ra sẽ có thể được bình duyệt và có thể bị sửa đổi, và không sẵn lòng cho phép phát hành lại, xin đừng nhấn nút “Lưu trang”. Đảm bảo rằng chính bạn là tác giả của những gì mình viết ra, hoặc chép nó từ một nguồn thuộc phạm vi công cộng hoặc tự do tương đương. ĐỪNG ĐĂNG NỘI DUNG CÓ BẢN QUYỀN MÀ CHƯA XIN PHÉP!

Hủy bỏ Trợ giúp sửa đổi (mở cửa sổ mới)

Bản mẫu dùng trong trang này: