Модуль:Convert

{{i}} Документація модуля]

Цей модуль перетворює значення з однієї одиниці вимірювання в іншу. Наприклад:

  • {{convert|123|lb|kg}} → 123 pounds (56 kg)

The module is called using a template—parameters passed to the template are used by this module to control how a conversion is performed. For example, units can be abbreviated (like kg), or displayed as names (like kilogram), and the output value can be rounded to a specified precision. For usage information, see Help:Convert.

Шаблон, що викликає цей модуль:

Наступні модулі є обов'язковими:

  • Module:Convert(цей модуль) код для перерахунку одиниць вимірювання
  • Module:Convert/data — означення одиниць вимірювання
  • Module:Convert/text — текстові повідомлення, і назви і значення параметрів

Наступні модулі є допоміжними і використовуються лише за потребою і я якщо існує такий модуль:

Є доступними наступні сторінки довідки:

  • Help:Convert — загальний огляд
  • Help:Convert messages — describes error and warning messages; messages link to this page so it is required when the module is copied to another wiki
  • Help:Convert units — загальний огляд одиниць вимірювання

Сторінка, що містить помилку в конвертації (модуля Convert), додаються до однієї з нижче наведених категорій, за умови якщо сторінка належить до визначеного простору назв (по стандартно, цим простором є статті):

Одиниці вимірювання визначені у вікітексті головного списпку одиниць.

  • Module:Convert/documentation/conversion data/doc — визначення головного списпку одиниць вимірювання
  • Module:Convert/makeunits — перетворює вікітекст з головного списпку одиниць в код Lua
  • Module talk:Convert/makeunits — результат роботи модуля makeunits; скопіюйте його до Module:Convert/data

Module:Convert/data is transcluded into every page using the convert module, so experimenting with a new unit in that module would involve a significant overhead. The Module:Convert/extra module is an alternative which is only transcluded on pages with a unit that is not defined in the main data module.

Пісочниця

При змін цього модуля, скопіюйте модулі на сторінки пісочниці, тоді відредагуйте спочатку ці копії в пісочниці:

Використовуйте наступний шаблон для перевірки результатів (приклад {{convert/sandbox|123|lb|kg}}):

Template:Convert/пісочниця робить виклик модулю Module:Convert/пісочниця із параметром |sandbox=on, який вказує, що треба використовувати модулі пісочниці, а не нормальні модулі.

Для перевірки результатів редагування модулів convert, необхідно використовувати наступні модулі.

Не обов'язково зберігати сторінку testcases для перегляду результатів тесту. Наприклад, можна відредагувати модуль Module:Convert/sandbox/testcases аби змінити тести. Під час редагування тієї сторінки, додайте "Module talk:Convert/sandbox/testcases" (без лапок) у полі заголовка сторінки під "Попередній перегляд сторінки з цим шаблоном", потім натисніть "Попередній перегляд".

Configuration

У шаблоні, що викликає цей модуль, можна зробити деякі налаштування.

Наприклад:

  • {{#invoke:convert|convert|numdot=,|numsep=`}}
Встановити десятковий розділювач — кому, а розділювач груп розрядів — гравіс (розділювачем груп має бути нерозривний пробіл, але чи працюватиме?).

Інші налаштування (та їх значення за замовчуванням):

  • |maxsigfig=14 – максимальна кількість значущих цифр
  • |nscat=0простори назв ((англ. namespaces, розділені комою). У разі виникнення в шаблоні помилок чи попереджень сторінки з перелічених просторів автоматично додаватимуться у відповідну категорію (з помилками чи попередженнями; звісно, такі категорії спочатку мають бути створені).
    • вважається, що автоматично відстежувати помилки слід лише на сторінках основного простору;
  • |warnings=0 – рівень попереджень, які буде показано:
    • 0 (нуль) — вимкнути всі попередження;
    • 1 — показувати лише важливі попередження;
    • 2 — показувати всі попередження.

Шаблон/модуль має можливість задати підсторінку, на якій роблять/перевіряють експериментальні зміни («пісочницю»). Вона задається значенням параметра |sandbox=. В англійській версії такі підсторінки зазвичай і називають sandbox, в українській локалізації їх зазвичай називають пісочниця.

  • |sandbox=пісочниця (після доробки/перевірки цей параметр слід прибрати)

All text used for input parameters and for output messages and categories can be customized. For example, at enwiki the option |lk=on can be used to link each displayed unit to its article. The "lk" and "on" can be replaced with any desired text. In addition, input and output numbers can be formatted and can use digits in the local language. See the translation guide for more information.

-- Convert a value from one unit of measurement to another.-- Example: {{convert|123|lb|kg}} --> 123 pounds (56 kg)-- See [[:en:Template:Convert/Transwiki guide]] if copying to another wiki.local MINUS = '−'  -- Unicode U+2212 MINUS SIGN (UTF-8: e2 88 92)local abs = math.abslocal floor = math.floorlocal format = string.formatlocal log10 = math.log10local ustring = mw.ustringlocal ulen = ustring.lenlocal usub = ustring.sub-- Configuration options to keep magic values in one location.-- Conversion data and message text are defined in separate modules.local config, maxsigfiglocal numdot  -- must be '.' or ',' or a character which works in a regexlocal numsep, numsep_remove, numsep_remove2local data_code, all_unitslocal text_codelocal varname        -- can be a code to use variable names that depend on valuelocal from_en_table  -- to translate an output string of en digits to local languagelocal to_en_table    -- to translate an input string of digits in local language to en-- Use translation_table in convert/text to change the following.local en_default           -- true uses lang=en unless convert has lang=local or local digitslocal group_method = 3     -- code for how many digits are in a grouplocal per_word = 'per'     -- for units like "liters per kilometer"local plural_suffix = 's'  -- only other useful value is probably '' to disable plural unit nameslocal omitsep              -- true to omit separator before local symbol/name-- All units should be defined in the data module. However, to cater for quick changes-- and experiments, any unknown unit is looked up in an extra data module, if it exists.-- That module would be transcluded in only a small number of pages, so there should be-- little server overhead from making changes, and changes should propagate quickly.local extra_module  -- name of module with extra unitslocal 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)-- Input is a string representing a number in en digits with '.' decimal mark,-- without digit grouping (which is done just after calling this).-- Return the translation of the string with numdot and digits in local language.if numdot ~= '.' thentext = text:gsub('%.', numdot)endif from_en_table thentext = text:gsub('%d', from_en_table)endreturn textendlocal function to_en(text)-- Input is a string representing a number in the local language with-- an optional numdot decimal mark and numsep digit grouping.-- Return the translation of the string with '.' mark and en digits,-- and no separators (they have to be removed here to handle cases like-- numsep = '.' and numdot = ',' with input "1.234.567,8").if to_en_table thentext = ustring.gsub(text, '%d', to_en_table)endif numsep_remove thentext = text:gsub(numsep_remove, '')endif numsep_remove2 thentext = text:gsub(numsep_remove2, '')endif numdot ~= '.' thentext = text:gsub(numdot, '.')endreturn textendlocal function decimal_mark(text)-- Return ',' if text probably is using comma for decimal mark, or has no decimal mark.-- Return '.' if text probably is using dot for decimal mark.-- Otherwise return nothing (decimal mark not known).if not text:find('[.,]') then return ',' endtext = text:gsub('^%-', ''):gsub('%+%d+/%d+$', ''):gsub('[Ee]%-?%d+$', '')local decimal =text:match('^0?([.,])%d+$') ortext:match('%d([.,])%d?%d?$') ortext:match('%d([.,])%d%d%d%d+$')if decimal then return decimal endif text:match('%.%d+%.') then return ',' endif text:match('%,%d+,') then return '.' endendlocal add_warning, with_separator  -- forward declarationslocal function to_en_with_check(text, parms)-- Version of to_en() for a wiki using numdot = ',' and numsep = '.' to check-- 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.-- From viwiki.if to_en_table thentext = ustring.gsub(text, '%d', to_en_table)endif decimal_mark(text) == '.' thenlocal original = texttext = text:gsub(',', '')  -- for example, interpret "1,234.5" as an enwiki valueif parms thenadd_warning(parms, 0, 'cvt_enwiki_num', original, with_separator({}, text))endelseif numsep_remove thentext = text:gsub(numsep_remove, '')endif numsep_remove2 thentext = text:gsub(numsep_remove2, '')endif numdot ~= '.' thentext = text:gsub(numdot, '.')endendreturn textendlocal function omit_separator(id)-- 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.-- The following kludge should be a sufficient test.if omitsep thenif id:sub(1, 2) == '-{' then  -- for "-{...}-" content language variantreturn trueendif id:byte() > 127 thenlocal first = usub(id, 1, 1)if first ~= 'Å' and first ~= '°' and first ~= 'µ' thenreturn trueendendendreturn id:sub(1, 1) == '/'  -- no separator before units like "/ha"endlocal spell_module  -- name of module that can spell numberslocal speller       -- function from that module to handle spelling (set if needed)local wikidata_module, wikidata_data_module  -- names of Wikidata moduleslocal wikidata_code, wikidata_data  -- exported tables from those modules (set if needed)local function set_config(args)-- Set configuration options from template #invoke or defaults.config = argsmaxsigfig = config.maxsigfig or 14  -- maximum number of significant figureslocal data_module, text_modulelocal sandbox = config.sandbox and ('/' .. config.sandbox) or ''data_module = "Module:Convert/data" .. sandboxtext_module = "Module:Convert/text" .. sandboxextra_module = "Module:Convert/extra" .. sandboxwikidata_module = "Module:Convert/wikidata" .. sandboxwikidata_data_module = "Module:Convert/wikidata/data" .. sandboxspell_module = "Module:ConvertNumeric"data_code = mw.loadData(data_module)text_code = mw.loadData(text_module)all_units = data_code.all_unitslocal translation = text_code.translation_tableif translation thennumdot = translation.numdotnumsep = translation.numsepif numdot == ',' and numsep == '.' thenif text_code.all_messages.cvt_enwiki_num thento_en = to_en_with_checkendendif translation.group thengroup_method = translation.groupendif translation.per_word thenper_word = translation.per_wordendif translation.plural_suffix thenplural_suffix = translation.plural_suffixendvarname = translation.varnamefrom_en_table = translation.from_enlocal use_workaround = trueif use_workaround then-- 2013-07-05 workaround bug by making a copy of the required table.-- mw.ustring.gsub fails with a table (to_en_table) as the replacement,-- if the table is accessed via mw.loadData.local source = translation.to_enif source thento_en_table = {}for k, v in pairs(source) doto_en_table[k] = vendendelseto_en_table = translation.to_enendif translation.lang == 'en default' thenen_default = true  -- for hiwikiendomitsep = translation.omitsep  -- for zhwikiendnumdot = config.numdot or numdot or '.'  -- decimal mark before fractional digitsnumsep = config.numsep or numsep or ','  -- group separator for numbers-- numsep should be ',' or '.' or '' or '&nbsp;' or a Unicode character.-- numsep_remove must work in a regex to identify separators to be removed.if numsep ~= '' thennumsep_remove = (numsep == '.') and '%.' or numsependif numsep ~= ',' and numdot ~= ',' thennumsep_remove2 = ','  -- so numbers copied from enwiki will workendendlocal function collection()-- Return a table to hold items.return {n = 0,add = function (self, item)self.n = self.n + 1self[self.n] = itemend,}endlocal function divide(numerator, denominator)-- Return integers quotient, remainder resulting from dividing the two-- given numbers, which should be unsigned integers.local quotient, remainder = floor(numerator / denominator), numerator % denominatorif not (0 <= remainder and remainder < denominator) then-- Floating point limits may need this, as in {{convert|160.02|Ym|ydftin}}.remainder = 0endreturn quotient, remainderendlocal function split(text, delimiter)-- Return a numbered table with fields from splitting text.-- The delimiter is used in a regex without escaping (for example, '.' would fail).-- Each field has any leading/trailing whitespace removed.local t = {}text = text .. delimiter  -- to get last itemfor item in text:gmatch('%s*(.-)%s*' .. delimiter) dotable.insert(t, item)endreturn tendlocal function strip(text)-- If text is a string, return its content with no leading/trailing-- whitespace. Otherwise return nil (a nil argument gives a nil result).if type(text) == 'string' thenreturn text:match("^%s*(.-)%s*$")endendlocal function table_len(t)-- Return length (<100) of a numbered table to replace #t which is-- documented to not work if t is accessed via mw.loadData().for i = 1, 100 doif t[i] == nil thenreturn i - 1endendendlocal function wanted_category(catkey, catsort, want_warning)-- Return message category if it is wanted in current namespace,-- otherwise return ''.local catlocal title = mw.title.getCurrentTitle()if title thenlocal nsdefault = '0'  -- default namespace: '0' = article; '0,10' = article and templatelocal namespace = title.namespacefor _, v in ipairs(split(config.nscat or nsdefault, ',')) doif namespace == tonumber(v) thencat = text_code.all_categories[want_warning and 'warning' or catkey]if catsort and catsort ~= '' and cat:sub(-2) == ']]' thencat = cat:sub(1, -3) .. '|' .. mw.text.nowiki(usub(catsort, 1, 20)) .. ']]'endbreakendendendreturn cat or ''endlocal function message(parms, mcode, is_warning)-- Return wikitext for an error message, including category if specified-- for the message type.-- mcode = numbered table specifying the message:--    mcode[1] = 'cvt_xxx' (string used as a key to get message info)--    mcode[2] = 'parm1' (string to replace '$1' if any in message)--    mcode[3] = 'parm2' (string to replace '$2' if any in message)--    mcode[4] = 'parm3' (string to replace '$3' if any in message)local msgif type(mcode) == 'table' thenif 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 ''endmsg = text_code.all_messages[mcode[1]]endparms.have_problem = truelocal function subparm(fmt, ...)local rep = {}for i, v in ipairs({...}) dorep['$' .. i] = vendreturn (fmt:gsub('$%d+', rep))endif msg thenlocal parts = {}local regex, replace = msg.regex, msg.replacefor i = 1, 3 dolocal limit = 40local s = mcode[i + 1]if s thenif regex and replace thens = s:gsub(regex, replace)limit = nil  -- allow long "should be" messagesend-- Escape user input so it does not break the message.-- To avoid tags (like {{convert|1<math>23</math>|m}}) breaking-- the mouseover title, any strip marker starting with char(127) is-- replaced with '...' (text not needing i18n).local appendlocal pos = s:find(string.char(127), 1, true)if pos thenappend = '...'s = s:sub(1, pos - 1)endif limit and ulen(s) > limit thens = usub(s, 1, limit)append = '...'ends = mw.text.nowiki(s) .. (append or '')elses = '?'endparts['$' .. i] = sendlocal function ispreview()-- Return true if a prominent message should be shown.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'endlocal success, revid = pcall(function ()return (parms.frame):preprocess('{{REVISIONID}}') end)return success and (revid == '')endlocal want_warning = is_warning andnot config.warnings and  -- show unobtrusive warnings if config.warnings not configurednot msg.nowarn           -- but use msg settings, not standard warning, if specifiedlocal 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 fmtkey = ispreview() and 'cvt_format_preview' or(want_warning and 'cvt_format2' or msg.format or 'cvt_format')local fmt = text_code.all_messages[fmtkey] or 'convert: bug'return subparm(fmt, title:gsub('"', '&quot;'), text, cat, anchor)endreturn 'Convert internal error: unknown message'endfunction add_warning(parms, level, key, text1, text2)  -- for forward declaration above-- 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.if level <= (tonumber(config.warnings) or 1) thenif parms.warnings == nil thenparms.warnings = message(parms, { key, text1, text2 }, true)endendendlocal function spell_number(parms, inout, number, numerator, denominator)-- Return result of spelling (number, numerator, denominator), or-- return nil if spelling is not available or not supported for given text.-- Examples (each value must be a string or nil):--   number  numerator  denominator  output--   ------  ---------  -----------  ---------------------   "1.23"    nil        nil        one point two three--    "1"      "2"        "3"        one and two thirds--    nil      "2"        "3"        two thirdsif not speller thenlocal function get_speller(module)return require(module).spell_numberendlocal successsuccess, speller = pcall(get_speller, spell_module)if not success or type(speller) ~= 'function' thenadd_warning(parms, 1, 'cvt_no_spell', 'spell')return nilendendlocal caseif parms.spell_upper == inout thencase = trueparms.spell_upper = nil  -- only uppercase first word in a multiple unitendlocal sp = not parms.opt_sp_uslocal adj = parms.opt_adjectivalreturn speller(number, numerator, denominator, case, sp, adj)end-------------------------------------------------------------------------- BEGIN: Code required only for built-in units.-- LATER: If need much more code, move to another module to simplify this module.local function speed_of_sound(altitude)-- This is for the Mach built-in unit of speed.-- Return speed of sound in metres per second at given altitude in feet.-- If no altitude given, use default (zero altitude = sea level).-- Table gives speed of sound in miles per hour at various altitudes:--   altitude = -17,499 to 402,499 feet-- mach_table[a + 4] = s where--   a = (altitude / 5000) rounded to nearest integer (-3 to 80)--   s = speed of sound (mph) at that altitude-- LATER: Should calculate result from an interpolation between the next-- lower and higher altitudes in table, rather than rounding to nearest.-- From: http://www.aerospaceweb.org/question/atmosphere/q0112.shtmllocal mach_table = {                                                       -- a =799.5, 787.0, 774.2, 761.207051,                                       -- -3 to  0748.0, 734.6, 721.0, 707.0, 692.8, 678.3, 663.5, 660.1, 660.1, 660.1,  --  1 to 10660.1, 660.1, 660.1, 662.0, 664.3, 666.5, 668.9, 671.1, 673.4, 675.6,  -- 11 to 20677.9, 683.7, 689.9, 696.0, 702.1, 708.1, 714.0, 719.9, 725.8, 731.6,  -- 21 to 30737.3, 737.7, 737.7, 736.2, 730.5, 724.6, 718.8, 712.9, 707.0, 701.0,  -- 31 to 40695.0, 688.9, 682.8, 676.6, 670.4, 664.1, 657.8, 652.9, 648.3, 643.7,  -- 41 to 50639.1, 634.4, 629.6, 624.8, 620.0, 615.2, 613.2, 613.2, 613.2, 613.5,  -- 51 to 60614.4, 615.3, 616.7, 619.8, 623.4, 629.7, 635.0, 641.1, 650.6, 660.0,  -- 61 to 70672.5, 674.3, 676.1, 677.9, 679.7, 681.5, 683.3, 685.1, 686.8, 688.6,  -- 71 to 80}altitude = altitude or 0local a = (altitude < 0) and -altitude or altitudea = floor(a / 5000 + 0.5)if altitude < 0 thena = -aendif a < -3 thena = -3elseif a > 80 thena = 80endreturn mach_table[a + 4] * 0.44704  -- mph converted to m/send-- END: Code required only for built-in units.------------------------------------------------------------------------local function add_style(parms, class)-- Add selected template style to parms if not already present.parms.templatestyles = parms.templatestyles or {}if not parms.templatestyles[class] thenparms.templatestyles[class] = parms.frame:extensionTag({name = 'templatestyles', args = { src = text_code.titles[class] }})endendlocal function get_styles(parms)-- Return string of required template styles, empty if none.if parms.templatestyles thenlocal t = {}for _, v in pairs(parms.templatestyles) dotable.insert(t, v)endreturn table.concat(t)endreturn ''endlocal function get_range(word)-- Return a range (string or table) corresponding to word (like "to"),-- or return nil if not a range word.local ranges = text_code.rangesreturn ranges.types[word] or ranges.types[ranges.aliases[word]]endlocal function check_mismatch(unit1, unit2)-- If unit1 cannot be converted to unit2, return an error message table.-- This allows conversion between units of the same type, and between-- Nm (normally torque) and ftlb (energy), as in gun-related articles.-- This works because Nm is the base unit (scale = 1) for both the-- primary type (torque), and the alternate type (energy, where Nm = J).-- A match occurs if the primary types are the same, or if unit1 matches-- the alternate type of unit2, and vice versa. That provides a whitelist-- of which conversions are permitted between normally incompatible types.if unit1.utype == unit2.utype or(unit1.utype == unit2.alttype and unit1.alttype == unit2.utype) thenreturn nilendreturn { 'cvt_mismatch', unit1.utype, unit2.utype }endlocal function override_from(out_table, in_table, fields)-- Copy the specified fields from in_table to out_table, but do not-- copy nil fields (keep any corresponding field in out_table).for _, field in ipairs(fields) doif in_table[field] thenout_table[field] = in_table[field]endendendlocal function shallow_copy(t)-- Return a shallow copy of table t.-- Do not need the features and overhead of the Scribunto mw.clone().local result = {}for k, v in pairs(t) doresult[k] = vendreturn resultendlocal unit_mt = {-- Metatable to get missing values for a unit that does not accept SI prefixes.-- Warning: The boolean value 'false' is returned for any missing field-- so __index is not called twice for the same field in a given unit.__index = function (self, key)local valueif key == 'name1' or key == 'sym_us' thenvalue = self.symbolelseif key == 'name2' thenvalue = self.name1 .. plural_suffixelseif key == 'name1_us' thenvalue = self.name1if not rawget(self, 'name2_us') then-- If name1_us is 'foot', do not make name2_us by appending plural_suffix.self.name2_us = self.name2endelseif key == 'name2_us' thenlocal raw1_us = rawget(self, 'name1_us')if raw1_us thenvalue = raw1_us .. plural_suffixelsevalue = self.name2endelseif key == 'link' thenvalue = self.name1elsevalue = falseendrawset(self, key, value)return valueend}local function prefixed_name(unit, name, index)-- Return unit name with SI prefix inserted at correct position.-- index = 1 (name1), 2 (name2), 3 (name1_us), 4 (name2_us).-- The position is a byte (not character) index, so use Lua's sub().local pos = rawget(unit, 'prefix_position')if type(pos) == 'string' thenpos = tonumber(split(pos, ',')[index])endif pos thenreturn name:sub(1, pos - 1) .. unit.si_name .. name:sub(pos)endreturn unit.si_name .. nameendlocal unit_prefixed_mt = {-- Metatable to get missing values for a unit that accepts SI prefixes.-- Before use, fields si_name, si_prefix must be defined.-- The unit must define _symbol, _name1 and-- may define _sym_us, _name1_us, _name2_us-- (_sym_us, _name2_us may be defined for a language using sp=us-- to refer to a variant unrelated to U.S. units).__index = function (self, key)local valueif key == 'symbol' thenvalue = self.si_prefix .. self._symbolif value == 'l' then value = 'L' endelseif key == 'sym_us' thenvalue = rawget(self, '_sym_us')if value thenvalue = self.si_prefix .. valueelsevalue = self.symbolendelseif key == 'name1' thenvalue = prefixed_name(self, self._name1, 1)elseif key == 'name2' thenvalue = rawget(self, '_name2')if value thenvalue = prefixed_name(self, value, 2)elsevalue = self.name1 .. plural_suffixendelseif key == 'name1_us' thenvalue = rawget(self, '_name1_us')if value thenvalue = prefixed_name(self, value, 3)elsevalue = self.name1endelseif key == 'name2_us' thenvalue = rawget(self, '_name2_us')if value thenvalue = prefixed_name(self, value, 4)elseif rawget(self, '_name1_us') thenvalue = self.name1_us .. plural_suffixelsevalue = self.name2endelseif key == 'link' thenvalue = self.name1elsevalue = falseendrawset(self, key, value)return valueend}local unit_per_mt = {-- 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-- 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)local valueif key == 'symbol' thenlocal per = self.perlocal unit1, unit2 = per[1], per[2]if unit1 thenvalue = unit1[key] .. '/' .. unit2[key]elsevalue = '/' .. unit2[key]endelseif key == 'sym_us' thenvalue = self.symbolelseif key == 'scale' thenlocal per = self.perlocal unit1, unit2 = per[1], per[2]value = (unit1 and unit1.scale or 1) * self.scalemultiplier / unit2.scaleelsevalue = falseendrawset(self, key, value)return valueend}local function make_per(unitcode, unit_table, ulookup)-- 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.local result = {unitcode = unitcode,utype = unit_table.utype,per = {}}override_from(result, unit_table, { 'invert', 'iscomplex', 'default', 'link', 'symbol', 'symlink' })result.symbol_raw = (result.symbol or false)  -- to distinguish between a defined exception and a metatable calculationlocal prefixfor i, v in ipairs(unit_table.per) doif 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] thenprefix = currency_text or velselocal success, t = ulookup(v)if not success then return false, t endresult.per[i] = tendendlocal multiplier = unit_table.multiplierif 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].utypelocal t = data_code.per_unit_fixups[utype]if t thenif type(t) == 'table' thenutype = t.utype or utyperesult.link = result.link or t.linkmultiplier = multiplier or t.multiplierelseutype = tendendresult.utype = utypeendresult.scalemultiplier = multiplier or 1result.vprefix = prefix or false  -- set to non-nil to avoid calling __indexreturn true, setmetatable(result, unit_per_mt)endlocal 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').-- If, for example, 'kg' is in this table, that entry is used;-- otherwise the prefix ('k') is applied to the base unit ('g').-- If unitcode is a known combination code (and if allowed by what),-- a table of output multiple unit tables is included in the result.-- For compatibility with the old template, an underscore in a unitcode is-- replaced with a space so usage like {{convert|350|board_feet}} works.-- Wikignomes may also put two spaces or "&nbsp;" in combinations, so-- replace underscore, "&nbsp;", and multiple spaces with a single space.utable = utable or parms.unittable or all_unitsfails = fails or {}depth = depth and depth + 1 or 1if depth > 9 then-- There are ways to mistakenly define units which result in infinite-- recursion when lookup() is called. That gives a long delay and very-- confusing error messages, so the depth parameter is used as a guard.return false, { 'cvt_lookup', unitcode }endif unitcode == nil or unitcode == '' thenreturn false, { 'cvt_no_unit' }endunitcode = 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)endlocal t = utable[unitcode]if t thenif t.shouldbe thenreturn false, { 'cvt_should_be', t.shouldbe }endif t.sp_us thenparms.opt_sp_us = trueendlocal target = t.target  -- nil, or unitcode is an alias for this targetif target thenlocal success, result = lookup(parms, target, what, utable, fails, depth)if not success then return false, result endoverride_from(result, t, { 'customary', 'default', 'link', 'symbol', 'symlink' })local multiplier = t.multiplierif multiplier thenresult.multiplier = tostring(multiplier)result.scale = result.scale * multiplierendreturn true, resultendif t.per thenreturn call_make_per(t)endlocal combo = t.combination  -- nil or a table of unitcodesif combo thenlocal multiple = t.multipleif what == 'no_combination' or (what == 'only_multiple' and not multiple) thenreturn false, { 'cvt_bad_unit', unitcode }end-- Recursively create a combination table containing the-- converter table of each unitcode.local result = { utype = t.utype, multiple = multiple, combination = {} }local cvt = result.combinationfor i, v in ipairs(combo) dolocal success, t = lookup(parms, v, multiple and 'no_combination' or 'only_multiple', utable, fails, depth)if not success then return false, t endcvt[i] = tendreturn true, resultendlocal result = shallow_copy(t)result.unitcode = unitcodeif result.prefixes thenresult.si_name = ''result.si_prefix = ''return true, setmetatable(result, unit_prefixed_mt)endreturn true, setmetatable(result, unit_mt)endlocal SIprefixes = text_code.SIprefixesfor plen = SIprefixes[1] or 2, 1, -1 do-- Look for an SI prefix; should never occur with an alias.-- Check for longer prefix first ('dam' is decametre).-- SIprefixes[1] = prefix maximum #characters (as seen by mw.ustring.sub).local prefix = usub(unitcode, 1, plen)local si = SIprefixes[prefix]if si thenlocal t = utable[usub(unitcode, plen+1)]if t and t.prefixes thenlocal result = shallow_copy(t)result.unitcode = unitcoderesult.si_name = parms.opt_sp_us and si.name_us or si.nameresult.si_prefix = si.prefix or prefixresult.scale = t.scale * 10 ^ (si.exponent * t.prefixes)return true, setmetatable(result, unit_prefixed_mt)endendend-- Accept user-defined combinations like "acre+m2+ha" or "acre m2 ha" for output.-- If '+' is used, each unit code can include a space, and any error is fatal.-- If ' ' is used and if each space-separated word is a unit code, it is a combo,-- but errors are not fatal so the unit code can be looked up as an extra unit.local err_is_fatallocal combo = collection()if unitcode:find('+', 1, true) thenerr_is_fatal = truefor item in (unitcode .. '+'):gmatch('%s*(.-)%s*%+') doif item ~= '' thencombo:add(item)endendelseif unitcode:find('%s') thenfor item in unitcode:gmatch('%S+') docombo:add(item)endendif combo.n > 1 thenlocal function lookup_combo()if what == 'no_combination' or what == 'only_multiple' thenreturn false, { 'cvt_bad_unit', unitcode }endlocal result = { combination = {} }local cvt = result.combinationfor i, v in ipairs(combo) dolocal success, t = lookup(parms, v, 'only_multiple', utable, fails, depth)if not success then return false, t endif i == 1 thenresult.utype = t.utypeelselocal mismatch = check_mismatch(result, t)if mismatch thenreturn false, mismatchendendcvt[i] = tendreturn true, resultendlocal success, result = lookup_combo()if success or err_is_fatal thenreturn success, resultendend-- 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 thenlocal engscale = text_code.eng_scales[exponent]if engscale thenlocal success, result = lookup(parms, baseunit, 'no_combination', utable, fails, depth)if success and not (result.offset or result.builtin or result.engscale) thenresult.unitcode = unitcode  -- 'e6cuft' not 'cuft'result.defkey = unitcode  -- key to lookup default exceptionresult.engscale = engscaleresult.scale = result.scale * 10 ^ tonumber(exponent)return true, resultendendend-- 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 thenreturn true, resultendendif 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 thenlocal success, extra = pcall(function () return require(extra_module).extra_units end)if success and type(extra) == 'table' thenextra_units = extraendendif extra_units then-- A unit in one data table might refer to a unit in the other table, so-- switch between them, relying on fails or depth to terminate loops.if not fails[unitcode] thenfails[unitcode] = truelocal other = (utable == all_units) and extra_units or all_unitslocal success, result = lookup(parms, unitcode, what, other, fails, depth)if success thenreturn true, resultendendendendif to_en_table then-- At fawiki it is common to translate all digits so a unit like "km2" becomes "km۲".local en_code = ustring.gsub(unitcode, '%d', to_en_table)if en_code ~= unitcode thenreturn lookup(parms, en_code, what, utable, fails, depth)endendreturn false, { 'cvt_unknown', unitcode }endlocal function valid_number(num)-- Return true if num is a valid number.-- In Scribunto (different from some standard Lua), when expressed as a string,-- overflow or other problems are indicated with text like "inf" or "nan"-- which are regarded as invalid here (each contains "n").if type(num) == 'number' and tostring(num):find('n', 1, true) == nil thenreturn trueendendlocal function hyphenated(name, parts)-- Return a hyphenated form of given name (for adjectival usage).-- The name may be linked and the target of the link must not be changed.-- Hypothetical examples:--   [[long ton|ton]]         →  [[long ton|ton]]          (no change)--   [[tonne|long ton]]       →  [[tonne|long-ton]]--   [[metric ton|long ton]]  →  [[metric ton|long-ton]]--   [[long ton]]             →  [[long ton|long-ton]]-- Input can also have multiple links in a single name like:--   [[United States customary units|U.S.]] [[US gallon|gallon]]--   [[mile]]s per [[United States customary units|U.S.]] [[quart]]--   [[long ton]]s per [[short ton]]-- Assume that links cannot be nested (never like "[[abc[[def]]ghi]]").-- This uses a simple and efficient procedure that works for most cases.-- Some units (if used) would require more, and can later think about-- adding a method to handle exceptions.-- The procedure is to replace each space with a hyphen, but-- not a space after ')' [for "(pre-1954&nbsp;US) nautical mile"], and-- not spaces immediately before '(' or in '(...)' [for cases like-- "British thermal unit (ISO)" and "Calorie (International Steam Table)"].if name:find(' ', 1, true) thenif parts thenlocal posif name:sub(1, 1) == '(' thenpos = name:find(')', 1, true)if pos thenreturn name:sub(1, pos+1) .. name:sub(pos+2):gsub(' ', '-')endelseif name:sub(-1) == ')' thenpos = name:find('(', 1, true)if pos thenreturn name:sub(1, pos-2):gsub(' ', '-') .. name:sub(pos-1)endendreturn name:gsub(' ', '-')endparts = collection()for before, item, after in name:gmatch('([^[]*)(%[%[[^[]*%]%])([^[]*)') doif item:find(' ', 1, true) thenlocal prefixlocal plen = item:find('|', 1, true)if plen thenprefix = item:sub(1, plen)item = item:sub(plen + 1, -3)elseprefix = item:sub(1, -3) .. '|'item = item:sub(3, -3)enditem = prefix .. hyphenated(item, parts) .. ']]'endparts:add(before:gsub(' ', '-') .. item .. after:gsub(' ', '-'))endif parts.n == 0 then-- No link like "[[...]]" was found in the original name.parts:add(hyphenated(name, parts))endreturn table.concat(parts)endreturn nameendlocal function hyphenated_maybe(parms, want_name, sep, id, inout)-- Return s, f where--   s = id, possibly modified--   f = true if hyphenated-- Possible modifications: hyphenate; prepend '-'; append mid text.if id == nil or id == '' thenreturn ''endlocal mid = (inout == (parms.opt_flip and 'out' or 'in')) and parms.mid or ''if want_name thenif parms.opt_adjectival thenreturn '-' .. hyphenated(id) .. mid, trueendif parms.opt_add_s and id:sub(-1) ~= 's' thenid = id .. 's'  -- for nowikiendendreturn sep .. id .. midendlocal function use_minus(text)-- Return text with Unicode minus instead of '-', if present.if text:sub(1, 1) == '-' thenreturn MINUS .. text:sub(2)endreturn textendlocal function digit_groups(parms, text, method)-- Return a numbered table of groups of digits (left-to-right, in local language).-- Parameter method is a number or nil:--   3 for 3-digit grouping (default), or--   2 for 3-then-2 grouping (only for digits before decimal mark).local len_rightlocal len_left = text:find('.', 1, true)if len_left thenlen_right = #text - len_leftlen_left = len_left - 1elselen_left = #textendlocal twos = method == 2 and len_left > 5local groups = collection()local run = len_leftlocal nif run < 4 or (run == 4 and parms.opt_comma5) thenif parms.opt_gaps thenn = runelsen = #textendelseif twos thenn = run % 2 == 0 and 1 or 2elsen = run % 3 == 0 and 3 or run % 3endwhile run > 0 dogroups:add(n)run = run - nn = (twos and run > 3) and 2 or 3endif len_right thenif groups.n == 0 thengroups:add(0)endif parms.opt_gaps and len_right > 3 thenlocal want4 = not parms.opt_gaps3  -- true gives no gap before trailing single digitlocal isfirst = truerun = len_rightwhile run > 0 don = (want4 and run == 4) and 4 or (run > 3 and 3 or run)if isfirst thenisfirst = falsegroups[groups.n] = groups[groups.n] + 1 + nelsegroups:add(n)endrun = run - nendelsegroups[groups.n] = groups[groups.n] + 1 + len_rightendendlocal pos = 1for i, length in ipairs(groups) dogroups[i] = from_en(text:sub(pos, pos + length - 1))pos = pos + lengthendreturn groupsendfunction with_separator(parms, text)  -- for forward declaration above-- Input text is a number in en digits with optional '.' decimal mark.-- Return an equivalent, formatted for display:--   with a custom decimal mark instead of '.', if wanted--   with thousand separators inserted, if wanted--   digits in local language-- The given text is like '123' or '123.' or '12345.6789'.-- The text has no sign (caller inserts that later, if necessary).-- When using gaps, they are inserted before and after the decimal mark.-- Separators are inserted only before the decimal mark.-- A trailing dot (as in '123.') is removed because their use appears to-- be accidental, and such a number should be shown as '123' or '123.0'.-- It is useful for convert to suppress the dot so, for example, '4000.'-- is a simple way of indicating that all the digits are significant.if text:sub(-1) == '.' thentext = text:sub(1, -2)endif #text < 4 or parms.opt_nocomma or numsep == '' thenreturn from_en(text)endlocal groups = digit_groups(parms, text, group_method)if parms.opt_gaps thenif groups.n <= 1 thenreturn groups[1] or ''endlocal 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 .. closeendreturn table.concat(groups, numsep)end-- An input value like 1.23e12 is displayed using scientific notation (1.23×10¹²).-- That also makes the output use scientific notation, except for small values.-- In addition, very small or very large output values use scientific notation.-- 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 function with_exponent(parms, show, exponent)-- Return wikitext to display the implied value in scientific notation.-- Input uses en digits; output uses digits in local language.return format(fmtpower, with_separator(parms, show), from_en('10'), use_minus(from_en(tostring(exponent))))endlocal function make_sigfig(value, sigfig)-- Return show, exponent that are equivalent to the result of-- converting the number 'value' (where value >= 0) to a string,-- rounded to 'sigfig' significant figures.-- The returned items are:--   show: a string of digits; no sign and no dot;--         there is an implied dot before show.--   exponent: a number (an integer) to shift the implied dot.-- Resulting value = tonumber('.' .. show) * 10^exponent.-- Examples:--   make_sigfig(23.456, 3) returns '235', 2 (.235 * 10^2).--   make_sigfig(0.0023456, 3) returns '235', -2 (.235 * 10^-2).--   make_sigfig(0, 3) returns '000', 1 (.000 * 10^1).if sigfig <= 0 thensigfig = 1elseif sigfig > maxsigfig thensigfig = maxsigfigendif value == 0 thenreturn string.rep('0', sigfig), 1endlocal exp, fracpart = math.modf(log10(value))if fracpart >= 0 thenfracpart = fracpart - 1exp = exp + 1endlocal digits = format('%.0f', 10^(fracpart + sigfig))if #digits > sigfig then-- Overflow (for sigfig=3: like 0.9999 rounding to "1000"; need "100").digits = digits:sub(1, sigfig)exp = exp + 1endassert(#digits == sigfig, 'Bug: rounded number has wrong length')return digits, expend-- Fraction output format.local fracfmt = {{ -- Like {{frac}} (fraction slash).'<span class="frac" role="math">{SIGN}<span class="num">{NUM}</span>&frasl;<span class="den">{DEN}</span></span>',  -- 1/2'<span class="frac" role="math">{SIGN}{WHOLE}<span class="sr-only">+</span><span class="num">{NUM}</span>&frasl;<span class="den">{DEN}</span></span>',  -- 1+2/3style = 'frac',},{ -- Like {{sfrac}} (stacked fraction, that is, horizontal bar).'<span class="sfrac tion" role="math">{SIGN}<span class="num">{NUM}</span><span class="sr-only">/</span><span class="den">{DEN}</span></span>',  -- 1//2'<span class="sfrac" role="math">{SIGN}{WHOLE}<span class="sr-only">+</span><span class="tion"><span class="num">{NUM}</span><span class="sr-only">/</span><span class="den">{DEN}</span></span></span>',  -- 1+2//3style = 'sfrac',},}local function format_fraction(parms, inout, negative, wholestr, numstr, denstr, do_spell, style)-- Return wikitext for a fraction, possibly spelled.-- Inputs use en digits and have no sign; output uses digits in local language.local wikitextif not style thenstyle = parms.opt_fraction_horizontal and 2 or 1endif wholestr == '' thenwholestr = nilendlocal substitute = {SIGN = negative and MINUS or '',WHOLE = wholestr and with_separator(parms, wholestr),NUM = from_en(numstr),DEN = from_en(denstr),}wikitext = fracfmt[style][wholestr and 2 or 1]:gsub('{(%u+)}', substitute)if do_spell thenif negative thenif wholestr thenwholestr = '-' .. wholestrelsenumstr = '-' .. numstrendendlocal s = spell_number(parms, inout, wholestr, numstr, denstr)if s thenreturn sendendadd_style(parms, fracfmt[style].style)return wikitextendlocal function format_number(parms, show, exponent, isnegative)-- Parameter show is a string or a table containing strings.-- Each string is a formatted number in en digits and optional '.' decimal mark.-- A table represents a fraction: integer, numerator, denominator;-- if a table is given, exponent must be nil.-- Return t where t is a table with fields:--   show = wikitext formatted to display implied value--          (digits in local language)--   is_scientific = true if show uses scientific notation--   clean = unformatted show (possibly adjusted and with inserted '.')--          (en digits)--   sign = '' or MINUS--   exponent = exponent (possibly adjusted)-- The clean and exponent fields can be used to calculate the-- rounded absolute value, if needed.---- The value implied by the arguments is found from:--   exponent is nil; and--   show is a string of digits (no sign), with an optional dot;--   show = '123.4' is value 123.4, '1234' is value 1234.0;-- or:--   exponent is an integer indicating where dot should be;--   show is a string of digits (no sign and no dot);--   there is an implied dot before show;--   show does not start with '0';--   show = '1234', exponent = 3 is value 0.1234*10^3 = 123.4.---- The formatted result:-- * Is for an output value and is spelled if wanted and possible.-- * Includes a Unicode minus if isnegative and not spelled.-- * Uses a custom decimal mark, if wanted.-- * Has digits grouped where necessary, if wanted.-- * Uses scientific notation if requested, or for very small or large values--   (which forces result to not be spelled).-- * Has no more than maxsigfig significant digits--   (same as old template and {{#expr}}).local xhi, xlo  -- these control when scientific notation (exponent) is usedif parms.opt_scientific thenxhi, xlo = 4, 2  -- default for output if input uses e-notationelseif parms.opt_scientific_always thenxhi, xlo = 0, 0  -- always use scientific notation (experimental)elsexhi, xlo = 10, 4  -- defaultendlocal sign = isnegative and MINUS or ''local maxlen = maxsigfiglocal tfracif type(show) == 'table' thentfrac = showshow = tfrac.wholestrassert(exponent == nil, 'Bug: exponent given with fraction')endif not tfrac and not exponent thenlocal integer, dot, decimals = show:match('^(%d*)(%.?)(.*)')if integer == '0' or integer == '' thenlocal zeros, figs = decimals:match('^(0*)([^0]?.*)')if #figs == 0 thenif #zeros > maxlen thenshow = '0.' .. zeros:sub(1, maxlen)endelseif #zeros >= xlo thenshow = figsexponent = -#zeroselseif #figs > maxlen thenshow = '0.' .. zeros .. figs:sub(1, maxlen)endelseif #integer >= xhi thenshow = integer .. decimalsexponent = #integerelsemaxlen = maxlen + #dotif #show > maxlen thenshow = show:sub(1, maxlen)endendendif exponent thenlocal function zeros(n)return string.rep('0', n)endif #show > maxlen thenshow = show:sub(1, maxlen)endif exponent > xhi or exponent <= -xlo or (exponent == xhi and show ~= '1' .. zeros(xhi - 1)) then-- 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 significandif #show > 1 thensignificand = show:sub(1, 1) .. '.' .. show:sub(2)elsesignificand = showendreturn {clean = '.' .. show,exponent = exponent,sign = sign,show = sign .. with_exponent(parms, significand, exponent-1),is_scientific = true,}endif exponent >= #show thenshow = show .. zeros(exponent - #show)  -- result has no dotelseif exponent <= 0 thenshow = '0.' .. zeros(-exponent) .. showelseshow = show:sub(1, exponent) .. '.' .. show:sub(exponent+1)endendlocal formatted_showif tfrac thenshow = tostring(tfrac.value)  -- to set clean in returned tableformatted_show = format_fraction(parms, 'out', isnegative, tfrac.wholestr, tfrac.numstr, tfrac.denstr, parms.opt_spell_out)elseif isnegative and show:match('^0.?0*$') thensign = ''  -- don't show minus if result is negative but rounds to zeroendformatted_show = sign .. with_separator(parms, show)if parms.opt_spell_out thenformatted_show = spell_number(parms, 'out', sign .. show) or formatted_showendendreturn {clean = show,sign = sign,show = formatted_show,is_scientific = false,  -- to avoid calling __index}endlocal function extract_fraction(parms, text, negative)-- If text represents a fraction, return--   value, altvalue, show, denominator-- where--   value is a number (value of the fraction in argument text)--   altvalue is an alternate interpretation of any fraction for the hands--        unit where "12.1+3/4" means 12 hands 1.75 inches--   show is a string (formatted text for display of an input value,--        and is spelled if wanted and possible)--   denominator is value of the denominator in the fraction-- Otherwise, return nil.-- Input uses en digits and '.' decimal mark (input has been translated).-- Output uses digits in local language and local decimal mark, if any.-------------------------------------------------------------------------- Originally this function accepted x+y/z where x, y, z were any valid-- numbers, possibly with a sign. For example '1.23e+2+1.2/2.4' = 123.5,-- and '2-3/8' = 1.625. However, such usages were found to be errors or-- misunderstandings, so since August 2014 the following restrictions apply:--   x (if present) is an integer or has a single digit after decimal mark--   y and z are unsigned integers--   e-notation is not accepted-- The overall number can start with '+' or '-' (so '12+3/4' and '+12+3/4'-- and '-12-3/4' are valid).-- Any leading negative sign is removed by the caller, so only inputs-- like the following are accepted here (may have whitespace):--   negative = false       false        true (there was a leading '-')--   text     = '2/3'       '+2/3'       '2/3'--   text     = '1+2/3'     '+1+2/3'     '1-2/3'--   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 = '/'endlocal numerator = tonumber(numstr)local denominator = tonumber(denstr)if numerator == nil or denominator == nil or (negative and leading_plus ~= '') thenreturn nilendlocal whole, wholestrif prefix == '' thenwholestr = ''whole = 0else-- 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 endif num2 == '' then  -- num2 must be '' or like '.1' but not '.' or '.12'wholestr = num1elseif #num2 ~= 2 then return nil endwholestr = num1 .. num2endif frac_sign ~= (negative and '-' or '+') then return nil endwhole = tonumber(wholestr)if whole == nil then return nil endendlocal value = whole + numerator / denominatorif not valid_number(value) then return nil endlocal altvalue = whole + numerator / (denominator * 10)local style = #slashes  -- kludge: 1 or 2 slashes can be used to select styleif style > 2 then style = 2 endlocal wikitext = format_fraction(parms, 'in', negative, leading_plus .. wholestr, numstr, denstr, parms.opt_spell_in, style)return value, altvalue, wikitext, denominatorendlocal function extract_number(parms, text, another, no_fraction)-- Return true, info if can extract a number from text,-- where info is a table with the result,-- or return false, t where t is an error message table.-- Input can use en digits or digits in local language and can-- have 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.-- Before processing, the input text is cleaned:-- * Any thousand separators (valid or not) are removed.-- * Any sign is replaced with '-' (if negative) or '' (otherwise).--   That replaces Unicode minus with '-'.-- If successful, the returned info table contains named fields:--   value    = a valid number--   altvalue = a valid number, usually same as value but different--              if fraction used (for hands unit)--   singular = true if value is 1 or -1 (to use singular form of units)--   clean    = cleaned text with any separators and sign removed--              (en digits and '.' decimal mark)--   show     = text formatted for output, possibly with ref strip markers--              (digits in local language and custom decimal mark)-- The resulting show:-- * Is for an input value and is spelled if wanted and possible.-- * Has a rounded value, if wanted.-- * Has digits grouped where necessary, if wanted.-- * If negative, a Unicode minus is used; otherwise the sign is--   '+' (if the input text used '+'), or is '' (if no sign in input).text = strip(text or '')local referencelocal pos = text:find('\127', 1, true)if pos thenlocal before = text:sub(1, pos - 1)local remainder = text:sub(pos)local refs = {}while #remainder > 0 dolocal ref, spacesref, spaces, remainder = remainder:match('^(\127[^\127]*UNIQ[^\127]*%-ref[^\127]*\127)(%s*)(.*)')if ref thentable.insert(refs, ref)elserefs = {}breakendendif #refs > 0 thentext = strip(before)reference = table.concat(refs)endendlocal clean = to_en(text, parms)if clean == '' thenreturn false, { another and 'cvt_no_num2' or 'cvt_no_num' }endlocal isnegative, propersign = false, ''  -- most common caselocal singular, show, denominatorlocal value = tonumber(clean)local altvalueif value thenlocal sign = clean:sub(1, 1)if sign == '+' or sign == '-' thenpropersign = (sign == '+') and '+' or MINUSclean = clean:sub(2)endif value < 0 thenisnegative = truevalue = -valueendelselocal valstrfor _, prefix in ipairs({ '-', MINUS, '&minus;' }) do-- Including '-' sets isnegative in case input is a fraction like '-2-3/4'.local plen = #prefixif clean:sub(1, plen) == prefix thenvalstr = clean:sub(plen + 1)if valstr:match('^%s') then  -- "- 1" is invalid but "-1 - 1/2" is okreturn false, { 'cvt_bad_num', text }endbreakendendif valstr thenisnegative = truepropersign = MINUSclean = valstrvalue = tonumber(clean)endif value == nil thenif not no_fraction thenvalue, altvalue, show, denominator = extract_fraction(parms, clean, isnegative)endif value == nil thenreturn false, { 'cvt_bad_num', text }endif value <= 1 thensingular = true  -- for example, "½ mile" or "one half mile" (singular unit)endendendif not valid_number(value) then  -- for example, "1e310" may overflowreturn false, { 'cvt_invalid_num' }endif show == nil then-- clean is a non-empty string with no spaces, and does not represent a fraction,-- and value = tonumber(clean) is a number >= 0.-- If the input uses e-notation, show will be displayed using a power of ten, but-- we use the number as given so it might not be normalized scientific notation.-- 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_riif precision thenlocal fmt = '%.' .. format('%d', precision) .. 'f'local result = fmt:format(tonumber(value) + 2e-14)  -- fudge for some common cases of bad roundingif not exponent thensingular = (tonumber(result) == 1)endreturn resultendreturn defaultendsingular = (value == 1)local scientificlocal significand, exponent = clean:match('^([%d.]+)[Ee]([+%-]?%d+)')if significand thenshow = with_exponent(parms, rounded(significand, significand, exponent), exponent)scientific = trueelseshow = with_separator(parms, rounded(value, clean))endshow = propersign .. showif parms.opt_spell_in thenshow = spell_number(parms, 'in', propersign .. rounded(value, clean)) or showscientific = falseendif scientific thenparms.opt_scientific = trueendendif isnegative and (value ~= 0) thenvalue = -valuealtvalue = -(altvalue or value)endreturn true, {value = value,altvalue = altvalue or value,singular = singular,clean = clean,show = show .. (reference or ''),denominator = denominator,}endlocal function get_number(text)-- Return v, f where:--   v = nil (text is not a number)-- or--   v = value of text (text is a number)--   f = true if value is an integer-- Input can use en digits or digits in local language or separators,-- but no Unicode minus, and no fraction.if text thenlocal number = tonumber(to_en(text))if number thenlocal _, fracpart = math.modf(number)return number, (fracpart == 0)endendendlocal function gcd(a, b)-- Return the greatest common denominator for the given values,-- which are known to be positive integers.if a > b thena, b = b, aendif a <= 0 thenreturn bendlocal r = b % aif r <= 0 thenreturn aendif r == 1 thenreturn 1endreturn gcd(r, a)endlocal function fraction_table(value, denominator)-- Return value as a string or a table:-- * If result is a string, there is no fraction, and the result--   is value formatted as a string of en digits.-- * If result is a table, it represents a fraction with named fields:--   wholestr, numstr, denstr (strings of en digits for integer, numerator, denominator).-- The result is rounded to the nearest multiple of (1/denominator).-- If the multiple is zero, no fraction is included.-- No fraction is included if value is very large as the fraction would-- be unhelpful, particularly if scientific notation is required.-- Input value is a non-negative number.-- Input denominator is a positive integer for the desired fraction.if value <= 0 thenreturn '0'endif denominator <= 0 or value > 1e8 thenreturn format('%.2f', value)endlocal integer, decimals = math.modf(value)local numerator = floor((decimals * denominator) +0.5 + 2e-14)  -- add fudge for some common cases of bad roundingif numerator >= denominator theninteger = integer + 1numerator = 0endlocal wholestr = tostring(integer)if numerator > 0 thenlocal div = gcd(numerator, denominator)if div > 1 thennumerator = numerator / divdenominator = denominator / divendreturn {wholestr = (integer > 0) and wholestr or '',numstr = tostring(numerator),denstr = tostring(denominator),value = value,}endreturn wholestrendlocal function preunits(count, preunit1, preunit2)-- If count is 1:--     ignore preunit2--     return p1-- else:--     preunit1 is used for preunit2 if the latter is empty--     return p1, p2-- where:--     p1 is text to insert before the input unit--     p2 is text to insert before the output unit--     p1 or p2 may be nil to mean "no preunit"-- Using '+' gives output like "5+ feet" (no space before, but space after).local function withspace(text, wantboth)-- Return text with space before and, if wantboth, after.-- However, no space is added if there is a space or '&nbsp;' or '-'-- at that position ('-' is for adjectival text).-- There is also no space if text starts with '&'-- (e.g. '&deg;' would display a degree symbol with no preceding space).local char = text:sub(1, 1)if char == '&' thenreturn text  -- an html entity can be used to specify the exact displayendif not (char == ' ' or char == '-' or char == '+') thentext = ' ' .. textendif wantboth thenchar = text:sub(-1, -1)if not (char == ' ' or char == '-' or text:sub(-6, -1) == '&nbsp;') thentext = text .. ' 'endendreturn textendlocal PLUS = '+ 'preunit1 = preunit1 or ''local trim1 = strip(preunit1)if count == 1 thenif trim1 == '' thenreturn nilendif trim1 == '+' thenreturn PLUSendreturn withspace(preunit1, true)endpreunit1 = withspace(preunit1)preunit2 = preunit2 or ''local trim2 = strip(preunit2)if trim1 == '+' thenif trim2 == '' or trim2 == '+' thenreturn PLUS, PLUSendpreunit1 = PLUSendif trim2 == '' thenif trim1 == '' thenreturn nil, nilendpreunit2 = preunit1elseif trim2 == '+' thenpreunit2 = PLUSelseif trim2 == '&#32;' then  -- trick to make preunit2 emptypreunit2 = nilelsepreunit2 = withspace(preunit2)endreturn preunit1, preunit2endlocal function range_text(range, want_name, parms, before, after, inout, options)-- Return before .. rtext .. after-- where rtext is the text that separates two values in a range.local rtext, adj_text, exceptionoptions = options or {}if type(range) == 'table' then-- Table must specify range text for ('off' and 'on') or ('input' and 'output'),-- and may specify range text for 'adj=on',-- and may specify exception = true.rtext = range[want_name and 'off' or 'on'] orrange[((inout == 'in') == (parms.opt_flip == true)) and 'output' or 'input']adj_text = range['adj']exception = range['exception']elsertext = rangeendif parms.opt_adjectival thenif want_name or (exception and parms.abbr_org == 'on') thenrtext = adj_text or rtext:gsub(' ', '-'):gsub('&nbsp;', '-')endendif rtext == '–' and (options.spaced or after:sub(1, #MINUS) == MINUS) thenrtext = '&nbsp;– 'endreturn before .. rtext .. afterendlocal function get_composite(parms, iparm, in_unit_table)-- Look for a composite input unit. For example, {{convert|1|yd|2|ft|3|in}}-- would result in a call to this function with--   iparm = 3 (parms[iparm] = "2", just after the first unit)--   in_unit_table = (unit table for "yd"; contains value 1 for number of yards)-- Return true, iparm, unit where--   iparm = index just after the composite units (7 in above example)--   unit = composite unit table holding all input units,-- or return true if no composite unit is present in parms,-- or return false, t where t is an error message table.local default, subinfolocal composite_units, count = { in_unit_table }, 1local fixups = {}local total = in_unit_table.valinfo[1].valuelocal subunit = in_unit_tablewhile subunit.subdivs do  -- subdivs is nil or a table of allowed subdivisionslocal subcode = strip(parms[iparm+1])local subdiv = subunit.subdivs[subcode] or subunit.subdivs[(all_units[subcode] or {}).target]if not subdiv thenbreakendlocal successsuccess, subunit = lookup(parms, subcode, 'no_combination')if not success then return false, subunit end  -- should never occursuccess, subinfo = extract_number(parms, parms[iparm])if not success then return false, subinfo endiparm = iparm + 2subunit.inout = 'in'subunit.valinfo = { subinfo }-- Recalculate total as a number of subdivisions.-- subdiv[1] = number of subdivisions per previous unit (integer > 1).total = total * subdiv[1] + subinfo.valueif not default then  -- set by the first subdiv with a default defineddefault = subdiv.defaultendcount = count + 1composite_units[count] = subunitif subdiv.unit or subdiv.name thenfixups[count] = { unit = subdiv.unit, name = subdiv.name, valinfo = subunit.valinfo }endendif count == 1 thenreturn true  -- no error and no composite unitendfor i, fixup in pairs(fixups) dolocal unit = fixup.unitlocal name = fixup.nameif not unit or (count > 2 and name) thencomposite_units[i].fixed_name = nameelselocal success, alternate = lookup(parms, unit, 'no_combination')if not success then return false, alternate end  -- should never occuralternate.inout = 'in'alternate.valinfo = fixup.valinfocomposite_units[i] = alternateendendreturn true, iparm, {utype = in_unit_table.utype,scale = subunit.scale,  -- scale of last (least significant) unitvalinfo = { { value = total, clean = subinfo.clean, denominator = subinfo.denominator } },composite = composite_units,default = default or in_unit_table.default}endlocal function translate_parms(parms, kv_pairs)-- Update fields in parms by translating each key:value in kv_pairs to terms-- used by this module (may involve translating from local language to English).-- 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.currency_text = nil  -- local testing can hold module in memory; must clear globalsif kv_pairs.adj and kv_pairs.sing then-- For enwiki (before translation), warn if attempt to use adj and sing-- as the latter is a deprecated alias for the former.if kv_pairs.adj ~= kv_pairs.sing and kv_pairs.sing ~= '' thenadd_warning(parms, 1, 'cvt_unknown_option', 'sing=' .. kv_pairs.sing)endkv_pairs.sing = nilendkv_pairs.comma = kv_pairs.comma or config.comma  -- for plwiki who want default comma=5for loc_name, loc_value in pairs(kv_pairs) dolocal en_name = text_code.en_option_name[loc_name]if en_name thenlocal en_value = text_code.en_option_value[en_name]if en_value == 'INTEGER' then  -- altitude_ft, altitude_m, frac, sigfigen_value = nilif loc_value == '' thenadd_warning(parms, 2, 'cvt_empty_option', loc_name)elselocal minimumlocal number, is_integer = get_number(loc_value)if en_name == 'sigfig' thenminimum = 1elseif en_name == 'frac' thenminimum = 2if number and number < 0 thenparms.opt_fraction_horizontal = truenumber = -numberendelseminimum = -1e6endif number and is_integer and number >= minimum thenen_value = numberelselocal mif en_name == 'frac' thenm = 'cvt_bad_frac'elseif en_name == 'sigfig' thenm = 'cvt_bad_sigfig'elsem = 'cvt_bad_altitude'endadd_warning(parms, 1, m, loc_name .. '=' .. loc_value)endendelseif en_value == 'TEXT' then  -- $, input, qid, qual, stylein, styleout, trackingen_value = loc_value ~= '' and loc_value or nil  -- accept non-empty user text with no validationif not en_value and (en_name == '$' or en_name == 'qid' or en_name == 'qual') thenadd_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_valueelseif 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 == ''endelseen_value = en_value[loc_value]if en_value and en_value:sub(-1) == '?' thenen_value = en_value:sub(1, -2)add_warning(parms, -1, 'cvt_deprecated', loc_name .. '=' .. loc_value)endif en_value == nil thenif loc_value == '' thenadd_warning(parms, 2, 'cvt_empty_option', loc_name)elseadd_warning(parms, 1, 'cvt_unknown_option', loc_name .. '=' .. loc_value)endelseif en_value == '' thenen_value = nil  -- an ignored option like adj=offelseif type(en_value) == 'string' and en_value:sub(1, 4) == 'opt_' thenfor _, v in ipairs(split(en_value, ',')) dolocal lhs, rhs = v:match('^(.-)=(.+)$')if rhs thenparms[lhs] = tonumber(rhs) or rhselseparms[v] = trueendenden_value = nilendendparms[en_name] = en_valueelseadd_warning(parms, 1, 'cvt_unknown_option', loc_name .. '=' .. loc_value)endendlocal abbr_entered = parms.abbrlocal cfg_abbr = config.abbrif cfg_abbr then-- Don't warn if invalid because every convert would show that warning.if cfg_abbr == 'on always' thenparms.abbr = 'on'elseif cfg_abbr == 'off always' thenparms.abbr = 'off'elseif parms.abbr == nil thenif cfg_abbr == 'on default' thenparms.abbr = 'on'elseif cfg_abbr == 'off default' thenparms.abbr = 'off'endendendif parms.abbr thenif parms.abbr == 'unit' thenparms.abbr = 'on'parms.number_word = trueendparms.abbr_org = parms.abbr  -- original abbr, before any flipelseif parms.opt_hand_hh thenparms.abbr_org = 'on'parms.abbr = 'on'elseparms.abbr = 'out'  -- default is to abbreviate output only (use symbol, not name)endif parms.opt_order_out then-- Disable options that do not work in a useful way with order=out.parms.opt_flip = nil  -- override adj=flipparms.opt_spell_in = nilparms.opt_spell_out = nilparms.opt_spell_upper = nilendif parms.opt_spell_out and not abbr_entered thenparms.abbr = 'off'  -- should show unit name when spelling the output valueendif parms.opt_flip thenlocal function swap_in_out(option)local value = parms[option]if value == 'in' thenparms[option] = 'out'elseif value == 'out' thenparms[option] = 'in'endendswap_in_out('abbr')swap_in_out('lk')if parms.opt_spell_in and not parms.opt_spell_out then-- For simplicity, and because it does not appear to be needed,-- user cannot set an option to spell the output only.parms.opt_spell_in = nilparms.opt_spell_out = trueendendif parms.opt_spell_upper thenparms.spell_upper = parms.opt_flip and 'out' or 'in'endif parms.opt_table or parms.opt_tablecen thenif abbr_entered == nil and parms.lk == nil thenparms.opt_values = trueendparms.table_align = parms.opt_table and 'right' or 'center'endif parms.table_align or parms.opt_sortable_on thenparms.need_table_or_sort = trueendlocal disp_joins = text_code.disp_joinslocal default_joins = disp_joins['b']parms.join_between = default_joins[3] or '; 'local disp = parms.dispif disp == nil then  -- special case for the most common settingparms.joins = default_joinselseif disp == 'x' then-- Later, parms.joins is set from the input parameters.else-- Old template does this.local abbr = parms.abbrif disp == 'slash' thenif abbr_entered == nil thendisp = 'slash-nbsp'elseif abbr == 'in' or abbr == 'out' thendisp = 'slash-sp'elsedisp = 'slash-nosp'endelseif disp == 'sqbr' thenif abbr == 'on' thendisp = 'sqbr-nbsp'elsedisp = 'sqbr-sp'endendparms.joins = disp_joins[disp] or default_joinsparms.join_between = parms.joins[3] or parms.join_betweenparms.wantname = parms.joins.wantnameendif (en_default and not parms.opt_lang_local and (parms[1] or ''):find('%d')) or parms.opt_lang_en thenfrom_en_table = nilendif en_default and from_en_table then-- For hiwiki: localized symbol/name is defined with the US symbol/name field,-- and is used if output uses localized numbers.parms.opt_sp_us = trueendreturn trueendlocal function get_values(parms)-- If successful, update parms and return true, v, i where--   v = table of input values--   i = index to next entry in parms after those processed here-- or return false, t where t is an error message table.local valinfo = collection()  -- numbered table of input valueslocal range = collection()  -- numbered table of range items (having, for example, 2 range items requires 3 input values)local had_nocomma  -- true if removed "nocomma" kludge from second parameter (like "tonocomma")local parm2 = strip(parms[2])if parm2 and parm2:sub(-7, -1) == 'nocomma' thenparms[2] = strip(parm2:sub(1, -8))parms.opt_nocomma = truehad_nocomma = trueendlocal function extractor(i)-- If the parameter is not a value, try unpacking it as a range ("1-23" for "1 to 23").-- However, "-1-2/3" is a negative fraction (-1⅔), so it must be extracted first.-- Do not unpack a parameter if it is like "3-1/2" which is sometimes incorrectly-- used instead of "3+1/2" (and which should not be interpreted as "3 to ½").-- Unpacked items are inserted into the parms table.-- The tail recursion allows combinations like "1x2 to 3x4".local valstr = strip(parms[i])  -- trim so any '-' as a negative sign will be at startlocal success, result = extract_number(parms, valstr, i > 1)if not success and valstr and i < 20 then  -- check i to limit abuselocal lhs, sep, rhs = valstr:match('^(%S+)%s+(%S+)%s+(%S.*)')if lhs and not (sep == '-' and rhs:match('/')) thenif sep:find('%d') thenreturn success, result  -- to reject {{convert|1 234 567|m}} with a decent message (en only)endparms[i] = rhstable.insert(parms, i, sep)table.insert(parms, i, lhs)return extractor(i)endif not valstr:match('%-.*/') thenfor _, sep in ipairs(text_code.ranges.words) dolocal start, stop = valstr:find(sep, 2, true)  -- start at 2 to skip any negative sign for range '-'if start thenparms[i] = valstr:sub(stop + 1)table.insert(parms, i, sep)table.insert(parms, i, valstr:sub(1, start - 1))return extractor(i)endendendendreturn success, resultendlocal i = 1local is_changewhile true dolocal success, info = extractor(i)  -- need to set parms.opt_nocomma before calling thisif not success then return false, info endi = i + 1if is_change theninfo.is_change = true  -- value is after "±" and so is a change (significant for range like {{convert|5|±|5|°C}})is_change = nilendvalinfo:add(info)local range_item = get_range(strip(parms[i]))if not range_item thenbreakendi = i + 1range:add(range_item)if type(range_item) == 'table' then-- 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_xparms.out_range_x = parms.out_range_x or range_item.out_range_xparms.abbr_range_x = parms.abbr_range_x or range_item.abbr_range_xis_change = range_item.is_range_changeendendif range.n > 0 thenif range.n > 30 then  -- limit abuse, although 4 is a more likely upper limitreturn false, { 'cvt_invalid_num' }  -- misleading message but it will doendparms.range = rangeelseif had_nocomma thenreturn false, { 'cvt_unknown', parm2 }endreturn true, valinfo, iendlocal function simple_get_values(parms)-- If input is like "{{convert|valid_value|valid_unit|...}}",-- return true, i, in_unit, in_unit_table-- i = index in parms of what follows valid_unit, if anything.-- The valid_value is not negative and does not use a fraction, and-- no options requiring further processing of the input are used.-- Otherwise, return nothing or return false, parm1 for caller to interpret.-- Testing shows this function is successful for 96% of converts in articles,-- and that on average it speeds up converts by 8%.local clean = to_en(strip(parms[1] or ''), parms)if parms.opt_ri or parms.opt_spell_in or #clean > 10 or not clean:match('^[0-9.]+$') thenreturn false, cleanendlocal value = tonumber(clean)if not value then return endlocal info = {value = value,altvalue = value,singular = (value == 1),clean = clean,show = with_separator(parms, clean),}local in_unit = strip(parms[2])local success, in_unit_table = lookup(parms, in_unit, 'no_combination')if not success then return endin_unit_table.valinfo = { info }return true, 3, in_unit, in_unit_tableendlocal function wikidata_call(parms, operation, ...)-- 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, ...)endlocal success, status, result = pcall(worker, ...)if success thenreturn status, resultendif parms.opt_sortable_debug then-- Use debug=yes to crash if an error while accessing Wikidata.error('Error accessing Wikidata: ' .. status, 0)endreturn false, { 'cvt_wd_fail' }endlocal 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--        converted to named arguments, and--   unit is the input unit 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-- unit code as the symbol and name, and with bad_mcode = message code table.-- MediaWiki removes leading and trailing whitespace from the values of-- named arguments. However, the values of numbered arguments include any-- whitespace entered in the template, and whitespace is used by some-- parameters (example: the numbered parameters associated with "disp=x").local kv_pairs = {}  -- table of input key:value pairs where key is a name; needed because cannot iterate parms and add new fields to itfor k, v in pairs(args) doif type(k) == 'number' or k == 'test' then  -- parameter "test" is reserved for testing and is not translatedparms[k] = velsekv_pairs[k] = vendendif parms.test == 'wikidata' thenlocal ulookup = function (ucode)-- Use empty table for parms so it does not accumulate results when used repeatedly.return lookup({}, ucode, 'no_combination')endreturn wikidata_call(parms, '_listunits', ulookup)endlocal success, msg = translate_parms(parms, kv_pairs)if not success then return false, msg endif parms.input thensuccess, msg = wikidata_call(parms, '_adjustparameters', parms, 1)if not success then return false, msg endendlocal success, i, in_unit, in_unit_table = simple_get_values(parms)if not success thenif 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' }endlocal valinfosuccess, valinfo, i = get_values(parms)if not success then return false, valinfo endin_unit = strip(parms[i])i = i + 1success, in_unit_table = lookup(parms, in_unit, 'no_combination')if not success thenin_unit = in_unit or ''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 unitendin_unit_table = setmetatable({symbol = in_unit, name2 = in_unit, utype = in_unit,scale = 1, default = '', defkey = '', linkey = '',bad_mcode = in_unit_table }, unit_mt)endin_unit_table.valinfo = valinfoendif parms.test == 'msg' then-- Am testing the messages produced when no output unit is specified, and-- the input unit has a missing or invalid default.-- Set two units for testing that.-- LATER: Remove this code.if in_unit == 'chain' thenin_unit_table.default = nil  -- no defaultelseif in_unit == 'rd' thenin_unit_table.default  = "ft!X!m"  -- an invalid expressionendendin_unit_table.inout = 'in'  -- this is an input unitif not parms.range thenlocal success, inext, composite_unit = get_composite(parms, i, in_unit_table)if not success then return false, inext endif composite_unit thenin_unit_table = composite_uniti = inextendendif in_unit_table.builtin == 'mach' then-- As with old template, a number following Mach as the input unit is the altitude.-- That is deprecated: should use altitude_ft=NUMBER or altitude_m=NUMBER.local success, infosuccess = tonumber(parms[i])  -- this will often work and will give correct result for values like 2e4 without forcing output scientific notationif success theninfo = { value = success }elsesuccess, info = extract_number(parms, parms[i], false, true)endif success theni = i + 1in_unit_table.altitude = info.valueendendlocal word = strip(parms[i])i = i + 1local precision, is_bad_precisionlocal function set_precision(text)local number, is_integer = get_number(text)if number thenif is_integer thenprecision = numberelseprecision = textis_bad_precision = trueendreturn true  -- text was used for precision, good or badendendif word and not set_precision(word) thenparms.out_unit = parms.out_unit or wordif set_precision(strip(parms[i])) theni = i + 1endendif parms.opt_adj_mid thenword = parms[i]i = i + 1if word then  -- mid-text wordsif word:sub(1, 1) == '-' thenparms.mid = wordelseparms.mid = ' ' .. wordendendendif parms.opt_one_preunit thenparms[parms.opt_flip and 'preunit2' or 'preunit1'] = preunits(1, parms[i])i = i + 1endif parms.disp == 'x' then-- Following is reasonably compatible with the old template.local first = parms[i] or ''local second = parms[i+1] or ''i = i + 2if strip(first) == '' then  -- user can enter '&#32;' rather than ' ' to avoid the defaultfirst = ' [&nbsp;' .. firstsecond = '&nbsp;]' .. secondendparms.joins = { first, second }elseif parms.opt_two_preunits thenlocal p1, p2 = preunits(2, parms[i], parms[i+1])i = i + 2if parms.preunit1 then-- To simplify documentation, allow unlikely use of adj=pre with disp=preunit-- (however, an output unit must be specified with adj=pre and with disp=preunit).parms.preunit1 = parms.preunit1 .. p1parms.preunit2 = p2elseparms.preunit1, parms.preunit2 = p1, p2endendif precision == nil thenif set_precision(strip(parms[i])) theni = i + 1endendif is_bad_precision thenadd_warning(parms, 1, 'cvt_bad_prec', precision)elseparms.precision = precisionendfor j = i, i + 3 dolocal parm = parms[j]  -- warn if find a non-empty extraneous parameterif parm and parm:match('%S') thenadd_warning(parms, 1, 'cvt_unknown_option', parm)breakendendreturn true, in_unit_tableendlocal function record_default_precision(parms, out_current, precision)-- If necessary, adjust parameters and return a possibly adjusted precision.-- When converting a range of values where a default precision is required,-- that default is calculated for each value because the result sometimes-- depends on the precise input and output values. This function may cause-- the entire convert process to be repeated in order to ensure that the-- same default precision is used for each individual convert.-- If that were not done, a range like 1000 to 1000.4 may give poor results-- because the first output could be heavily rounded, while the second is not.-- For range 1000.4 to 1000, this function can give the second convert the-- same default precision that was used for the first.if not parms.opt_round_each thenlocal maxdef = out_current.max_default_precisionif maxdef thenif maxdef < precision thenparms.do_convert_again = trueout_current.max_default_precision = precisionelseprecision = out_current.max_default_precisionendelseout_current.max_default_precision = precisionendendreturn precisionendlocal function default_precision(parms, invalue, inclean, denominator, outvalue, in_current, out_current, extra)-- Return a default value for precision (an integer like 2, 0, -2).-- If denominator is not nil, it is the value of the denominator in inclean.-- Code follows procedures used in old template.local fudge = 1e-14  -- {{Order of magnitude}} adds this, so we do toolocal prec, minprec, adjustlocal subunit_ignore_trailing_zerolocal subunit_more_precision  -- kludge for "in" used in input like "|2|ft|6|in"local composite = in_current.compositeif composite thensubunit_ignore_trailing_zero = true  -- input "|2|st|10|lb" has precision 0, not -1if composite[#composite].exception == 'subunit_more_precision' thensubunit_more_precision = true  -- do not use standard precision with input like "|2|ft|6|in"endendif denominator and denominator > 0 thenprec = math.max(log10(denominator), 1)else-- Count digits after decimal mark, handling cases like '12.345e6'.local exponentlocal integer, dot, decimals, expstr = inclean:match('^(%d*)(%.?)(%d*)(.*)')local e = expstr:sub(1, 1)if e == 'e' or e == 'E' thenexponent = tonumber(expstr:sub(2))endif dot == '' thenprec = subunit_ignore_trailing_zero and 0 or -integer:match('0*$'):len()elseprec = #decimalsendif exponent then-- So '1230' and '1.23e3' both give prec = -1, and '0.00123' and '1.23e-3' give 5.prec = prec - exponentendendif in_current.istemperature and out_current.istemperature then-- Converting between common temperatures (°C, °F, °R, K); not keVT.-- 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.-- Calculate minimum precision from absolute value.adjust = 0local kelvin = abs((invalue - in_current.offset) * in_current.scale)if kelvin < 1e-8 then  -- assume nonzero due to input or calculation precision problemminprec = 2elseminprec = 2 - floor(log10(kelvin) + fudge)  -- 3 sigfigs in kelvinendelseif invalue == 0 or outvalue <= 0 then-- We are never called with a negative outvalue, but it might be zero.-- This is special-cased to avoid calculation exceptions.return record_default_precision(parms, out_current, 0)endif out_current.exception == 'integer_more_precision' and floor(invalue) == invalue then-- With certain output units that sometimes give poor results-- with default rounding, use more precision when the input-- value is equal to an integer. An example of a poor result-- is when input 50 gives a smaller output than input 49.5.-- Experiment shows this helps, but it does not eliminate all-- surprises because it is not clear whether "50" should be-- interpreted as "from 45 to 55" or "from 49.5 to 50.5".adjust = -log10(in_current.scale)elseif subunit_more_precision then-- Conversion like "{{convert|6|ft|1|in|cm}}" (where subunit is "in")-- has a non-standard adjust value, to give more output precision.adjust = log10(out_current.scale) + 2elseadjust = log10(abs(invalue / outvalue))endadjust = adjust + log10(2)-- Ensure that the output has at least two significant figures.minprec = 1 - floor(log10(outvalue) + fudge)endif extra thenadjust = extra.adjust or adjustminprec = extra.minprec or minprecendreturn record_default_precision(parms, out_current, math.max(floor(prec + adjust), minprec))endlocal function convert(parms, invalue, info, in_current, out_current)-- Convert given input value from one unit to another.-- Return output_value (a number) if a simple convert, or-- return f, t where--   f = true, t = table of information with results, or--   f = false, t = error message table.local inscale = in_current.scalelocal outscale = out_current.scaleif not in_current.iscomplex and not out_current.iscomplex thenreturn invalue * (inscale / outscale)  -- minimize overhead for most common caseendif in_current.invert or out_current.invert then-- Inverted units, such as inverse length, inverse time, or-- fuel efficiency. Built-in units do not have invert set.if (in_current.invert or 1) * (out_current.invert or 1) < 0 thenreturn 1 / (invalue * inscale * outscale)endreturn invalue * (inscale / outscale)elseif in_current.offset then-- Temperature (there are no built-ins for this type of unit).if info.is_change thenreturn invalue * (inscale / outscale)endreturn (invalue - in_current.offset) * (inscale / outscale) + out_current.offsetelse-- Built-in unit.local in_builtin = in_current.builtinlocal out_builtin = out_current.builtinif in_builtin and out_builtin thenif in_builtin == out_builtin thenreturn invalueend-- There are no cases (yet) where need to convert from one-- built-in unit to another, so this should never occur.return false, { 'cvt_bug_convert' }endif in_builtin == 'mach' or out_builtin == 'mach' then-- Should check that only one altitude is given but am planning to remove-- in_current.altitude (which can only occur when Mach is the input unit),-- and out_current.altitude cannot occur.local alt = parms.altitude_ft or in_current.altitudeif not alt and parms.altitude_m thenalt = parms.altitude_m / 0.3048  -- 1 ft = 0.3048 mendlocal spd = speed_of_sound(alt)if in_builtin == 'mach' theninscale = spdreturn invalue * (inscale / outscale)endoutscale = spdlocal adjust = 0.1 / inscalereturn true, {outvalue = invalue * (inscale / outscale),adjust = log10(adjust) + log10(2),}elseif in_builtin == 'hand' then-- 1 hand = 4 inches; 1.2 hands = 6 inches.-- Decimals of a hand are only defined for the first digit, and-- the first fractional digit should be a number of inches (1, 2 or 3).-- However, this code interprets the entire fractional part as the number-- of inches / 10 (so 1.75 inches would be 0.175 hands).-- A value like 12.3 hands is exactly 12*4 + 3 inches; base default precision on that.local integer, fracpart = math.modf(invalue)local inch_value = 4 * integer + 10 * fracpart  -- equivalent number of incheslocal factor = inscale / outscaleif factor == 4 then-- Am converting to inches: show exact result, and use "inches" not "in" by default.if parms.abbr_org == nil thenout_current.usename = trueendlocal show = format('%g', abs(inch_value))  -- show and clean are unsignedif not show:find('e', 1, true) thenreturn true, {invalue = inch_value,outvalue = inch_value,clean = show,show = show,}endendlocal outvalue = (integer + 2.5 * fracpart) * factorlocal fracstr = info.clean:match('%.(.*)') or ''local fmtif fracstr == '' thenfmt = '%.0f'elsefmt = '%.' .. format('%d', #fracstr - 1) .. 'f'endreturn true, {invalue = inch_value,clean = format(fmt, inch_value),outvalue = outvalue,minprec = 0,}endendreturn false, { 'cvt_bug_convert' }  -- should never occurendlocal 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 thenstyle = style:gsub('"', '')if style ~= '' thenif style:sub(-1) ~= ';' thenstyle = style .. ';'endreturn styleendendreturn ''endlocal 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_valueif parms.opt_sortable_on thenlocal base = {  -- a fake unit with enough fields for a valid convertscale = 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 thenoutvalue = extra.outvalueendif 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 thenoutvalue = 0endendif scaled_top and outvalue ~= 0 thenoutvalue = scaled_top / outvalueendscaled_value = outvalueif not valid_number(outvalue) thenif outvalue < 0 thensortkey = '1000000000000000000'elsesortkey = '9000000000000000000'endelseif outvalue == 0 thensortkey = '5000000000000000000'elselocal mag = floor(log10(abs(outvalue)) + 1e-14)local prefixif outvalue > 0 thenprefix = 7000 + magelseprefix = 2999 - magoutvalue = outvalue + 10^(mag+1)endsortkey = format('%d', prefix) .. format('%015.0f', floor(outvalue * 10^(14-mag)))endendlocal sortspanif sortkey and not parms.table_align thensortspan = 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 = sortspanendif parms.table_align thenlocal sortif sortkey thensort = ' data-sort-value="' .. sortkey .. '"'if parms.opt_sortable_debug thenparms.join_before = '<span style="border:1px solid">' .. sortkey .. '</span>'endelsesort = ''endlocal style = 'style="text-align:' .. parms.table_align .. ';'local joins = {}for i = 1, 2 dojoins[i] = (i == 1 and '' or '\n|') .. style .. user_style(parms, i) .. '"' .. sort .. '|'endparms.table_joins = joinsendreturn sortspan, scaled_valueendlocal cvt_to_handlocal function cvtround(parms, info, in_current, out_current)-- 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,--      using the rounding specified in parms.--   singular = true if result (after rounding and ignoring any negative sign)--      is "1", or like "1.00", or is a fraction with value < 1;--   (and more fields shown below, and a calculated 'absvalue' field).-- or return false, t where t is an error message table.-- 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.if out_current.builtin == 'hand' thenreturn cvt_to_hand(parms, info, in_current, out_current)endlocal invalue = in_current.builtin == 'hand' and info.altvalue or info.valuelocal outvalue, extra = convert(parms, invalue, info, in_current, out_current)if parms.need_table_or_sort thenparms.need_table_or_sort = nil  -- process using first input value onlymake_table_or_sort(parms, invalue, info, in_current)endif extra thenif not outvalue then return false, extra endinvalue = extra.invalue or invalueoutvalue = extra.outvalueendif not valid_number(outvalue) thenreturn false, { 'cvt_invalid_num' }endlocal isnegativeif outvalue < 0 thenisnegative = trueoutvalue = -outvalueendlocal precision, show, exponentlocal denominator = out_current.fracif denominator thenshow = fraction_table(outvalue, denominator)elseprecision = parms.precisionif not precision thenif parms.sigfig thenshow, exponent = make_sigfig(outvalue, parms.sigfig)elseif parms.opt_round thenlocal n = parms.opt_roundif n == 0.5 thenlocal integer, fracpart = math.modf(floor(2 * outvalue + 0.5) / 2)if fracpart == 0 thenshow = format('%.0f', integer)elseshow = format('%.1f', integer + fracpart)endelseshow = format('%.0f', floor((outvalue / n) + 0.5) * n)endelseif in_current.builtin == 'mach' thenlocal sigfig = info.clean:gsub('^[0.]+', ''):gsub('%.', ''):len() + 1show, exponent = make_sigfig(outvalue, sigfig)elselocal inclean = info.cleanif extra theninclean = extra.clean or incleanshow = extra.showendif not show thenprecision = default_precision(parms, invalue, inclean, info.denominator, outvalue, in_current, out_current, extra)endendendendif precision thenif precision >= 0 thenlocal fudgeif precision <= 8 then-- Add a fudge to handle common cases of bad rounding due to inability-- to precisely represent some values. This makes the following work:-- {{convert|-100.1|C|K}} and {{convert|5555000|um|m|2}}.-- Old template uses #expr round, which invokes PHP round().-- LATER: Investigate how PHP round() works.fudge = 2e-14elsefudge = 0endlocal fmt = '%.' .. format('%d', precision) .. 'f'local successsuccess, show = pcall(format, fmt, outvalue + fudge)if not success thenreturn false, { 'cvt_big_prec', tostring(precision) }endelseprecision = -precision  -- #digits to zero (in addition to any digits after dot)local shift = 10 ^ precisionshow = format('%.0f', outvalue/shift)if show ~= '0' thenexponent = #show + precisionendendendlocal t = format_number(parms, show, exponent, isnegative)if type(show) == 'string' then-- Set singular using match because on some systems 0.99999999999999999 is 1.0.if exponent thent.singular = (exponent == 1 and show:match('^10*$'))elset.singular = (show == '1' or show:match('^1%.0*$'))endelset.fraction_table = showt.singular = (outvalue <= 1)  -- cannot have 'fraction == 1', but if it were possible it would be singularendt.raw_absvalue = outvalue  -- absolute value before roundingreturn true, setmetatable(t, {__index = function (self, key)if key == 'absvalue' then-- Calculate absolute value after rounding, if needed.local clean, exponent = rawget(self, 'clean'), rawget(self, 'exponent')local value = tonumber(clean)  -- absolute value (any negative sign has been ignored)if exponent thenvalue = value * 10^exponentendrawset(self, key, value)return valueendend })endfunction cvt_to_hand(parms, info, in_current, out_current)-- Convert input to hands, inches.-- Return true, t where t is a table with the conversion results;-- or return false, t where t is an error message table.if parms.abbr_org == nil thenout_current.usename = true  -- default is to show name not symbolendlocal precision = parms.precisionlocal frac = out_current.fracif not frac and precision and precision > 1 thenfrac = (precision == 2) and 2 or 4endlocal out_next = out_current.out_nextif out_next then-- Use magic knowledge to determine whether the next unit is inches without requiring i18n.-- The following ensures that when the output combination "hand in" is used, the inches-- value is rounded to match the hands value. Also, displaying say "61½" instead of 61.5-- is better as 61.5 implies the value is not 61.4.if out_next.exception == 'subunit_more_precision' thenout_next.frac = fracendend-- Convert to inches; calculate hands from that.local dummy_unit_table = { scale = out_current.scale / 4, frac = frac }local success, outinfo = cvtround(parms, info, in_current, dummy_unit_table)if not success then return false, outinfo endlocal tfrac = outinfo.fraction_tablelocal inches = outinfo.raw_absvalueif tfrac theninches = floor(inches)  -- integer part only; fraction added laterelseinches = floor(inches + 0.5)  -- a hands measurement never shows decimals of an inchendlocal hands, inches = divide(inches, 4)outinfo.absvalue = hands + inches/4  -- supposed to be the absolute rounded value, but this is close enoughlocal inchstr = tostring(inches)  -- '0', '1', '2' or '3'if precision and precision <= 0 then  -- using negative or 0 for precision rounds to nearest handhands = floor(outinfo.raw_absvalue/4 + 0.5)inchstr = ''elseif tfrac then-- Always show an integer before fraction (like "15.0½") because "15½" means 15-and-a-half hands.inchstr = numdot .. format_fraction(parms, 'out', false, inchstr, tfrac.numstr, tfrac.denstr)elseinchstr = numdot .. from_en(inchstr)endoutinfo.show = outinfo.sign .. with_separator(parms, format('%.0f', hands)) .. inchstrreturn true, outinfoendlocal function evaluate_condition(value, condition)-- Return true or false from applying a conditional expression to value,-- or throw an error if invalid.-- A very limited set of expressions is supported:--    v < 9--    v * 9 < 9-- where--    'v' is replaced with value--    9 is any number (as defined by Lua tonumber)--      only en digits are accepted--    '<' can also be '<=' or '>' or '>='-- In addition, the following form is supported:--    LHS and RHS-- where--    LHS, RHS = any of above expressions.local function compare(value, text)local arithop, factor, compop, limit = text:match('^%s*v%s*([*]?)(.-)([<>]=?)(.*)$')if arithop == nil thenerror('Invalid default expression', 0)elseif arithop == '*' thenfactor = tonumber(factor)if factor == nil thenerror('Invalid default expression', 0)endvalue = value * factorendlimit = tonumber(limit)if limit == nil thenerror('Invalid default expression', 0)endif compop == '<' thenreturn value < limitelseif compop == '<=' thenreturn value <= limitelseif compop == '>' thenreturn value > limitelseif compop == '>=' thenreturn value >= limitenderror('Invalid default expression', 0)  -- should not occurendlocal lhs, rhs = condition:match('^(.-%W)and(%W.*)')if lhs == nil thenreturn compare(value, condition)endreturn compare(value, lhs) and compare(value, rhs)endlocal function get_default(value, unit_table)-- Return true, s where s = name of unit's default output unit,-- or return false, t where t is an error message table.-- Some units have a default that depends on the input value-- (the first value if a range of values is used).-- If '!' is in the default, the first bang-delimited field is an-- expression that uses 'v' to represent the input value.-- Example: 'v < 120 ! small ! big ! suffix' (suffix is optional)-- evaluates 'v < 120' as a boolean with result-- 'smallsuffix' if (value < 120), or 'bigsuffix' otherwise.-- Input must use en digits and '.' decimal mark.local default = data_code.default_exceptions[unit_table.defkey or unit_table.symbol] or unit_table.defaultif not default thenlocal per = unit_table.perif per thenlocal function a_default(v, u)local success, ucode = get_default(v, u)if not success thenreturn '?'  -- an unlikely error has occurred; will cause lookup of default to failend-- 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 thenlocal combo = t.combinationif 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 1ucode = combo[i]endelse-- Try for an automatically generated combination.local item = ucode:match('^(.-)%+') or ucode:match('^(%S+)%s')if all_units[item] thenreturn itemendendreturn ucodeendlocal 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 denominatorreturn true, def1 .. '/' .. def2endreturn false, { 'cvt_no_default', unit_table.symbol }endif default:find('!', 1, true) == nil thenreturn true, defaultendlocal t = split(default, '!')if #t == 3 or #t == 4 thenlocal success, result = pcall(evaluate_condition, value, t[1])if success thendefault = result and t[2] or t[3]if #t == 4 thendefault = default .. t[4]endreturn true, defaultendendreturn false, { 'cvt_bad_default', unit_table.symbol }endlocal linked_pages  -- to record linked pages so will not link to the same page more than oncelocal 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] = nilendlocal function make_link(link, id, unit_table)-- Return wikilink "[[link|id]]", possibly abbreviated as in examples:--   [[Mile|mile]]  --> [[mile]]--   [[Mile|miles]] --> [[mile]]s-- However, just id is returned if:-- * 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).local link_keyif unit_table thenlink_key = unit_table.unitcode or unit_tableelselink_key = linkendif not link or link == '' or linked_pages[link_key] thenreturn idendlinked_pages[link_key] = true-- Following only works for language en, but it should be safe on other wikis,-- and overhead of doing it generally does not seem worthwhile.local l = link:sub(1, 1):lower() .. link:sub(2)if link == id or l == id thenreturn '[[' .. id .. ']]'elseif link .. 's' == id or l .. 's' == id thenreturn '[[' .. id:sub(1, -2) .. ']]s'elsereturn '[[' .. link .. '|' .. id .. ']]'endendlocal function variable_name(clean, unit_table)-- For slwiki, a unit name depends on the value.-- Parameter clean is the unsigned rounded value in en digits, as a string.-- Value             Source    Example for "m"-- integer 1:        name1     meter  (also is the name of the unit)-- integer 2:        var{1}    metra-- integer 3 and 4:  var{2}    metri-- integer else:     var{3}    metrov (0 and 5 or more)-- real/fraction:    var{4}    metra-- var{i} means the i'th field in unit_table.varname if it exists and has-- an i'th field, otherwise name2.-- Fields are separated with "!" and are not empty.-- A field for a unit using an SI prefix has the prefix name inserted,-- replacing '#' if found, or before the field otherwise.local vnameif clean == '1' thenvname = unit_table.name1elseif unit_table.varname thenlocal iif clean == '2' theni = 1elseif clean == '3' or clean == '4' theni = 2elseif clean:find('.', 1, true) theni = 4elsei = 3endif i > 1 and varname == 'pl' theni = i - 1endvname = split(unit_table.varname, '!')[i]endif vname thenlocal si_name = rawget(unit_table, 'si_name') or ''local pos = vname:find('#', 1, true)if pos thenvname = vname:sub(1, pos - 1) .. si_name .. vname:sub(pos + 1)elsevname = si_name .. vnameendreturn vnameendreturn unit_table.name2endlocal function linked_id(parms, unit_table, key_id, want_link, clean)-- Return final unit id (symbol or name), optionally with a wikilink,-- and update unit_table.sep if required.-- key_id is one of: 'symbol', 'sym_us', 'name1', 'name1_us', 'name2', 'name2_us'.local abbr_on = (key_id == 'symbol' or key_id == 'sym_us')if abbr_on and want_link thenlocal symlink = rawget(unit_table, 'symlink')if symlink thenreturn symlink  -- for exceptions that have the linked symbol built-inendendlocal multiplier = rawget(unit_table, 'multiplier')local per = unit_table.perif per thenlocal paren1, paren2 = '', ''  -- possible parentheses around bottom unitlocal unit1 = per[1]  -- top unit_table, or nillocal unit2 = per[2]  -- bottom unit_tableif abbr_on thenif not unit1 thenunit_table.sep = ''  -- no separator in "$2/acre"endif not want_link thenlocal symbol = unit_table.symbol_rawif symbol thenreturn symbol  -- for exceptions that have the symbol built-inendendif (unit2.symbol):find('⋅', 1, true) thenparen1, paren2 = '(', ')'endendlocal key_id2  -- unit2 is always singularif key_id == 'name2' thenkey_id2 = 'name1'elseif key_id == 'name2_us' thenkey_id2 = 'name1_us'elsekey_id2 = key_idendlocal resultif abbr_on thenresult = '/'elseif omitsep thenresult = per_wordelseif unit1 thenresult = ' ' .. per_word .. ' 'elseresult = per_word .. ' 'endif want_link and unit_table.link thenif abbr_on or not varname thenresult = (unit1 and linked_id(parms, unit1, key_id, false, clean) or '') .. result .. linked_id(parms, unit2, key_id2, false, '1')elseresult = (unit1 and variable_name(clean, unit1) or '') .. result .. variable_name('1', unit2)endif omit_separator(result) thenunit_table.sep = ''endreturn make_link(unit_table.link, result, unit_table)endif unit1 thenresult = linked_id(parms, unit1, key_id, want_link, clean) .. resultif unit1.sep thenunit_table.sep = unit1.sependelseif omitsep thenunit_table.sep = ''endreturn result .. paren1 .. linked_id(parms, unit2, key_id2, want_link, '1') .. paren2endif multiplier then-- A multiplier (like "100" in "100km") forces the unit to be plural.multiplier = from_en(multiplier)if not omitsep thenmultiplier = multiplier .. (abbr_on and '&nbsp;' or ' ')endif not abbr_on thenif key_id == 'name1' thenkey_id = 'name2'elseif key_id == 'name1_us' thenkey_id = 'name2_us'endendelsemultiplier = ''endlocal id = unit_table.fixed_name or ((varname and not abbr_on) and variable_name(clean, unit_table) or unit_table[key_id])if omit_separator(id) thenunit_table.sep = ''endif want_link thenlocal link = data_code.link_exceptions[unit_table.linkey or unit_table.symbol] or unit_table.linkif link thenlocal before = ''local i = unit_table.customaryif i == 1 and parms.opt_sp_us theni = 2  -- show "U.S." not "US"endif i == 3 and abbr_on theni = 4  -- abbreviate "imperial" to "imp"endlocal customary = text_code.customary_units[i]if customary then-- LATER: This works for language en only, but it's esoteric so ignore for now.local pertextif id:sub(1, 1) == '/' then-- Want unit "/USgal" to display as "/U.S. gal", not "U.S. /gal".pertext = '/'id = id:sub(2)elseif id:sub(1, 4) == 'per ' then-- Similarly want "per U.S. gallon", not "U.S. per gallon" (but in practice this is unlikely to be used).pertext = 'per 'id = id:sub(5)elsepertext = ''end-- Omit any "US"/"U.S."/"imp"/"imperial" from start of id since that will be inserted.local removes = (i < 3) and { 'US&nbsp;', 'US ', 'U.S.&nbsp;', 'U.S. ' } or { 'imp&nbsp;', 'imp ', 'imperial ' }for _, prefix in ipairs(removes) dolocal plen = #prefixif id:sub(1, plen) == prefix thenid = id:sub(plen + 1)breakendendbefore = pertext .. make_link(customary.link, customary[1]) .. ' 'endid = before .. make_link(link, id, unit_table)endendreturn multiplier .. idendlocal function make_id(parms, which, unit_table)-- Return id, f where--   id = unit name or symbol, possibly modified--   f = true if id is a name, or false if id is a symbol-- using the value for index 'which', and for 'in' or 'out' (unit_table.inout).-- Result is '' if no symbol/name is to be used.-- In addition, set unit_table.sep = ' ' or '&nbsp;' or ''-- (the separator that caller will normally insert before the id).if parms.opt_values thenunit_table.sep = ''return ''endlocal inout = unit_table.inoutlocal info = unit_table.valinfo[which]local abbr_org = parms.abbr_orglocal adjectival = parms.opt_adjectivallocal lk = parms.lklocal want_link = (lk == 'on' or lk == inout)local usename = unit_table.usenamelocal singular = info.singularlocal want_nameif usename thenwant_name = trueelseif abbr_org == nil thenif parms.wantname thenwant_name = trueendif unit_table.usesymbol thenwant_name = falseendendif want_name == nil thenlocal abbr = parms.abbrif abbr == 'on' or abbr == inout or (abbr == 'mos' and inout == 'out') thenwant_name = falseelsewant_name = trueendendendlocal keyif want_name thenif lk == nil and unit_table.builtin == 'hand' thenwant_link = trueendif parms.opt_use_nbsp thenunit_table.sep = '&nbsp;'elseunit_table.sep = ' 'endif parms.opt_singular thenlocal valueif inout == 'in' thenvalue = info.valueelsevalue = info.absvalueendif value then  -- some unusual units do not always set value fieldvalue = abs(value)singular = (0 < value and value < 1.0001)endendif unit_table.engscale then-- engscale: so "|1|e3kg" gives "1 thousand kilograms" (plural)singular = falseendkey = (adjectival or singular) and 'name1' or 'name2'if parms.opt_sp_us thenkey = key .. '_us'endelseif unit_table.builtin == 'hand' thenif parms.opt_hand_hh thenunit_table.symbol = 'hh'  -- LATER: might want i18n applied to thisendendunit_table.sep = '&nbsp;'key = parms.opt_sp_us and 'sym_us' or 'symbol'endreturn linked_id(parms, unit_table, key, want_link, info.clean), want_nameendlocal function decorate_value(parms, unit_table, which, number_word)-- If needed, update unit_table so values will be shown with extra information.-- For consistency with the old template (but different from fmtpower),-- the style to display powers of 10 includes "display:none" to allow some-- browsers to copy, for example, "10³" as "10^3", rather than as "103".local infolocal engscale = unit_table.engscalelocal prefix = unit_table.vprefixif engscale or prefix theninfo = unit_table.valinfo[which]if info.decorated thenreturn  -- do not redecorate if repeating convertendinfo.decorated = trueif engscale thenlocal inout = unit_table.inoutlocal abbr = parms.abbrif (abbr == 'on' or abbr == inout) and not parms.number_word theninfo.show = info.show ..'<span style="margin-left:0.2em">×<span style="margin-left:0.1em">' ..from_en('10') ..'</span></span><s style="display:none">^</s><sup>' ..from_en(tostring(engscale.exponent)) .. '</sup>'elseif number_word thenlocal number_idlocal lk = parms.lkif lk == 'on' or lk == inout thennumber_id = make_link(engscale.link, engscale[1])elsenumber_id = engscale[1]end-- WP:NUMERAL recommends "&nbsp;" in values like "12 million".info.show = info.show .. (parms.opt_adjectival and '-' or '&nbsp;') .. number_idendendif prefix theninfo.show = prefix .. info.showendendendlocal function process_input(parms, in_current)-- Processing required once per conversion.-- Return block of text to represent input (value/unit).if parms.opt_output_only or parms.opt_output_number_only or parms.opt_output_unit_only thenparms.joins = { '', '' }return ''endlocal first_unitlocal composite = in_current.composite  -- nil or table of unitsif composite thenfirst_unit = composite[1]elsefirst_unit = in_currentendlocal id1, want_name = make_id(parms, 1, first_unit)local sep = first_unit.sep  -- separator between value and unit, set by make_idlocal preunit = parms.preunit1if preunit thensep = ''  -- any separator is included in preunitelsepreunit = ''endif parms.opt_input_unit_only thenparms.joins = { '', '' }if composite thenlocal parts = { id1 }for i, unit in ipairs(composite) doif i > 1 thentable.insert(parts, (make_id(parms, 1, unit)))endendid1 = table.concat(parts, ' ')endif want_name and parms.opt_adjectival thenreturn preunit .. hyphenated(id1)endreturn  preunit .. id1endif parms.opt_also_symbol and not composite and not parms.opt_flip thenlocal join1 = parms.joins[1]if join1 == ' (' or join1 == ' [' thenparms.joins = { ' [' .. first_unit[parms.opt_sp_us and 'sym_us' or 'symbol'] .. ']' .. join1 , parms.joins[2] }endendif in_current.builtin == 'mach' and first_unit.sep ~= '' then  -- '' means omitsep with non-enwiki namelocal prefix = id1 .. '&nbsp;'local range = parms.rangelocal valinfo = first_unit.valinfolocal result = prefix .. valinfo[1].showif range then-- For simplicity and because more not needed, handle one range item only.local prefix2 = make_id(parms, 2, first_unit) .. '&nbsp;'result = range_text(range[1], want_name, parms, result, prefix2 .. valinfo[2].show, 'in', {spaced=true})endreturn preunit .. resultendif composite then-- Simplify: assume there is no range, and no decoration.local mid = (not parms.opt_flip) and parms.mid or ''local sep1 = '&nbsp;'local sep2 = ' 'if parms.opt_adjectival and want_name thensep1 = '-'sep2 = '-'endif omitsep and sep == '' then-- Testing the id of the most significant unit should be sufficient.sep1 = ''sep2 = ''endlocal parts = { first_unit.valinfo[1].show .. sep1 .. id1 }for i, unit in ipairs(composite) doif i > 1 thentable.insert(parts, unit.valinfo[1].show .. sep1 .. (make_id(parms, 1, unit)))endendreturn table.concat(parts, sep2) .. midendlocal add_unit = (parms.abbr == 'mos') orparms[parms.opt_flip and 'out_range_x' or 'in_range_x'] or(not want_name and parms.abbr_range_x)local range = parms.rangeif range and not add_unit thenunlink(first_unit)endlocal id = range and make_id(parms, range.n + 1, first_unit) or id1local extra, was_hyphenated = hyphenated_maybe(parms, want_name, sep, id, 'in')if was_hyphenated thenadd_unit = falseendlocal resultlocal valinfo = first_unit.valinfoif range thenfor i = 0, range.n dolocal number_wordif i == range.n thenadd_unit = falsenumber_word = trueenddecorate_value(parms, first_unit, i+1, number_word)local show = valinfo[i+1].showif add_unit thenshow = show .. first_unit.sep .. (i == 0 and id1 or make_id(parms, i+1, first_unit))endif i == 0 thenresult = showelseresult = range_text(range[i], want_name, parms, result, show, 'in')endendelsedecorate_value(parms, first_unit, 1, true)result = valinfo[1].showendreturn result .. preunit .. extraendlocal function process_one_output(parms, out_current)-- Processing required for each output unit.-- Return block of text to represent output (value/unit).local inout = out_current.inout  -- normally 'out' but can be 'in' for order=outlocal id1, want_name = make_id(parms, 1, out_current)local sep = out_current.sep  -- set by make_idlocal preunit = parms.preunit2if preunit thensep = ''  -- any separator is included in preunitelsepreunit = ''endif parms.opt_output_unit_only thenif want_name and parms.opt_adjectival thenreturn preunit .. hyphenated(id1)endreturn preunit .. id1endif out_current.builtin == 'mach' and out_current.sep ~= '' then  -- '' means omitsep with non-enwiki namelocal prefix = id1 .. '&nbsp;'local range = parms.rangelocal valinfo = out_current.valinfolocal result = prefix .. valinfo[1].showif range then-- For simplicity and because more not needed, handle one range item only.result = range_text(range[1], want_name, parms, result, prefix .. valinfo[2].show, inout, {spaced=true})endreturn preunit .. resultendlocal add_unit = (parms[parms.opt_flip and 'in_range_x' or 'out_range_x'] or(not want_name and parms.abbr_range_x)) andnot parms.opt_output_number_onlylocal range = parms.rangeif range and not add_unit thenunlink(out_current)endlocal id = range and make_id(parms, range.n + 1, out_current) or id1local extra, was_hyphenated = hyphenated_maybe(parms, want_name, sep, id, inout)if was_hyphenated thenadd_unit = falseendlocal resultlocal valinfo = out_current.valinfoif range thenfor i = 0, range.n dolocal number_wordif i == range.n thenadd_unit = falsenumber_word = trueenddecorate_value(parms, out_current, i+1, number_word)local show = valinfo[i+1].showif add_unit thenshow = show .. out_current.sep .. (i == 0 and id1 or make_id(parms, i+1, out_current))endif i == 0 thenresult = showelseresult = range_text(range[i], want_name, parms, result, show, inout)endendelsedecorate_value(parms, out_current, 1, true)result = valinfo[1].showendif parms.opt_output_number_only thenreturn resultendreturn result .. preunit .. extraendlocal function make_output_single(parms, in_unit_table, out_unit_table)-- Return true, item where item = wikitext of the conversion result-- for a single output (which is not a combination or a multiple);-- or return false, t where t is an error message table.if parms.opt_order_out and in_unit_table.unitcode == out_unit_table.unitcode thenout_unit_table.valinfo = in_unit_table.valinfoelseout_unit_table.valinfo = collection()for _, v in ipairs(in_unit_table.valinfo) dolocal success, info = cvtround(parms, v, in_unit_table, out_unit_table)if not success then return false, info endout_unit_table.valinfo:add(info)endendreturn true, process_one_output(parms, out_unit_table)endlocal function make_output_multiple(parms, in_unit_table, out_unit_table)-- Return true, item where item = wikitext of the conversion result-- for an output which is a multiple (like 'ftin');-- 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=outlocal 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 abbr = parms.abbrlocal abbr_org = parms.abbr_orglocal disp = parms.displocal want_name = (abbr_org == nil and (disp == 'or' or disp == 'slash')) ornot (abbr == 'on' or abbr == inout or abbr == 'mos')local want_link = (parms.lk == 'on' or parms.lk == inout)local mid = parms.opt_flip and parms.mid or ''local sep1 = '&nbsp;'local sep2 = ' 'if parms.opt_adjectival and want_name thensep1 = '-'sep2 = '-'endlocal do_spell = parms.opt_spell_outparms.opt_spell_out = nil  -- so the call to cvtround does not spell the valuelocal function make_result(info, isfirst)local fmt, outvalue, signlocal results = {}for i = 1, #combos dolocal tfrac, thisvalue, strforcelocal out_current = combos[i]out_current.inout = inoutlocal scale = multiple[i]if i == 1 then  -- least significant unit ('in' from 'ftin')local decimalsout_current.frac = out_unit_table.fraclocal success, outinfo = cvtround(parms, info, in_unit_table, out_current)if not success then return false, outinfo endif isfirst thenout_unit_table.valinfo = { outinfo }  -- in case output value of first least significant unit is neededendsign = outinfo.signtfrac = outinfo.fraction_tableif outinfo.is_scientific thenstrforce = outinfo.showdecimals = ''elseif tfrac thendecimals = ''elselocal show = outinfo.show  -- number as a string in local languagelocal p1, p2 = show:find(numdot, 1, true)decimals = p1 and show:sub(p2 + 1) or ''  -- text after numdot, if anyendfmt = '%.' .. ulen(decimals) .. 'f'  -- to reproduce precisionif decimals == '' thenif tfrac thenoutvalue = floor(outinfo.raw_absvalue)  -- integer part only; fraction added laterelseoutvalue = floor(outinfo.raw_absvalue + 0.5)  -- keep all integer digits of least significant unitendelseoutvalue = outinfo.absvalueendendif scale thenoutvalue, thisvalue = divide(outvalue, scale)elsethisvalue = outvalueendlocal idif want_name thenif varname thenlocal cleanif strforce or tfrac thenclean = '.1'  -- dummy value to force name for floating pointelseclean = format(fmt, thisvalue)endid = variable_name(clean, out_current)elselocal key = 'name2'if parms.opt_adjectival thenkey = 'name1'elseif tfrac thenif thisvalue == 0 thenkey = 'name1'endelseif parms.opt_singular thenif 0 < thisvalue and thisvalue < 1.0001 thenkey = 'name1'endelseif thisvalue == 1 thenkey = 'name1'endendid = out_current[key]endelseid = out_current['symbol']endif i == 1 and omit_separator(id) then-- Testing the id of the least significant unit should be sufficient.sep1 = ''sep2 = ''endif want_link thenlocal link = out_current.linkif link thenid = make_link(link, id, out_current)endendlocal strvallocal spell_inout = (i == #combos or outvalue == 0) and inout or ''  -- trick so the last value processed (first displayed) has uppercase, if requestedif strforce and outvalue == 0 thensign = ''  -- any sign is in strforcestrval = strforce  -- show small values in scientific notation; will only use least significant unitelseif tfrac thenlocal wholestr = (thisvalue > 0) and tostring(thisvalue) or nilstrval = format_fraction(parms, spell_inout, false, wholestr, tfrac.numstr, tfrac.denstr, do_spell)elsestrval = (thisvalue == 0) and from_en('0') or with_separator(parms, format(fmt, thisvalue))if do_spell thenstrval = spell_number(parms, spell_inout, strval) or strvalendendtable.insert(results, strval .. sep1 .. id)if outvalue == 0 thenbreakendfmt = '%.0f'  -- only least significant unit can have a non-integral valueendlocal reversed, count = {}, #resultsfor i = 1, count doreversed[i] = results[count + 1 - i]endreturn true, sign .. table.concat(reversed, sep2)endlocal valinfo = in_unit_table.valinfolocal success, result = make_result(valinfo[1], true)if not success then return false, result endlocal range = parms.rangeif range thenfor i = 1, range.n dolocal success, result2 = make_result(valinfo[i+1])if not success then return false, result2 endresult = range_text(range[i], want_name, parms, result, result2, inout, {spaced=true})endendreturn true, result .. midendlocal function process(parms, in_unit_table, out_unit_table)-- Return true, s, outunit where s = final wikitext result,-- or return false, t where t is an error message table.linked_pages = {}local success, bad_outputlocal bad_input_mcode = in_unit_table.bad_mcode  -- nil if input unit is a valid convert unitlocal out_unit = parms.out_unitif out_unit == nil or out_unit == '' or type(out_unit) == 'function' thenif bad_input_mcode or parms.opt_input_unit_only thenbad_output = ''elselocal getdef = type(out_unit) == 'function' and out_unit or get_defaultsuccess, out_unit = getdef(in_unit_table.valinfo[1].value, in_unit_table)parms.out_unit = out_unitif not success thenbad_output = out_unitendendendif not bad_output and not out_unit_table thensuccess, out_unit_table = lookup(parms, out_unit, 'any_combination')if success thenlocal mismatch = check_mismatch(in_unit_table, out_unit_table)if mismatch thenbad_output = mismatchendelsebad_output = out_unit_tableendendlocal lhs, rhslocal flipped = parms.opt_flip and not bad_input_mcodeif bad_output thenrhs = (bad_output == '') and '' or message(parms, bad_output)elseif parms.opt_input_unit_only thenrhs = ''elselocal combos  -- nil (for 'ft' or 'ftin'), or table of unit tables (for 'm ft')if not out_unit_table.multiple then  -- nil/false ('ft' or 'm ft'), or table of factors ('ftin')combos = out_unit_table.combinationendlocal frac = parms.frac  -- nil or denominator of fraction for output valuesif frac then-- Apply fraction to the unit (if only one), or to non-SI units (if a combination),-- except that if a precision is also specified, the fraction only applies to-- the hand unit; that allows the following result:-- {{convert|156|cm|in hand|1|frac=2}} → 156 centimetres (61.4 in; 15.1½ hands)-- However, the following is handled elsewhere as a special case:-- {{convert|156|cm|hand in|1|frac=2}} → 156 centimetres (15.1½ hands; 61½ in)if combos thenlocal precision = parms.precisionfor _, unit in ipairs(combos) doif unit.builtin == 'hand' or (not precision and not unit.prefixes) thenunit.frac = fracendendelseout_unit_table.frac = fracendendlocal outputs = {}local imax = combos and #combos or 1  -- 1 (single unit) or number of unit tablesif imax == 1 thenparms.opt_order_out = nil  -- only useful with an output combinationendif not flipped and not parms.opt_order_out then-- Process left side first so any duplicate links (from lk=on) are suppressed-- on right. Example: {{convert|28|e9pc|e9ly|abbr=off|lk=on}}lhs = process_input(parms, in_unit_table)endfor i = 1, imax dolocal success, itemlocal out_current = combos and combos[i] or out_unit_tableout_current.inout = 'out'if i == 1 thenif imax > 1 and out_current.builtin == 'hand' thenout_current.out_next = combos[2]  -- built-in hand can influence next unit in a combinationendif parms.opt_order_out thenout_current.inout = 'in'endendif out_current.multiple thensuccess, item = make_output_multiple(parms, in_unit_table, out_current)elsesuccess, item = make_output_single(parms, in_unit_table, out_current)endif not success then return false, item endoutputs[i] = itemendif parms.opt_order_out thenlhs = outputs[1]table.remove(outputs, 1)endlocal sep = parms.table_joins and parms.table_joins[2] or parms.join_betweenrhs = table.concat(outputs, sep)endif flipped or not lhs thenlocal input = process_input(parms, in_unit_table)if flipped thenlhs = rhsrhs = inputelselhs = inputendendif parms.join_before thenlhs = parms.join_before .. lhsendlocal wikitextif bad_input_mcode thenif bad_input_mcode == '' thenwikitext = lhselsewikitext = lhs .. message(parms, bad_input_mcode)endelseif parms.table_joins thenwikitext = parms.table_joins[1] .. lhs .. parms.table_joins[2] .. rhselsewikitext = lhs .. parms.joins[1] .. rhs .. parms.joins[2]endif parms.warnings and not bad_input_mcode thenwikitext = wikitext .. parms.warningsendreturn true, get_styles(parms) .. wikitext, out_unit_tableendlocal function main_convert(frame)-- Do convert, and if needed, do it again with higher default precision.local parms = { frame = frame }  -- will hold template arguments, after translationset_config(frame.args)local success, result = get_parms(parms, frame:getParent().args)if success thenif type(result) ~= 'table' thenreturn tostring(result)endlocal in_unit_table = resultlocal out_unit_tablefor _ = 1, 2 do  -- use counter so cannot get stuck repeating convertsuccess, result, out_unit_table = process(parms, in_unit_table, out_unit_table)if success and parms.do_convert_again thenparms.do_convert_again = falseelsebreakendendend-- If input=x gives a problem, the result should be just the user input-- (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 thenif success and not parms.have_problem thenreturn resultendlocal catif 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)endreturn parms.input_text .. (cat or '')endreturn success and result or message(parms, result)endlocal 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 knownunitcode = strip(unitcode)if unitcode == nil or unitcode == '' thenreturn nilendset_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/extraopt_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],}}endlocal success, unit_table = lookup(parms, unitcode, 'no_combination')if not success thenunit_table = setmetatable({symbol = unitcode, name2 = unitcode, utype = unitcode,scale = 1, default = '', defkey = '', linkey = '' }, unit_mt)endlocal value = tonumber(options.value) or 1local 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_valueif options.sort thensortspan, scaled_value = make_table_or_sort(parms, value, info, unit_table, options.scaled_top)endreturn {text = make_id(parms, 1, unit_table),sortspan = sortspan,scaled_value = scaled_value,unknown = not success and true or nil,}endreturn { convert = main_convert, _unit = _unit }