Module:ConvertIB
Jump to navigation
Jump to search
This Lua module is used on approximately 216,000 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 implements {{convinfobox}}: a wrapper around {{convert}} designed for infoboxes.
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
require('strict')
local p = {}
local getArgs = require('Module:Arguments').getArgs
-- Units accepted by {{convert}} that come in groups (e.g., "5 ft 6 in")
local multiple =
{'mich', 'michlk', 'michainlk', 'miyd', 'miydftin', 'mift', 'ydftin', 'ydft',
'ftin', 'footin', 'handin', 'lboz', 'stlb', 'stlboz', 'stlb'}
-- Convert unit list to hash
local mult_table = {}
for _, v in ipairs(multiple) do
mult_table[v] = true
end
-- 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
-- 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 mult_table[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
-- 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
-- Call {{convert}} with innerArgs
local frame = mw.getCurrentFrame()
return frame:expandTemplate{title='Convert', args=innerArgs}
end
function p.convert(frame)
local args = getArgs(frame)
return p._convert(args) or ""
end
return p