Module:Check height
Appearance
-- Validates height formatting in infoboxes and adds error tracking categories when issues are found
local p = {}
-- Default ranges
local DEFAULT_FT_MIN = 5.00
local DEFAULT_FT_MAX = 6.11
local DEFAULT_CM_MIN = 152
local DEFAULT_CM_MAX = 212
-- Helper function to parse and validate ftin_range parameter
local function parseFtInRange(rangeStr)
if not rangeStr or rangeStr == '' then
return DEFAULT_FT_MIN, DEFAULT_FT_MAX
end
-- Pattern: number (with optional decimal) - number (with optional decimal)
local minStr, maxStr = rangeStr:match('^(%d+%.%d+)%-(%d+%.%d+)$')
if not minStr or not maxStr then
return DEFAULT_FT_MIN, DEFAULT_FT_MAX
end
local minVal = tonumber(minStr)
local maxVal = tonumber(maxStr)
if not minVal or not maxVal then
return DEFAULT_FT_MIN, DEFAULT_FT_MAX
end
-- Validate that decimal part is exactly 2 digits and represents inches (00-11)
local minInches = minStr:match('%.(%d+)$')
local maxInches = maxStr:match('%.(%d+)$')
if not minInches or not maxInches then
return DEFAULT_FT_MIN, DEFAULT_FT_MAX
end
-- Check exactly 2 digits
if #minInches ~= 2 or #maxInches ~= 2 then
return DEFAULT_FT_MIN, DEFAULT_FT_MAX
end
-- Check range 00-11
local minInchesNum = tonumber(minInches)
local maxInchesNum = tonumber(maxInches)
if not minInchesNum or not maxInchesNum or
minInchesNum < 0 or minInchesNum > 11 or
maxInchesNum < 0 or maxInchesNum > 11 then
return DEFAULT_FT_MIN, DEFAULT_FT_MAX
end
return minVal, maxVal
end
-- Helper function to parse and validate cm_range parameter
local function parseCmRange(rangeStr)
if not rangeStr or rangeStr == '' then
return DEFAULT_CM_MIN, DEFAULT_CM_MAX
end
-- Pattern: number - number (no decimals allowed)
local minStr, maxStr = rangeStr:match('^(%d+)%-(%d+)$')
if not minStr or not maxStr then
return DEFAULT_CM_MIN, DEFAULT_CM_MAX
end
local minVal = tonumber(minStr)
local maxVal = tonumber(maxStr)
if not minVal or not maxVal then
return DEFAULT_CM_MIN, DEFAULT_CM_MAX
end
return minVal, maxVal
end
-- Helper function to remove fraction HTML markup
local function removeFractionMarkup(str)
if not str then return str end
-- Remove templatestyles strip marker
str = str:gsub("\127'\"`UNIQ%-%-templatestyles.-QINU`\"'\127", '')
-- Remove span tags with specific classes while preserving inner content appropriately
-- For sr-only spans, we want to convert the + to actual +
str = str:gsub('<span class="sr%-only">%+</span>', '+')
-- Remove the frac wrapper span (keep contents)
str = str:gsub('<span class="frac">(.-)</span>', '%1')
-- Remove num spans (keep contents)
str = str:gsub('<span class="num">(.-)</span>', '%1')
-- Remove den spans (keep contents)
str = str:gsub('<span class="den">(.-)</span>', '%1')
return str
end
-- Helper function to remove references/inline tags and everything after
local function removeReferences(str)
if not str then return str end
-- Pattern for MediaWiki strip markers (references)
local refPattern = "\127'\"`UNIQ.-QINU`\"'\127"
-- Find the first reference and remove it and everything after
local refStart = str:find(refPattern)
if refStart then
str = str:sub(1, refStart - 1)
end
-- Find the first inline tag and remove it and everything after
local catStart = str:find('%[%[Category:')
if catStart then
str = str:sub(1, catStart - 1)
end
return str
end
-- Helper function to extract regular and converted heights
local function extractHeights(str)
if not str then return nil, nil, false end
local regular, converted = str:match('^(.-)%s%((.-)%)$')
if not regular or not converted then
return nil, nil, false
end
local reconstructed = str:gsub('^' .. regular:gsub('([%(%)%.%%%+%-%*%?%[%]%^%$])', '%%%1'), '')
local expectedRemainder = ' (' .. converted .. ')'
local actualRemainder = reconstructed
local remainderCheck = actualRemainder:gsub(converted:gsub('([%(%)%.%%%+%-%*%?%[%]%^%$])', '%%%1'), '')
if remainderCheck ~= ' ()' then
return nil, nil, false
end
return regular, converted, true
end
-- Validate imperial height format
local function validateImperial(height, ftMin, ftMax)
if not height then return false end
-- Extract feet and inches parts from the min/max values
local minFeet = math.floor(ftMin)
local minInches = math.floor((ftMin - minFeet) * 100 + 0.5)
local maxFeet = math.floor(ftMax)
local maxInches = math.floor((ftMax - maxFeet) * 100 + 0.5)
-- Pattern 1: X ft Y in (simple)
local feet, inches = height:match('^(%d+) ft (%d+) in$')
if feet and inches then
feet = tonumber(feet)
inches = tonumber(inches)
-- Check if within range
if feet < minFeet or feet > maxFeet then
return false
end
if feet == minFeet and inches < minInches then
return false
end
if feet == maxFeet and inches > maxInches then
return false
end
if inches < 0 or inches > 11 then
return false
end
return true
end
-- Pattern 2: X ft Y+N⁄D in (with fraction)
local feet2, inches2, num, den = height:match('^(%d+) ft (%d*)%+?(%d+)⁄(%d+) in$')
if feet2 and inches2 and num and den then
feet2 = tonumber(feet2)
inches2 = tonumber(inches2) or 0
num = tonumber(num)
den = tonumber(den)
-- Check if within range
if feet2 < minFeet or feet2 > maxFeet then
return false
end
if feet2 == minFeet and inches2 < minInches then
return false
end
if feet2 == maxFeet and inches2 > maxInches then
return false
end
if inches2 < 0 or inches2 > 11 then
return false
end
if den <= 1 or num >= den then
return false
end
return true
end
return false
end
-- Validate metric height format (meters)
local function validateMeters(height, cmMin, cmMax)
if not height then return false end
-- Pattern: X.XX m
local meters = height:match('^(%d+%.%d+) m$')
if meters then
-- Remove decimal point and check value
local metersNoDot = meters:gsub('%.', '')
local numericValue = tonumber(metersNoDot)
if numericValue and numericValue >= cmMin and numericValue <= cmMax then
return true
end
end
return false
end
-- Validate metric height format (centimeters)
local function validateCentimeters(height, cmMin, cmMax)
if not height then return false end
-- Pattern: XXX cm
local cm = height:match('^(%d+) cm$')
if cm then
local numericValue = tonumber(cm)
if numericValue and numericValue >= cmMin and numericValue <= cmMax then
return true
end
end
return false
end
-- Validate metric height based on metric parameter
local function validateMetric(height, metricParam, cmMin, cmMax)
if not height then return false end
metricParam = metricParam or 'both'
if metricParam == 'm' then
return validateMeters(height, cmMin, cmMax)
elseif metricParam == 'cm' then
return validateCentimeters(height, cmMin, cmMax)
else -- 'both' or default
return validateMeters(height, cmMin, cmMax) or validateCentimeters(height, cmMin, cmMax)
end
end
-- Main function
function p.main(frame)
local arguments = require('Module:Arguments')
local personHeight = require('Module:Person height')
local args = arguments.getArgs(frame, {trim = true})
local parentArgs = frame:getParent().args
-- Get parameters
local height = args[1] or parentArgs[1] or ''
local metricParam = args['metric'] or parentArgs['metric'] or 'both'
local catParam = args['cat'] or parentArgs['cat'] or ''
local ftinRangeParam = args['ftin_range'] or parentArgs['ftin_range'] or ''
local cmRangeParam = args['cm_range'] or parentArgs['cm_range'] or ''
-- Parse range parameters
local ftMin, ftMax = parseFtInRange(ftinRangeParam)
local cmMin, cmMax = parseCmRange(cmRangeParam)
-- If no height provided, return empty
if height == '' then
return ''
end
-- Set enforce based on metric parameter (nil if 'both')
local enforceValue = nil
if metricParam == 'm' then
enforceValue = 'm'
elseif metricParam == 'cm' then
enforceValue = 'cm'
end
local heightArgs = {
[1] = height,
['enforce'] = enforceValue,
['ri'] = 'cmin'
}
local formattedHeight = personHeight.main(frame:newChild{ args = heightArgs })
-- Remove fraction markup
local cleanedHeight = removeFractionMarkup(formattedHeight)
-- Remove references and everything after
cleanedHeight = removeReferences(cleanedHeight)
-- Trim any trailing whitespace
cleanedHeight = cleanedHeight:gsub('%s+$', '')
-- Extract regular and converted heights
local regularHeight, convertedHeight, structureValid = extractHeights(cleanedHeight)
-- Check if structure is valid
if not structureValid then
-- Add error category
mw.addWarning('<span style="color:#d33">Height format error: The height format does not match the expected pattern.</span>')
if catParam ~= '' then
return '[[Category:' .. catParam .. ']]'
end
return ''
end
-- Validate the heights
local regularValid = false
local convertedValid = false
-- Check if regular height is imperial or metric
if validateImperial(regularHeight, ftMin, ftMax) then
regularValid = true
-- If regular is imperial, converted should be metric
convertedValid = validateMetric(convertedHeight, metricParam, cmMin, cmMax)
elseif validateMetric(regularHeight, metricParam, cmMin, cmMax) then
regularValid = true
-- If regular is metric, converted should be imperial
convertedValid = validateImperial(convertedHeight, ftMin, ftMax)
end
-- If validation failed, add error category
if not regularValid or not convertedValid then
if catParam ~= '' then
-- Create dynamic error message based on current ranges
local ftMinFeet = math.floor(ftMin)
local ftMinInches = math.floor((ftMin - ftMinFeet) * 100 + 0.5)
local ftMaxFeet = math.floor(ftMax)
local ftMaxInches = math.floor((ftMax - ftMaxFeet) * 100 + 0.5)
local errorMsg = string.format(
'<span style="color:#d33">Height format error: The height values or format are not within acceptable ranges. Height must be between %d\'%d" and %d\'%d" (%d–%d cm).</span>',
ftMinFeet, ftMinInches, ftMaxFeet, ftMaxInches, cmMin, cmMax
)
mw.addWarning(errorMsg)
return '[[Category:' .. catParam .. ']]'
end
end
-- All checks passed, return empty
return ''
end
return p