Module:TemplateHelpers: Difference between revisions

// via Wikitext Extension for VSCode
Tag: Reverted
// via Wikitext Extension for VSCode
 
(47 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Module:TemplateHelpers
--[[
-- Common helper functions for template modules promoting code reuse and consistency.
* Name: TemplateHelpers
-- Provides utilities for string processing, field handling, normalization, and block rendering.
* Author: Mark W. Datysgeld
--
* Description: Common helper functions for template modules promoting code reuse and consistency across string processing, field handling, normalization, and block rendering
-- This module contains the following sections:
* Notes: String processing functions for manipulating strings and template arguments; field processing functions for handling template fields and values; normalization wrappers for standardizing data formats; block generation helpers for rendering template blocks; category and semantic utilities (DEPRECATED wrappers for SemanticCategoryHelpers); configuration standardization for creating standard config structures; includes caching mechanism and multi-value string processing
-- * String Processing Functions - For manipulating strings and template arguments
]]
-- * Field Processing Functions - For handling template fields and values
-- * Normalization Wrappers - For standardizing data formats
-- * Block Generation Helpers - For rendering template blocks
-- * Category and Semantic Utilities - DEPRECATED wrappers for SemanticCategoryHelpers
-- * Configuration Standardization - For creating standard config structures


local p = {}
local p = {}
-- Dependencies
local linkParser = require('Module:LinkParser')
local CountryData = require('Module:CountryData')
local dateNormalization = require('Module:NormalizationDate')
local CanonicalForms = require('Module:CanonicalForms')
local NormalizationText = require('Module:NormalizationText')
local ListGeneration = require('Module:ListGeneration')


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 74: Line 77:
p.FIELD_FORMAT_WITH_TOOLTIP = FIELD_FORMAT_WITH_TOOLTIP
p.FIELD_FORMAT_WITH_TOOLTIP = FIELD_FORMAT_WITH_TOOLTIP


-- Dependencies
-- Registry for declarative list post-processors
local linkParser = require('Module:LinkParser')
local listPostProcessors = {
local CountryData = require('Module:CountryData')
    language = function(itemContent)
local dateNormalization = require('Module:NormalizationDate')
        local NormalizationLanguage = require('Module:NormalizationLanguage')
local CanonicalForms = require('Module:CanonicalForms')
        local langName = NormalizationLanguage.normalize(itemContent)
local SemanticCategoryHelpers = require('Module:SemanticCategoryHelpers')
        local nativeName = NormalizationLanguage.getNativeForm(langName)
local NormalizationText = require('Module:NormalizationText')
        if nativeName and langName ~= "English" then
            return string.format('%s<br/><span style="display:inline-block; width:0.1em; visibility:hidden;">*</span><span style="font-size:75%%;">%s</span>', langName, nativeName)
        else
            return langName
        end
    end,
    website = function(itemContent)
        local linkUrl = itemContent
        if not linkUrl:match("^%a+://") then
            linkUrl = "https://" .. linkUrl
        end
        return string.format("[%s %s]", linkUrl, require('Module:LinkParser').strip(itemContent))
    end,
    autoWikiLink = function(itemContent)
        -- Trim whitespace and check for existing wiki links
        local trimmedContent = p.trim(itemContent)
        if linkParser.processWikiLink(trimmedContent, "check") then
            -- Extract page name and display text
            local pageName, displayText = trimmedContent:match("^%[%[([^|]+)|?(.*)%]%]$")
           
            -- Normalize by trimming whitespace
            pageName = p.trim(pageName or "")
            displayText = p.trim(displayText or "")
           
            -- Reconstruct link, omitting display text if it's same as page name
            if displayText == "" or displayText == pageName then
                return string.format("[[%s]]", pageName)
            else
                return string.format("[[%s|%s]]", pageName, displayText)
            end
        else
            -- Not a wiki link, so just wrap it
            return string.format("[[%s]]", trimmedContent)
        end
    end
}


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 94: Line 132:
     -- Process all keys
     -- Process all keys
     for key, value in pairs(args) do
     for key, value in pairs(args) do
         -- Skip empty numeric parameters (created by leading pipes after template name)
         -- Determine if this key is numeric (number type or numeric-string)
         if type(key) == "number" and (value == nil or value == "") then
         local isNumericKey = type(key) == "number" or (type(key) == "string" and tonumber(key) ~= nil)
            -- Do nothing with empty numeric parameters
        -- Skip all numeric parameters entirely
         else
         if not isNumericKey then
             -- For all other parameters, add lowercase version
             -- Preserve original key and lowercase variant
            normalized[key] = value
             if type(key) == "string" then
             if type(key) == "string" then
                 normalized[key:lower()] = value
                 normalized[key:lower()] = value
             end
             end
            -- Preserve original key as well
            normalized[key] = value
         end
         end
     end
     end
Line 118: Line 155:
function p.joinValues(values, delimiter)
function p.joinValues(values, delimiter)
     return NormalizationText.joinValues(values, delimiter)
     return NormalizationText.joinValues(values, delimiter)
end
-- Removes duplicate values from an array while preserving order
-- @param t table The array to deduplicate
-- @return table A new array with duplicates removed
function p.removeDuplicates(t)
    -- Type checking
    if type(t) ~= 'table' then
        return {}
    end
   
    -- Helper function to check if a value is NaN
    local function isNan(v)
        return type(v) == 'number' and tostring(v) == '-nan'
    end
   
    -- Pre-allocate result table (maximum possible size is #t)
    local ret, exists = {}, {}
   
    -- Process each value, preserving order
    for i, v in ipairs(t) do
        if isNan(v) then
            -- NaNs can't be table keys, and they are also unique
            ret[#ret + 1] = v
        else
            if not exists[v] then
                ret[#ret + 1] = v
                exists[v] = true
            end
        end
    end
   
    return ret
end
end


Line 132: Line 202:
local wikiLinkCache = {}
local wikiLinkCache = {}


-- Process wiki links with different modes: extract, strip, or check
-- @deprecated See LinkParser.processWikiLink
-- @deprecated Direct implementation moved to LinkParser.lua
function p.processWikiLink(value, mode)
function p.processWikiLink(value, mode)
     local LinkParser = require('Module:LinkParser')
     return require('Module:LinkParser').processWikiLink(value, mode)
    return LinkParser.processWikiLink(value, mode)
end
end


-- Extract page name from wiki link [[Name]] or [[Name|Text]]
-- @deprecated Direct implementation moved to LinkParser.lua
function p.extractFromWikiLink(value)
    local LinkParser = require('Module:LinkParser')
    return LinkParser.extractFromWikiLink(value)
end


-- Module-level pattern categories for sanitizing user input
-- Module-level pattern categories for sanitizing user input
Line 153: Line 215:
             pattern = "%[%[([^|%]]+)%]%]",  
             pattern = "%[%[([^|%]]+)%]%]",  
             replacement = function(match)  
             replacement = function(match)  
                 return p.processWikiLink("[[" .. match .. "]]", "strip")  
                 return linkParser.processWikiLink("[[" .. match .. "]]", "strip")  
             end  
             end  
         },
         },
Line 159: Line 221:
             pattern = "%[%[([^|%]]+)|([^%]]+)%]%]",  
             pattern = "%[%[([^|%]]+)|([^%]]+)%]%]",  
             replacement = function(match1, match2)  
             replacement = function(match1, match2)  
                 return p.processWikiLink("[[" .. match1 .. "|" .. match2 .. "]]", "strip")  
                 return linkParser.processWikiLink("[[" .. match1 .. "|" .. match2 .. "]]", "strip")  
             end  
             end  
         }
         }
Line 190: Line 252:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Retrieves a field value from args using either multiple possible keys or a single key
-- Supports case-insensitive lookup by default
function p.getFieldValue(args, field)
function p.getFieldValue(args, field)
     if field.keys then
     -- Cache lookup for performance
        for _, key in ipairs(field.keys) do
    local cacheKey = p.generateCacheKey("getFieldValue", field.key or table.concat(field.keys or {}, ","), args)
            -- First try exact match to maintain backward compatibility
    local cached = p.withCache(cacheKey, function()
            if args[key] and args[key] ~= "" then
        -- Case-insensitive lookup logic
                 return key, args[key]
        if field.keys then
            for _, key in ipairs(field.keys) do
                if args[key] and args[key] ~= "" then
                    return { key = key, value = args[key] }
                end
                 local lowerKey = key:lower()
                if args[lowerKey] and args[lowerKey] ~= "" and lowerKey ~= key then
                    return { key = lowerKey, value = args[lowerKey] }
                end
             end
             end
              
             return { key = nil, value = nil }
             -- Then try lowercase version
        end
             local lowerKey = key:lower()
 
             if args[lowerKey] and args[lowerKey] ~= "" and lowerKey ~= key then
        if field.key then
                 return lowerKey, args[lowerKey]
             if args[field.key] and args[field.key] ~= "" then
                return { key = field.key, value = args[field.key] }
            end
             local lowerKey = field.key:lower()
             if args[lowerKey] and args[lowerKey] ~= "" and lowerKey ~= field.key then
                 return { key = lowerKey, value = args[lowerKey] }
             end
             end
            return { key = field.key, value = nil }
         end
         end
         return nil, nil
 
     end
         return { key = nil, value = nil }
      
     end)
    -- First try exact match
     return cached.key, cached.value
    if args[field.key] and args[field.key] ~= "" then
        return field.key, args[field.key]
    end
   
    -- Then try lowercase version
    local lowerKey = field.key:lower()
    if args[lowerKey] and args[lowerKey] ~= "" and lowerKey ~= field.key then
        return lowerKey, args[lowerKey]
    end
   
    return field.key, nil
end
end


Line 247: Line 310:
-- Normalization Wrappers
-- Normalization Wrappers
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Formats website URLs as an HTML unordered list of links, ensuring consistent emoji display
-- Uses splitMultiValueString for more flexible delimiter handling
function p.normalizeWebsites(value)
    if not value or value == "" then return "" end
   
    -- Get websites as a table, handling both single and multiple cases
    local websites
   
    -- Quick check for single website (no delimiters)
    if not value:match(";") and not value:match("%s+and%s+") then
        -- Single website case - create a single-item table
        websites = {value}
    else
        -- Multiple websites case
        websites = p.splitMultiValueString(value)
    end
   
    -- Handle all websites consistently using the list format
    if #websites > 0 then
        -- Pre-allocate listItems table based on number of websites
        local listItems = {}
        local index = 1
       
        for _, site in ipairs(websites) do
            -- Ensure the site has a protocol prefix for proper linking
            local linkUrl = site
            if not linkUrl:match("^%a+://") then
                linkUrl = "https://" .. linkUrl
            end
           
            local formattedLink = string.format("[%s %s]", linkUrl, linkParser.strip(site))
            listItems[index] = string.format("<li>%s</li>", formattedLink)
            index = index + 1
        end
        return string.format("<ul class=\"template-list template-list-website\">%s</ul>", table.concat(listItems, ""))
    end
   
    return ""
end


-- Wrapper around CountryData for consistent country formatting
-- Wrapper around CountryData for consistent country formatting
Line 297: Line 320:
     -- Use the caching wrapper
     -- Use the caching wrapper
     return p.withCache(cacheKey, function()
     return p.withCache(cacheKey, function()
         -- First try to process as ISO-2 or country name
         return CountryData.formatCountries(value)
        local result = CountryData.formatCountries(value)
       
        -- If no output was generated, check if input might contain ISO-3 codes
        if result == "" then
            -- Split multi-value input
            local iso2Values = {}
            for country in string.gmatch(value, "[^;]+") do
                local trimmed = NormalizationText.trim(country)
                if trimmed and trimmed ~= "" then
                    -- Try to get ISO-2 from ISO-3
                    local iso2 = CountryData.getCountryCodeByIso3(trimmed)
                    if iso2 then
                        table.insert(iso2Values, iso2)
                    end
                end
            end
           
            -- If we found any ISO-2 codes, join them and try again
            if #iso2Values > 0 then
                result = CountryData.formatCountries(table.concat(iso2Values, "; "))
            end
        end
       
        return result
     end)
     end)
end
end
Line 359: Line 358:
     local showSingleDate = options.showSingleDate ~= false -- true by default
     local showSingleDate = options.showSingleDate ~= false -- true by default
     local consolidateIdenticalDates = options.consolidateIdenticalDates ~= false -- true by default
     local consolidateIdenticalDates = options.consolidateIdenticalDates ~= false -- true by default
    -- Global fallback: if only end date is present, treat end as start
    if (not startDate or startDate == "") and endDate and endDate ~= "" then
        startDate, endDate = endDate, nil
    end
      
      
     -- Handle empty input
     -- Handle empty input
Line 418: Line 422:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Generates a standard title block with configurable class and text
-- @deprecated See TemplateStructure.renderTitleBlock and AchievementSystem.renderTitleBlockWithAchievement
-- Enhanced to support achievement integration with options
-- @deprecated Direct implementation moved to TemplateStructure.lua and AchievementSystem.lua
function p.renderTitleBlock(args, titleClass, titleText, options)
function p.renderTitleBlock(args, titleClass, titleText, options)
     options = options or {}
     options = options or {}
Line 426: Line 428:
     -- If achievement support is needed, use AchievementSystem
     -- If achievement support is needed, use AchievementSystem
     if options.achievementSupport then
     if options.achievementSupport then
         local AchievementSystem = require('Module:AchievementSystem')
         return require('Module:AchievementSystem').renderTitleBlockWithAchievement(
        local achievementClass = options.achievementClass or ""
            args, titleClass, titleText,
        local achievementId = options.achievementId or ""
            options.achievementClass or "",
        local achievementName = options.achievementName or ""
            options.achievementId or "",
       
            options.achievementName or ""
        return AchievementSystem.renderTitleBlockWithAchievement(
            args, titleClass, titleText, achievementClass, achievementId, achievementName
         )
         )
     else
     else
         -- Otherwise use the basic title block from TemplateStructure
         -- Otherwise use the basic title block from TemplateStructure
         local TemplateStructure = require('Module:TemplateStructure')
         return require('Module:TemplateStructure').renderTitleBlock(args, titleClass, titleText)
        return TemplateStructure.renderTitleBlock(args, titleClass, titleText)
     end
     end
end
end


-- Helper function for wiki link handling
-- Adds wiki links to a value if needed based on field configuration
-- @param value The value to process
-- @param field The field configuration
-- @return The processed value with wiki links added if needed
-- @deprecated Direct implementation moved to LinkParser.lua
function p.applyWikiLinkHandling(value, field)
    local LinkParser = require('Module:LinkParser')
    return LinkParser.applyWikiLinkHandling(value, field)
end
-- Helper function to preserve wiki links in processed values
-- If the original value had wiki links but the processed value doesn't,
-- returns the original value to preserve the wiki links
-- @param originalValue The original value before processing
-- @param processedValue The value after processing
-- @param preserveWikiLinks Whether to preserve wiki links
-- @return The value with wiki links preserved if needed
-- @deprecated Direct implementation moved to LinkParser.lua
function p.preserveWikiLinks(originalValue, processedValue, preserveWikiLinks)
    local LinkParser = require('Module:LinkParser')
    return LinkParser.preserveWikiLinks(originalValue, processedValue, preserveWikiLinks)
end
-- Get property description from a property page
-- @param propertyName string The name of the property (e.g., "Has interview format")
-- @return string|nil The property description or nil if not found
-- @deprecated Direct implementation moved to SemanticCategoryHelpers.lua
function p.getPropertyDescription(propertyName)
    return SemanticCategoryHelpers.getPropertyDescription(propertyName)
end
-- Create info icon HTML with tooltip
-- @param tooltip string The tooltip text
-- @return string The HTML for the info icon with tooltip
-- @deprecated This function is kept for backward compatibility but no longer adds an icon
function p.createInfoIconWithTooltip(tooltip)
    -- Return empty string as we now handle tooltips directly on the cell
    return ""
end


-- Renders a standard fields block based on field definitions and processors
-- Renders a standard fields block based on field definitions and processors
Line 488: Line 447:
     processors = processors or {}
     processors = processors or {}
     propertyMappings = propertyMappings or {}
     propertyMappings = propertyMappings or {}
    -- filter out hidden fields
    local filteredFields = {}
    for _, f in ipairs(fields) do
        if not f.hidden then
            table.insert(filteredFields, f)
        end
    end
      
      
     -- Pre-allocate output table - estimate based on number of fields
     -- Pre-allocate output table - estimate based on number of fields
Line 494: Line 461:
     local outIndex = 1
     local outIndex = 1
      
      
     for _, field in ipairs(fields) do
     for _, field in ipairs(filteredFields) do
         local key, value = p.getFieldValue(args, field)
         local key, value = p.getFieldValue(args, field)
         if value then
         if value then
            -- Create sanitization options
             local continue = false
             local sanitizeOptions = {
                preserveWikiLinks = field.autoWikiLink or field.preserveWikiLinks
            }
              
              
             -- Get property name for this field if available
             -- Handle declarative lists first
            local propertyName = nil
            if field.list then
            for propName, fieldName in pairs(propertyMappings) do
                -- Use a safe, semicolon-only pattern to avoid breaking up valid names.
                if fieldName == key then
                local semicolonOnlyPattern = {{pattern = ";%s*", replacement = ";"}}
                     propertyName = propName
                local items = NormalizationText.splitMultiValueString(value, semicolonOnlyPattern)
                     break
                if #items > 0 then
                    local options = {}
                    if type(field.list) == 'string' then
                        options.mode = field.list
                    elseif type(field.list) == 'table' then
                        for k, v in pairs(field.list) do
                            options[k] = v
                        end
                    end
 
                    -- Chain post-processors declaratively
                    local processorsToApply = {}
                    if field.autoWikiLink then
                        table.insert(processorsToApply, "autoWikiLink")
                    end
                    if options.postProcess then
                        table.insert(processorsToApply, options.postProcess)
                    end
 
                    for _, processorName in ipairs(processorsToApply) do
                        local processorFunc = listPostProcessors[processorName]
                        if processorFunc then
                            local originalHook = options.itemHook
                            options.itemHook = function(item)
                                if type(originalHook) == 'function' then
                                    item = originalHook(item)
                                end
                               
                                local itemContent = type(item) == 'table' and item.content or item
                                local processedContent = processorFunc(itemContent)
 
                                if type(item) == 'table' then
                                    item.content = processedContent
                                    return item
                                else
                                    return processedContent
                                end
                            end
                        end
                    end
                   
                    local listOutput = ListGeneration.createList(items, options)
                    out[outIndex] = string.format(FIELD_FORMAT, field.label, listOutput)
                     outIndex = outIndex + 1
                     continue = true
                 end
                 end
             end
             end
              
 
            -- Get tooltip text if property exists
             if not continue then
            local tooltipText = ""
                -- Create sanitization options
            if propertyName then
                local sanitizeOptions = {
                tooltipText = p.getPropertyDescription(propertyName) or ""
                    preserveWikiLinks = field.autoWikiLink or field.preserveWikiLinks
            end
                }
           
               
            -- Prepare tooltip attributes if tooltip text exists
                -- Get property name for this field if available (case-insensitive)
            local tooltipClass = ""
                local propertyName = nil
            local tooltipAttr = ""
                for propName, fieldName in pairs(propertyMappings) do
            if tooltipText and tooltipText ~= "" then
                    if key and (fieldName == key or tostring(fieldName):lower() == tostring(key):lower()) then
                 -- Escape quotes in tooltip text to prevent HTML attribute issues
                        propertyName = propName
                 local escapedTooltip = tooltipText:gsub('"', '&quot;')
                        break
                tooltipClass = " has-tooltip"
                    end
                 tooltipAttr = string.format('data-tooltip="%s"', escapedTooltip)
                end
            end
               
           
                 -- Get tooltip text if property exists
            -- Apply processor if available for this field
                 local tooltipText = ""
            if key and processors[key] and type(processors[key]) == "function" then
                 if propertyName then
                 local processedValue = processors[key](value, args)
                    tooltipText = require('Module:SemanticCategoryHelpers').getPropertyDescription(propertyName) or ""
                 end
                  
                  
                 -- Preserve wiki links if needed
                 -- Prepare tooltip attributes if tooltip text exists
                 processedValue = p.preserveWikiLinks(
                 local tooltipClass = ""
                     value,  
                local tooltipAttr = ""
                     processedValue,
                if tooltipText and tooltipText ~= "" then
                     sanitizeOptions.preserveWikiLinks
                    -- Escape quotes in tooltip text to prevent HTML attribute issues
                 )
                     local escapedTooltip = tooltipText:gsub('"', '"')
                     tooltipClass = " has-tooltip"
                     tooltipAttr = string.format('data-tooltip="%s"', escapedTooltip)
                 end
                  
                  
                 -- Handle the case where a processor returns complete HTML
                 -- Apply processor if available for this field
                if type(processedValue) == "table" and processedValue.isCompleteHtml then
                if key and processors[key] and type(processors[key]) == "function" then
                    -- Add the complete HTML as is
                    local processedValue = processors[key](value, args)
                    out[outIndex] = processedValue.html
                   
                    outIndex = outIndex + 1
                    -- Preserve wiki links if needed
                elseif processedValue ~= nil and processedValue ~= false then
                    processedValue = linkParser.preserveWikiLinks(
                        value,
                        processedValue,
                        sanitizeOptions.preserveWikiLinks
                    )
                   
                    -- Handle the case where a processor returns complete HTML
                    if type(processedValue) == "table" and processedValue.isCompleteHtml then
                        -- Add the complete HTML as is
                        out[outIndex] = processedValue.html
                        outIndex = outIndex + 1
                    elseif processedValue ~= nil and processedValue ~= false then
                        -- Apply wiki link handling
                        processedValue = linkParser.applyWikiLinkHandling(processedValue, field)
                       
                        -- Standard field rendering with tooltip
                        out[outIndex] = string.format(FIELD_FORMAT_WITH_TOOLTIP,
                            tooltipClass, tooltipAttr, field.label, processedValue)
                        outIndex = outIndex + 1
                    end
                else
                    -- Standard field rendering without processor
                    -- Apply sanitization with preserveWikiLinks option if needed
                    local finalValue
                    if sanitizeOptions.preserveWikiLinks then
                        finalValue = value
                    else
                        finalValue = p.sanitizeUserInput(value, nil, nil, sanitizeOptions)
                    end
                   
                     -- Apply wiki link handling
                     -- Apply wiki link handling
                     processedValue = p.applyWikiLinkHandling(processedValue, field)
                     finalValue = linkParser.applyWikiLinkHandling(finalValue, field)
                      
                      
                     -- Standard field rendering with tooltip
                     -- Use format with tooltip
                     out[outIndex] = string.format(FIELD_FORMAT_WITH_TOOLTIP,  
                     out[outIndex] = string.format(FIELD_FORMAT_WITH_TOOLTIP,  
                         tooltipClass, tooltipAttr, field.label, processedValue)
                         tooltipClass, tooltipAttr, field.label, finalValue)
                     outIndex = outIndex + 1
                     outIndex = outIndex + 1
                 end
                 end
            else
                -- Standard field rendering without processor
                -- Apply sanitization with preserveWikiLinks option if needed
                local finalValue
                if sanitizeOptions.preserveWikiLinks then
                    finalValue = value
                else
                    finalValue = p.sanitizeUserInput(value, nil, nil, sanitizeOptions)
                end
               
                -- Apply wiki link handling
                finalValue = p.applyWikiLinkHandling(finalValue, field)
               
                -- Use format with tooltip
                out[outIndex] = string.format(FIELD_FORMAT_WITH_TOOLTIP,
                    tooltipClass, tooltipAttr, field.label, finalValue)
                outIndex = outIndex + 1
             end
             end
         end
         end
Line 576: Line 600:
end
end


-- Renders a standard divider block with optional label
-- @deprecated See TemplateStructure.renderDividerBlock
-- @deprecated Direct implementation moved to TemplateStructure.lua
function p.renderDividerBlock(label)
function p.renderDividerBlock(label)
     local TemplateStructure = require('Module:TemplateStructure')
    return require('Module:TemplateStructure').renderDividerBlock(label)
     return TemplateStructure.renderDividerBlock(label)
end
 
-- Extracts semantic value from a field, handling wiki links appropriately
-- @param fieldValue The value to extract semantic data from
-- @param fieldName The name of the field (for error reporting)
-- @param errorContext Optional error context for error handling
-- @return The extracted semantic value or nil if the input is empty
function p.extractSemanticValue(fieldValue, fieldName, errorContext)
    if not fieldValue or fieldValue == "" then
        return nil
    end
   
    -- If the value already has wiki links, extract the name using LinkParser
     local LinkParser = require('Module:LinkParser')
     if LinkParser.processWikiLink(fieldValue, "check") then
        -- Use the standardized error handling helper
        return p.withErrorHandling(
            errorContext,
            "extractFromWikiLink_" .. fieldName,
            LinkParser.extractFromWikiLink,
            fieldValue,  -- fallback to original value on error
            fieldValue
        )
    else
        -- Otherwise, use the plain text value
        return fieldValue
    end
end
end


Line 686: Line 735:
-- Configuration Standardization
-- Configuration Standardization
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Creates a standardized configuration structure for template modules
-- @deprecated Direct implementation moved to ConfigRepository.lua
function p.createStandardConfig(config)
    local ConfigRepository = require('Module:ConfigRepository')
    return ConfigRepository.createStandardConfig(config)
end


--[[
--[[