🇮🇷 Iran Proxy | https://www.wikipedia.org/wiki/Module:Check_height
Jump to content

Module:Check height

From Wikipedia, the free encyclopedia
-- 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&nbsp;ft Y&nbsp;in (simple)
    local feet, inches = height:match('^(%d+)&nbsp;ft (%d+)&nbsp;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&nbsp;ft Y+N&frasl;D&nbsp;in (with fraction)
    local feet2, inches2, num, den = height:match('^(%d+)&nbsp;ft (%d*)%+?(%d+)&frasl;(%d+)&nbsp;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&nbsp;m
    local meters = height:match('^(%d+%.%d+)&nbsp;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&nbsp;cm
    local cm = height:match('^(%d+)&nbsp;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