Module:Convert/makeunits
-- 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 &quot;== Conversions ==&quot;.
 -- * Process the following lines:
 --   * Find next level-3 heading like &quot;=== Length ===&quot;.
 --   * Parse each following line starting with &quot;|&quot;
 --     (but ignore lines starting with &quot;|-&quot; or &quot;|}&quot;.
 --   * Split such lines into fields (delimiter &quot;||&quot;) and trim
 --     leading/trailing whitespace from each field.
 --     Remove any &quot;colspan&quot; 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 &quot;5/9&quot;, 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 (&quot;==&quot; but not &quot;===&quot;).
 -- * Repeat above for each heading listed at prepare_data().
 -- * Output Lua source for the units table.
 --
 -- -- Output has the following form.
 -- local all_units = {
 --     [&quot;unitcode&quot;] = {                        -- standard format
 --         name1    = &quot;singular name&quot;,         -- omitted if redundant
 --         name1_us = &quot;singular name sp=us&quot;,   -- omitted if redundant
 --         name2    = &quot;plural name&quot;,           -- omitted if redundant
 --         name2_us = &quot;plural name sp=us&quot;,     -- omitted if redundant
 --         symbol   = &quot;symbol&quot;,
 --         sym_us   = &quot;symbol sp=us&quot;,          -- omitted if redundant
 --         usename  = 1,                       -- omitted if empty
 --         utype    = &quot;unit type&quot;,             -- from level-3 heading
 --         scale    = 1,                       -- a value, if necessary from evaluating an expression
 --         subdivs  = { [&quot;ft&quot;] = { 5280, default = &quot;km&quot; }, [&quot;yd&quot;] = { 1760 } }  -- composite input; omitted if empty
 --         link     = &quot;title of article for wikilink&quot;,  -- omitted if empty or redundant
 --         ...                                 -- other values
 --     },
 --     [&quot;unitcode&quot;] = {        -- alternative format to generate an alias
 --         target   = &quot;unit code&quot;,
 --         ...                                 -- optional values to override those of target
 --     },
 --     [&quot;unitcode&quot;] = {        -- alternative format to generate a &quot;per&quot; unit like $/acre or BTU/h
 --         per      = {u1, u2},                -- numbered table of unitcodes (u1 may be a currency symbol)
 --         ...                                 -- optional values
 --     },
 --     [&quot;unitcode&quot;] = {        -- alternative format to generate an error message
 --         shouldbe = &quot;message that some other unit code should be used&quot;,
 --     },
 --     [&quot;unitcode&quot;] = {        -- alternative format for combination outputs (like 'm ft')
 --         combination = {u1, u2, ...},        -- numbered table of unitcodes
 --         utype    = &quot;unit type&quot;,             -- as for standard format
 --     },
 --     [&quot;unitcode&quot;] = {        -- alternative format for output multiples (like 'ftin')
 --         combination = {u1, u2, ...},        -- numbered table of unitcodes
 --         multiple = {f1, f2, ...},           -- numbered table of integer factors
 --         utype    = &quot;unit type&quot;,             -- 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 = {
 		-- [&quot;unit type in local language&quot;] = &quot;name_used_in_this_script&quot;
 		[&quot;fuel efficiency&quot;] = &quot;type_fuel_efficiency&quot;,
 		[&quot;length&quot;] = &quot;type_length&quot;,
 		[&quot;temperature&quot;] = &quot;type_temperature&quot;,
 		[&quot;volume&quot;] = &quot;type_volume&quot;,
 	},
 	ucode = {
 		exception = {
 			-- [&quot;unit code in local language&quot;] = &quot;name_used_in_module_convert&quot;
 			[&quot;ft&quot;] = &quot;integer_more_precision&quot;,
 			[&quot;in&quot;] = &quot;subunit_more_precision&quot;,
 			[&quot;lb&quot;] = &quot;integer_more_precision&quot;,
 		},
 		istemperature = {
 			-- Common temperature scales (not keVT or MK).
 			-- [&quot;unit code in local language&quot;] = true
 			[&quot;C&quot;] = true,
 			[&quot;F&quot;] = true,
 			[&quot;K&quot;] = true,
 			[&quot;R&quot;] = true,
 		},
 		usesymbol = {
 			-- Use unit symbol not name if abbr not specified.
 			-- [&quot;unit code in local language&quot;] = 1
 			[&quot;C&quot;] = 1,
 			[&quot;F&quot;] = 1,
 			[&quot;K&quot;] = 1,
 			[&quot;R&quot;] = 1,
 			[&quot;C-change&quot;] = 1,
 			[&quot;F-change&quot;] = 1,
 			[&quot;K-change&quot;] = 1,
 		},
 		alttype = {
 			-- Unit has an alternate type that is a valid conversion.
 			-- [&quot;unit code in local language&quot;] = &quot;alternate type in local language&quot;
 			[&quot;Nm&quot;] = &quot;energy&quot;,
 			[&quot;ftlb&quot;] = &quot;torque&quot;,
 			[&quot;ftlb-f&quot;] = &quot;torque&quot;,
 			[&quot;ftlbf&quot;] = &quot;torque&quot;,
 			[&quot;inlb&quot;] = &quot;torque&quot;,
 			[&quot;inlb-f&quot;] = &quot;torque&quot;,
 			[&quot;inlbf&quot;] = &quot;torque&quot;,
 			[&quot;inoz-f&quot;] = &quot;torque&quot;,
 			[&quot;inozf&quot;] = &quot;torque&quot;,
 		},
 	},
 }
 
 -- 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    = 'Overrides',
 		conversions  = 'Conversions',
 		outmultiples = 'Output multiples',
 		combinations = 'Combinations',
 		inmultiples  = 'Input multiples',
 		defaults     = 'Defaults',
 		links        = 'Links',
 		perunits     = 'Automatic per units',
 		varnames     = 'Variable names',
 	},
 	titles = {
 		-- name_used_in_this_script = 'Title of page'
 		conversion_data = 'Module:Convert/documentation/conversion data',
 	},
 	messages = {
 		-- name_used_in_this_script = 'Error message ($1 = first parameter, $2 = second)'
 		m_als_bad   = 'Alias has invalid text in field &quot;$1&quot;.',
 		m_als_dup   = 'Alias &quot;$1&quot; already defined.',
 		m_als_link  = 'Alias &quot;$1&quot; must include a wikilink (&quot;[[...]]&quot;) in the symlink text.',
 		m_als_mul   = 'Alias &quot;$1&quot; has multiplier &quot;$2&quot; which is not a number.',
 		m_als_same  = 'Should omit &quot;$1&quot; for alias &quot;$2&quot; because it is the same as its target.',
 		m_als_type  = 'Target of alias &quot;$1&quot; has wrong type.',
 		m_als_undef = 'Primary unit must be defined before alias &quot;=$1&quot;',
 		m_cmb_miss  = 'Missing unit code for a combination.',
 		m_cmb_none  = 'No units specified for combination &quot;$1&quot;',
 		m_cmb_one   = 'Only one unit specified for combination &quot;$1&quot;',
 		m_cmb_type  = 'Unit &quot;$1&quot; in combination &quot;$2&quot; has wrong type.',
 		m_cmb_undef = 'Unit &quot;$1&quot; in combination &quot;$2&quot; not defined.',
 		m_cmp_def   = 'Composite &quot;$1&quot; must specify a default unit code.',
 		m_cmp_int   = 'Composite &quot;$1&quot; has components where scale ratios are not integers.',
 		m_cmp_inval = 'Composite &quot;$1&quot; has a component with an invalid scale, &quot;$2&quot;.',
 		m_cmp_many  = 'Composite &quot;$1&quot; has too many fields.',
 		m_cmp_miss  = 'Missing unit code for a composite.',
 		m_cmp_order = 'Composite &quot;$1&quot; has components in wrong order or with invalid scales.',
 		m_cmp_scale = 'Alternate unit &quot;$1&quot; in composite &quot;$2&quot; has wrong scale.',
 		m_cmp_two   = 'Composite &quot;$1&quot; must specify exactly two unit codes.',
 		m_cmp_type  = 'Unit &quot;$1&quot; in composite &quot;$2&quot; has wrong type.',
 		m_cmp_undef = 'Unit &quot;$1&quot; in composite &quot;$2&quot; not defined.',
 		m_def_cond  = 'Invalid condition in default &quot;$1&quot; for unit &quot;$2&quot;.',
 		m_def_fmt   = 'Default output &quot;$1&quot; for unit &quot;$2&quot; should have 2 or 3 &quot;!&quot;.',
 		m_def_rpt   = 'Default output &quot;$1&quot; for unit &quot;$2&quot; is repeated.',
 		m_def_same  = 'Default output for unit &quot;$1&quot; is the same unit.',
 		m_def_type  = 'Default output &quot;$1&quot; for unit &quot;$2&quot; has wrong type.',
 		m_def_undef = 'Default output &quot;$1&quot; for unit &quot;$2&quot; is not defined.',
 		m_dfs_code  = 'Defaults section: no unit code specified.',
 		m_dfs_dup   = 'Defaults section: unit &quot;$1&quot; has already been specified.',
 		m_dfs_none  = 'Defaults section: unit &quot;$1&quot; has no default specified.',
 		m_dfs_sym   = 'Defaults section: unit &quot;$1&quot; must have a symbol.',
 		m_dfs_two   = 'Defaults section: unit &quot;$1&quot; should have two fields only.',
 		m_dfs_undef = 'Defaults section: unit &quot;$1&quot; is not defined.',
 		m_dup_code  = 'Unit code &quot;$1&quot; has already been defined.',
 		m_error     = 'Error:',
 		m_ftl_read  = 'Could not read wikitext from &quot;[[$1]]&quot;.',
 		m_ftl_table = '[[$1]] should export table &quot;$2&quot;.',
 		m_ftl_type  = 'Fatal error: unknown data type for &quot;$1&quot;',
 		m_hdg_lev2  = 'Level 2 heading &quot;$1&quot; not found.',
 		m_hdg_lev3  = 'No level 3 heading before: $1',
 		m_line_num  = ' (line $1).',
 		m_lnk_brack = 'Link &quot;$1&quot; has wrong number of brackets.',
 		m_lnk_dup   = 'Link exception &quot;$1&quot; is already defined.',
 		m_lnk_miss  = 'Missing unit code for a link.',
 		m_lnk_none  = 'No link defined for unit &quot;$1&quot;.',
 		m_lnk_sym   = 'Unit code &quot;$1&quot; for a link must have a symbol.',
 		m_lnk_two   = 'Row for unit &quot;$1&quot; link should have two fields only.',
 		m_lnk_type  = 'Link exception &quot;$1&quot; has wrong type.',
 		m_lnk_undef = 'Unit code &quot;$1&quot; 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 &quot;$1&quot; 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 &quot;$1&quot;',
 		m_mul_one   = 'Only one unit specified for multiple &quot;$1&quot;',
 		m_mul_order = 'Multiple &quot;$1&quot; has components in wrong order or with invalid scales.',
 		m_mul_scale = 'Multiple &quot;$1&quot; has a component with an invalid scale, &quot;$2&quot;.',
 		m_mul_std   = 'Unit &quot;$1&quot; in multiple &quot;$2&quot; must be a standard unit.',
 		m_mul_type  = 'Unit &quot;$1&quot; in multiple &quot;$2&quot; has wrong type.',
 		m_mul_undef = 'Unit &quot;$1&quot; in multiple &quot;$2&quot; not defined.',
 		m_no_title  = 'Need title of page with unit definitions.',
 		m_ovr_dup   = 'Override &quot;$1&quot; is already defined.',
 		m_ovr_miss  = 'Missing unit code for an override.',
 		m_per_dup   = 'Per unit &quot;$1&quot; already defined.',
 		m_per_empty = 'Unit &quot;$1&quot; has an empty field in the &quot;per&quot;.',
 		m_per_fuel  = 'Unit &quot;$1&quot; has invalid unit types for fuel efficiency.',
 		m_per_inv   = 'Invalid field for a &quot;per&quot;.',
 		m_per_two   = 'Unit &quot;$1&quot; does not have exactly 2 fields in the &quot;per&quot;.',
 		m_per_undef = 'Unit &quot;$1&quot; has undefined unit code &quot;$2&quot; in the &quot;per&quot;.',
 		m_percent_s = 'Field &quot;$1&quot; must not contain &quot;%s&quot;.',
 		m_pfx_bad   = 'Unknown prefix: &quot;$1&quot;.',
 		m_pfx_name  = 'Unit with Prefix set must include Name.',
 		m_scl_bad   = 'Scale expression is invalid: &quot;$1&quot;.',
 		m_scl_miss  = 'Missing scale.',
 		m_scl_oflow = 'Scale expression gives an invalid value: &quot;$1&quot;.',
 		m_var_cnt   = 'Variable names section: each row must have the configured number of columns.',
 		m_var_dup   = 'Unit &quot;$1&quot; already has a variable name.',
 		m_var_miss  = 'Missing field for a variable name.',
 		m_var_undef = 'Unit &quot;$1&quot; 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 &quot;5/9&quot;.
 -- 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 &quot;' .. self.text:sub(self.pos) .. '&quot;')
 			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(&quot;^%s*(.-)%s*$&quot;)
 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 &quot;per&quot; 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 &quot;per&quot; 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 &quot;US&quot; or &quot;U.S.&quot; or &quot;Imperial&quot; 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 &quot;5/9&quot; 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 &quot;#INF&quot; while Scribunto gives &quot;inf&quot;. 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 &quot;per&quot;, and add them to unit.
 	-- For an alias, target is a valid unit; for a &quot;per&quot;, 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 &quot;per&quot; unit, or return nil if symbol is not of form &quot;x/y&quot;.
 	-- 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',
 		'sym_us',
 		'scale',
 		'extra',
 		'name1',
 		'name2',
 		'name1_us',
 		'name2_us',
 		'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 &quot;per&quot; unit like &quot;$/acre&quot; or &quot;BTU/h&quot;.
 			-- For an alias, symbol is the unit code of the actual unit.
 			-- For a &quot;per&quot;, symbol is of form &quot;x/y&quot; 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, &quot;BTU/h&quot; can be defined before &quot;h&quot;.
 			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 &quot;kW&quot; = &quot;kilo&quot; .. &quot;watt&quot; (name for &quot;W&quot;).
 				-- The &quot;not prefixes&quot; 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 &quot;acre&quot; 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 &quot;pitch&quot; 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 &quot;kilogram&quot; for kg, and &quot;yottagram&quot; 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 &quot;square metre&quot; and &quot;cubic metre&quot; (&quot;square meter&quot; and &quot;cubic meter&quot; 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 &quot;5,7,9,11&quot; 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 = '&quot;' .. positions:join(',') .. '&quot;'
 					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 &quot;%s&quot; (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 &quot;length/time&quot;
 			elseif i == 2 then
 				rhs = v  -- like &quot;speed&quot;
 			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 = &quot;' .. rhs .. '&quot;')
 				end
 				if link then
 					parts:add('link = &quot;' .. link .. '&quot;')
 				end
 				if multiplier then
 					parts:add('multiplier = ' .. multiplier)
 				end
 				rhs = '{ ' .. parts:join(', ') .. ' }'
 			else
 				rhs = '&quot;' .. rhs .. '&quot;'
 			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('[&quot;%s&quot;] = { %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 &quot;5/9&quot;),
 		-- 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 = [=[
 -- Conversion data used by [[Module:Convert]] which uses mw.loadData() for
 -- read-only access to this module so that it is loaded only once per page.
 -- See [[:en:Template:Convert/Transwiki guide]] if copying to another wiki.
 --
 -- These data tables follow:
 --   all_units           all properties for a unit, including default output
 --   default_exceptions  exceptions for default output ('kg' and 'g' have different defaults)
 --   link_exceptions     exceptions for links ('kg' and 'g' have different links)
 --
 -- These tables are generated by a script which reads the wikitext of a page that
 -- documents the required properties of each unit; see [[:en:Module:Convert/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 = [[
 ---------------------------------------------------------------------------
 -- 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 all_units = {]]
 
 local out_unit_suffix = [[
 }
 ]]
 
 local out_default_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 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 = [[
 	[&quot;{symbol}&quot;] = &quot;{default}&quot;,]]
 
 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 = [[
 	[&quot;{symbol}&quot;] = &quot;{link}&quot;,]]
 
 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 &quot;x/y&quot; may have their unit type
 	-- changed, for example, &quot;length/time&quot; is changed to &quot;speed&quot;.
 	-- Other adjustments can also be specified.]]
 
 local out_perunit_suffix = [[
 }
 ]]
 
 local out_perunit_item = [[
 	[&quot;{lhs}&quot;] = {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 = '    [&quot;' .. unit.unitcode .. '&quot;] = {'
 	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, '&quot;' .. v .. '&quot;')
 		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 &quot;||&quot;.
 	-- 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 &quot;colspan&quot; at the front of lines like:
 	-- &quot;| unitcode || colspan=&quot;11&quot; | !Text to display for an error message&quot;.
 	local t = {}
 	line = line .. &quot;||&quot;  -- to get last field
 	for item in line:gmatch(&quot;%s*(.-)%s*||&quot;) 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, &quot;acre ft&quot; 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 = '\nlocal default_exceptions = {'
 			out_default_suffix = '}'
 			out_default_item = '\t[&quot;{symbol}&quot;] = &quot;{default}&quot;,'
 			out_link_prefix = '\nlocal link_exceptions = {'
 			out_link_suffix = '}'
 			out_link_item = '\t[&quot;{symbol}&quot;] = &quot;{link}&quot;,'
 			out_perunit_prefix = '\nlocal per_unit_fixups = {'
 			out_perunit_suffix = '}'
 			out_perunit_item = '\t[&quot;{lhs}&quot;] = {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 &quot;Variable names&quot; 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 '&lt;pre&gt;\n' .. mw.text.nowiki(warn .. results:join()) .. '\n&lt;/pre&gt;\n'
 end