Module:LuaTemplateBlueprint: Difference between revisions
// via Wikitext Extension for VSCode Tag: Reverted |
// via Wikitext Extension for VSCode |
||
| (46 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 = '%[%[.-%]%]' | ||
-- 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 ========== | ||
-- 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) | ||
-- | -- Use ConfigHelpers to deep merge configurations | ||
local config = | local config = ConfigHelpers.deepMerge(baseConfig, configOverrides) | ||
-- 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 | end | ||
| Line 830: | Line 792: | ||
end | end | ||
return processedArgs | return processedArgs | ||
end | end | ||
| Line 858: | Line 810: | ||
end | end | ||
-- | -- Validate property value to prevent SMW parser issues | ||
-- @param value string The value to | -- @param value string The value to validate | ||
-- @return string The validated value | |||
local function validatePropertyValue(value) | |||
-- @return string The | |||
local function | |||
if not value or value == '' then | if not value or value == '' then | ||
return '' | return '' | ||
end | end | ||
if | -- Convert to string if needed | ||
value = tostring(value) | |||
-- Remove potentially problematic wiki markup | |||
value = value:gsub('{{.-}}', '') -- Remove template calls | |||
value = value:gsub('%[%[Category:.-]]', '') -- Remove categories | |||
-- Escape pipe characters that might break SMW | |||
value = value:gsub('|', '{{!}}') | |||
return | return value | ||
end | end | ||
| Line 905: | 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 912: | Line 872: | ||
} | } | ||
-- | -- Build initial property mapping (like original code) | ||
local allProperties = {} | local allProperties = {} | ||
-- | -- Process basic properties - just map property names to field names | ||
for property, param in pairs(properties) do | 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 | end | ||
-- Process additional properties with | -- Create collector for deduplication of additional properties and providers | ||
local collector = { | |||
seen = {}, -- Track property:value signatures | |||
properties = {}, -- Final deduplicated properties | |||
count = 0 -- Track total property count | |||
} | |||
-- Process additional properties with early deduplication and multi-value handling | |||
for property, fields in pairs(additionalProperties) do | for property, fields in pairs(additionalProperties) do | ||
-- Skip properties that are explicitly marked to skip | -- Skip properties that are explicitly marked to skip | ||
| Line 930: | Line 899: | ||
local rawValue = args[fieldName] | local rawValue = args[fieldName] | ||
if rawValue and rawValue ~= '' then | if rawValue and rawValue ~= '' then | ||
local | -- Handle multi-value fields by splitting first | ||
local values | |||
if rawValue:find(';') then | |||
values = TemplateHelpers.splitMultiValueString(rawValue) | |||
else | |||
values = {rawValue} | |||
end | |||
-- Process each value individually | |||
for _, singleValue in ipairs(values) do | |||
local trimmedValue = singleValue:match("^%s*(.-)%s*$") | |||
if trimmedValue and trimmedValue ~= '' then | |||
-- Apply transform if available | |||
local finalValue = trimmedValue | |||
if transform then | |||
finalValue = p.protectedExecute( | |||
template, | |||
'Transform_' .. property, | |||
function() return transform(trimmedValue, args, template) end, | |||
trimmedValue, | |||
trimmedValue, | |||
args, | |||
template | |||
) | |||
end | end | ||
-- | |||
-- Validate and add to collector | |||
finalValue = validatePropertyValue(finalValue) | |||
if finalValue and finalValue ~= '' then | |||
if not collector.properties[property] then | |||
collector.properties[property] = {} | |||
end | end | ||
table.insert(collector.properties[property], finalValue) | |||
collector.count = collector.count + 1 | |||
table.insert( | |||
end | end | ||
end | end | ||
| Line 957: | Line 941: | ||
end | end | ||
-- Process property providers with deduplication | -- Process property providers with early deduplication | ||
if template._propertyProviders then | if template._propertyProviders then | ||
for _, provider in ipairs(template._propertyProviders) do | for _, provider in ipairs(template._propertyProviders) do | ||
| Line 1,016: | Line 955: | ||
if providerResult and next(providerResult) then | if providerResult and next(providerResult) then | ||
-- | -- Process provider properties through deduplication | ||
for property, value in pairs(providerResult) do | for property, value in pairs(providerResult) do | ||
-- Skip properties marked to skip | -- Skip properties marked to skip | ||
| Line 1,022: | Line 961: | ||
if type(value) == "table" then | if type(value) == "table" then | ||
-- Provider returned an array of values | -- Provider returned an array of values | ||
for _, v in ipairs(value) do | |||
local validated = validatePropertyValue(v) | |||
if validated and validated ~= '' then | |||
if not collector.properties[property] then | |||
collector.properties[property] = {} | |||
if not | |||
end | end | ||
table.insert(collector.properties[property], validated) | |||
collector.count = collector.count + 1 | |||
end | end | ||
end | end | ||
else | else | ||
-- Provider returned a single value | -- Provider returned a single value | ||
local validated = validatePropertyValue(value) | |||
if validated and validated ~= '' then | |||
if not collector.properties[property] then | |||
collector.properties[property] = {} | |||
end | end | ||
table.insert(collector.properties[property], validated) | |||
collector.count = collector.count + 1 | |||
end | end | ||
end | end | ||
| Line 1,097: | 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 cr = require('Module:ConfigRepository') | |||
local cd = require('Module:CountryData') | |||
local norm = p.protectedExecute( | |||
template, | |||
'CountryData_Override', | |||
{}, | function() return cd.getSemanticCountryRegionProperties(args.country) end, | ||
{}, | |||
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 {}) | |||
) | ) | ||
-- Send all properties to SemanticAnnotations in one batch | |||
local semanticOutput = SemanticAnnotations.setSemanticProperties( | local semanticOutput = SemanticAnnotations.setSemanticProperties( | ||
args, | args, | ||
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,213: | 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,223: | 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,234: | 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,249: | Line 1,227: | ||
end | end | ||
local | 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 | |||
return | |||
end | end | ||