---
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 ), 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 || | !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