fbpx
วิกิพีเดีย

มอดูล:Convert/makeunit

คู่มือการใช้งานมอดูล[สร้าง]
-- This module generates the wikitext required at Module:Convert/data -- by reading and processing the wikitext of the master list of units -- (see conversion_data for the page title). -- -- Script method: -- * Read lines, ignoring everything before "== Conversions ==". -- * Process the following lines: -- * Find next level-3 heading like "=== Length ===". -- * Parse each following line starting with "|" -- (but ignore lines starting with "|-" or "|}". -- * Split such lines into fields (delimiter "||") and trim -- leading/trailing whitespace from each field. -- Remove any "colspan" at front of second field (symbol). -- * Remove thousand separators (commas) from the scale field. -- If the scale is a number, do not change it. -- Otherwise, it should be an expression like "5/9", in -- which case it is replaced by the value of the expression. -- * Remove wiki formatting '[[...]]' from the link field. -- * Remove redundant fields from the unit to reduce size of data table. -- * Create alternative forms of a unit such as an alias or a combination. -- * Stop processing when encounter end of text or a line starting -- with a level-2 heading ("==" but not "==="). -- * Repeat above for each heading listed at prepare_data(). -- * Output Lua source for the units table. -- -- -- Output has the following form. -- local all_units = { -- ["unitcode"] = { -- standard format -- name1 = "singular name", -- omitted if redundant -- name1_us = "singular name sp=us", -- omitted if redundant -- name2 = "plural name", -- omitted if redundant -- name2_us = "plural name sp=us", -- omitted if redundant -- symbol = "symbol", -- sym_us = "symbol sp=us", -- omitted if redundant -- usename = 1, -- omitted if empty -- utype = "unit type", -- from level-3 heading -- scale = 1, -- a value, if necessary from evaluating an expression -- subdivs = { ["ft"] = { 5280, default = "km" }, ["yd"] = { 1760 } } -- composite input; omitted if empty -- link = "title of article for wikilink", -- omitted if empty or redundant -- ... -- other values -- }, -- ["unitcode"] = { -- alternative format to generate an alias -- target = "unit code", -- ... -- optional values to override those of target -- }, -- ["unitcode"] = { -- alternative format to generate a "per" unit like $/acre or BTU/h -- per = {u1, u2}, -- numbered table of unitcodes (u1 may be a currency symbol) -- ... -- optional values -- }, -- ["unitcode"] = { -- alternative format to generate an error message -- shouldbe = "message that some other unit code should be used", -- }, -- ["unitcode"] = { -- alternative format for combination outputs (like 'm ft') -- combination = {u1, u2, ...}, -- numbered table of unitcodes -- utype = "unit type", -- as for standard format -- }, -- ["unitcode"] = { -- alternative format for output multiples (like 'ftin') -- combination = {u1, u2, ...}, -- numbered table of unitcodes -- multiple = {f1, f2, ...}, -- numbered table of integer factors -- utype = "unit type", -- as for standard format -- }, -- ... -- }  local ulower = mw.ustring.lower local usub = mw.ustring.sub local text_code  local specials = {  -- This table is used to add extra fields when defining some units which  -- require exceptions to normal processing.  -- Each key is in the local language, while each value is fixed text.  -- However, this script should NOT be edited.  -- Instead, the translation_table in Module:Convert/text can be edited,  -- and this script will replace sections of the following with localized  -- definitions from Module:Convert/text, if given.  -- Ask for assistance at [[:en:Module talk:Convert]].  -- LATER: It would be better if this was defined in the conversion data.  utype = {  -- ["unit type in local language"] = "name_used_in_this_script"  ["fuel efficiency"] = "type_fuel_efficiency",  ["length"] = "type_length",  ["temperature"] = "type_temperature",  ["volume"] = "type_volume",  },  ucode = {  exception = {  -- ["unit code in local language"] = "name_used_in_module_convert"  ["ft"] = "integer_more_precision",  ["in"] = "subunit_more_precision",  ["lb"] = "integer_more_precision",  },  istemperature = {  -- Common temperature scales (not keVT or MK).  -- ["unit code in local language"] = true  ["C"] = true,  ["F"] = true,  ["K"] = true,  ["R"] = true,  },  usesymbol = {  -- Use unit symbol not name if abbr not specified.  -- ["unit code in local language"] = 1  ["C"] = 1,  ["F"] = 1,  ["K"] = 1,  ["R"] = 1,  ["C-change"] = 1,  ["F-change"] = 1,  ["K-change"] = 1,  },  alttype = {  -- Unit has an alternate type that is a valid conversion.  -- ["unit code in local language"] = "alternate type in local language"  ["Nm"] = "energy",  ["ftlb"] = "torque",  ["ftlb-f"] = "torque",  ["ftlbf"] = "torque",  ["inlb"] = "torque",  ["inlb-f"] = "torque",  ["inlbf"] = "torque",  ["inoz-f"] = "torque",  ["inozf"] = "torque",  },  }, }  -- Module text for the local language (localization). -- A default table of text for enwiki is provided here. -- If needed for another wiki, wanted sections from the table can be -- copied into translation_table in Module:Convert/text. -- For example, copying and modifying only the titles section may give: -- -- local translation_table = { -- ... -- other items -- mtext = { -- titles = { -- -- name_used_in_this_script = 'Title of page' -- conversion_data = 'Modul:Convert/documentation/conversion data/dok', -- }, -- }, -- } local mtext = {  section_names = {  -- name_used_in_this_script = 'Section title used in conversion data'  overrides = 'การเขียนทับ',  conversions = 'การแปลง',  outmultiples = 'การส่งออกหลายค่า',  combinations = 'ชุดค่าผสม',  inmultiples = 'การนำเข้าหลายค่า',  defaults = 'ค่าปริยาย',  links = 'ลิงก์',  perunits = 'ค่าอัตโนมัติต่อหน่วย',  varnames = 'ชื่อตัวแปร',  },  titles = {  -- name_used_in_this_script = 'Title of page'  conversion_data = 'มอดูล:Convert/documentation/conversion data/doc',  },  messages = {  -- name_used_in_this_script = 'Error message ($1 = first parameter, $2 = second)'  m_als_bad = 'Alias has invalid text in field "$1".',  m_als_dup = 'Alias "$1" already defined.',  m_als_link = 'Alias "$1" must include a wikilink ("[[...]]") in the symlink text.',  m_als_mul = 'Alias "$1" has multiplier "$2" which is not a number.',  m_als_same = 'Should omit "$1" for alias "$2" because it is the same as its target.',  m_als_type = 'Target of alias "$1" has wrong type.',  m_als_undef = 'Primary unit must be defined before alias "=$1"',  m_cmb_miss = 'Missing unit code for a combination.',  m_cmb_none = 'No units specified for combination "$1"',  m_cmb_one = 'Only one unit specified for combination "$1"',  m_cmb_type = 'Unit "$1" in combination "$2" has wrong type.',  m_cmb_undef = 'Unit "$1" in combination "$2" not defined.',  m_cmp_def = 'Composite "$1" must specify a default unit code.',  m_cmp_int = 'Composite "$1" has components where scale ratios are not integers.',  m_cmp_inval = 'Composite "$1" has a component with an invalid scale, "$2".',  m_cmp_many = 'Composite "$1" has too many fields.',  m_cmp_miss = 'Missing unit code for a composite.',  m_cmp_order = 'Composite "$1" has components in wrong order or with invalid scales.',  m_cmp_scale = 'Alternate unit "$1" in composite "$2" has wrong scale.',  m_cmp_two = 'Composite "$1" must specify exactly two unit codes.',  m_cmp_type = 'Unit "$1" in composite "$2" has wrong type.',  m_cmp_undef = 'Unit "$1" in composite "$2" not defined.',  m_def_cond = 'Invalid condition in default "$1" for unit "$2".',  m_def_fmt = 'Default output "$1" for unit "$2" should have 2 or 3 "!".',  m_def_rpt = 'Default output "$1" for unit "$2" is repeated.',  m_def_same = 'Default output for unit "$1" is the same unit.',  m_def_type = 'Default output "$1" for unit "$2" has wrong type.',  m_def_undef = 'Default output "$1" for unit "$2" is not defined.',  m_dfs_code = 'Defaults section: no unit code specified.',  m_dfs_dup = 'Defaults section: unit "$1" has already been specified.',  m_dfs_none = 'Defaults section: unit "$1" has no default specified.',  m_dfs_sym = 'Defaults section: unit "$1" must have a symbol.',  m_dfs_two = 'Defaults section: unit "$1" should have two fields only.',  m_dfs_undef = 'Defaults section: unit "$1" is not defined.',  m_dup_code = 'Unit code "$1" has already been defined.',  m_error = 'Error:',  m_ftl_read = 'Could not read wikitext from "[[$1]]".',  m_ftl_table = '[[$1]] should export table "$2".',  m_ftl_type = 'Fatal error: unknown data type for "$1"',  m_hdg_lev2 = 'Level 2 heading "$1" not found.',  m_hdg_lev3 = 'No level 3 heading before: $1',  m_line_num = ' (line $1).',  m_lnk_brack = 'Link "$1" has wrong number of brackets.',  m_lnk_dup = 'Link exception "$1" is already defined.',  m_lnk_miss = 'Missing unit code for a link.',  m_lnk_none = 'No link defined for unit "$1".',  m_lnk_sym = 'Unit code "$1" for a link must have a symbol.',  m_lnk_two = 'Row for unit "$1" link should have two fields only.',  m_lnk_type = 'Link exception "$1" has wrong type.',  m_lnk_undef = 'Unit code "$1" for a link is not defined.',  m_miss_code = 'Missing unit code.',  m_miss_sym = 'Missing symbol.',  m_miss_type = 'Missing unit type.',  m_mul_int = 'Multiple "$1" has components where scale ratios are not integers.',  m_mul_miss = 'Missing unit code for a multiple.',  m_mul_none = 'No units specified for multiple "$1"',  m_mul_one = 'Only one unit specified for multiple "$1"',  m_mul_order = 'Multiple "$1" has components in wrong order or with invalid scales.',  m_mul_scale = 'Multiple "$1" has a component with an invalid scale, "$2".',  m_mul_std = 'Unit "$1" in multiple "$2" must be a standard unit.',  m_mul_type = 'Unit "$1" in multiple "$2" has wrong type.',  m_mul_undef = 'Unit "$1" in multiple "$2" not defined.',  m_no_title = 'Need title of page with unit definitions.',  m_ovr_dup = 'Override "$1" is already defined.',  m_ovr_miss = 'Missing unit code for an override.',  m_per_dup = 'Per unit "$1" already defined.',  m_per_empty = 'Unit "$1" has an empty field in the "per".',  m_per_fuel = 'Unit "$1" has invalid unit types for fuel efficiency.',  m_per_inv = 'Invalid field for a "per".',  m_per_two = 'Unit "$1" does not have exactly 2 fields in the "per".',  m_per_undef = 'Unit "$1" has undefined unit code "$2" in the "per".',  m_percent_s = 'Field "$1" must not contain "%s".',  m_pfx_bad = 'Unknown prefix: "$1".',  m_pfx_name = 'Unit with Prefix set must include Name.',  m_scl_bad = 'Scale expression is invalid: "$1".',  m_scl_miss = 'Missing scale.',  m_scl_oflow = 'Scale expression gives an invalid value: "$1".',  m_var_cnt = 'Variable names section: each row must have the configured number of columns.',  m_var_dup = 'Unit "$1" already has a variable name.',  m_var_miss = 'Missing field for a variable name.',  m_var_undef = 'Unit "$1" in variable names is not defined.',  m_warning = 'Warning:',  m_wrn_more = ' (and more not shown)',  m_wrn_nbsp = 'Line $1 contains a nonbreaking space.',  m_wrn_nodef = 'Units with the following unit codes have no default output.',  m_wrn_ucode = ' $1',  }, }  local function message(key, ...)  -- Return a message from the message table, which can be localized.  -- '$1', '$2', ... are replaced with the first, second, ... parameters,  -- each of which must be a string or a number.  -- The global variable is_test_run can be set by a testing program to  -- check the messages generated by this program.  local rep = {}  for i, v in ipairs({...}) do  rep['$' .. i] = v  end  key = key or '???'  local extra  if is_test_run and key ~= 'm_line_num' then  extra = key .. ': '  else  extra = ''  end  return extra .. string.gsub(mtext.messages[key] or key, '$%d+', rep) end  local function quit(key, ...)  -- Use error() to pass an error message to the surrounding pcall().  error(message(key, ...), 0) end  local function quit_no_message()  -- Throw an error.  -- This is used in some functions which can throw an error with a message,  -- but where the message is in fact never displayed because the calling  -- function uses pcall to catch errors, and any message is ignored.  -- Using this function documents that the message (which may be useful in  -- some other application) does not need translation as it never appears.  error('this message is not displayed', 0) end  local function collection()  -- Return a table to hold items.  return {  n = 0,  add = function (self, item)  self.n = self.n + 1  self[self.n] = item  end,  pop = function (self, item)  if self.n > 0 then  local top = self[self.n]  self.n = self.n - 1  return top  end  end,  join = function (self, sep)  return table.concat(self, sep or '\n')  end,  } end  local warnings = collection() local function add_warning(key, ...)  -- Add a warning that will be inserted before the final result.  warnings:add(message(key, ...)) end  ---Begin code to evaluate expressions----------------------------------- -- This is needed because Lua's loadstring() is not available in Scribunto, -- and each scale value can be specifed as an expression such as "5/9". -- More complex expressions are supported, including use of parentheses -- and the binary operators: + - * / ^  local operators = {  ['+'] = { precedence = 1, associativity = 1, func = function (a, b) return a + b end },  ['-'] = { precedence = 1, associativity = 1, func = function (a, b) return a - b end },  ['*'] = { precedence = 2, associativity = 1, func = function (a, b) return a * b end },  ['/'] = { precedence = 2, associativity = 1, func = function (a, b) return a / b end },  ['^'] = { precedence = 3, associativity = 2, func = function (a, b) return a ^ b end },  ['('] = '(',  [')'] = ')', }  local function tokenizer(text)  -- Function 'next' returns the next token which is one of:  -- number  -- table (operator)  -- string ('(' or ')')  -- nil (end of text)  -- If invalid, an error is thrown.  -- The number is unsigned (unary operators are not supported).  return {  pos = 1,  maxpos = #text,  text = text,  next = function(self)  if self.pos <= self.maxpos then  local p1, p2, hit = self.text:find('^%s*([+%-*/^()])', self.pos)  if hit then  self.pos = p2 + 1  return operators[hit]  end  p1, p2, hit = self.text:find('^%s*(%d*%.?%d*[eE][+-]?%d*)', self.pos)  if not hit then  p1, p2, hit = self.text:find('^%s*(%d*%.?%d*)', self.pos)  end  local value = tonumber(hit)  if value then  self.pos = p2 + 1  return value  end  quit_no_message('invalid number "' .. self.text:sub(self.pos) .. '"')  end  end  } end  local function evaluate_tokens(tokens, inparens)  -- Return the value from evaluating tokenized expression, or throw an error.  local numstack, opstack = collection(), collection()  local function perform_ops(precedence, associativity)  while opstack.n > 0 and (opstack[opstack.n].precedence > precedence or  (opstack[opstack.n].precedence == precedence and associativity == 1)) do  local rhs = numstack:pop()  local lhs = numstack:pop()  if not (rhs and lhs) then quit_no_message('missing number') end  local op = opstack:pop()  numstack:add(op.func(lhs, rhs))  end  end  local token_last  local function set_state(token_type)  if token_last == token_type then  local missing = (token_type == 'number') and 'operator' or 'number'  quit_no_message('missing ' .. missing)  end  token_last = token_type  end  while true do  local token = tokens:next()  if type(token) == 'number' then  set_state('number')  numstack:add(token)  elseif type(token) == 'table' then  set_state('operator')  perform_ops(token.precedence, token.associativity)  opstack:add(token)  elseif token == '(' then  set_state('number')  numstack:add(evaluate_tokens(tokens, true))  elseif token == ')' then  if inparens then  break  end  quit_no_message('unbalanced parentheses')  else  break  end  end  perform_ops(0)  if numstack.n > 1 then quit_no_message('missing operator') end  if numstack.n < 1 then quit_no_message('missing number') end  return numstack:pop() end  local function evaluate(expression)  -- Return value (a number) from evaluating expression (a string),  -- or throw an error if invalid.  -- This is not bullet proof, but it should support the expressions used.  return evaluate_tokens(tokenizer(expression)) end ---End code to evaluate expressions------------------------------------- ---Begin code adapted from Module:Convert-------------------------------  local plural_suffix = 's' -- may be changed from translation.plural_suffix below  local function shallow_copy(t)  -- Return a shallow copy of t.  -- Do not need the features and overhead of mw.clone() provided by Scribunto.  local result = {}  for k, v in pairs(t) do  result[k] = v  end  return result end  local 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 item  for item in text:gmatch('%s*(.-)%s*' .. delimiter) do  table.insert(t, item)  end  return t end  local 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 value  if key == 'name1' or key == 'sym_us' then  value = self.symbol  elseif key == 'name2' then  value = self.name1 .. plural_suffix  elseif key == 'name1_us' then  value = self.name1  if not rawget(self, 'name2_us') then  -- If name1_us is 'foot', do not make name2_us by appending plural_suffix.  self.name2_us = self.name2  end  elseif key == 'name2_us' then  local raw1_us = rawget(self, 'name1_us')  if raw1_us then  value = raw1_us .. plural_suffix  else  value = self.name2  end  elseif key == 'link' then  value = self.name1  else  value = false  end  rawset(self, key, value)  return value  end }  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' then  pos = tonumber(split(pos, ',')[index])  end  if pos then  return name:sub(1, pos - 1) .. unit.si_name .. name:sub(pos)  end  return unit.si_name .. name end  local 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 value  if key == 'symbol' then  value = self.si_prefix .. self._symbol  elseif key == 'sym_us' then  value = rawget(self, '_sym_us')  if value then  value = self.si_prefix .. value  else  value = self.symbol  end  elseif key == 'name1' then  value = prefixed_name(self, self._name1, 1)  elseif key == 'name2' then  value = rawget(self, '_name2')  if value then  value = prefixed_name(self, value, 2)  else  value = self.name1 .. plural_suffix  end  elseif key == 'name1_us' then  value = rawget(self, '_name1_us')  if value then  value = prefixed_name(self, value, 3)  else  value = self.name1  end  elseif key == 'name2_us' then  value = rawget(self, '_name2_us')  if value then  value = prefixed_name(self, value, 4)  elseif rawget(self, '_name1_us') then  value = self.name1_us .. plural_suffix  else  value = self.name2  end  elseif key == 'link' then  value = self.name1  else  value = false  end  rawset(self, key, value)  return value  end }  local function lookup(units, unitcode, sp, what)  -- Return a copy of the unit if found, or return nil.  -- In this cut-down code, sp is always nil, and what is ignored.  local t = units[unitcode]  if t then  if t.shouldbe then  return nil  end  local result = shallow_copy(t)  if result.prefixes then  result.si_name = ''  result.si_prefix = ''  return setmetatable(result, unit_prefixed_mt)  end  return setmetatable(result, unit_mt)  end  local SIprefixes = text_code.SIprefixes  for 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 then  local t = units[usub(unitcode, plen+1)]  if t and t.prefixes then  local result = shallow_copy(t)  if (sp == 'us' or t.sp_us) and si.name_us then  result.si_name = si.name_us  else  result.si_name = si.name  end  result.si_prefix = si.prefix or prefix  -- In this script, each scale is a string.  result.scale = tostring(tonumber(t.scale) * 10 ^ (si.exponent * t.prefixes))  result.prefixes = nil -- a prefixed unit does not take more prefixes (in this script, the returned unit may be added to the list of units)  return setmetatable(result, unit_prefixed_mt)  end  end  end  local exponent, baseunit = unitcode:match('^e(%d+)(.*)')  if exponent then  local engscale = text_code.eng_scales[exponent]  if engscale then  local result = lookup(units, baseunit, sp, 'no_combination')  if not result then return nil end  if not (result.offset or result.builtin or result.engscale) then  result.defkey = unitcode -- key to lookup default exception  result.engscale = engscale  -- Do not set result.scale as this code is called for units where that is not set.  return result  end  end  end  return nil end  local 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)  -- '<' 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 then  quit_no_message('Invalid default expression.')  elseif arithop == '*' then  factor = tonumber(factor)  if factor == nil then  quit_no_message('Invalid default expression.')  end  value = value * factor  end  limit = tonumber(limit)  if limit == nil then  quit_no_message('Invalid default expression.')  end  if compop == '<' then  return value < limit  elseif compop == '<=' then  return value <= limit  elseif compop == '>' then  return value > limit  elseif compop == '>=' then  return value >= limit  end  quit_no_message('Invalid default expression.') -- should not occur  end  local lhs, rhs = condition:match('^(.-%W)and(%W.*)')  if lhs == nil then  return compare(value, condition)  end  return compare(value, lhs) and compare(value, rhs) end  ---End adapted code-----------------------------------------------------  local function strip(text)  -- Return text with no leading/trailing whitespace.  return text:match("^%s*(.-)%s*$") end  local function empty(text)  -- Return true if text is nil or empty (assuming a string).  return text == nil or text == '' end  -- Tables of units: k = unit code, v = unit table. local units_index = {} -- all units: normal, alias, per, combination, or multiple local alias_index = {} -- all aliases (to detect attempts to define more than once) local per_index = {} -- all "per" units (to detect attempts to define more than once)  local function get_unit(ucode, utype)  -- Look up unit code in our cache of units.  -- If utype == nil, the unit should already have been defined.  -- Otherwise, ucode may represent an automatically generated combination  -- where each component must have the given utype; a dummy unit is returned.  if empty(ucode) then  return nil  end  local unit = lookup(units_index, ucode)  if unit or not utype then  return unit  end  local combo = collection()  if ucode:find('+', 1, true) then  for item in (ucode .. '+'):gmatch('%s*(.-)%s*%+') do  if item ~= '' then  combo:add(item)  end  end  elseif ucode:find('%s') then  for item in ucode:gmatch('%S+') do  combo:add(item)  end  end  if combo.n > 1 then  local result = setmetatable({ utype = utype }, {  __index = function (self, key)  error('Bug: invalid use of automatically generated unit')  end })  for _, v in ipairs(combo) do  local component = lookup(units_index, v)  if not component or component.shouldbe or component.combination then  return nil  end  if utype ~= component.utype then  result.utype = component.utype -- set wrong type which caller will detect  break  end  end  return result  end end  local overrides = {} -- read from input for unit codes that should not be checked for a duplicate  local function insert_unique_unit(data, unit, index)  -- After inserting any required built-in data, insert the unit into the  -- data table and (if index not nil) add to index,  -- but not if the unit code is already defined.  local ucode = unit.unitcode  local known = get_unit(ucode)  if known and not overrides[ucode] then  quit('m_dup_code', ucode)  end  for item, t in pairs(specials.ucode) do  unit[item] = t[ucode]  end  if index then  index[ucode] = unit  end  table.insert(data, unit) end  local function check_condition(condition)  -- Return true if condition appears to be valid; otherwise return false.  for _, value in ipairs({ 0, 0.1, 1, 1.1, 10, 100, 1000, 1e4, 1e5 }) do  local success, result = pcall(evaluate_condition, value, condition)  if not success then  return false  end  end  return true end  local function check_default_expression(default, ucode)  -- Return a numbered table of names present in param default  -- (two names if an expression, or one name (param default) otherwise).  -- Throw an error if a problem occurs.  -- An expression uses pipe-delimited fields with 'v' representing  -- the input value for the conversion.  -- Example (suffix is optional): 'v < 120 ! small ! big ! suffix'  -- returns { 'smallsuffix', 'bigsuffix' }.  if not default:find('!', 1, true) then  return { default }  end  local t = {}  for item in (default .. '!'):gmatch('%s*(.-)%s*!') do  t[#t+1] = item -- split on '!', removing leading/trailing whitespace  end  if not (#t == 3 or #t == 4) then  quit('m_def_fmt', default, ucode)  end  local condition, default1, default2 = t[1], t[2], t[3]  if #t == 4 then  default1 = default1 .. t[4]  default2 = default2 .. t[4]  end  if not check_condition(condition) then  quit('m_def_cond', default, ucode)  end  return { default1, default2 } end  local function check_default(default, ucode, utype, unit_table)  -- Check the given name (or expression) of a default output.  -- Normally a unit must not define itself as its default. However,  -- some units are defined merely for use in per units, and they have  -- the same ucode, utype and default.  -- Example: unit cent which cannot be converted to anything other than  -- a cent, but which can work, for example, in cent/km and cent/mi.  -- Throw an error if a problem occurs.  local done = {}  for _, default in ipairs(check_default_expression(default, ucode)) do  if done[default] then  quit('m_def_rpt', default, ucode)  end  if default == ucode and ucode ~= utype then  quit('m_def_same', ucode)  end  local default_table = get_unit(default, utype)  if not default_table then  quit('m_def_undef', default, ucode)  end  if not (utype == unit_table.utype and utype == default_table.utype) then  quit('m_def_type', default, ucode)  end  done[default] = true  end end  local function check_all_defaults(cfg, units)  -- Check each default in units and warn if needed.  -- This is done after all input data has been processed.  -- Throw an error if a problem occurs.  local errors = collection()  local missing = collection() -- unitcodes with missing defaults  for _, unit in ipairs(units) do  if not unit.shouldbe and not unit.combination then  -- This is a standard unit or an alias/per (not shouldbe, combo).  -- An alias may have a default defined, but it is optional.  local default = unit.default  local ucode = unit.unitcode  if empty(default) then  if not unit.target then -- unit should have a default  missing:add(ucode)  end  else  local ok, msg = pcall(check_default, default, ucode, unit.utype, unit)  if not ok then  errors:add(msg)  if errors.n >= cfg.maxerrors then  break  end  end  end  end  end  if errors.n > 0 then  error(errors:join(), 0)  end  if missing.n > 0 then  add_warning('m_wrn_nodef')  local limit = cfg.maxerrors  for _, v in ipairs(missing) do  limit = limit - 1  if limit < 0 then  add_warning('m_wrn_more')  break  end  add_warning('m_wrn_ucode', v)  end  end end  local function check_all_pers(cfg, units)  -- Check each component of each "per" unit and warn if needed.  -- In addition, add any required extra fields for some types of units.  -- This is done after all input data has been processed.  -- Throw an error if a problem occurs.  local errors = collection()  local function errmsg(key, ...)  errors:add(message(key, ...))  end  for _, unit in ipairs(units) do  local per = unit.per  if per then  local ucode = unit.unitcode  if #per ~= 2 then  errmsg('m_per_two', ucode)  else  local types = {}  for i, v in ipairs(per) do  if empty(v) then  errmsg('m_per_empty', ucode)  end  if not text_code.currency[v] then  local t = get_unit(v)  if t then  types[i] = t.utype  else  errmsg('m_per_undef', ucode, v)  end  end  end  if specials.utype[unit.utype] == 'type_fuel_efficiency' then  local expected = { type_volume = 1, type_length = 2 }  local top_type = expected[specials.utype[types[1]]]  local bot_type = expected[specials.utype[types[2]]]  if top_type and bot_type and top_type ~= bot_type then  unit.iscomplex = true  if top_type == 1 then  unit.invert = 1  else  unit.invert = -1  end  else  errmsg('m_per_fuel', ucode)  end  end  end  end  if errors.n >= cfg.maxerrors then  break  end  end  if errors.n > 0 then  error(errors:join(), 0)  end end  local function update_units(units, composites, varnames)  -- Update some unit definitions with extra data defined in other sections.  -- This is done after all input data has been processed.  for _, unit in ipairs(units) do  local comp = composites[unit.unitcode]  if comp then  unit.subdivs = '{ ' .. table.concat(comp.subdivs, ', ') .. ' }'  end  local vn = varnames[unit.unitcode]  if vn then  unit.varname = vn  end  end end  local function make_override(cfg, data)  -- Return a function which, when called, stores a unit code that is not to be  -- checked for a duplicate. The table is stored in data (also a table).  return function (utype, fields)  local ucode = fields[1]  if empty(ucode) then  quit('m_ovr_miss')  end  if data[ucode] then  quit('m_ovr_dup', ucode)  end  data[ucode] = true  end end  local function make_default(cfg, data)  -- Return a function which, when called, stores a table that defines a  -- default output unit. The table is stored in data (also a table).  local defaults_index = {} -- to detect attempts to define a default twice  return function (utype, fields)  -- Store a table defining a unit.  -- This is for a unit such as 'kg' that has a default output unit  -- different from what is defined for the base unit ('g').  -- Throw an error if a problem occurs.  local ucode = fields[1]  local default = fields[2]  if empty(ucode) then  quit('m_dfs_code')  end  if empty(default) then  quit('m_dfs_none', ucode)  end  if #fields ~= 2 then  quit('m_dfs_two', ucode)  end  local unit_table = get_unit(ucode)  if not unit_table then  quit('m_dfs_undef', ucode)  end  local symbol = unit_table.defkey or unit_table.symbol  if empty(symbol) then  quit('m_dfs_sym', ucode)  end  check_default(default, ucode, utype, unit_table)  if defaults_index[ucode] then  quit('m_dfs_dup', ucode)  end  defaults_index[ucode] = default  table.insert(data, { symbol = symbol, default = default })  end end  local function clean_link(link, name)  -- Return link, customary where:  -- link = given link after removing any '[[...]]' wiki formatting  -- and removing any leading '+' or '*' or '@';  -- customary = 1 if leading '+', or 2 if '*' or 3 if '@', or nil  -- (for extra "US" or "U.S." or "Imperial" customary units link).  -- Result has leading/trailing whitespace removed, and is nil if empty  -- or if link matches the name, if a name is specified.  -- Exception: If the link is empty and the name starts with '[[',  -- the link is stored as '' (for a unit name which is always linked).  -- If the resulting link is nil, no link field is stored, and  -- if a link is required, it will be set from the unit's name.  local original = link  if empty(link) then  return (name and name:sub(1, 2) == '[[') and '' or nil  end  local prefixes = { ['+'] = 1, ['*'] = 2, ['@'] = 3 }  local customary = prefixes[link:sub(1, 1)]  if customary then  link = strip(link:sub(2))  end  if link:sub(1, 2) == '[[' then  link = link:sub(3)  end  if link:sub(-2) == ']]' then  link = link:sub(1, -3)  end  link = strip(link)  if link:sub(1, 1) == '[' or link:sub(-1) == ']' then  quit('m_lnk_brack', original)  end  if link == '' then  link = nil  elseif name then  local l = ulower(usub(link, 1, 1)) .. usub(link, 2)  local n = ulower(usub(name, 1, 1)) .. usub(name, 2)  if l == n then  link = nil -- link == name, ignoring case of first letter  end  end  return link, customary end  local function make_link(cfg, data)  -- Return a function which, when called, stores a table that defines a  -- link exception. The table is stored in data (also a table).  local links_index = {} -- to detect attempts to define a link twice  return function (utype, fields)  -- Store a table defining a unit.  -- This is for a unit such as 'kg' that has a linked article  -- different from what is defined for the base unit ('g').  -- Throw an error if a problem occurs.  local ucode = fields[1]  local link = clean_link(fields[2])  if empty(ucode) then  quit('m_lnk_miss')  end  if empty(link) then  quit('m_lnk_none', ucode)  end  if #fields ~= 2 then  quit('m_lnk_two', ucode)  end  local unit_table = get_unit(ucode)  if not unit_table then  quit('m_lnk_undef', ucode)  end  if utype ~= unit_table.utype then  quit('m_lnk_type', ucode)  end  local symbol = unit_table.symbol  if empty(symbol) then  quit('m_lnk_sym', ucode)  end  if links_index[ucode] then  quit('m_lnk_dup', ucode)  end  links_index[ucode] = link  table.insert(data, { symbol = symbol, link = link })  end end  local function clean_scale(scale)  -- Return cleaned scale as a string, after evaluating any expression.  -- It would be better to retain scale expressions like "5/9" so that  -- the expression is evaluated on the server and maintains the full  -- resolution of the server. However, there are many such expressions  -- in the table of all units, and it seems pointless to require the  -- server to evaluate all of them just to do one convert.  if empty(scale) then  quit('m_scl_miss')  end  assert(type(scale) == 'string', 'Bug: scale has an unexpected type')  scale = string.gsub(scale, ',', '') -- remove comma separators  if tonumber(scale) then -- not an expression  return scale  end  local status, value = pcall(evaluate, scale)  if not (status and type(value) == 'number') then  quit('m_scl_bad', scale)  end  local result = string.format('%.17g', value)  if result:find('[#n]') then  -- Lua can give results like "#INF" while Scribunto gives "inf". Either is an error.  quit('m_scl_oflow', scale)  end  -- Omit redundant zeros from results like '1.2e-005'.  -- Do not bother looking for results like '1.2e+005' as none occur in practice.  local lhs, zeros, rhs = result:match('^(.-e%-)(0+)(.*)')  if zeros then  result = lhs .. rhs  end  return result end  local function add_alias_optional_fields(unit, start, fields, target)  -- Inspect fields[i] for i = start, start+1 ..., and extract any  -- definitions appropriate for an alias or "per", and add them to unit.  -- For an alias, target is a valid unit; for a "per", target is nil.  -- Throw error if encounter an invalid entry.  for i = start, #fields do  local field = fields[i]  if not empty(field) then  local lhs, rhs = field:match('^%s*(.-)%s*=%s*(.-)%s*$')  local good  if not empty(rhs) then  for _, item in ipairs({ 'sp', 'default', 'link', 'multiplier', 'symbol', 'symlink' }) do  if lhs == item then  if item == 'sp' then  if rhs == 'us' then  unit.sp_us = true  good = true  end  elseif item == 'link' then  local tlink  if target then  tlink = target[item]  end  local link, customary = clean_link(rhs, tlink)  if link then  unit[item] = link  end  if customary then  unit.customary = customary  end  good = true  elseif item == 'symlink' then  local pos1 = rhs:find('[[', 1, true)  local pos2 = rhs:find(']]', 1, true)  if not (pos1 and pos2 and (pos1 < pos2)) then  quit('m_als_link', unit.unitcode)  end  unit.symlink = rhs  good = true  elseif item == 'multiplier' then  if not tonumber(rhs) then  quit('m_als_mul', unit.unitcode, rhs)  end  unit[item] = rhs  good = true  else  if target and rhs == target[item] then  quit('m_als_same', item, unit.unitcode)  end  unit[item] = rhs  good = true  end  break  end  end  end  if not good then  quit('m_als_bad', field)  end  end  end end  local function make_alias(fields, ucode, utype, symbol)  -- Return a new alias unit, or return nil if symbol is not already  -- defined as the unit code of the target unit.  -- Throw an error if invalid.  local target = get_unit(symbol)  if not target then  return nil  end  local unit = { unitcode = ucode, utype = utype, target = symbol }  add_alias_optional_fields(unit, 3, fields, target)  if alias_index[ucode] then  quit('m_als_dup', ucode)  else  alias_index[ucode] = unit  end  if target.utype ~= utype then  quit('m_als_type', ucode)  end  return unit end  local function make_per(fields, ucode, utype, symbol)  -- Return a new "per" unit, or return nil if symbol is not of form "x/y".  -- Throw an error if invalid.  -- The top, bottom unit codes are checked later, after all units are defined.  local top, bottom = symbol:match('^(.-)/(.*)$')  if not top then  return nil  end  local unit = { unitcode = ucode, utype = utype, per = { strip(top), strip(bottom) } }  add_alias_optional_fields(unit, 3, fields)  if per_index[ucode] then  quit('m_per_dup', ucode)  else  per_index[ucode] = unit  end  return unit end  local function make_unit(cfg, data)  -- Return a function which, when called, stores a table that defines a  -- single unit. The table is stored in data (also a table).  local fieldnames = {  -- Fields in the Conversions section are assumed to be in the following order.  'unitcode',  'symbol',  'scale',  'extra',  'name1',  'prefixes',  'default',  'link',  }  return function (utype, fields)  -- Store a table defining a unit.  -- Throw an error if a problem occurs.  local ucode, symbol = fields[1], fields[2]  if empty(utype) then  quit('m_miss_type')  end  if empty(ucode) then  quit('m_miss_code')  end  if empty(symbol) then  quit('m_miss_sym')  end  local prefix = symbol:sub(1, 1)  if prefix == '~' or prefix == '=' or prefix == '!' or prefix == '*' then  if symbol:sub(1, 2) == '==' then  prefix = symbol:sub(1, 2)  end  symbol = strip(symbol:sub(#prefix + 1)) -- omit prefix and any following whitespace  fields[2] = symbol  else  prefix = nil -- not a valid prefix  end  if prefix == '=' or prefix == '==' then  -- ucode is an alias (a fake unit code used in a convert template), or  -- defines a "per" unit like "$/acre" or "BTU/h".  -- For an alias, symbol is the unit code of the actual unit.  -- For a "per", symbol is of form "x/y" where x and y are unit codes,  -- or x is a recognized currency symbol and y is a unit code.  -- Checking that x and y are valid is deferred until all units have  -- been defined so, for example, "BTU/h" can be defined before "h".  local unit  if prefix == '=' then  unit = make_alias(fields, ucode, utype, symbol)  else  unit = make_per(fields, ucode, utype, symbol)  end  if not unit then  -- Do not define an alias in terms of another alias.  quit('m_als_undef', symbol)  end  insert_unique_unit(data, unit, units_index)  return  elseif prefix == '!' then  -- ucode may be incorrectly entered as a unit code.  -- symbol is a message saying what unit code should be used.  local unit = { unitcode = ucode, shouldbe = symbol }  insert_unique_unit(data, unit, nil)  return  end  -- Make the unit.  local unit = { utype = utype }  for i, name in ipairs(fieldnames) do  if not empty(fields[i]) then  unit[name] = fields[i]  end  end  -- Remove redundancy from unit.  if unit.sym_us == symbol then  unit.sym_us = nil  end  local prefixes = unit.prefixes  local name1, name2 = unit.name1, unit.name2  if name1 then  if name1 == symbol and not prefixes then  -- A unit which takes an SI prefix must not have a nil name because,  -- for example, the name for "kW" = "kilo" .. "watt" (name for "W").  -- The "not prefixes" test is needed for bnwiki where the  -- watt unit has the same name and symbol.  unit.name1 = nil  end  else  name1 = symbol  end  if name2 then  if name2 == name1 .. plural_suffix then  unit.name2 = nil  end  else  name2 = name1 .. plural_suffix  end  local name1_us, name2_us = unit.name1_us, unit.name2_us  if name1_us then  if name1_us == name1 then  unit.name1_us = nil  end  end  if name2_us then  if unit.name1_us then  if name2_us == unit.name1_us .. plural_suffix then  unit.name2_us = nil  end  elseif name2_us == name2 then  unit.name2_us = nil  end  end  -- Other changes to unit.  unit.scale = clean_scale(unit.scale)  local extra = unit.extra  if not empty(extra) then  -- Set appropriate fields for a unit that needs more than a simple  -- multiplication by a ratio of unit scales to convert values.  unit.iscomplex = true  if extra == 'volume/length' then  unit.invert = 1  elseif extra == 'length/volume' then  unit.invert = -1  elseif specials.utype[utype] == 'type_temperature' then  unit.offset = extra  elseif extra == 'invert' then  unit.invert = -1  else  unit.builtin = extra  end  end  if prefix == '~' then  -- Magic code for units like "acre" where the symbol is not really a  -- symbol, and output should use the singular or plural name instead.  unit.usename = 1  elseif prefix == '*' then  -- Magic code for units like "pitch" which have a symbol that is the same as  -- another unit with entries defined in the default or link exceptions tables.  unit.defkey = ucode -- key for default exceptions  unit.linkey = ucode -- key for link exceptions  end  local name_for_link  if prefixes then  if prefixes == 'SI' then  unit.prefixes = 1  elseif prefixes == 'SI2' then  unit.prefixes = 2  elseif prefixes == 'SI3' then  unit.prefixes = 3  else  quit('m_pfx_bad', prefixes)  end  else  -- Only units which do not accept SI prefixes have name_for_link set.  -- That is because, for example, if set name_for_link = name1 for unit g,  -- then the link is "kilogram" for kg, and "yottagram" for Yg, and so on  -- for all prefixes. That might be desirable for some units, but not all.  name_for_link = name1  end  unit.link, unit.customary = clean_link(unit.link, name_for_link)  if prefixes then  -- The SI prefix is always at the start (position = 1) for symbol and sym_us.  -- However, each name (name1, name2, name1_us, name2_us) can have the SI prefix  -- at any position, and that position can be different for each name.  -- For enwiki, the only units with names where the prefix is not at the start  -- are "square metre" and "cubic metre" ("square meter" and "cubic meter" for sp=us).  -- Some other wikis want the flexibility that the prefix position can be different  -- so the position is stored as nil (if always 1), or N (an integer, if always N),  -- or a string of four comma-separated numbers such as "5,7,9,11" which means the  -- prefix position for (name1, name2, name1_us, name2_us) is (5, 7, 9, 11)  -- respectively.  local name1, name1_us = unit.name1, unit.name1_us -- after redundancy removed  if not name1 then  quit('m_pfx_name')  end  local positions = collection()  for i, k in ipairs({ 'name1', 'name2', 'name1_us', 'name2_us' }) do  local name = unit[k]  local pos  if name then  pos = name:find('%s', 1, true)  if pos then  unit[k] = name:sub(1, pos - 1) .. name:sub(pos + 2)  end  elseif i == 2 or i == 3 then  pos = positions[1]  elseif i == 4 then  pos = positions[unit.name1_us and 3 or 2]  end  positions:add(pos or 1)  end  local pos = positions[1]  for i = 2, positions.n do  if pos ~= positions[i] then  pos = '"' .. positions:join(',') .. '"'  break  end  end  if pos ~= 1 then  unit.prefix_position = pos  end  for _, name in ipairs({ 'symbol', 'sym_us', 'name1', 'name1_us', 'name2', 'name2_us' }) do  unit['_' .. name] = unit[name]  unit[name] = nil -- force call to __index metamethod so any SI prefix can be handled  end  end  for name, v in pairs(unit) do  -- Reject if a string field includes "%s" (should not occur after above).  if type(v) == 'string' and v:find('%s', 1, true) then  quit('m_percent_s', name)  end  end  insert_unique_unit(data, unit, units_index)  end end  local function make_combination(cfg, data)  -- Return a function which, when called, stores a table that defines a  -- single combination unit. The table is stored in data (also a table).  return function (utype, fields)  -- Store a table defining a unit.  -- This is for a combination unit that specifies more than one output.  -- The target units must be defined first.  -- Throw an error if a problem occurs.  local unit = { utype = utype, combination = {} }  for i, v in ipairs(fields) do  if i == 1 then -- unitcode  if v == '' then  quit('m_cmb_miss')  end  unit.unitcode = v  elseif v == '' then  -- Ignore empty fields.  else  local target = get_unit(v)  if not target then  quit('m_cmb_undef', v, unit.unitcode)  end  if target.utype ~= utype then  quit('m_cmb_type', v, unit.unitcode)  end  table.insert(unit.combination, v)  end  end  if #unit.combination < 2 then  quit(#unit.combination == 0 and 'm_cmb_none' or 'm_cmb_one', unit.unitcode)  end  insert_unique_unit(data, unit, units_index)  end end  local function make_perunit(cfg, data)  -- Return a function which, when called, stores a table that defines a  -- fixup for an automatic per unit. The table is stored in data (also a table).  local pertype_index = {} -- to detect attempts to define a fixup twice  return function (utype, fields)  -- Store a table to define a fixup.  -- Typos or other errors in the input are not detected!  -- Parameter utype is ignored (it is nil).  -- Throw an error if a problem occurs.  local lhs, rhs, link, multiplier  for i, v in ipairs(fields) do  if v == '' then  -- Ignore empty fields.  elseif i == 1 then  lhs = v -- like "length/time"  elseif i == 2 then  rhs = v -- like "speed"  elseif i == 3 then  link = v  elseif i == 4 then  if not tonumber(v) then  quit('m_per_inv')  end  multiplier = v  else  quit('m_per_inv')  end  end  if lhs and (rhs or link or multiplier) then  if link or multiplier then  local parts = collection()  if rhs then  parts:add('utype = "' .. rhs .. '"')  end  if link then  parts:add('link = "' .. link .. '"')  end  if multiplier then  parts:add('multiplier = ' .. multiplier)  end  rhs = '{ ' .. parts:join(', ') .. ' }'  else  rhs = '"' .. rhs .. '"'  end  if pertype_index[lhs] then  quit('m_per_dup', lhs)  end  pertype_index[lhs] = rhs  table.insert(data, { lhs = lhs, rhs = rhs })  else  quit('m_per_inv')  end  end end  local function make_varname(cfg, data)  -- Return a function which, when called, stores a table that defines a  -- variable name for a unit. The table is stored in data (also a table).  return function (utype, fields)  -- Set or update an entry in the data table to record that a unit has a variable name.  -- This is for slwiki where a unit name depends on the value.  -- The target units must be defined first.  -- Parameter utype is ignored (it is nil).  -- Throw an error if a problem occurs.  local count = #fields  if count ~= cfg.varcolumns then  quit('m_var_cnt')  end  local ucode  local names = {}  for i = 1, count do  local v = fields[i]  if empty(v) then  quit('m_var_miss')  end  if i == 1 then -- unitcode  ucode = v  if not get_unit(v) then  quit('m_var_undef', v)  end  else  table.insert(names, v)  end  end  if data[ucode] then  quit('m_var_dup', ucode)  end  data[ucode] = table.concat(names, '!')  end end  local function reversed(t)  -- Return a numbered table in reverse order.  local reversed, count = {}, #t  for i = 1, count do  reversed[i] = t[count + 1 - i]  end  return reversed end  local function make_inputmultiple(cfg, data)  -- Return a function which, when called, stores a table that defines a  -- single composite (multiple input) unit. The table is stored in data (also a table).  return function (utype, fields)  -- Set or update an entry in the data table to record that a unit  -- accepts subdivisions to make a composite input unit like '|2|ft|6|in'.  -- The target units must be defined first.  -- Throw an error if a problem occurs.  local unitcode -- dummy code required for simplicity, but which is not used in output  local alternate_code -- an alternative unit code can be specified to replace convert input  local fixed_name -- a fixed name can be specified to replace the unit's normal symbol/name  local default_code  local ucodes, scales = {}, {}  for i, v in ipairs(fields) do  -- 1=composite, 2=ucode1, 3=ucode2, 4=default, 5=alternate, 6=name  if i == 1 then  if v == '' then  quit('m_cmp_miss')  end  unitcode = v  elseif 2 <= i and i <= 5 then  if not (i == 5 and v == '') then  local target = get_unit(v, (i == 4) and utype or nil) -- the default may be an auto combination  if not target then  quit('m_cmp_undef', v, unitcode)  end  if target.utype ~= utype then  quit('m_cmp_type', v, unitcode)  end  if i < 4 then  if not target.scale then  quit('m_mul_std', v, unitcode)  end  table.insert(ucodes, v)  table.insert(scales, target.scale)  elseif i == 4 then  default_code = v  else  if scales[#scales] ~= target.scale then  quit('m_cmp_scale', v, unitcode)  end  alternate_code = v  end  end  elseif i == 6 then  if v ~= '' then  fixed_name = v  end  else  quit('m_cmp_many', unitcode)  end  end  if #ucodes ~= 2 then  quit('m_cmp_two', unitcode)  end  if not default_code then  quit('m_cmp_def', unitcode)  end  -- Component units must be specified from most-significant to least-significant,  -- and each ratio of a pair of scales must be very close to an integer.  -- Currently, there will be exactly two scales and one ratio.  local ratios, count = {}, #scales  for i = 1, count do  local scale = tonumber(scales[i])  if scale == nil or scale <= 0 then  quit('m_cmp_inval', unitcode, scales[i])  end  scales[i] = scale  end  for i = 1, count - 1 do  local ratio = scales[i] / scales[i + 1]  local rounded = math.floor(ratio + 0.5)  if rounded < 2 then  quit('m_cmp_order', unitcode)  end  if math.abs(ratio - rounded)/ratio > 1e-6 then  quit('m_cmp_int', unitcode)  end  ratios[i] = rounded  end  local text = { tostring(ratios[1]) }  local function add_text(key, value)  table.insert(text, string.format('%s = %q', key, value))  end  if default_code then  add_text('default', default_code)  end  if alternate_code then  add_text('unit', alternate_code)  end  if fixed_name then  add_text('name', fixed_name)  end  local subdiv = string.format('["%s"] = { %s }', ucodes[2], table.concat(text, ', '))  local main_code = ucodes[1]  local item = data[main_code]  if item then  table.insert(item.subdivs, subdiv)  else  data[main_code] = { subdivs = { subdiv } }  end  end end  local function make_outputmultiple(cfg, data)  -- Return a function which, when called, stores a table that defines a  -- single multiple output unit. The table is stored in data (also a table).  return function (utype, fields)  -- Store a table defining a unit.  -- This is for a multiple unit like 'ydftin' (result in yards, feet, inches).  -- The target units must be defined first.  -- Throw an error if a problem occurs.  local unit = { utype = utype }  local ucodes, scales = {}, {}  for i, v in ipairs(fields) do  if i == 1 then -- unitcode  if v == '' then  quit('m_mul_miss')  end  unit.unitcode = v  elseif v == '' then  -- Ignore empty fields.  else  local target = get_unit(v)  if not target then  quit('m_mul_undef', v, unit.unitcode)  end  if target.utype ~= utype then  quit('m_mul_type', v, unit.unitcode)  end  if not target.scale then  quit('m_mul_std', v, unit.unitcode)  end  table.insert(ucodes, v)  table.insert(scales, target.scale)  end  end  if #ucodes < 2 then  quit(#ucodes == 0 and 'm_mul_none' or 'm_mul_one', unit.unitcode)  end  -- Component units must be specified from most-significant to least-significant  -- (so scale values will be in descending order),  -- and each ratio of a pair of scales must be very close to an integer.  -- The componenets and ratios are stored in reverse order (least significant first).  -- This script stores a unit scale as a string (might be an expression like "5/9"),  -- but scales in a multiple are handled as numbers (should never be expressions).  local ratios, count = {}, #scales  for i = 1, count do  local scale = tonumber(scales[i])  if scale == nil or scale <= 0 then  quit('m_mul_scale', unit.unitcode, scales[i])  end  scales[i] = scale  end  for i = 1, count - 1 do  local ratio = scales[i] / scales[i + 1]  local rounded = math.floor(ratio + 0.5)  if rounded < 2 then  quit('m_mul_order', unit.unitcode)  end  if math.abs(ratio - rounded)/ratio > 1e-6 then  quit('m_mul_int', unit.unitcode)  end  ratios[i] = rounded  end  unit.combination = reversed(ucodes)  unit.multiple = reversed(ratios)  insert_unique_unit(data, unit, units_index)  end end  -- To make updating the data module easier, this script inserts a preamble -- and a postamble so the result can be used to replace the whole page. local data_preamble = [=[ -- ข้อมูลการแปลง ถูกใช้โดย [[Module:Convert]] ซึ่งใช้ mw.loadData() สำหรับกาารเข้า -- ถึงข้อมูลในหน้านี้แบบอ่านอย่างเดียว และข้อมูลจะถูกโหลดเพียงแค่ครั้งเดียวในหน้าแต่ละหน้า ดูที่ -- [[w:en:Template:Convert/Transwiki guide]] สำหรับคู่มือการปรับให้เข้ากับวิกิท้องถิ่น -- -- มีตารางข้อมูลดิบดังต่อไปนี้: -- all_units ข้อมูลแทบทั้งหมดของแต่ะหน่วย รวมค่าปริยาย -- default_exceptions ข้อมูลที่ไม่รวมค่าปริยาย ('kg' และ 'g' มีค่าปริยายต่างกัน) -- link_exceptions ข้อมูลที่ไม่มีค่าลิงก์ ('kg' และ 'g' มีลิงก์ต่างกัน) -- -- ตารางข้อมูลเหล่านี้ถูกสร้างผ่านสคริปต์โดยอ่านข้อมูลตารางดิบจากหน้า  -- [[มอดูล:Convert/documentation/conversion data/doc]] ]=]  local data_postamble = [=[ return {  all_units = all_units,  default_exceptions = default_exceptions,  link_exceptions = link_exceptions,  per_unit_fixups = per_unit_fixups, }]=]  local out_unit_prefix = [[ --------------------------------------------------------------------------- -- / \ -- / ! \ อย่าแก้ไขหน้าข้อมูลนี้โดยตรงเนื่องจากข้อมูลในหน้านี้จะถูกสร้างอัตโนมัติด้วยสคริปต์ -- /_ _ _\ จากข้อมูลในหน้าวิกิ (อ่านหมายเหตุด้านบน) --  --------------------------------------------------------------------------- local all_units = {]]  local out_unit_suffix = [[ } ]]  local out_default_prefix = [[ --------------------------------------------------------------------------- -- / \ -- / ! \ อย่าแก้ไขหน้าข้อมูลนี้โดยตรงเนื่องจากข้อมูลในหน้านี้จะถูกสร้างอัตโนมัติด้วยสคริปต์ -- /_ _ _\ จากข้อมูลในหน้าวิกิ (อ่านหมายเหตุด้านบน) --  --------------------------------------------------------------------------- local default_exceptions = {  -- Prefixed units with a default different from that of the base unit.  -- Each key item is a prefixed symbol (unitcode for engineering notation).]]  local out_default_suffix = [[ } ]]  local out_default_item = [[  ["{symbol}"] = "{default}",]]  local out_link_prefix = [[ --------------------------------------------------------------------------- -- Do not change the data in this table because it is created by running -- -- a script that reads the wikitext from a wiki page (see note above). -- --------------------------------------------------------------------------- local link_exceptions = {  -- Prefixed units with a linked article different from that of the base unit.  -- Each key item is a prefixed symbol (not unitcode).]]  local out_link_suffix = [[ } ]]  local out_link_item = [[  ["{symbol}"] = "{link}",]]  local out_perunit_prefix = [[ --------------------------------------------------------------------------- -- Do not change the data in this table because it is created by running -- -- a script that reads the wikitext from a wiki page (see note above). -- --------------------------------------------------------------------------- local per_unit_fixups = {  -- Automatically created per units of form "x/y" may have their unit type  -- changed, for example, "length/time" is changed to "speed".  -- Other adjustments can also be specified.]]  local out_perunit_suffix = [[ } ]]  local out_perunit_item = [[  ["{lhs}"] = {rhs},]]  local combination_specification = { -- pure combination like 'm ft', or a multiple like 'ftin'  'combination',  'multiple',  'utype', }  local alias_specification = {  'target',  'symbol',  'sp_us',  'default',  'link',  'symlink',  'customary',  'multiplier', }  local per_specification = {  'per',  'symbol',  'sp_us',  'utype',  'invert',  'iscomplex',  'default',  'link',  'symlink',  'customary',  'multiplier', }  local shouldbe_specification = {  'shouldbe', }  local unit_specification = {  '_name1',  '_name1_us',  '_name2',  '_name2_us',  '_symbol',  '_sym_us',  'prefix_position',  'name1',  'name1_us',  'name2',  'name2_us',  'varname',  'symbol',  'sym_us',  'usename',  'usesymbol',  'utype',  'alttype',  'builtin',  'scale',  'offset',  'invert',  'iscomplex',  'istemperature',  'exception',  'prefixes',  'default',  'subdivs',  'defkey',  'linkey',  'link',  'customary',  'sp_us', }  local no_quotes = {  combination = true,  customary = true,  multiple = true,  multiplier = true,  offset = true,  per = true,  prefix_position = true,  scale = true,  subdivs = true, }  local function add_unit_lines(results, unit, spec)  -- Add lines of Lua source to define a unit to the results collection.  local function add_line(line)  -- Had planned to replace sequences of spaces with 4-column tabs here  -- (because the CodeEditor now assumes the use of such tabs).  -- However, 4-column tabs are only visible when editing a module  -- with browser scripting and the CodeEditor enabled, and that is rare.  -- A module is usually viewed (with 8-column tabs), and some indents  -- would be messed up unless 8-column tabs are used. Therefore,  -- have decided to simply replace 8 spaces at start of line with a single  -- tab which reduces the size of the module, and is correct for viewing.  if line:sub(1, 8) == string.rep(' ', 8) then  line = '\t' .. line:sub(9)  end  results:add(line)  end  local first_item = ' ["' .. unit.unitcode .. '"] = {'  local last_item = ' },'  add_line(first_item)  for _, k in ipairs(spec) do  local v = unit[k]  if v then  local want_quotes = (type(v) == 'string' and not no_quotes[k])  if type(v) == 'boolean' then  v = tostring(v)  elseif type(v) == 'number' or k == 'scale' then  -- Replace results like '1e-006' with '1e-6'.  v = string.gsub(tostring(v), '(e[+-])0+([1-9].*)', '%1%2', 1)  elseif type(v) ~= 'string' then  quit('m_ftl_type', unit.unitcode)  end  local fmt = string.format('%8s%%-9s= %%%s,', '', want_quotes and 'q' or 's')  add_line(fmt:format(k, v))  end  end  add_line(last_item) end  local function numbered_table_as_string(data, unit)  local t = {}  for _, v in ipairs(data) do  if type(v) == 'string' then  table.insert(t, '"' .. v .. '"')  elseif type(v) == 'number' then  table.insert(t, tostring(v))  else  quit('m_ftl_type', unit.unitcode)  end  end  return '{ ' .. table.concat(t, ', ') .. ' }' end  local function extract_heading(line)  -- Return n, s where n = heading level number (nil if none), and  -- s = heading text (with leading/trailing whitespace removed).  local pattern = '^(==+)%s*(.-)%s*(==+)%s*$'  local before, heading, after = line:match(pattern)  if heading and #heading > 0 then  -- Don't bother checking if before == after.  return #before, heading  end end  local function fields(line)  -- Return a numbered table of fields split from line.  -- Items are delimited by "||".  -- Each item has leading/trailing whitespace removed, and any encoded pipe  -- characters are decoded.  -- The second field (for symbol when processing units) is adjusted to  -- remove any "colspan" at the front of lines like:  -- "| unitcode || colspan="11" | !Text to display for an error message".  local t = {}  line = line .. "||" -- to get last field  for item in line:gmatch("%s*(.-)%s*||") do  table.insert(t, (item:gsub('&#124;', '|')))  end  if t[2] then  local cleaned = t[2]:match('^%s*colspan%s*=.-|%s*(.*)$')  if cleaned then  t[2] = cleaned  end  end  return t end  local function prepare_section(cfg, maker, lines, section, need_section, need_utype)  -- Process the first level-two section with the given section name  -- in the given table of lines of wikitext.  -- If successful, maker inserts each item into a table.  -- Otherwise, an error is thrown.  local skip = true  local errors = collection()  local utype -- unit type (from level-three heading)  local nbsp = '\194\160' -- nonbreaking space is utf-8 encoded as hex c2 a0  for linenumber, line in ipairs(lines) do  if skip then  -- Skip down to and including the starting heading.  local level, heading = extract_heading(line)  if level == 2 and heading == section then  skip = false  end  else  -- Accummulate unit definitions.  local c1 = line:sub(1, 1)  local c2 = line:sub(2, 2)  if c1 == '|' and not (c2 == '-' or c2 == '}') then  if need_utype and empty(utype) then  quit('m_hdg_lev3', line)  end  if line:find(nbsp, 1, true) then  -- For example, "acre ft" does not work if it contains nbsp.  add_warning('m_wrn_nbsp', linenumber)  end  local ok, msg = pcall(maker, utype, fields(line:sub(2)))  if not ok then  if msg:sub(-1) == '.' then msg = msg:sub(1, -2) end  errors:add(msg .. message('m_line_num', linenumber))  if errors.n >= cfg.maxerrors then  break  end  end  else  local level, heading = extract_heading(line)  if level == 3 then  utype = ulower(heading)  elseif level == 2 then  break  end  end  end  end  if skip and need_section then  quit('m_hdg_lev2', section)  end  if errors.n > 0 then  error(errors:join(), 0)  end end  local function get_page_lines(page_title)  -- Read the wikitext of the page at the given title; split the text into  -- lines with leading and trailing space removed from each line.  -- Return a numbered table of the lines, or throw an error.  if empty(page_title) then  quit('m_no_title')  end  local t = mw.title.new(page_title)  if t then  local content = t:getContent()  if content then  if content:sub(-1) ~= '\n' then  content = content .. '\n'  end  local lines = collection()  for line in string.gmatch(content, '[\t ]*(.-)[\t\r ]*\n') do  lines:add(line)  end  return lines  end  end  quit('m_ftl_read', page_title) end  local function prepare_data(cfg, is_sandbox)  -- Read the page of conversion data, and process the wikitext  -- in the sections with wanted level-two headings.  -- Return units, defaults, links (three tables).  -- Throw an error if a problem occurs.  local composites, defaults, links, units, perunits, varnames = {}, {}, {}, {}, {}, {}  local sections = {  { 'overrides' , make_override , overrides , 0 },  { 'conversions' , make_unit , units , 0 },  { 'outmultiples', make_outputmultiple, units , 0 },  { 'combinations', make_combination , units , 0 },  { 'inmultiples' , make_inputmultiple , composites, 0 }, -- after all units defined so default will be defined  { 'defaults' , make_default , defaults , 0 },  { 'links' , make_link , links , 0 },  { 'perunits' , make_perunit , perunits , 1 },  { 'varnames' , make_varname , varnames , 1 },  }  local lines = get_page_lines(cfg.data_title)  for _, section in ipairs(sections) do  local heading = mtext.section_names[section[1]]  local maker = section[2](cfg, section[3])  local code = section[4]  local need_section, need_utype  if code == 0 and not is_sandbox then  need_section = true  end  if code == 0 then  need_utype = true  end  prepare_section(cfg, maker, lines, heading, need_section, need_utype)  end  check_all_defaults(cfg, units)  check_all_pers(cfg, units)  update_units(units, composites, varnames)  return units, defaults, links, perunits end  local function _makeunits(cfg, results)  -- Read the wikitext for the conversion data.  -- Append output to given results collection, or throw error if a problem.  text_code = require(cfg.text_title)  for _, name in ipairs({ 'SIprefixes', 'eng_scales', 'currency' }) do  if type(text_code[name]) ~= 'table' then  quit('m_ftl_table', cfg.text_title, name)  end  end  local translation = text_code.translation_table  if translation then  if translation.plural_suffix then  plural_suffix = translation.plural_suffix  end  local ts = translation.specials  if ts then  if ts.utype then  specials.utype = ts.utype  end  if ts.ucode then  specials.ucode = ts.ucode  end  end  local tm = translation.mtext  if tm then  if tm.section_names then  mtext.section_names = tm.section_names  end  if tm.titles then  mtext.titles = tm.titles  end  if tm.messages then  mtext.messages = tm.messages  end  end  end  local is_sandbox  local conversion_data_title = mtext.titles.conversion_data  if cfg.data_title and cfg.data_title ~= conversion_data_title then  if is_test_run then  is_sandbox = true  data_preamble = nil  data_postamble = nil  out_unit_prefix = 'local all_units = {'  out_unit_suffix = '}'  out_default_prefix = '\nlocal default_exceptions = {'  out_default_suffix = '}'  out_default_item = '\t["{symbol}"] = "{default}",'  out_link_prefix = '\nlocal link_exceptions = {'  out_link_suffix = '}'  out_link_item = '\t["{symbol}"] = "{link}",'  out_perunit_prefix = '\nlocal per_unit_fixups = {'  out_perunit_suffix = '}'  out_perunit_item = '\t["{lhs}"] = {rhs},'  end  else  cfg.data_title = conversion_data_title  end  local units, defaults, links, perunits = prepare_data(cfg, is_sandbox)  if data_preamble then  results:add(data_preamble)  end  results:add(out_unit_prefix)  for _, unit in ipairs(units) do  local spec  if unit.target then  spec = alias_specification  elseif unit.per then  spec = per_specification  unit.per = numbered_table_as_string(unit.per, unit)  elseif unit.shouldbe then  spec = shouldbe_specification  elseif unit.combination then  spec = combination_specification  unit.combination = numbered_table_as_string(unit.combination, unit)  if unit.multiple then  unit.multiple = numbered_table_as_string(unit.multiple, unit)  end  else  spec = unit_specification  end  add_unit_lines(results, unit, spec)  end  results:add(out_unit_suffix)  for _, t in ipairs({  { defaults, out_default_prefix, out_default_item, out_default_suffix },  { links , out_link_prefix , out_link_item , out_link_suffix },  { perunits, out_perunit_prefix, out_perunit_item, out_perunit_suffix } }) do  local data, prefix, item, suffix = t[1], t[2], t[3], t[4]  if #data > 0 or not is_sandbox then  results:add(prefix)  for _, unit in ipairs(data) do  results:add((item:gsub('{([%w_]+)}', unit)))  end  results:add(suffix)  end  end  if data_postamble then  results:add(data_postamble)  end end  local function makeunits(frame)  local args = frame.args  local config = {  data_title = args[1],  text_title = args[2] or 'Module:Convert/text',  varcolumns = tonumber(args.varcolumns) or 5, -- #columns in "Variable names" section; slwiki uses 5  maxerrors = 20,  }  local results = collection()  local ok, msg = pcall(_makeunits, config, results)  if not ok then  results:add(message('m_error'))  results:add('')  results:add(msg)  end  local warn = ''  if warnings.n > 0 then  warn = message('m_warning') .. '\n\n' .. warnings:join() .. '\n\n'  end  -- Pre tags returned by a module are html tags, not like wikitext <pre>...</pre>.  -- The following renders the text as is, and preserves tab characters.  return ":[https://th.wikipedia.org/wiki/%E0%B8%84%E0%B8%B8%E0%B8%A2%E0%B9%80%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B8%A1%E0%B8%AD%E0%B8%94%E0%B8%B9%E0%B8%A5:Convert/makeunit?action=render&templates=expand กดที่นี่อาจทำให้อ่านง่ายขึ้น] '''[https://th.wikipedia.org/w/index.php?title=%E0%B8%84%E0%B8%B8%E0%B8%A2%E0%B9%80%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B8%A1%E0%B8%AD%E0%B8%94%E0%B8%B9%E0%B8%A5:Convert/makeunit&action=purge ล้างแคชหน้านี้]''' แก้ไขข้อมูลสำหรับแสดงที่นี่ได้ที่ [[มอดูล:Convert/documentation/conversion data/doc]]\n<pre>\n" .. mw.text.nowiki(warn .. results:join()) .. '\n</pre>\n' end  return { makeunits = makeunits } 

มอด, convert, makeunit, อการใช, งานมอด, สร, าง, ณอาจจะต, องการสร, างค, อการใช, งานของมอด, ลน, เข, ยนสามารถทำการทดลองได, กระบะทราย, สร, าง, ดลอก, และช, ดทดสอบ, สร, าง, ของมอด, ลน, โปรดเพ, มหมวดหม, ไปท, หน, าย, อย, หน, าย, อยของมอด, ลน, this, module, generates, . khumuxkarichnganmxdul srang khunxaccatxngkarsrangkhumuxkarichngankhxngmxdulniphuekhiynsamarththakarthdlxngidthikrabathray srang khdlxk aelachudthdsxb srang khxngmxdulnioprdephimhmwdhmuipthihnayxy doc hnayxykhxngmxdulni This module generates the wikitext required at Module Convert data by reading and processing the wikitext of the master list of units see conversion data for the page title Script method Read lines ignoring everything before Conversions Process the following lines Find next level 3 heading like Length Parse each following line starting with but ignore lines starting with or Split such lines into fields delimiter and trim leading trailing whitespace from each field Remove any colspan at front of second field symbol Remove thousand separators commas from the scale field If the scale is a number do not change it Otherwise it should be an expression like 5 9 in which case it is replaced by the value of the expression Remove wiki formatting from the link field Remove redundant fields from the unit to reduce size of data table Create alternative forms of a unit such as an alias or a combination Stop processing when encounter end of text or a line starting with a level 2 heading but not Repeat above for each heading listed at prepare data Output Lua source for the units table Output has the following form local all units unitcode standard format name1 singular name omitted if redundant name1 us singular name sp us omitted if redundant name2 plural name omitted if redundant name2 us plural name sp us omitted if redundant symbol symbol sym us symbol sp us omitted if redundant usename 1 omitted if empty utype unit type from level 3 heading scale 1 a value if necessary from evaluating an expression subdivs ft 5280 default km yd 1760 composite input omitted if empty link title of article for wikilink omitted if empty or redundant other values unitcode alternative format to generate an alias target unit code optional values to override those of target unitcode alternative format to generate a per unit like acre or BTU h per u1 u2 numbered table of unitcodes u1 may be a currency symbol optional values unitcode alternative format to generate an error message shouldbe message that some other unit code should be used unitcode alternative format for combination outputs like m ft combination u1 u2 numbered table of unitcodes utype unit type as for standard format unitcode alternative format for output multiples like ftin combination u1 u2 numbered table of unitcodes multiple f1 f2 numbered table of integer factors utype unit type as for standard format local ulower mw ustring lower local usub mw ustring sub local text code local specials This table is used to add extra fields when defining some units which require exceptions to normal processing Each key is in the local language while each value is fixed text However this script should NOT be edited Instead the translation table in Module Convert text can be edited and this script will replace sections of the following with localized definitions from Module Convert text if given Ask for assistance at en Module talk Convert LATER It would be better if this was defined in the conversion data utype unit type in local language name used in this script fuel efficiency type fuel efficiency length type length temperature type temperature volume type volume ucode exception unit code in local language name used in module convert ft integer more precision in subunit more precision lb integer more precision istemperature Common temperature scales not keVT or MK unit code in local language true C true F true K true R true usesymbol Use unit symbol not name if abbr not specified unit code in local language 1 C 1 F 1 K 1 R 1 C change 1 F change 1 K change 1 alttype Unit has an alternate type that is a valid conversion unit code in local language alternate type in local language Nm energy ftlb torque ftlb f torque ftlbf torque inlb torque inlb f torque inlbf torque inoz f torque inozf torque Module text for the local language localization A default table of text for enwiki is provided here If needed for another wiki wanted sections from the table can be copied into translation table in Module Convert text For example copying and modifying only the titles section may give local translation table other items mtext titles name used in this script Title of page conversion data Modul Convert documentation conversion data dok local mtext section names name used in this script Section title used in conversion data overrides karekhiynthb conversions karaeplng outmultiples karsngxxkhlaykha combinations chudkhaphsm inmultiples karnaekhahlaykha defaults khapriyay links lingk perunits khaxtonmtitxhnwy varnames chuxtwaepr titles name used in this script Title of page conversion data mxdul Convert documentation conversion data doc messages name used in this script Error message 1 first parameter 2 second m als bad Alias has invalid text in field 1 m als dup Alias 1 already defined m als link Alias 1 must include a wikilink in the symlink text m als mul Alias 1 has multiplier 2 which is not a number m als same Should omit 1 for alias 2 because it is the same as its target m als type Target of alias 1 has wrong type m als undef Primary unit must be defined before alias 1 m cmb miss Missing unit code for a combination m cmb none No units specified for combination 1 m cmb one Only one unit specified for combination 1 m cmb type Unit 1 in combination 2 has wrong type m cmb undef Unit 1 in combination 2 not defined m cmp def Composite 1 must specify a default unit code m cmp int Composite 1 has components where scale ratios are not integers m cmp inval Composite 1 has a component with an invalid scale 2 m cmp many Composite 1 has too many fields m cmp miss Missing unit code for a composite m cmp order Composite 1 has components in wrong order or with invalid scales m cmp scale Alternate unit 1 in composite 2 has wrong scale m cmp two Composite 1 must specify exactly two unit codes m cmp type Unit 1 in composite 2 has wrong type m cmp undef Unit 1 in composite 2 not defined m def cond Invalid condition in default 1 for unit 2 m def fmt Default output 1 for unit 2 should have 2 or 3 m def rpt Default output 1 for unit 2 is repeated m def same Default output for unit 1 is the same unit m def type Default output 1 for unit 2 has wrong type m def undef Default output 1 for unit 2 is not defined m dfs code Defaults section no unit code specified m dfs dup Defaults section unit 1 has already been specified m dfs none Defaults section unit 1 has no default specified m dfs sym Defaults section unit 1 must have a symbol m dfs two Defaults section unit 1 should have two fields only m dfs undef Defaults section unit 1 is not defined m dup code Unit code 1 has already been defined m error Error m ftl read Could not read wikitext from 1 m ftl table 1 should export table 2 m ftl type Fatal error unknown data type for 1 m hdg lev2 Level 2 heading 1 not found m hdg lev3 No level 3 heading before 1 m line num line 1 m lnk brack Link 1 has wrong number of brackets m lnk dup Link exception 1 is already defined m lnk miss Missing unit code for a link m lnk none No link defined for unit 1 m lnk sym Unit code 1 for a link must have a symbol m lnk two Row for unit 1 link should have two fields only m lnk type Link exception 1 has wrong type m lnk undef Unit code 1 for a link is not defined m miss code Missing unit code m miss sym Missing symbol m miss type Missing unit type m mul int Multiple 1 has components where scale ratios are not integers m mul miss Missing unit code for a multiple m mul none No units specified for multiple 1 m mul one Only one unit specified for multiple 1 m mul order Multiple 1 has components in wrong order or with invalid scales m mul scale Multiple 1 has a component with an invalid scale 2 m mul std Unit 1 in multiple 2 must be a standard unit m mul type Unit 1 in multiple 2 has wrong type m mul undef Unit 1 in multiple 2 not defined m no title Need title of page with unit definitions m ovr dup Override 1 is already defined m ovr miss Missing unit code for an override m per dup Per unit 1 already defined m per empty Unit 1 has an empty field in the per m per fuel Unit 1 has invalid unit types for fuel efficiency m per inv Invalid field for a per m per two Unit 1 does not have exactly 2 fields in the per m per undef Unit 1 has undefined unit code 2 in the per m percent s Field 1 must not contain s m pfx bad Unknown prefix 1 m pfx name Unit with Prefix set must include Name m scl bad Scale expression is invalid 1 m scl miss Missing scale m scl oflow Scale expression gives an invalid value 1 m var cnt Variable names section each row must have the configured number of columns m var dup Unit 1 already has a variable name m var miss Missing field for a variable name m var undef Unit 1 in variable names is not defined m warning Warning m wrn more and more not shown m wrn nbsp Line 1 contains a nonbreaking space m wrn nodef Units with the following unit codes have no default output m wrn ucode 1 local function message key Return a message from the message table which can be localized 1 2 are replaced with the first second parameters each of which must be a string or a number The global variable is test run can be set by a testing program to check the messages generated by this program local rep for i v in ipairs do rep i v end key key or local extra if is test run and key m line num then extra key else extra end return extra string gsub mtext messages key or key d rep end local function quit key Use error to pass an error message to the surrounding pcall error message key 0 end local function quit no message Throw an error This is used in some functions which can throw an error with a message but where the message is in fact never displayed because the calling function uses pcall to catch errors and any message is ignored Using this function documents that the message which may be useful in some other application does not need translation as it never appears error this message is not displayed 0 end local function collection Return a table to hold items return n 0 add function self item self n self n 1 self self n item end pop function self item if self n gt 0 then local top self self n self n self n 1 return top end end join function self sep return table concat self sep or n end end local warnings collection local function add warning key Add a warning that will be inserted before the final result warnings add message key end Begin code to evaluate expressions This is needed because Lua s loadstring is not available in Scribunto and each scale value can be specifed as an expression such as 5 9 More complex expressions are supported including use of parentheses and the binary operators local operators precedence 1 associativity 1 func function a b return a b end precedence 1 associativity 1 func function a b return a b end precedence 2 associativity 1 func function a b return a b end precedence 2 associativity 1 func function a b return a b end precedence 3 associativity 2 func function a b return a b end local function tokenizer text Function next returns the next token which is one of number table operator string or nil end of text If invalid an error is thrown The number is unsigned unary operators are not supported return pos 1 maxpos text text text next function self if self pos lt self maxpos then local p1 p2 hit self text find s self pos if hit then self pos p2 1 return operators hit end p1 p2 hit self text find s d d eE d self pos if not hit then p1 p2 hit self text find s d d self pos end local value tonumber hit if value then self pos p2 1 return value end quit no message invalid number self text sub self pos end end end local function evaluate tokens tokens inparens Return the value from evaluating tokenized expression or throw an error local numstack opstack collection collection local function perform ops precedence associativity while opstack n gt 0 and opstack opstack n precedence gt precedence or opstack opstack n precedence precedence and associativity 1 do local rhs numstack pop local lhs numstack pop if not rhs and lhs then quit no message missing number end local op opstack pop numstack add op func lhs rhs end end local token last local function set state token type if token last token type then local missing token type number and operator or number quit no message missing missing end token last token type end while true do local token tokens next if type token number then set state number numstack add token elseif type token table then set state operator perform ops token precedence token associativity opstack add token elseif token then set state number numstack add evaluate tokens tokens true elseif token then if inparens then break end quit no message unbalanced parentheses else break end end perform ops 0 if numstack n gt 1 then quit no message missing operator end if numstack n lt 1 then quit no message missing number end return numstack pop end local function evaluate expression Return value a number from evaluating expression a string or throw an error if invalid This is not bullet proof but it should support the expressions used return evaluate tokens tokenizer expression end End code to evaluate expressions Begin code adapted from Module Convert local plural suffix s may be changed from translation plural suffix below local function shallow copy t Return a shallow copy of t Do not need the features and overhead of mw clone provided by Scribunto local result for k v in pairs t do result k v end return result end local 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 item for item in text gmatch s s delimiter do table insert t item end return t end local 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 value if key name1 or key sym us then value self symbol elseif key name2 then value self name1 plural suffix elseif key name1 us then value self name1 if not rawget self name2 us then If name1 us is foot do not make name2 us by appending plural suffix self name2 us self name2 end elseif key name2 us then local raw1 us rawget self name1 us if raw1 us then value raw1 us plural suffix else value self name2 end elseif key link then value self name1 else value false end rawset self key value return value end 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 then pos tonumber split pos index end if pos then return name sub 1 pos 1 unit si name name sub pos end return unit si name name end local 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 value if key symbol then value self si prefix self symbol elseif key sym us then value rawget self sym us if value then value self si prefix value else value self symbol end elseif key name1 then value prefixed name self self name1 1 elseif key name2 then value rawget self name2 if value then value prefixed name self value 2 else value self name1 plural suffix end elseif key name1 us then value rawget self name1 us if value then value prefixed name self value 3 else value self name1 end elseif key name2 us then value rawget self name2 us if value then value prefixed name self value 4 elseif rawget self name1 us then value self name1 us plural suffix else value self name2 end elseif key link then value self name1 else value false end rawset self key value return value end local function lookup units unitcode sp what Return a copy of the unit if found or return nil In this cut down code sp is always nil and what is ignored local t units unitcode if t then if t shouldbe then return nil end local result shallow copy t if result prefixes then result si name result si prefix return setmetatable result unit prefixed mt end return setmetatable result unit mt end local SIprefixes text code SIprefixes for 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 then local t units usub unitcode plen 1 if t and t prefixes then local result shallow copy t if sp us or t sp us and si name us then result si name si name us else result si name si name end result si prefix si prefix or prefix In this script each scale is a string result scale tostring tonumber t scale 10 si exponent t prefixes result prefixes nil a prefixed unit does not take more prefixes in this script the returned unit may be added to the list of units return setmetatable result unit prefixed mt end end end local exponent baseunit unitcode match e d if exponent then local engscale text code eng scales exponent if engscale then local result lookup units baseunit sp no combination if not result then return nil end if not result offset or result builtin or result engscale then result defkey unitcode key to lookup default exception result engscale engscale Do not set result scale as this code is called for units where that is not set return result end end end return nil end local 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 lt 9 v 9 lt 9 where v is replaced with value 9 is any number as defined by Lua tonumber lt can also be lt or gt or gt 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 lt gt if arithop nil then quit no message Invalid default expression elseif arithop then factor tonumber factor if factor nil then quit no message Invalid default expression end value value factor end limit tonumber limit if limit nil then quit no message Invalid default expression end if compop lt then return value lt limit elseif compop lt then return value lt limit elseif compop gt then return value gt limit elseif compop gt then return value gt limit end quit no message Invalid default expression should not occur end local lhs rhs condition match W and W if lhs nil then return compare value condition end return compare value lhs and compare value rhs end End adapted code local function strip text Return text with no leading trailing whitespace return text match s s end local function empty text Return true if text is nil or empty assuming a string return text nil or text end Tables of units k unit code v unit table local units index all units normal alias per combination or multiple local alias index all aliases to detect attempts to define more than once local per index all per units to detect attempts to define more than once local function get unit ucode utype Look up unit code in our cache of units If utype nil the unit should already have been defined Otherwise ucode may represent an automatically generated combination where each component must have the given utype a dummy unit is returned if empty ucode then return nil end local unit lookup units index ucode if unit or not utype then return unit end local combo collection if ucode find 1 true then for item in ucode gmatch s s do if item then combo add item end end elseif ucode find s then for item in ucode gmatch S do combo add item end end if combo n gt 1 then local result setmetatable utype utype index function self key error Bug invalid use of automatically generated unit end for v in ipairs combo do local component lookup units index v if not component or component shouldbe or component combination then return nil end if utype component utype then result utype component utype set wrong type which caller will detect break end end return result end end local overrides read from input for unit codes that should not be checked for a duplicate local function insert unique unit data unit index After inserting any required built in data insert the unit into the data table and if index not nil add to index but not if the unit code is already defined local ucode unit unitcode local known get unit ucode if known and not overrides ucode then quit m dup code ucode end for item t in pairs specials ucode do unit item t ucode end if index then index ucode unit end table insert data unit end local function check condition condition Return true if condition appears to be valid otherwise return false for value in ipairs 0 0 1 1 1 1 10 100 1000 1e4 1e5 do local success result pcall evaluate condition value condition if not success then return false end end return true end local function check default expression default ucode Return a numbered table of names present in param default two names if an expression or one name param default otherwise Throw an error if a problem occurs An expression uses pipe delimited fields with v representing the input value for the conversion Example suffix is optional v lt 120 small big suffix returns smallsuffix bigsuffix if not default find 1 true then return default end local t for item in default gmatch s s do t t 1 item split on removing leading trailing whitespace end if not t 3 or t 4 then quit m def fmt default ucode end local condition default1 default2 t 1 t 2 t 3 if t 4 then default1 default1 t 4 default2 default2 t 4 end if not check condition condition then quit m def cond default ucode end return default1 default2 end local function check default default ucode utype unit table Check the given name or expression of a default output Normally a unit must not define itself as its default However some units are defined merely for use in per units and they have the same ucode utype and default Example unit cent which cannot be converted to anything other than a cent but which can work for example in cent km and cent mi Throw an error if a problem occurs local done for default in ipairs check default expression default ucode do if done default then quit m def rpt default ucode end if default ucode and ucode utype then quit m def same ucode end local default table get unit default utype if not default table then quit m def undef default ucode end if not utype unit table utype and utype default table utype then quit m def type default ucode end done default true end end local function check all defaults cfg units Check each default in units and warn if needed This is done after all input data has been processed Throw an error if a problem occurs local errors collection local missing collection unitcodes with missing defaults for unit in ipairs units do if not unit shouldbe and not unit combination then This is a standard unit or an alias per not shouldbe combo An alias may have a default defined but it is optional local default unit default local ucode unit unitcode if empty default then if not unit target then unit should have a default missing add ucode end else local ok msg pcall check default default ucode unit utype unit if not ok then errors add msg if errors n gt cfg maxerrors then break end end end end end if errors n gt 0 then error errors join 0 end if missing n gt 0 then add warning m wrn nodef local limit cfg maxerrors for v in ipairs missing do limit limit 1 if limit lt 0 then add warning m wrn more break end add warning m wrn ucode v end end end local function check all pers cfg units Check each component of each per unit and warn if needed In addition add any required extra fields for some types of units This is done after all input data has been processed Throw an error if a problem occurs local errors collection local function errmsg key errors add message key end for unit in ipairs units do local per unit per if per then local ucode unit unitcode if per 2 then errmsg m per two ucode else local types for i v in ipairs per do if empty v then errmsg m per empty ucode end if not text code currency v then local t get unit v if t then types i t utype else errmsg m per undef ucode v end end end if specials utype unit utype type fuel efficiency then local expected type volume 1 type length 2 local top type expected specials utype types 1 local bot type expected specials utype types 2 if top type and bot type and top type bot type then unit iscomplex true if top type 1 then unit invert 1 else unit invert 1 end else errmsg m per fuel ucode end end end end if errors n gt cfg maxerrors then break end end if errors n gt 0 then error errors join 0 end end local function update units units composites varnames Update some unit definitions with extra data defined in other sections This is done after all input data has been processed for unit in ipairs units do local comp composites unit unitcode if comp then unit subdivs table concat comp subdivs end local vn varnames unit unitcode if vn then unit varname vn end end end local function make override cfg data Return a function which when called stores a unit code that is not to be checked for a duplicate The table is stored in data also a table return function utype fields local ucode fields 1 if empty ucode then quit m ovr miss end if data ucode then quit m ovr dup ucode end data ucode true end end local function make default cfg data Return a function which when called stores a table that defines a default output unit The table is stored in data also a table local defaults index to detect attempts to define a default twice return function utype fields Store a table defining a unit This is for a unit such as kg that has a default output unit different from what is defined for the base unit g Throw an error if a problem occurs local ucode fields 1 local default fields 2 if empty ucode then quit m dfs code end if empty default then quit m dfs none ucode end if fields 2 then quit m dfs two ucode end local unit table get unit ucode if not unit table then quit m dfs undef ucode end local symbol unit table defkey or unit table symbol if empty symbol then quit m dfs sym ucode end check default default ucode utype unit table if defaults index ucode then quit m dfs dup ucode end defaults index ucode default table insert data symbol symbol default default end end local function clean link link name Return link customary where link given link after removing any wiki formatting and removing any leading or or customary 1 if leading or 2 if or 3 if or nil for extra US or U S or Imperial customary units link Result has leading trailing whitespace removed and is nil if empty or if link matches the name if a name is specified Exception If the link is empty and the name starts with the link is stored as for a unit name which is always linked If the resulting link is nil no link field is stored and if a link is required it will be set from the unit s name local original link if empty link then return name and name sub 1 2 and or nil end local prefixes 1 2 3 local customary prefixes link sub 1 1 if customary then link strip link sub 2 end if link sub 1 2 then link link sub 3 end if link sub 2 then link link sub 1 3 end link strip link if link sub 1 1 or link sub 1 then quit m lnk brack original end if link then link nil elseif name then local l ulower usub link 1 1 usub link 2 local n ulower usub name 1 1 usub name 2 if l n then link nil link name ignoring case of first letter end end return link customary end local function make link cfg data Return a function which when called stores a table that defines a link exception The table is stored in data also a table local links index to detect attempts to define a link twice return function utype fields Store a table defining a unit This is for a unit such as kg that has a linked article different from what is defined for the base unit g Throw an error if a problem occurs local ucode fields 1 local link clean link fields 2 if empty ucode then quit m lnk miss end if empty link then quit m lnk none ucode end if fields 2 then quit m lnk two ucode end local unit table get unit ucode if not unit table then quit m lnk undef ucode end if utype unit table utype then quit m lnk type ucode end local symbol unit table symbol if empty symbol then quit m lnk sym ucode end if links index ucode then quit m lnk dup ucode end links index ucode link table insert data symbol symbol link link end end local function clean scale scale Return cleaned scale as a string after evaluating any expression It would be better to retain scale expressions like 5 9 so that the expression is evaluated on the server and maintains the full resolution of the server However there are many such expressions in the table of all units and it seems pointless to require the server to evaluate all of them just to do one convert if empty scale then quit m scl miss end assert type scale string Bug scale has an unexpected type scale string gsub scale remove comma separators if tonumber scale then not an expression return scale end local status value pcall evaluate scale if not status and type value number then quit m scl bad scale end local result string format 17g value if result find n then Lua can give results like INF while Scribunto gives inf Either is an error quit m scl oflow scale end Omit redundant zeros from results like 1 2e 005 Do not bother looking for results like 1 2e 005 as none occur in practice local lhs zeros rhs result match e 0 if zeros then result lhs rhs end return result end local function add alias optional fields unit start fields target Inspect fields i for i start start 1 and extract any definitions appropriate for an alias or per and add them to unit For an alias target is a valid unit for a per target is nil Throw error if encounter an invalid entry for i start fields do local field fields i if not empty field then local lhs rhs field match s s s s local good if not empty rhs then for item in ipairs sp default link multiplier symbol symlink do if lhs item then if item sp then if rhs us then unit sp us true good true end elseif item link then local tlink if target then tlink target item end local link customary clean link rhs tlink if link then unit item link end if customary then unit customary customary end good true elseif item symlink then local pos1 rhs find 1 true local pos2 rhs find 1 true if not pos1 and pos2 and pos1 lt pos2 then quit m als link unit unitcode end unit symlink rhs good true elseif item multiplier then if not tonumber rhs then quit m als mul unit unitcode rhs end unit item rhs good true else if target and rhs target item then quit m als same item unit unitcode end unit item rhs good true end break end end end if not good then quit m als bad field end end end end local function make alias fields ucode utype symbol Return a new alias unit or return nil if symbol is not already defined as the unit code of the target unit Throw an error if invalid local target get unit symbol if not target then return nil end local unit unitcode ucode utype utype target symbol add alias optional fields unit 3 fields target if alias index ucode then quit m als dup ucode else alias index ucode unit end if target utype utype then quit m als type ucode end return unit end local function make per fields ucode utype symbol Return a new per unit or return nil if symbol is not of form x y Throw an error if invalid The top bottom unit codes are checked later after all units are defined local top bottom symbol match if not top then return nil end local unit unitcode ucode utype utype per strip top strip bottom add alias optional fields unit 3 fields if per index ucode then quit m per dup ucode else per index ucode unit end return unit end local function make unit cfg data Return a function which when called stores a table that defines a single unit The table is stored in data also a table local fieldnames Fields in the Conversions section are assumed to be in the following order unitcode symbol scale extra name1 prefixes default link return function utype fields Store a table defining a unit Throw an error if a problem occurs local ucode symbol fields 1 fields 2 if empty utype then quit m miss type end if empty ucode then quit m miss code end if empty symbol then quit m miss sym end local prefix symbol sub 1 1 if prefix or prefix or prefix or prefix then if symbol sub 1 2 then prefix symbol sub 1 2 end symbol strip symbol sub prefix 1 omit prefix and any following whitespace fields 2 symbol else prefix nil not a valid prefix end if prefix or prefix then ucode is an alias a fake unit code used in a convert template or defines a per unit like acre or BTU h For an alias symbol is the unit code of the actual unit For a per symbol is of form x y where x and y are unit codes or x is a recognized currency symbol and y is a unit code Checking that x and y are valid is deferred until all units have been defined so for example BTU h can be defined before h local unit if prefix then unit make alias fields ucode utype symbol else unit make per fields ucode utype symbol end if not unit then Do not define an alias in terms of another alias quit m als undef symbol end insert unique unit data unit units index return elseif prefix then ucode may be incorrectly entered as a unit code symbol is a message saying what unit code should be used local unit unitcode ucode shouldbe symbol insert unique unit data unit nil return end Make the unit local unit utype utype for i name in ipairs fieldnames do if not empty fields i then unit name fields i end end Remove redundancy from unit if unit sym us symbol then unit sym us nil end local prefixes unit prefixes local name1 name2 unit name1 unit name2 if name1 then if name1 symbol and not prefixes then A unit which takes an SI prefix must not have a nil name because for example the name for kW kilo watt name for W The not prefixes test is needed for bnwiki where the watt unit has the same name and symbol unit name1 nil end else name1 symbol end if name2 then if name2 name1 plural suffix then unit name2 nil end else name2 name1 plural suffix end local name1 us name2 us unit name1 us unit name2 us if name1 us then if name1 us name1 then unit name1 us nil end end if name2 us then if unit name1 us then if name2 us unit name1 us plural suffix then unit name2 us nil end elseif name2 us name2 then unit name2 us nil end end Other changes to unit unit scale clean scale unit scale local extra unit extra if not empty extra then Set appropriate fields for a unit that needs more than a simple multiplication by a ratio of unit scales to convert values unit iscomplex true if extra volume length then unit invert 1 elseif extra length volume then unit invert 1 elseif specials utype utype type temperature then unit offset extra elseif extra invert then unit invert 1 else unit builtin extra end end if prefix then Magic code for units like acre where the symbol is not really a symbol and output should use the singular or plural name instead unit usename 1 elseif prefix then Magic code for units like pitch which have a symbol that is the same as another unit with entries defined in the default or link exceptions tables unit defkey ucode key for default exceptions unit linkey ucode key for link exceptions end local name for link if prefixes then if prefixes SI then unit prefixes 1 elseif prefixes SI2 then unit prefixes 2 elseif prefixes SI3 then unit prefixes 3 else quit m pfx bad prefixes end else Only units which do not accept SI prefixes have name for link set That is because for example if set name for link name1 for unit g then the link is kilogram for kg and yottagram for Yg and so on for all prefixes That might be desirable for some units but not all name for link name1 end unit link unit customary clean link unit link name for link if prefixes then The SI prefix is always at the start position 1 for symbol and sym us However each name name1 name2 name1 us name2 us can have the SI prefix at any position and that position can be different for each name For enwiki the only units with names where the prefix is not at the start are square metre and cubic metre square meter and cubic meter for sp us Some other wikis want the flexibility that the prefix position can be different so the position is stored as nil if always 1 or N an integer if always N or a string of four comma separated numbers such as 5 7 9 11 which means the prefix position for name1 name2 name1 us name2 us is 5 7 9 11 respectively local name1 name1 us unit name1 unit name1 us after redundancy removed if not name1 then quit m pfx name end local positions collection for i k in ipairs name1 name2 name1 us name2 us do local name unit k local pos if name then pos name find s 1 true if pos then unit k name sub 1 pos 1 name sub pos 2 end elseif i 2 or i 3 then pos positions 1 elseif i 4 then pos positions unit name1 us and 3 or 2 end positions add pos or 1 end local pos positions 1 for i 2 positions n do if pos positions i then pos positions join break end end if pos 1 then unit prefix position pos end for name in ipairs symbol sym us name1 name1 us name2 name2 us do unit name unit name unit name nil force call to index metamethod so any SI prefix can be handled end end for name v in pairs unit do Reject if a string field includes s should not occur after above if type v string and v find s 1 true then quit m percent s name end end insert unique unit data unit units index end end local function make combination cfg data Return a function which when called stores a table that defines a single combination unit The table is stored in data also a table return function utype fields Store a table defining a unit This is for a combination unit that specifies more than one output The target units must be defined first Throw an error if a problem occurs local unit utype utype combination for i v in ipairs fields do if i 1 then unitcode if v then quit m cmb miss end unit unitcode v elseif v then Ignore empty fields else local target get unit v if not target then quit m cmb undef v unit unitcode end if target utype utype then quit m cmb type v unit unitcode end table insert unit combination v end end if unit combination lt 2 then quit unit combination 0 and m cmb none or m cmb one unit unitcode end insert unique unit data unit units index end end local function make perunit cfg data Return a function which when called stores a table that defines a fixup for an automatic per unit The table is stored in data also a table local pertype index to detect attempts to define a fixup twice return function utype fields Store a table to define a fixup Typos or other errors in the input are not detected Parameter utype is ignored it is nil Throw an error if a problem occurs local lhs rhs link multiplier for i v in ipairs fields do if v then Ignore empty fields elseif i 1 then lhs v like length time elseif i 2 then rhs v like speed elseif i 3 then link v elseif i 4 then if not tonumber v then quit m per inv end multiplier v else quit m per inv end end if lhs and rhs or link or multiplier then if link or multiplier then local parts collection if rhs then parts add utype rhs end if link then parts add link link end if multiplier then parts add multiplier multiplier end rhs parts join else rhs rhs end if pertype index lhs then quit m per dup lhs end pertype index lhs rhs table insert data lhs lhs rhs rhs else quit m per inv end end end local function make varname cfg data Return a function which when called stores a table that defines a variable name for a unit The table is stored in data also a table return function utype fields Set or update an entry in the data table to record that a unit has a variable name This is for slwiki where a unit name depends on the value The target units must be defined first Parameter utype is ignored it is nil Throw an error if a problem occurs local count fields if count cfg varcolumns then quit m var cnt end local ucode local names for i 1 count do local v fields i if empty v then quit m var miss end if i 1 then unitcode ucode v if not get unit v then quit m var undef v end else table insert names v end end if data ucode then quit m var dup ucode end data ucode table concat names end end local function reversed t Return a numbered table in reverse order local reversed count t for i 1 count do reversed i t count 1 i end return reversed end local function make inputmultiple cfg data Return a function which when called stores a table that defines a single composite multiple input unit The table is stored in data also a table return function utype fields Set or update an entry in the data table to record that a unit accepts subdivisions to make a composite input unit like 2 ft 6 in The target units must be defined first Throw an error if a problem occurs local unitcode dummy code required for simplicity but which is not used in output local alternate code an alternative unit code can be specified to replace convert input local fixed name a fixed name can be specified to replace the unit s normal symbol name local default code local ucodes scales for i v in ipairs fields do 1 composite 2 ucode1 3 ucode2 4 default 5 alternate 6 name if i 1 then if v then quit m cmp miss end unitcode v elseif 2 lt i and i lt 5 then if not i 5 and v then local target get unit v i 4 and utype or nil the default may be an auto combination if not target then quit m cmp undef v unitcode end if target utype utype then quit m cmp type v unitcode end if i lt 4 then if not target scale then quit m mul std v unitcode end table insert ucodes v table insert scales target scale elseif i 4 then default code v else if scales scales target scale then quit m cmp scale v unitcode end alternate code v end end elseif i 6 then if v then fixed name v end else quit m cmp many unitcode end end if ucodes 2 then quit m cmp two unitcode end if not default code then quit m cmp def unitcode end Component units must be specified from most significant to least significant and each ratio of a pair of scales must be very close to an integer Currently there will be exactly two scales and one ratio local ratios count scales for i 1 count do local scale tonumber scales i if scale nil or scale lt 0 then quit m cmp inval unitcode scales i end scales i scale end for i 1 count 1 do local ratio scales i scales i 1 local rounded math floor ratio 0 5 if rounded lt 2 then quit m cmp order unitcode end if math abs ratio rounded ratio gt 1e 6 then quit m cmp int unitcode end ratios i rounded end local text tostring ratios 1 local function add text key value table insert text string format s q key value end if default code then add text default default code end if alternate code then add text unit alternate code end if fixed name then add text name fixed name end local subdiv string format s s ucodes 2 table concat text local main code ucodes 1 local item data main code if item then table insert item subdivs subdiv else data main code subdivs subdiv end end end local function make outputmultiple cfg data Return a function which when called stores a table that defines a single multiple output unit The table is stored in data also a table return function utype fields Store a table defining a unit This is for a multiple unit like ydftin result in yards feet inches The target units must be defined first Throw an error if a problem occurs local unit utype utype local ucodes scales for i v in ipairs fields do if i 1 then unitcode if v then quit m mul miss end unit unitcode v elseif v then Ignore empty fields else local target get unit v if not target then quit m mul undef v unit unitcode end if target utype utype then quit m mul type v unit unitcode end if not target scale then quit m mul std v unit unitcode end table insert ucodes v table insert scales target scale end end if ucodes lt 2 then quit ucodes 0 and m mul none or m mul one unit unitcode end Component units must be specified from most significant to least significant so scale values will be in descending order and each ratio of a pair of scales must be very close to an integer The componenets and ratios are stored in reverse order least significant first This script stores a unit scale as a string might be an expression like 5 9 but scales in a multiple are handled as numbers should never be expressions local ratios count scales for i 1 count do local scale tonumber scales i if scale nil or scale lt 0 then quit m mul scale unit unitcode scales i end scales i scale end for i 1 count 1 do local ratio scales i scales i 1 local rounded math floor ratio 0 5 if rounded lt 2 then quit m mul order unit unitcode end if math abs ratio rounded ratio gt 1e 6 then quit m mul int unit unitcode end ratios i rounded end unit combination reversed ucodes unit multiple reversed ratios insert unique unit data unit units index end end To make updating the data module easier this script inserts a preamble and a postamble so the result can be used to replace the whole page local data preamble khxmulkaraeplng thukichody Module Convert sungich mw loadData sahrbkaarekha thungkhxmulinhnaniaebbxanxyangediyw aelakhxmulcathukohldephiyngaekhkhrngediywinhnaaetlahna duthi w en Template Convert Transwiki guide sahrbkhumuxkarprbihekhakbwikithxngthin mitarangkhxmuldibdngtxipni all units khxmulaethbthnghmdkhxngaetahnwy rwmkhapriyay default exceptions khxmulthiimrwmkhapriyay kg aela g mikhapriyaytangkn link exceptions khxmulthiimmikhalingk kg aela g milingktangkn tarangkhxmulehlanithuksrangphanskhriptodyxankhxmultarangdibcakhna mxdul Convert documentation conversion data doc local data postamble return all units all units default exceptions default exceptions link exceptions link exceptions per unit fixups per unit fixups local out unit prefix xyaaekikhhnakhxmulniodytrngenuxngcakkhxmulinhnanicathuksrangxtonmtidwyskhript cakkhxmulinhnawiki xanhmayehtudanbn local all units local out unit suffix local out default prefix xyaaekikhhnakhxmulniodytrngenuxngcakkhxmulinhnanicathuksrangxtonmtidwyskhript cakkhxmulinhnawiki xanhmayehtudanbn local default exceptions Prefixed units with a default different from that of the base unit Each key item is a prefixed symbol unitcode for engineering notation local out default suffix local out default item symbol default local out link prefix Do not change the data in this table because it is created by running a script that reads the wikitext from a wiki page see note above local link exceptions Prefixed units with a linked article different from that of the base unit Each key item is a prefixed symbol not unitcode local out link suffix local out link item symbol link local out perunit prefix Do not change the data in this table because it is created by running a script that reads the wikitext from a wiki page see note above local per unit fixups Automatically created per units of form x y may have their unit type changed for example length time is changed to speed Other adjustments can also be specified local out perunit suffix local out perunit item lhs rhs local combination specification pure combination like m ft or a multiple like ftin combination multiple utype local alias specification target symbol sp us default link symlink customary multiplier local per specification per symbol sp us utype invert iscomplex default link symlink customary multiplier local shouldbe specification shouldbe local unit specification name1 name1 us name2 name2 us symbol sym us prefix position name1 name1 us name2 name2 us varname symbol sym us usename usesymbol utype alttype builtin scale offset invert iscomplex istemperature exception prefixes default subdivs defkey linkey link customary sp us local no quotes combination true customary true multiple true multiplier true offset true per true prefix position true scale true subdivs true local function add unit lines results unit spec Add lines of Lua source to define a unit to the results collection local function add line line Had planned to replace sequences of spaces with 4 column tabs here because the CodeEditor now assumes the use of such tabs However 4 column tabs are only visible when editing a module with browser scripting and the CodeEditor enabled and that is rare A module is usually viewed with 8 column tabs and some indents would be messed up unless 8 column tabs are used Therefore have decided to simply replace 8 spaces at start of line with a single tab which reduces the size of the module and is correct for viewing if line sub 1 8 string rep 8 then line t line sub 9 end results add line end local first item unit unitcode local last item add line first item for k in ipairs spec do local v unit k if v then local want quotes type v string and not no quotes k if type v boolean then v tostring v elseif type v number or k scale then Replace results like 1e 006 with 1e 6 v string gsub tostring v e 0 1 9 1 2 1 elseif type v string then quit m ftl type unit unitcode end local fmt string format 8s 9s s want quotes and q or s add line fmt format k v end end add line last item end local function numbered table as string data unit local t for v in ipairs data do if type v string then table insert t v elseif type v number then table insert t tostring v else quit m ftl type unit unitcode end end return table concat t end local function extract heading line Return n s where n heading level number nil if none and s heading text with leading trailing whitespace removed local pattern s s s local before heading after line match pattern if heading and heading gt 0 then Don t bother checking if before after return before heading end end local function fields line Return a numbered table of fields split from line Items are delimited by Each item has leading trailing whitespace removed and any encoded pipe characters are decoded The second field for symbol when processing units is adjusted to remove any colspan at the front of lines like unitcode colspan 11 Text to display for an error message local t line line to get last field for item in line gmatch s s do table insert t item gsub amp 124 end if t 2 then local cleaned t 2 match s colspan s s if cleaned then t 2 cleaned end end return t end local function prepare section cfg maker lines section need section need utype Process the first level two section with the given section name in the given table of lines of wikitext If successful maker inserts each item into a table Otherwise an error is thrown local skip true local errors collection local utype unit type from level three heading local nbsp 194 160 nonbreaking space is utf 8 encoded as hex c2 a0 for linenumber line in ipairs lines do if skip then Skip down to and including the starting heading local level heading extract heading line if level 2 and heading section then skip false end else Accummulate unit definitions local c1 line sub 1 1 local c2 line sub 2 2 if c1 and not c2 or c2 then if need utype and empty utype then quit m hdg lev3 line end if line find nbsp 1 true then For example acre ft does not work if it contains nbsp add warning m wrn nbsp linenumber end local ok msg pcall maker utype fields line sub 2 if not ok then if msg sub 1 then msg msg sub 1 2 end errors add msg message m line num linenumber if errors n gt cfg maxerrors then break end end else local level heading extract heading line if level 3 then utype ulower heading elseif level 2 then break end end end end if skip and need section then quit m hdg lev2 section end if errors n gt 0 then error errors join 0 end end local function get page lines page title Read the wikitext of the page at the given title split the text into lines with leading and trailing space removed from each line Return a numbered table of the lines or throw an error if empty page title then quit m no title end local t mw title new page title if t then local content t getContent if content then if content sub 1 n then content content n end local lines collection for line in string gmatch content t t r n do lines add line end return lines end end quit m ftl read page title end local function prepare data cfg is sandbox Read the page of conversion data and process the wikitext in the sections with wanted level two headings Return units defaults links three tables Throw an error if a problem occurs local composites defaults links units perunits varnames local sections overrides make override overrides 0 conversions make unit units 0 outmultiples make outputmultiple units 0 combinations make combination units 0 inmultiples make inputmultiple composites 0 after all units defined so default will be defined defaults make default defaults 0 links make link links 0 perunits make perunit perunits 1 varnames make varname varnames 1 local lines get page lines cfg data title for section in ipairs sections do local heading mtext section names section 1 local maker section 2 cfg section 3 local code section 4 local need section need utype if code 0 and not is sandbox then need section true end if code 0 then need utype true end prepare section cfg maker lines heading need section need utype end check all defaults cfg units check all pers cfg units update units units composites varnames return units defaults links perunits end local function makeunits cfg results Read the wikitext for the conversion data Append output to given results collection or throw error if a problem text code require cfg text title for name in ipairs SIprefixes eng scales currency do if type text code name table then quit m ftl table cfg text title name end end local translation text code translation table if translation then if translation plural suffix then plural suffix translation plural suffix end local ts translation specials if ts then if ts utype then specials utype ts utype end if ts ucode then specials ucode ts ucode end end local tm translation mtext if tm then if tm section names then mtext section names tm section names end if tm titles then mtext titles tm titles end if tm messages then mtext messages tm messages end end end local is sandbox local conversion data title mtext titles conversion data if cfg data title and cfg data title conversion data title then if is test run then is sandbox true data preamble nil data postamble nil out unit prefix local all units out unit suffix out default prefix n local default exceptions out default suffix out default item t symbol default out link prefix n local link exceptions out link suffix out link item t symbol link out perunit prefix n local per unit fixups out perunit suffix out perunit item t lhs rhs end else cfg data title conversion data title end local units defaults links perunits prepare data cfg is sandbox if data preamble then results add data preamble end results add out unit prefix for unit in ipairs units do local spec if unit target then spec alias specification elseif unit per then spec per specification unit per numbered table as string unit per unit elseif unit shouldbe then spec shouldbe specification elseif unit combination then spec combination specification unit combination numbered table as string unit combination unit if unit multiple then unit multiple numbered table as string unit multiple unit end else spec unit specification end add unit lines results unit spec end results add out unit suffix for t in ipairs defaults out default prefix out default item out default suffix links out link prefix out link item out link suffix perunits out perunit prefix out perunit item out perunit suffix do local data prefix item suffix t 1 t 2 t 3 t 4 if data gt 0 or not is sandbox then results add prefix for unit in ipairs data do results add item gsub w unit end results add suffix end end if data postamble then results add data postamble end end local function makeunits frame local args frame args local config data title args 1 text title args 2 or Module Convert text varcolumns tonumber args varcolumns or 5 columns in Variable names section slwiki uses 5 maxerrors 20 local results collection local ok msg pcall makeunits config results if not ok then results add message m error results add results add msg end local warn if warnings n gt 0 then warn message m warning n n warnings join n n end Pre tags returned by a module are html tags not like wikitext lt pre gt lt pre gt The following renders the text as is and preserves tab characters return https th wikipedia org wiki E0 B8 84 E0 B8 B8 E0 B8 A2 E0 B9 80 E0 B8 A3 E0 B8 B7 E0 B9 88 E0 B8 AD E0 B8 87 E0 B8 A1 E0 B8 AD E0 B8 94 E0 B8 B9 E0 B8 A5 Convert makeunit action render amp templates expand kdthinixacthaihxanngaykhun https th wikipedia org w index php title E0 B8 84 E0 B8 B8 E0 B8 A2 E0 B9 80 E0 B8 A3 E0 B8 B7 E0 B9 88 E0 B8 AD E0 B8 87 E0 B8 A1 E0 B8 AD E0 B8 94 E0 B8 B9 E0 B8 A5 Convert makeunit amp action purge langaekhchhnani aekikhkhxmulsahrbaesdngthiniidthi mxdul Convert documentation conversion data doc n lt pre gt n mw text nowiki warn results join n lt pre gt n end return makeunits makeunits ekhathungcak https th wikipedia org w index php title mxdul Convert makeunit amp oldid 9150255, wikipedia, วิกิ หนังสือ, หนังสือ, ห้องสมุด,

บทความ

, อ่าน, ดาวน์โหลด, ฟรี, ดาวน์โหลดฟรี, mp3, วิดีโอ, mp4, 3gp, jpg, jpeg, gif, png, รูปภาพ, เพลง, เพลง, หนัง, หนังสือ, เกม, เกม