Jump to content

Module:ConvertIB

Permanently protected module
From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Hike395 (talk | contribs) at 14:08, 24 September 2025 (use lower case comparison for pref=dunam). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

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