Module:Check height
-- Validates height formatting in infoboxes and adds error tracking categories when issues are found
local p = {}
-- Default ranges
local DEFAULT_FT_MIN = 4.10
local DEFAULT_FT_MAX = 6.11
local DEFAULT_CM_MIN = 147
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
local function removeReferences(str)
if not str then return str end
-- Pattern for MediaWiki strip markers (references)
str = str:gsub("\127'\"`UNIQ%-%-ref.-QINU`\"'\127", '')
-- Pattern for inline tags (e.g. citation needed)
str = str:gsub('%[%[Category:.-%</sup>', '')
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
-- Helper function to delink wikilinks
local function delink(str)
if not str then return str end
return str:gsub("%[%[(.-)%]%]", function(match)
local pipePos = match:find("|")
if pipePos then
return match:sub(pipePos + 1)
else
return match
end
end)
end
-- Helper function to check for invalid decimal feet/inches format (e.g., "5.5 ft" instead of "5 ft 6 in")
local function hasDecimalImperial(str)
if not str then return false end
-- Delink the string and convert to lowercase
local testStr = delink(str)
local lowerStr = testStr:lower()
-- Replace with regular space
lowerStr = lowerStr:gsub(' ', ' ')
-- Regex pattern
if lowerStr:match('%d[,.]%d%s*foot') or
lowerStr:match('%d[,.]%d%s*feet') or
lowerStr:match('%d[,.]%d%s*ft') or
lowerStr:match("%d%.%d+%s*[′']") or
lowerStr:match('%d[,.]%d%s*in') then
return true
end
return false
end
-- Helper function to check for parentheses without inside them
local function hasParenWithoutNbsp(str)
if not str then return false end
-- Remove references and inline tags
str = removeReferences(str)
-- Find all parenthetical expressions and check if any lack
for parenContent in str:gmatch('%((.-)%)') do
if not parenContent:find(' ') then
return true
end
end
return false
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 2a: X ft Y+N⁄D in (whole inches plus fraction, e.g., "6 ft 2+1/2 in")
local feet2, inches2, num2, den2 = height:match('^(%d+) ft (%d+)%+(%d+)⁄(%d+) in$')
if feet2 and inches2 and num2 and den2 then
feet2 = tonumber(feet2)
inches2 = tonumber(inches2)
num2 = tonumber(num2)
den2 = tonumber(den2)
-- Reject "0+" before fraction (should use pattern 2b instead)
if inches2 == 0 then
return false
end
-- 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
-- Numerator must be at least 1, denominator must be > 1, and num < den
if num2 < 1 or den2 <= 1 or num2 >= den2 then
return false
end
return true
end
-- Pattern 2b: X ft N⁄D in (fraction only, e.g., "6 ft 1/2 in")
local feet3, num3, den3 = height:match('^(%d+) ft (%d+)⁄(%d+) in$')
if feet3 and num3 and den3 then
feet3 = tonumber(feet3)
num3 = tonumber(num3)
den3 = tonumber(den3)
-- Check if within range (inches = 0 for range checking purposes)
if feet3 < minFeet or feet3 > maxFeet then
return false
end
if feet3 == minFeet and 0 < minInches then
return false
end
if feet3 == maxFeet and 0 > maxInches then
return false
end
-- Numerator must be at least 1, denominator must be > 1, and num < den
if num3 < 1 or den3 <= 1 or num3 >= den3 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
-- Check raw input for invalid decimal feet format (e.g., "5.5 ft" instead of "5 ft 6 in")
if hasDecimalImperial(height) then
mw.addWarning('<span style="color:#d33">Height format error: The raw height input does not match the expected pattern (feet or inches value uses decimal).</span>')
if catParam ~= '' then
return '[[Category:' .. catParam .. ']]'
end
return ''
end
-- Check raw input for parentheses without inside
if hasParenWithoutNbsp(height) then
mw.addWarning('<span style="color:#d33">Height format error: The raw height input should not include the converted height in parentheses (this should be calculated automatically or by a template).</span>')
if catParam ~= '' then
return '[[Category:' .. catParam .. ']]'
end
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'
}
-- Use pcall to catch any errors from Module:Person height
local success, formattedHeight = pcall(function()
return personHeight.main(frame:newChild{ args = heightArgs })
end)
-- If Module:Person height produced an error, treat as invalid structure
if not success then
if catParam ~= '' then
return '[[Category:' .. catParam .. ']]'
end
return ''
end
-- Remove fraction markup
local cleanedHeight = removeFractionMarkup(formattedHeight)
-- Trim any trailing whitespace
cleanedHeight = cleanedHeight:gsub('%s+$', '')
-- Remove references and inline tags
cleanedHeight = removeReferences(cleanedHeight)
-- 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
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.