Jump to content

Module:TemplateHelpers: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
Line 239: Line 239:
end
end


-- Adds semantic properties for multiple regions by working with the original regions array
-- Adds semantic properties for multiple regions by handling both semicolons and "and" conjunctions
-- This is a targeted helper that can be used within existing semantic property generation
-- This is a targeted helper that can be used within existing semantic property generation
function p.addMultiRegionSemanticProperties(regionValue, semanticOutput)
function p.addMultiRegionSemanticProperties(regionValue, semanticOutput)
Line 245: Line 245:
      
      
     local RegionalMappingICANN = require('Module:RegionalMappingICANN')
     local RegionalMappingICANN = require('Module:RegionalMappingICANN')
    local processedRegions = {}
      
      
     -- Get normalized regions array directly
     -- First, replace "and" with semicolons to standardize the delimiter
     local regions = RegionalMappingICANN.normalizeRegions(regionValue)
    local standardizedInput = regionValue:gsub("%s+and%s+", ";")
   
    -- Now use the normalizeRegions function which handles semicolon-delimited lists
     local regions = RegionalMappingICANN.normalizeRegions(standardizedInput)
      
      
     -- Add each region as a separate semantic property
     -- Add each region as a separate semantic property
     for _, region in ipairs(regions) do
     for _, region in ipairs(regions) do
         if region and region ~= "" then
         if region and region ~= "" and not processedRegions[region] then
            processedRegions[region] = true
           
             -- Add as semantic property
             -- Add as semantic property
             if mw.smw then
             if mw.smw then

Revision as of 03:20, 6 April 2025

Documentation for this module may be created at Module:TemplateHelpers/doc

-- Module:TemplateHelpers
-- Common helper functions for template modules to promote code reuse and consistency. This module provides utilities for string processing, field handling, normalization, and standardized block rendering to be used across multiple template types.

local p = {}

-- Dependencies
local linkParser = require('Module:LinkParser')
local MultiCountryDisplay = require('Module:MultiCountryDisplay')
local dateNormalization = require('Module:DateNormalization')
local CanonicalForms = require('Module:CanonicalForms')

--------------------------------------------------------------------------------
-- String Processing Functions
--------------------------------------------------------------------------------

-- Trims leading and trailing whitespace from a string
function p.trim(s)
    return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end

-- Splits a semicolon-delimited string into a table of trimmed non-empty values
function p.splitSemicolonValues(value)
    if not value or value == "" then return {} end
    local items = {}
    for item in string.gmatch(value, "[^;]+") do
        local trimmed = item:match("^%s*(.-)%s*$")
        if trimmed and trimmed ~= "" then
            table.insert(items, trimmed)
        end
    end
    return items
end

-- Joins a table of values with the specified delimiter
function p.joinValues(values, delimiter)
    delimiter = delimiter or "; "
    if not values or #values == 0 then return "" end
    return table.concat(values, delimiter)
end

--------------------------------------------------------------------------------
-- Field Processing Functions
--------------------------------------------------------------------------------

-- Retrieves a field value from args using either multiple possible keys or a single key
function p.getFieldValue(args, field)
    if field.keys then
        for _, key in ipairs(field.keys) do
            if args[key] and args[key] ~= "" then
                return key, args[key]
            end
        end
        return nil, nil
    end
    return field.key, (args[field.key] and args[field.key] ~= "") and args[field.key] or nil
end

-- Processes multiple values with a given processor function
function p.processMultipleValues(values, processor)
    if not values or values == "" then return {} end
    local items = p.splitSemicolonValues(values)
    local results = {}
    for _, item in ipairs(items) do
        local processed = processor(item)
        if processed and processed ~= "" then
            table.insert(results, processed)
        end
    end
    return results
end

--------------------------------------------------------------------------------
-- Normalization Wrappers
--------------------------------------------------------------------------------

-- Formats website URLs as either a single link or an HTML unordered list of links
function p.normalizeWebsites(value)
    if not value or value == "" then return "" end
    local websites = p.splitSemicolonValues(value)
    if #websites > 1 then
        local listItems = {}
        for _, site in ipairs(websites) do
            local formattedLink = string.format("[%s %s]", site, linkParser.strip(site))
            table.insert(listItems, string.format("<li>%s</li>", formattedLink))
        end
        return string.format("<ul class=\"template-list template-list-website\" style=\"margin:0; padding-left:1em;\">%s</ul>", table.concat(listItems, ""))
    elseif #websites == 1 then
        return string.format("[%s %s]", websites[1], linkParser.strip(websites[1]))
    end
    return ""
end

-- Wrapper around MultiCountryDisplay for consistent country formatting
function p.normalizeCountries(value)
    if not value or value == "" then return "" end
    return MultiCountryDisplay.formatCountries(value)
end

-- Wrapper around DateNormalization for consistent date formatting
function p.normalizeDates(value)
    if not value or value == "" then return "" end
    return tostring(dateNormalization.formatDate(value))
end

--------------------------------------------------------------------------------
-- Block Generation Helpers
--------------------------------------------------------------------------------

-- Generates a standard title block with configurable class and text
-- Enhanced to support achievement integration with options
function p.renderTitleBlock(args, titleClass, titleText, options)
    options = options or {}
    titleClass = titleClass or "template-title"
    
    -- Basic title block without achievement integration
    if not options.achievementSupport then
        return string.format('|-\n! colspan="2" class="%s" | %s', titleClass, titleText)
    end
    
    -- With achievement support
    local achievementClass = options.achievementClass or ""
    local achievementId = options.achievementId or ""
    local achievementName = options.achievementName or ""
    
    -- Only add achievement attributes if they exist
    if achievementClass ~= "" and achievementId ~= "" then
        return string.format(
            '|-\n! colspan="2" class="%s %s" data-achievement-id="%s" data-achievement-name="%s" | %s',
            titleClass, achievementClass, achievementId, achievementName, titleText
        )
    else
        -- Clean row with no achievement data
        return string.format('|-\n! colspan="2" class="%s" | %s', titleClass, titleText)
    end
end

-- Renders a standard fields block based on field definitions and processors
-- Enhanced to support complete HTML blocks and custom field rendering
function p.renderFieldsBlock(args, fields, processors)
    processors = processors or {}
    local out = {}
    
    for _, field in ipairs(fields) do
        local key, value = p.getFieldValue(args, field)
        if value then
            -- Apply processor if available for this field
            if key and processors[key] and type(processors[key]) == "function" then
                local processedValue = processors[key](value, args)
                
                -- Handle the case where a processor returns complete HTML
                if type(processedValue) == "table" and processedValue.isCompleteHtml then
                    -- Add the complete HTML as is
                    table.insert(out, processedValue.html)
                elseif processedValue ~= nil and processedValue ~= false then
                    -- Standard field rendering
                    table.insert(out, string.format("|-\n| '''%s''':\n| %s", field.label, processedValue))
                end
            else
                -- Standard field rendering without processor
                table.insert(out, string.format("|-\n| '''%s''':\n| %s", field.label, value))
            end
        end
    end
    
    return table.concat(out, "\n")
end

-- Renders a standard divider block with optional label
function p.renderDividerBlock(label)
    if label and label ~= "" then
        return string.format('|-\n| colspan="2" class="template-divider" |\n|-\n| colspan="2" class="icannwiki-centered" | <span class="icannwiki-bold">%s</span>', label)
    else
        return '|-\n| colspan="2" class="template-divider" |'
    end
end


--------------------------------------------------------------------------------
-- Category Utilities
--------------------------------------------------------------------------------

-- Builds a category string from a table of category names
function p.buildCategories(categories)
    if not categories or #categories == 0 then return "" end
    local formatted = {}
    for _, cat in ipairs(categories) do
        -- Check if the category already has the [[ ]] wrapper
        if not string.match(cat, "^%[%[Category:") then
            table.insert(formatted, string.format("[[Category:%s]]", cat))
        else
            table.insert(formatted, cat)
        end
    end
    return table.concat(formatted, "\n")
end

-- Adds categories based on a canonical mapping
function p.addMappingCategories(value, mapping)
    if not value or value == "" or not mapping then return {} end
    local categories = {}
    local canonical = select(1, CanonicalForms.normalize(value, mapping))
    
    if canonical then
        for _, group in ipairs(mapping) do
            if group.canonical == canonical and group.category then
                table.insert(categories, group.category)
                break
            end
        end
    end
    
    return categories
end

--------------------------------------------------------------------------------
-- Semantic Property Helpers
--------------------------------------------------------------------------------

-- Adds semantic properties for multiple countries
-- This is a targeted helper that can be used within existing semantic property generation
function p.addMultiCountrySemanticProperties(countryValue, semanticOutput)
    if not countryValue or countryValue == "" then return semanticOutput end
    
    local MultiCountryDisplay = require('Module:MultiCountryDisplay')
    local normalizedCountries = MultiCountryDisplay.getCountriesForCategories(countryValue)
    if #normalizedCountries > 1 then
        for _, countryName in ipairs(normalizedCountries) do
            if mw.smw then
                mw.smw.set({["Has country"] = countryName})
            else
                semanticOutput = semanticOutput .. "\n" .. 
                    '<div style="display:none;">\n  {{#set: Has country=' .. 
                    countryName .. ' }}\n</div>'
            end
        end
    end
    
    return semanticOutput
end

-- Adds semantic properties for multiple regions by handling both semicolons and "and" conjunctions
-- This is a targeted helper that can be used within existing semantic property generation
function p.addMultiRegionSemanticProperties(regionValue, semanticOutput)
    if not regionValue or regionValue == "" then return semanticOutput end
    
    local RegionalMappingICANN = require('Module:RegionalMappingICANN')
    local processedRegions = {}
    
    -- First, replace "and" with semicolons to standardize the delimiter
    local standardizedInput = regionValue:gsub("%s+and%s+", ";")
    
    -- Now use the normalizeRegions function which handles semicolon-delimited lists
    local regions = RegionalMappingICANN.normalizeRegions(standardizedInput)
    
    -- Add each region as a separate semantic property
    for _, region in ipairs(regions) do
        if region and region ~= "" and not processedRegions[region] then
            processedRegions[region] = true
            
            -- Add as semantic property
            if mw.smw then
                mw.smw.set({["Has region"] = region})
            else
                semanticOutput = semanticOutput .. "\n" .. 
                    '<div style="display:none;">\n  {{#set: Has region=' .. 
                    region .. ' }}\n</div>'
            end
        end
    end
    
    return semanticOutput
end

-- Adds semantic properties for multiple languages with proper normalization
-- This is a targeted helper that can be used within existing semantic property generation
function p.addMultiLanguageSemanticProperties(languagesValue, semanticOutput)
    if not languagesValue or languagesValue == "" then return semanticOutput end
    
    local LanguageNormalization = require('Module:LanguageNormalization')
    
    -- Split by semicolons
    local languageCodes = p.splitSemicolonValues(languagesValue)
    
    -- Add each normalized language as a separate semantic property
    for _, langCode in ipairs(languageCodes) do
        -- Get canonical language name using the public normalize function
        local canonicalLang = LanguageNormalization.normalize(langCode)
        
        -- Only add if we got a valid canonical name
        if canonicalLang and canonicalLang ~= "" then
            if mw.smw then
                mw.smw.set({["Speaks language"] = canonicalLang})
            else
                semanticOutput = semanticOutput .. "\n" .. 
                    '<div style="display:none;">\n  {{#set: Speaks language=' .. 
                    canonicalLang .. ' }}\n</div>'
            end
        end
    end
    
    return semanticOutput
end

-- Generates semantic properties based on configuration
-- Enhanced to handle common patterns while allowing template-specific customization
function p.generateSemanticProperties(args, semanticConfig, options)
    if not args or not semanticConfig then return "" end
    
    local SemanticAnnotations = require('Module:SemanticAnnotations')
    options = options or {}
    
    -- Set options
    local semanticOptions = {
        transform = semanticConfig.transforms or options.transform
    }
    
    -- Set basic properties
    local semanticOutput = SemanticAnnotations.setSemanticProperties(
        args, 
        semanticConfig.properties, 
        semanticOptions
    )
    
    -- Handle boolean flags
    if options.booleanFlags then
        for property, flagName in pairs(options.booleanFlags) do
            if args[flagName] then
                if mw.smw then
                    mw.smw.set({[property] = "true"})
                else
                    semanticOutput = semanticOutput .. "\n" .. 
                        '<div style="display:none;">\n  {{#set: ' .. property .. '=true }}\n</div>'
                end
            end
        end
    end
    
    -- Handle multiple values
    if semanticConfig.additionalProperties then
        for property, sourceFields in pairs(semanticConfig.additionalProperties) do
            for _, fieldName in ipairs(sourceFields) do
                if args[fieldName] and args[fieldName] ~= "" then
                    local value = args[fieldName]
                    
                    -- Apply transformation if available
                    if semanticConfig.transforms and semanticConfig.transforms[property] then
                        value = semanticConfig.transforms[property](value)
                    end
                    
                    if mw.smw then
                        mw.smw.set({[property] = value})
                    else
                        semanticOutput = semanticOutput .. "\n" .. 
                            '<div style="display:none;">\n  {{#set: ' .. property .. '=' .. 
                            value .. ' }}\n</div>'
                    end
                end
            end
        end
    end
    
    -- Handle multi-value fields that need to be split
    if options.multiValueFields then
        for property, config in pairs(options.multiValueFields) do
            local fieldName = config.field
            local splitter = config.splitter or p.splitSemicolonValues
            local processor = config.processor
            
            if args[fieldName] and args[fieldName] ~= "" then
                local values = splitter(args[fieldName])
                for _, value in ipairs(values) do
                    -- Apply processor if available
                    if processor and type(processor) == "function" then
                        value = processor(value)
                    end
                    
                    if value and value ~= "" then
                        if mw.smw then
                            mw.smw.set({[property] = value})
                        else
                            semanticOutput = semanticOutput .. "\n" .. 
                                '<div style="display:none;">\n  {{#set: ' .. property .. '=' .. 
                                value .. ' }}\n</div>'
                        end
                    end
                end
            end
        end
    end
    
    -- Handle custom special cases
    if options.specialCaseHandler and type(options.specialCaseHandler) == "function" then
        local specialCaseOutput = options.specialCaseHandler(args, semanticOutput, semanticConfig)
        if specialCaseOutput then
            semanticOutput = specialCaseOutput
        end
    end
    
    return semanticOutput
end

--------------------------------------------------------------------------------
-- Configuration Standardization
--------------------------------------------------------------------------------

-- Creates a standardized configuration structure for template modules
-- This ensures all templates have a consistent configuration format
function p.createStandardConfig(config)
    config = config or {}
    
    -- Initialize with defaults
    local standardConfig = {
        meta = config.meta or {
            description = "Template module configuration"
        },
        mappings = config.mappings or {},
        fields = config.fields or {},
        semantics = config.semantics or {
            properties = {},
            transforms = {},
            additionalProperties = {}
        },
        constants = config.constants or {},
        patterns = config.patterns or {}
    }
    
    return standardConfig
end

return p