Module:LuaTemplateBlueprint: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
 
(36 intermediate revisions by the same user not shown)
Line 65: Line 65:
-- Wiki link pattern for regex matching
-- Wiki link pattern for regex matching
local WIKI_LINK_PATTERN = '%[%[.-%]%]'
local WIKI_LINK_PATTERN = '%[%[.-%]%]'
-- Semantic property HTML templates
local PROPERTY_DIV_PREFIX = '<div style="display:none;">\n  {{#set: '
local PROPERTY_DIV_SUFFIX = ' }}\n</div>'
-- Empty table for returning when needed
-- Empty table for returning when needed
local EMPTY_OBJECT = {}
local EMPTY_OBJECT = {}
Line 75: Line 72:
local ErrorHandling = require('Module:ErrorHandling')
local ErrorHandling = require('Module:ErrorHandling')
local ConfigRepository = require('Module:ConfigRepository')
local ConfigRepository = require('Module:ConfigRepository')
local ConfigHelpers = require('Module:ConfigHelpers')
local TemplateHelpers = require('Module:TemplateHelpers')
local TemplateHelpers = require('Module:TemplateHelpers')
local TemplateStructure = require('Module:TemplateStructure')
local TemplateStructure = require('Module:TemplateStructure')
Line 343: Line 341:


-- ========== Configuration Integration ==========
-- ========== Configuration Integration ==========
-- Standard configuration sections used by templates
p.configSections = {
    'meta',
    'categories',
    'patterns',
    'fields',
    'mappings',
    'constants',
    'semantics'
}
-- Initialize the standard configuration for a template
-- Initialize the standard configuration for a template
-- Combines base config from ConfigRepository with template overrides
-- Combines base config from ConfigRepository with template overrides
Line 361: Line 348:
     local templateType = template.type
     local templateType = template.type
     local configOverrides = template.config or {}
     local configOverrides = template.config or {}
   
 
     -- Get base configuration from repository
     -- Get base configuration from repository
     local baseConfig = ConfigRepository.getStandardConfig(templateType)
     local baseConfig = ConfigRepository.getStandardConfig(templateType)
   
 
     -- Apply overrides to each section
     -- Use ConfigHelpers to deep merge configurations
     local config = {}
     local config = ConfigHelpers.deepMerge(baseConfig, configOverrides)
    for _, section in ipairs(p.configSections) do
 
        config[section] = config[section] or {}
       
        -- Copy base config for this section if available
        if baseConfig[section] then
            for k, v in pairs(baseConfig[section]) do
                config[section][k] = v
            end
        end
       
        -- Apply overrides for this section if available
        if configOverrides[section] then
            for k, v in pairs(configOverrides[section]) do
                config[section][k] = v
            end
        end
    end
   
     -- Store complete config in template
     -- Store complete config in template
     template.config = config
     template.config = config
Line 398: Line 368:
         end)
         end)
     end
     end
   
 
     return config
     return config
end
end
Line 439: Line 409:
                     args,
                     args,
                     TEMPLATE_TITLE_CLASS_PREFIX .. string.lower(templateId),
                     TEMPLATE_TITLE_CLASS_PREFIX .. string.lower(templateId),
                     titleText
                     titleText,
                    template.titleId
                 )
                 )
                 end,
                 end,
Line 703: Line 674:
     template._processors = TemplateFieldProcessor.initializeProcessors(template)
     template._processors = TemplateFieldProcessor.initializeProcessors(template)
     return template._processors
     return template._processors
end
-- Get field value from args (delegated to TemplateHelpers)
-- @param field table The field definition
-- @param args table The template arguments
-- @return string|nil The field value or nil if not found
function p.getFieldValue(field, args, template)
    local _, value = TemplateHelpers.getFieldValue(args, field)
    return value
end
end


Line 846: Line 808:
      
      
     return template
     return template
end
-- Process a value using a transform function
-- @param value string The value to transform
-- @param property string The property name (for error context)
-- @param transform function The transform function
-- @param args table The template arguments
-- @param template table The template object
-- @return string The transformed value
local function applyTransform(value, property, transform, args, template)
    if not value or value == '' then
        return ''
    end
   
    if not transform then
        return value
    end
   
    local transformedValue = p.protectedExecute(
        template,
        'Transform_' .. property,
        function() return transform(value, args, template) end,
        value,
        value,
        args,
        template
    )
   
    return transformedValue or ''
end
end


-- Validate property value to prevent SMW parser issues
-- Validate property value to prevent SMW parser issues
-- @param value string The value to validate
-- @param value string The value to validate
-- @param property string The property name (for context)
-- @return string The validated value
-- @return string The validated value
local function validatePropertyValue(value, property)
local function validatePropertyValue(value)
     if not value or value == '' then
     if not value or value == '' then
         return ''
         return ''
Line 897: Line 829:
      
      
     return value
     return value
end
-- Property deduplication helper
-- Maintains a signature-based deduplication system
-- @param collector table The collector object with seen and properties tables
-- @param property string The property name
-- @param value string The property value
local function deduplicateProperty(collector, property, value)
    if not value or value == '' then return end
   
    -- Validate the value first
    value = validatePropertyValue(value, property)
    if value == '' then return end
   
    -- Create signature for this property-value pair
    local signature = property .. ":" .. tostring(value)
   
    -- Initialize collector structure if needed
    collector.seen = collector.seen or {}
    collector.properties = collector.properties or {}
    collector.count = collector.count or 0
   
    -- Skip if we've seen this exact property-value pair
    if collector.seen[signature] then return end
   
    -- Mark as seen
    collector.seen[signature] = true
    collector.count = collector.count + 1
   
    -- Add to properties
    if not collector.properties[property] then
        collector.properties[property] = value
    else
        -- Convert to array if needed
        if type(collector.properties[property]) ~= "table" then
            collector.properties[property] = {collector.properties[property]}
        end
        -- Add unique value
        table.insert(collector.properties[property], value)
    end
end
end


Line 957: Line 849:
       (not template._propertyProviders or #template._propertyProviders == 0) then
       (not template._propertyProviders or #template._propertyProviders == 0) then
         return EMPTY_STRING
         return EMPTY_STRING
    end
   
    -- Set time budget for semantic property processing (450ms)
    local startTime = os.clock()
    local timeLimit = 0.45  -- seconds
    local checkInterval = 10  -- check every N properties
    local propertyCounter = 0
   
    local function checkTimeLimit()
        propertyCounter = propertyCounter + 1
        if propertyCounter % checkInterval == 0 then
            if os.clock() - startTime > timeLimit then
                return true  -- time exceeded
            end
        end
        return false
     end
     end
      
      
Line 964: Line 872:
     }
     }
      
      
     -- Create collector with early deduplication
    -- Build initial property mapping (like original code)
    local allProperties = {}
   
    -- Process basic properties - just map property names to field names
    for property, param in pairs(properties) do
        if not skipProperties[property] then
            -- Just map the property to the field name
            -- SemanticAnnotations will handle value extraction and transforms
            allProperties[property] = param
        end
    end
   
     -- Create collector for deduplication of additional properties and providers
     local collector = {
     local collector = {
         seen = {},        -- Track property:value signatures
         seen = {},        -- Track property:value signatures
Line 970: Line 890:
         count = 0        -- Track total property count
         count = 0        -- Track total property count
     }
     }
   
    -- Process basic properties with early deduplication
    for property, param in pairs(properties) do
        if not skipProperties[property] then
            -- Perform case-insensitive lookup for the parameter key
            local keyName, value = TemplateHelpers.getFieldValue(args, { key = param })
            if value and value ~= '' then
                -- Apply transform if needed
                if transforms[property] then
                    value = applyTransform(value, property, transforms[property], args, template)
                end
                deduplicateProperty(collector, property, value)
            end
        end
    end
      
      
     -- Process additional properties with early deduplication and multi-value handling
     -- Process additional properties with early deduplication and multi-value handling
Line 1,002: Line 907:
                     end
                     end
                      
                      
                     -- Process each value individually through deduplication
                     -- Process each value individually
                     for _, singleValue in ipairs(values) do
                     for _, singleValue in ipairs(values) do
                         local trimmedValue = singleValue:match("^%s*(.-)%s*$")
                         local trimmedValue = singleValue:match("^%s*(.-)%s*$")
                         if trimmedValue and trimmedValue ~= '' then
                         if trimmedValue and trimmedValue ~= '' then
                             local transformed = applyTransform(trimmedValue, property, transform, args, template)
                            -- Apply transform if available
                             if transformed and transformed ~= '' then
                             local finalValue = trimmedValue
                                 deduplicateProperty(collector, property, transformed)
                            if transform then
                                finalValue = p.protectedExecute(
                                    template,
                                    'Transform_' .. property,
                                    function() return transform(trimmedValue, args, template) end,
                                    trimmedValue,
                                    trimmedValue,
                                    args,
                                    template
                                )
                            end
                           
                            -- Validate and add to collector
                            finalValue = validatePropertyValue(finalValue)
                             if finalValue and finalValue ~= '' then
                                 if not collector.properties[property] then
                                    collector.properties[property] = {}
                                end
                                table.insert(collector.properties[property], finalValue)
                                collector.count = collector.count + 1
                             end
                             end
                         end
                         end
Line 1,017: Line 941:
     end
     end
      
      
    -- Process country and region properties with early deduplication
    -- if a country field exists and country is not in skipProperties
    if args.country and args.country ~= '' and not skipProperties["Has country"] then
        -- Load CountryData lazily to avoid circular dependencies
        local CountryData = require('Module:CountryData')
        local countryProperties = p.protectedExecute(
            template,
            'CountryData_Properties',
            function() return CountryData.getSemanticCountryRegionProperties(args.country) end,
            {},
            args.country
        )
       
        -- Process country properties through deduplication
        if countryProperties and next(countryProperties) then
            for property, values in pairs(countryProperties) do
                -- Skip if explicitly marked to skip
                if not skipProperties[property] then
                    if type(values) == "table" then
                        -- Process each value through deduplication
                        for _, value in ipairs(values) do
                            deduplicateProperty(collector, property, value)
                        end
                    else
                        -- Single value
                        deduplicateProperty(collector, property, values)
                    end
                end
            end
        end
    end
      
      
     -- Process property providers with early deduplication
     -- Process property providers with early deduplication
Line 1,069: Line 962:
                             -- Provider returned an array of values
                             -- Provider returned an array of values
                             for _, v in ipairs(value) do
                             for _, v in ipairs(value) do
                                 deduplicateProperty(collector, property, v)
                                 local validated = validatePropertyValue(v)
                                if validated and validated ~= '' then
                                    if not collector.properties[property] then
                                        collector.properties[property] = {}
                                    end
                                    table.insert(collector.properties[property], validated)
                                    collector.count = collector.count + 1
                                end
                             end
                             end
                         else
                         else
                             -- Provider returned a single value
                             -- Provider returned a single value
                             deduplicateProperty(collector, property, value)
                             local validated = validatePropertyValue(value)
                            if validated and validated ~= '' then
                                if not collector.properties[property] then
                                    collector.properties[property] = {}
                                end
                                table.insert(collector.properties[property], validated)
                                collector.count = collector.count + 1
                            end
                         end
                         end
                     end
                     end
Line 1,089: Line 996:
         and centralizes normalization logic for clarity and consistency.
         and centralizes normalization logic for clarity and consistency.
     ]]
     ]]
     -- Override raw country/region with normalized names
     -- Override raw country/region with normalized names if country field exists
     local cr = require('Module:ConfigRepository')
     if args.country and args.country ~= '' then
    local cd = require('Module:CountryData')
        local cr = require('Module:ConfigRepository')
    local norm = p.protectedExecute(
        local cd = require('Module:CountryData')
        template,
        local norm = p.protectedExecute(
        'CountryData_Override',
            template,
        function() return cd.getSemanticCountryRegionProperties(args.country) end,
            'CountryData_Override',
         {},
            function() return cd.getSemanticCountryRegionProperties(args.country) end,
         args.country
            {},
            args.country
        )
        if norm then
            local countryKey = cr.semanticProperties.country
            local regionKey = cr.semanticProperties.region
            if norm[countryKey] then
                collector.properties[countryKey] = norm[countryKey]
            end
            if norm[regionKey] then
                collector.properties[regionKey] = norm[regionKey]
            end
        end
    end
   
    -- Merge basic properties mapping with deduplicated additional properties
    -- Basic properties (allProperties) contains field mappings
    -- Additional properties (collector.properties) contains actual values
    local finalProperties = {}
   
    -- Copy basic property mappings
    for property, fieldName in pairs(allProperties) do
        finalProperties[property] = fieldName
    end
   
    -- Add deduplicated additional properties (these have actual values)
    for property, value in pairs(collector.properties) do
         finalProperties[property] = value -- This might be an array of values or a single value
    end
 
    -- Process fixed properties
    if semanticConfig.fixedProperties and type(semanticConfig.fixedProperties) == 'table' then
        for propName, propValue in pairs(semanticConfig.fixedProperties) do
            if not skipProperties[propName] then -- Check skipProperties as well
                local validatedValue = validatePropertyValue(propValue)
                if validatedValue and validatedValue ~= '' then
                    -- Pass as a single-item array to be processed by the
                    -- 'Direct array of values' path in SemanticAnnotations.lua
                    finalProperties[propName] = {validatedValue}  
                end
            end
        end
    end
   
    -- Add debug info as HTML comment (Phase 1 monitoring)
    local basicCount = 0
    for _ in pairs(allProperties) do basicCount = basicCount + 1 end
   
    local debugInfo = string.format(
        "<!-- SMW Debug: basic_props=%d, additional_props=%d, unique_signatures=%d -->",
        basicCount,
         collector.count or 0,
        table.maxn(collector.seen or {})
     )
     )
     local countryKey = cr.semanticProperties.country
      
     local regionKey = cr.semanticProperties.region
     -- Send all properties to SemanticAnnotations in one batch
    allProperties[countryKey] = norm[countryKey]
    allProperties[regionKey]  = norm[regionKey]
     local semanticOutput = SemanticAnnotations.setSemanticProperties(
     local semanticOutput = SemanticAnnotations.setSemanticProperties(
         args,
         args,
         allProperties,
         finalProperties,
         semanticOptions
         semanticOptions
     )
     )
      
      
     return semanticOutput
     -- Append debug info to output
    if semanticOutput and semanticOutput ~= '' then
        return semanticOutput .. '\n' .. debugInfo
    else
        return debugInfo
    end
end
end


Line 1,205: Line 1,167:
-- @return string The rendered template HTML
-- @return string The rendered template HTML
function p.renderTemplate(template, frame)
function p.renderTemplate(template, frame)
    template.current_frame = frame -- Store frame on template instance
   
    -- Generate a unique ID for the title element for ARIA
    local pageId = TemplateHelpers.getCurrentPageId() or '0'
    template.titleId = 'template-title-' .. template.type .. '-' .. pageId
    -- Check recursion depth to prevent infinite loops
    local depth = 0
    if frame.args and frame.args._recursion_depth then
        depth = tonumber(frame.args._recursion_depth) or 0
    elseif frame:getParent() and frame:getParent().args and frame:getParent().args._recursion_depth then
        depth = tonumber(frame:getParent().args._recursion_depth) or 0
    end
   
    if depth > 3 then
        return '<span class="error">Template recursion depth exceeded (limit: 3)</span>'
    end
   
     if not template._errorContext then
     if not template._errorContext then
         template._errorContext = p.createErrorContext(template)
         template._errorContext = p.createErrorContext(template)
     end
     end
   
    ErrorHandling.addStatus(template._errorContext, "LuaTemplateBlueprint", "Now rendering " .. template.type)
      
      
     if not template.config.meta then
     if not template.config.meta then
Line 1,215: Line 1,197:
     local args = frame:getParent().args or {}
     local args = frame:getParent().args or {}
     args = TemplateHelpers.normalizeArgumentCase(args)
     args = TemplateHelpers.normalizeArgumentCase(args)
   
    -- Increment recursion depth for any child template calls
    args._recursion_depth = tostring(depth + 1)
      
      
     args = p.runPreprocessors(template, args)
     args = p.runPreprocessors(template, args)
Line 1,226: Line 1,211:
         tableClass = tableClass,
         tableClass = tableClass,
         blocks = {},
         blocks = {},
         containerTag = template.features.fullPage and "div" or "table"
         containerTag = template.features.fullPage and "div" or "table",
        ariaLabelledBy = template.titleId
     }
     }
      
      
Line 1,241: Line 1,227:
     end
     end
      
      
     return TemplateStructure.render(args, structureConfig, template._errorContext)
     local result = TemplateStructure.render(args, structureConfig, template._errorContext)
   
    -- Append status and error divs to the final output
    result = result .. ErrorHandling.formatCombinedOutput(template._errorContext)
   
    template.current_frame = nil -- Clear frame from template instance
   
    return result
end
end