Module:ConvertIB
Appearance
| This Lua module is used on approximately 762,000 pages, or roughly 1% of all pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
A Lua module that wraps {{convert}}, designed for infoboxes. It implements:
- {{convinfobox}}
- {{Infobox settlement/areadisp}}
- {{Infobox settlement/lengthdisp}}
- {{Infobox settlement/densdisp}}
Usage
{{#invoke:ConvertIB|convert}}
- Like {{convinfobox}}, accepts alternating series of pairs of [blank|value], unit . When a unit has a non-blank value, it will get converted to all other units that do have blank values
- Accepts all named parameters that {{convert}} does
- Accepts groups of multiple units (e.g., "5 ft 6 in") that {{convert}} does
{{#invoke:ConvertIB|area}}
- Implements {{Infobox settlement/areadisp}}, automatically converting area units, using the output order specified by MOS:UNIT
{{#invoke:ConvertIB|length}}
- Implements {{Infobox settlement/lengthdisp}}, automatically converting length units, using the output order specified by MOS:UNIT
{{#invoke:ConvertIB|density}}
- Implements {{Infobox settlement/densdisp}}, parsing population and area, producting density in inhabitants per square km and square mile.
require('strict')
local p = {}
local getArgs = require('Module:Arguments').getArgs
-- Function to pull out values and units from numeric args
-- Returns:
-- values: list of numeric values, or "false" if no numeric argument is given
-- units: list of units (str)
-- value: if there is a last numeric value unpaired with a unit, it becomes the precision
-- anyValue: whether there is a non-false value in the values list
local function parseValuesUnits(args)
local values = {}
local units = {}
local indx = 1
local value = nil
local anyValue = false
-- loop through numeric arguments in pairs
while args[indx] or args[indx+1] do
value = args[indx]
anyValue = anyValue or value
-- if there is a unit, save in output lists
if args[indx+1] then
table.insert(values, value or false)
table.insert(units, args[indx+1])
value = nil
end
indx = indx+2
end
return values, units, value, anyValue
end
-- Function to identify multiple units and rewrite them as new input or output groups
-- Args:
-- values, units: numeric values and units, as lists with same length
-- Returns:
-- newValues, newUnits: same lists rewritten
local function parseMultiples(values, units)
local newValues = {}
local newUnits = {}
local i = 1
-- we will search for multiples with up to 4 entries (depending on length)
local maxMultiple = math.min(4,#units-1)
local valueFound = false -- flag to suppress second (and later) input values
--- Hack for handling "stone": check if only value supplied is "lb"
local onlyPounds = true
for i = 1, #units do
if values[i] and units[i] ~= 'lb' then
onlyPounds = false
break
end
end
local multiple = mw.loadData('Module:ConvertIB/data').multiple
-- sweep through units
while i <= #units do
-- determine index of last possible unit that could contain a multiple
local last_unit = math.min(i+maxMultiple-1,#units)
local multipleFound = false
-- try from longest multiple down to double multiple (prefer longest ones)
for j = last_unit, i+1, -1 do
local key = table.concat({unpack(units,i,j)}, '')
if multiple[key] then
-- we found a multiple unit
multipleFound = true
-- Hack for "stone": add either 'lb' or multiple unit string to output units
-- depending on whether 'lb' was the only unit string with a value
if mw.ustring.sub(key,1,2) == 'st' then
table.insert(newValues, false)
table.insert(newUnits, onlyPounds and key or 'lb')
end
-- if there are any value in the span of the multiple,
-- then the multiple is an input
-- assume all missing values after the first are zero
local firstValueFound = false
for k = i, j do
firstValueFound = not valueFound and (firstValueFound or values[k])
if firstValueFound then
table.insert(newValues, values[k] or 0)
table.insert(newUnits, units[k])
end
end
valueFound = valueFound or firstValueFound
-- if no values in the span of the multiple,
-- then the multiple is an output. Insert combined string as output unit
if not firstValueFound then
table.insert(newValues, false)
table.insert(newUnits, key)
end
i = j+1
break
end
end
--- If no multiple unit was found, insert value[i] and unit[i] into rewritten lists
if not multipleFound then
if valueFound then
table.insert(newValues, false) -- skip writing value if it is a duplicate
else
table.insert(newValues,values[i])
valueFound = values[i]
end
table.insert(newUnits, units[i])
i = i+1
end
end
return newValues, newUnits
end
-- Call {{convert}} with args
local function callConvert(args)
local frame = mw.getCurrentFrame()
return frame:expandTemplate{title='Convert', args=args}
end
-- Implement {{convinfobox}}
function p._convert(args)
-- find all values and units in numeric args (and the precision, if it exists)
local values, units, precision, anyValue = parseValuesUnits(args)
-- bail if no values at all
if not anyValue then
return nil
end
-- rewrite values and units if multiple units are found
values, units = parseMultiples(values, units)
-- sort input and outputs into different buckets
local input_values = {}
local input_units = {}
local output_units = {}
for i = 1, #units do
if values[i] then
table.insert(input_values, values[i])
table.insert(input_units, units[i])
else
table.insert(output_units, units[i])
end
end
-- bail if nothing to convert
if #input_values == 0 or #output_units == 0 then
return nil
end
-- assemble argument list to {{convert}}
local innerArgs = {}
-- First, pass all input unit(s)
for i, v in ipairs(input_values) do
table.insert(innerArgs,v)
table.insert(innerArgs,input_units[i])
end
-- Then the output unit(s) [concatenated as single argument]
table.insert(innerArgs,table.concat(output_units,"+"))
if precision then
table.insert(innerArgs,precision) -- last non-nil value contains precision
end
-- now handle all non-numeric arguments, passing to {{convert}}
innerArgs.abbr = 'on' -- abbr=on by default
for k, v in pairs(args) do
if not tonumber(k) then
innerArgs[k] = v
end
end
return callConvert(innerArgs)
end
local function impUnitPref(pref,country)
pref = mw.ustring.lower(pref)
country = mw.ustring.lower(country)
local impPref = mw.loadData('Module:ConvertIB/data').impPref
return impPref[pref] or mw.ustring.find(country,"united states",1,true) or mw.ustring.find(country,"united kingdom",1,true)
end
-- Implement {{Infobox settlement/areadisp}}
function p._area(args)
local pref = args['pref'] or ''
local country = args['name'] or ''
local impus = impUnitPref(pref, country)
local km2 = args['km2']
local mi2 = args['mi2'] or args['sqmi']
local ha = args['ha']
local acre = args['acre']
local dunam = args['dunam'] or args['dunum']
local link = args['link']
local innerArgs = {}
innerArgs.abbr = 'on'
innerArgs.order = 'out'
if km2 then
table.insert(innerArgs,km2)
table.insert(innerArgs,'km2')
elseif mi2 then
table.insert(innerArgs,mi2)
table.insert(innerArgs,'sqmi')
end
if km2 or mi2 then
table.insert(innerArgs,impus and 'sqmi km2' or 'km2 sqmi')
return callConvert(innerArgs)
end
if ha then
table.insert(innerArgs,ha)
table.insert(innerArgs,'ha')
elseif acre then
table.insert(innerArgs,acre)
table.insert(innerArgs,'acre')
end
if ha or acre then
table.insert(innerArgs,impus and 'acre ha' or 'ha acre')
return callConvert(innerArgs)
end
if dunam then
table.insert(innerArgs,dunam)
table.insert(innerArgs,'dunam')
pref = mw.ustring.lower(pref)
local order = pref == 'dunam' and 'dunam ' or ''
dunam = mw.getContentLanguage():parseFormattedNumber(dunam)
if impus then
order = order..(dunam and dunam < 2589 and 'acre ha' or 'sqmi km2')
else
order = order..(dunam and dunam < 1000 and 'ha acre' or 'km2 sqmi')
end
table.insert(innerArgs,order)
local yesNo = require('Module:Yesno')
if yesNo(link,true) and link ~= 'none' then
innerArgs.lk = 'in'
end
return callConvert(innerArgs)
end
return nil
end
function p.convert(frame)
local args = getArgs(frame)
return p._convert(args) or ""
end
function p.area(frame)
local args = getArgs(frame)
return p._area(args) or ""
end
return p