Module:LuaTemplateBlueprint
Appearance
Documentation for this module may be created at Module:LuaTemplateBlueprint/doc
--[[
* LuaTemplateBlueprint.lua
* Provides a unified foundation for all ICANNWiki templates with feature toggling
*
* This module standardizes template architecture, provides feature toggling,
* centralizes common functionality, and improves maintainability.
*
* Integration with other modules:
* - ErrorHandling: All operations are protected with centralized error handling
* - ConfigRepository: Templates load configuration from this central repository
* - TemplateHelpers: Common utilities for rendering and normalization
* - TemplateStructure: Block-based rendering engine
* - TemplateFieldProcessor: Field processing and value retrieval
* - CountryData: Country normalization and region derivation
* - SocialMedia: Social media icon rendering
*
* Feature Configuration:
* Templates must explicitly specify which features they want to use when registering:
*
* local template = Blueprint.registerTemplate('TemplateName', {
* features = {
* title = true,
* fields = true,
* semanticProperties = true,
* categories = true,
* errorReporting = true
* -- logo and socialMedia intentionally disabled
* }
* })
*
* Alternatively, templates can use predefined feature sets:
*
* local template = Blueprint.registerTemplate('TemplateName', {
* features = Blueprint.featureSets.minimal
* })
*
* Available feature sets:
* - minimal: Just title, fields, and error reporting
* - standard: All features enabled
* - content: Standard without social media
* - data: Just fields, semantics, categories, and error reporting
*
* Note on parameter handling:
* Template parameters are extracted using frame:getParent().args and normalized
* with TemplateHelpers.normalizeArgumentCase() for case-insensitive access.
]]
local p = {}
-- ========== Constants as Upvalues ==========
-- Common empty string for fast returns
local EMPTY_STRING = ''
-- Common separator for concatenation
local NEWLINE = '\n'
-- Common semicolon separator for multi-values
local SEMICOLON = ';'
-- Common category prefix/suffix
local CATEGORY_PREFIX = '[[Category:'
local CATEGORY_SUFFIX = ']]'
-- Template class prefixes
local TEMPLATE_TITLE_CLASS_PREFIX = 'template-title template-title-'
local TEMPLATE_LOGO_CLASS_PREFIX = 'template-logo template-logo-'
-- Default table class
local DEFAULT_TABLE_CLASS = 'template-table'
-- Wiki link pattern for regex matching
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
local EMPTY_OBJECT = {}
-- ========== Required modules ==========
-- Core modules that are always needed
local ErrorHandling = require('Module:ErrorHandling')
local ConfigRepository = require('Module:ConfigRepository')
local TemplateHelpers = require('Module:TemplateHelpers')
local TemplateStructure = require('Module:TemplateStructure')
local SemanticAnnotations = require('Module:SemanticAnnotations')
local LinkParser = require('Module:LinkParser')
local TemplateFieldProcessor = require('Module:TemplateFieldProcessor')
local mw = mw -- MediaWiki API
-- Module-level caches for expensive operations
local featureCache = {}
local moduleCache = {} -- Cache for lazy-loaded modules
-- Lazy module loader
local function lazyRequire(moduleName)
return function()
if not moduleCache[moduleName] then
moduleCache[moduleName] = require(moduleName)
end
return moduleCache[moduleName]
end
end
-- Lazily loaded modules
local getSocialMedia = lazyRequire('Module:SocialMedia')
local getNormalizationDate = lazyRequire('Module:NormalizationDate')
local getCountryData = lazyRequire('Module:CountryData')
-- ========== Template Registry ==========
-- Registry to store all registered templates
p.registry = {}
-- Element registry for custom Blueprint elements
p.elementRegistry = {}
-- Register and lookup functions for Blueprint elements
function p.registerElement(name, module)
if not name or not module then return end
p.elementRegistry[name] = module
return module
end
function p.getElement(name)
return p.elementRegistry[name]
end
function p.getAllElements()
local list = {}
for k, v in pairs(p.elementRegistry) do list[k] = v end
return list
end
-- Create a Blueprint block for a registered element
function p.createElementBlock(name)
local element = p.getElement(name)
if not element or not element.createBlock then return end
return {
feature = name,
render = function(template, args)
return p.protectedExecute(
template,
"ElementBlock_" .. name,
function()
return element.createBlock()(template, args)
end,
"",
args
)
end
}
end
-- Add a registered element's block to a template at a given position
function p.addElementToTemplate(template, name, position)
local block = p.createElementBlock(name)
if not block then return false end
template.config.blocks = template.config.blocks or {}
template.config.blocks[name] = block
template.features[name] = true
-- Build a fresh block sequence
local seq = {}
for _, b in ipairs(template.config.blockSequence or p.standardBlockSequence) do
table.insert(seq, b)
end
if position and type(position) == "number" and position <= #seq + 1 then
table.insert(seq, position, name)
else
table.insert(seq, name)
end
template.config.blockSequence = seq
return true
end
-- Automatically initialize and register built-in elements
function p.initializeElements()
local ok, mod = pcall(require, 'Module:ElementNavigation')
if ok and mod and mod.elementName then
p.registerElement(mod.elementName, mod)
end
end
-- ========== Feature Management ==========
-- Common feature sets that templates can use as a starting point
-- Templates must explicitly include the features they want to use
p.featureSets = {
-- Minimal feature set with just the essentials
minimal = {
title = true,
fields = true,
errorReporting = true
},
-- Standard feature set with all common features
standard = {
-- Core rendering features
title = true,
logo = true,
fields = true,
socialMedia = true,
-- Semantic features
semanticProperties = true,
categories = true,
-- Error handling
errorReporting = true
},
}
-- Create a cache key for features
-- @param featureOverrides table Optional table of feature overrides
-- @return string Cache key
local function createFeatureCacheKey(featureOverrides)
if not featureOverrides or not next(featureOverrides) then
return 'default'
end
local keyCount = 0
for _ in pairs(featureOverrides) do keyCount = keyCount + 1 end
local keys = {}
local keyIndex = 1
for k in pairs(featureOverrides) do
keys[keyIndex] = k
keyIndex = keyIndex + 1
end
table.sort(keys)
local parts = {}
for i = 1, keyCount do
local k = keys[i]
parts[i] = k .. '=' .. tostring(featureOverrides[k])
end
return table.concat(parts, ',')
end
-- Initialize feature toggles for a template
-- @param featureOverrides table Table of feature configurations
-- @return table The initialized features
function p.initializeFeatures(featureOverrides)
-- If no features are specified, return an empty features table
-- This ensures templates must explicitly define their features
if not featureOverrides then
return {}
end
-- Check if we have a cached version of this feature set
local cacheKey = createFeatureCacheKey(featureOverrides)
if featureCache[cacheKey] then
-- Return a copy of the cached features to prevent modification
local features = {}
for k, v in pairs(featureCache[cacheKey]) do
features[k] = v
end
return features
end
-- Create a new features table with only the specified features
local features = {}
for featureId, enabled in pairs(featureOverrides) do
features[featureId] = enabled
end
-- Cache the features for future use
featureCache[cacheKey] = {}
for k, v in pairs(features) do
featureCache[cacheKey][k] = v
end
return features
end
-- ========== Template Registration ==========
-- Register a new template
-- @param templateType string The template type (e.g., "Event", "Person", "TLD")
-- @param config table Configuration overrides for the template
-- @return table The registered template object
function p.registerTemplate(templateType, config)
local template = {
type = templateType,
config = config or {},
features = p.initializeFeatures(config and config.features or nil)
}
template.render = function(frame)
return p.renderTemplate(template, frame)
end
p.registry[templateType] = template
return template
end
-- ========== Error Handling Integration ==========
-- Create an error context for a template
-- @param template table The template object
-- @return table The error context
function p.createErrorContext(template)
local context = ErrorHandling.createContext(template.type .. "Template")
template._errorContext = context
return context
end
-- Execute a function with error protection
-- @param template table The template object
-- @param functionName string Name of the function being protected
-- @param func function The function to execute
-- @param fallback any The fallback value if an error occurs
-- @param ... any Arguments to pass to the function
-- @return any The result of the function or fallback
function p.protectedExecute(template, functionName, func, fallback, ...)
if not template._errorContext then
template._errorContext = p.createErrorContext(template)
end
return ErrorHandling.protect(
template._errorContext,
functionName,
func,
fallback,
...
)
end
-- ========== Configuration Integration ==========
-- Standard configuration sections used by templates
p.configSections = {
'meta',
'categories',
'patterns',
'fields',
'mappings',
'constants',
'semantics'
}
-- Initialize the standard configuration for a template
-- Combines base config from ConfigRepository with template overrides
-- @param template table The template object
-- @return table The complete configuration
function p.initializeConfig(template)
local templateType = template.type
local configOverrides = template.config or {}
-- Get base configuration from repository
local baseConfig = ConfigRepository.getStandardConfig(templateType)
-- Apply overrides to each section
local config = {}
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
template.config = config
return config
end
-- ========== Block Framework ==========
-- Standard sequence of blocks for template rendering
p.standardBlockSequence = {
'title',
'logo',
'fields',
'socialMedia',
'semanticProperties',
'categories',
'errors'
}
-- Standard blocks available to all templates
p.standardBlocks = {
-- Title block - renders the template title
title = {
feature = 'title',
render = function(template, args)
return p.protectedExecute(
template,
'StandardBlock_title',
function()
-- Get title from config if available, otherwise use template type
local titleText = template.type
if template.config.constants and template.config.constants.title then
titleText = template.config.constants.title
end
-- Get template ID from config or use the template name as fallback
local templateId = template.type
if template.config.meta and template.config.meta.templateId then
templateId = template.config.meta.templateId
end
return TemplateHelpers.renderTitleBlock(
args,
TEMPLATE_TITLE_CLASS_PREFIX .. string.lower(templateId),
titleText,
template.config.meta and template.config.meta.titleOptions or {}
)
end,
EMPTY_STRING,
args
)
end
},
-- Logo block - renders the template logo/image
logo = {
feature = 'logo',
render = function(template, args)
return p.protectedExecute(
template,
'StandardBlock_logo',
function()
local logoClass = TEMPLATE_LOGO_CLASS_PREFIX .. string.lower(template.type)
local logoOptions = template.config.meta and template.config.meta.logoOptions or {}
-- Use logoField from config or default to 'logo'
local logoField = logoOptions.logoField or 'logo'
local logoValue = args[logoField]
if not logoValue or logoValue == '' then
return EMPTY_STRING
end
-- Create options object for renderLogoBlock
local options = {
cssClass = logoClass,
errorContext = template._errorContext
}
-- Add any additional options from logoOptions
if logoOptions then
for k, v in pairs(logoOptions) do
options[k] = v
end
end
-- Create args object with logo value
local logoArgs = {
logo = logoValue
}
return TemplateHelpers.renderLogoBlock(logoArgs, options)
end,
EMPTY_STRING,
args
)
end
},
-- Fields block - renders all configured fields
fields = {
feature = 'fields',
render = function(template, args)
return p.protectedExecute(
template,
'StandardBlock_fields',
function()
-- Get field definitions from config
local fieldDefs = template.config.fields or {}
local fields = {}
-- Get property mappings from semantics config
local propertyMappings = template.config.semantics and
template.config.semantics.properties or {}
-- Add special processor for logo field when logo feature is enabled
if not template._processors then
template._processors = p.initializeProcessors(template)
end
-- If logo feature is enabled, add a processor to skip the logo field
if template.features.logo then
template._processors.logo = function() return false end
end
-- Process field values using appropriate processors
for _, field in ipairs(fieldDefs) do
-- Skip hidden fields
if not field.hidden then
local fieldKey = field.key or (field.keys and field.keys[1] or "unknown")
local fieldValue = p.processField(template, field, args)
-- Only include fields with values
if fieldValue and fieldValue ~= '' then
table.insert(fields, {
label = field.label or field.key,
value = fieldValue,
class = field.class
})
end
end
end
-- Create a processors table for renderFieldsBlock
local processors = {}
for _, field in ipairs(fields) do
local fieldKey = field.label or field.key or (field.keys and field.keys[1] or "unknown")
processors[fieldKey] = function(value, args)
return field.value
end
end
-- Use renderFieldsBlock which generates only rows, not a complete table
-- Pass property mappings for tooltip generation
local result = TemplateHelpers.renderFieldsBlock(
args,
fieldDefs,
template._processors,
propertyMappings
)
return result
end,
'',
args
)
end
},
-- Social media block - renders social media links
socialMedia = {
feature = 'socialMedia',
render = function(template, args)
return p.protectedExecute(
template,
'StandardBlock_socialMedia',
function()
-- Use lazily loaded SocialMedia module
return getSocialMedia().render(args)
end,
'',
args
)
end
},
-- Semantic properties block - renders semantic properties
semanticProperties = {
feature = 'semanticProperties',
render = function(template, args)
return p.protectedExecute(
template,
'StandardBlock_semanticProperties',
function()
return p.generateSemanticProperties(template, args)
end,
'',
args
)
end
},
-- Categories block - renders category links
categories = {
feature = 'categories',
render = function(template, args)
return p.protectedExecute(
template,
'StandardBlock_categories',
function()
return p.generateCategories(template, args)
end,
'',
args
)
end
},
-- Errors block - renders error messages
errors = {
feature = 'errorReporting',
render = function(template, args)
return p.protectedExecute(
template,
'StandardBlock_errors',
function()
if not template._errorContext then
return ''
end
return ErrorHandling.formatOutput(template._errorContext)
end,
'',
args
)
end
}
}
-- Initialize blocks for a template
-- @param template table The template object
-- @return table The initialized blocks
function p.initializeBlocks(template)
local customBlocks = template.config.blocks or {}
local blocks = {}
local blockSequence = template.config.blockSequence or p.standardBlockSequence
for _, blockId in ipairs(blockSequence) do
blocks[blockId] = customBlocks[blockId] or p.standardBlocks[blockId]
end
template._blocks = blocks
return blocks
end
-- Get block rendering function, respecting feature toggles
-- @param template table The template object
-- @param blockId string The ID of the block to get
-- @return function|nil The rendering function or nil if disabled
function p.getBlockRenderer(template, blockId)
if not template._blocks then
template._blocks = p.initializeBlocks(template)
end
local block = template._blocks[blockId]
if not block then
return nil
end
if block.feature and not template.features[block.feature] then
return nil -- Feature is disabled
end
return block.render
end
-- Build block rendering sequence for template
-- @param template table The template object
-- @return table Array of rendering functions
function p.buildRenderingSequence(template)
local sequence = template.config.blockSequence or p.standardBlockSequence
local renderingFunctions = {}
local funcIndex = 1
for _, blockId in ipairs(sequence) do
local renderer = p.getBlockRenderer(template, blockId)
if renderer then
renderingFunctions[funcIndex] = function(args)
return renderer(template, args)
end
funcIndex = funcIndex + 1
end
end
renderingFunctions._length = funcIndex - 1
return renderingFunctions
end
-- ========== Field Processing System ==========
-- Initialize processors for a template
-- @param template table The template object
-- @return table The initialized processors
function p.initializeProcessors(template)
if template._processors then
return template._processors
end
template._processors = TemplateFieldProcessor.initializeProcessors(template)
return template._processors
end
-- Get processor for a field
-- @param template table The template object
-- @param field table The field definition
-- @return function The processor function
function p.getFieldProcessor(template, field)
if not template._processors then
template._processors = p.initializeProcessors(template)
end
return TemplateFieldProcessor.getFieldProcessor(template._processors, field)
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
-- Process a field using its processor
-- @param template table The template object
-- @param field table The field definition
-- @param args table The template arguments
-- @return string The processed field value
function p.processField(template, field, args)
if not field then
return EMPTY_STRING
end
-- Initialize processors if needed
if not template._processors then
template._processors = p.initializeProcessors(template)
end
-- Use the TemplateFieldProcessor module with error context
return p.protectedExecute(
template,
'processField',
function()
return TemplateFieldProcessor.processField(template, field, args, template._errorContext)
end,
EMPTY_STRING,
template,
field,
args
)
end
-- ========== Preprocessing Pipeline ==========
-- Standard preprocessors
p.preprocessors = {
-- Derive region from country values
deriveRegionFromCountry = function(template, args)
if (not args.region or args.region == "") and args.country then
-- Use lazily loaded CountryData module
local region = getCountryData().getRegionByCountry(args.country)
if region and region ~= "" then
args.region = region
end
end
return args
end,
-- Set the ID field to the current page ID
setPageIdField = function(template, args)
-- Get the current page ID and set it in the args table
local pageId = TemplateHelpers.getCurrentPageId()
args.ID = tostring(pageId or "")
args.id = args.ID
return args
end
}
-- Register a preprocessor with a template
-- @param template table The template object
-- @param preprocessor function|string The preprocessor function or name
-- @return table The template object (for chaining)
function p.addPreprocessor(template, preprocessor)
-- Initialize preprocessors array if not exists
template._preprocessors = template._preprocessors or {}
-- Add preprocessor to array
table.insert(template._preprocessors, preprocessor)
return template
end
-- Run all preprocessors in sequence
-- @param template table The template object
-- @param args table The template arguments
-- @return table The processed arguments
function p.runPreprocessors(template, args)
if not template._preprocessors or #template._preprocessors == 0 then
return args
end
local processedArgs = {}
for k, v in pairs(args) do
processedArgs[k] = v
end
local preprocessorCount = #template._preprocessors
for i = 1, preprocessorCount do
local preprocessor = template._preprocessors[i]
local preprocessorType = type(preprocessor)
if preprocessorType == "function" then
local result = preprocessor(template, processedArgs)
if result then
processedArgs = result
end
elseif preprocessorType == "string" then
local namedPreprocessor = p.preprocessors[preprocessor]
if namedPreprocessor then
local result = namedPreprocessor(template, processedArgs)
if result then
processedArgs = result
end
end
end
end
return processedArgs
end
-- ========== Semantic and Category Integration ==========
-- Register semantic property provider function
-- @param template table The template object
-- @param provider function The provider function
-- @return table The template object (for chaining)
function p.registerPropertyProvider(template, provider)
-- Initialize property providers array if not exists
template._propertyProviders = template._propertyProviders or {}
-- Add provider to array
table.insert(template._propertyProviders, provider)
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
-- Generate semantic properties for template
-- @param template table The template object
-- @param args table The template arguments
-- @return string The generated semantic properties HTML
function p.generateSemanticProperties(template, args)
if not template.features.semanticProperties then
return EMPTY_STRING
end
local semanticConfig = template.config.semantics or {}
local properties = semanticConfig.properties or {}
local transforms = semanticConfig.transforms or {}
local additionalProperties = semanticConfig.additionalProperties or {}
if not next(properties) and not next(additionalProperties) and
(not template._propertyProviders or #template._propertyProviders == 0) then
return EMPTY_STRING
end
-- Set options for SemanticAnnotations
local semanticOptions = {
transform = transforms
}
-- Collect all properties in a single batch for complete deduplication
local allProperties = {}
-- Add basic properties with transforms handled by SemanticAnnotations
for property, param in pairs(properties) do
-- Perform case-insensitive lookup for the parameter key
local keyName, _ = TemplateHelpers.getFieldValue(args, { key = param })
allProperties[property] = keyName or param
end
-- Process additional properties with transforms
for property, fields in pairs(additionalProperties) do
local transform = transforms[property]
for _, fieldName in ipairs(fields) do
local rawValue = args[fieldName]
if rawValue and rawValue ~= '' then
local transformed = applyTransform(rawValue, property, transform, args, template)
if transformed and transformed ~= '' then
local existing = allProperties[property]
if existing == nil then
allProperties[property] = transformed
else
if type(existing) ~= "table" then
allProperties[property] = { existing }
end
-- Add only if not already present
local found = false
for _, v in ipairs(allProperties[property]) do
if v == transformed then
found = true
break
end
end
if not found then
table.insert(allProperties[property], transformed)
end
end
end
end
end
end
-- Process property providers with deduplication
if template._propertyProviders then
for _, provider in ipairs(template._propertyProviders) do
local providerResult = p.protectedExecute(
template,
'PropertyProvider',
function() return provider(template, args) end,
{},
template,
args
)
if providerResult and next(providerResult) then
-- Merge provider properties into allProperties with deduplication
for property, value in pairs(providerResult) do
if type(value) == "table" then
-- Provider returned an array of values
if allProperties[property] then
-- Property already exists, merge arrays with deduplication
if type(allProperties[property]) ~= "table" then
-- Convert existing value to array
allProperties[property] = {allProperties[property]}
end
-- Track seen values
local seenValues = {}
for _, v in ipairs(allProperties[property]) do
seenValues[v] = true
end
-- Add unique values from provider
for _, v in ipairs(value) do
if not seenValues[v] then
seenValues[v] = true
table.insert(allProperties[property], v)
end
end
else
-- Property doesn't exist yet, add with internal deduplication
local seenValues = {}
local uniqueValues = {}
for _, v in ipairs(value) do
if not seenValues[v] then
seenValues[v] = true
table.insert(uniqueValues, v)
end
end
allProperties[property] = uniqueValues
end
else
-- Provider returned a single value
if allProperties[property] then
-- Property already exists
if type(allProperties[property]) == "table" then
-- Existing property is an array, add value if unique
local seen = false
for _, v in ipairs(allProperties[property]) do
if v == value then
seen = true
break
end
end
if not seen then
table.insert(allProperties[property], value)
end
elseif allProperties[property] ~= value then
-- Convert to array with both values
allProperties[property] = {allProperties[property], value}
end
-- If existing value equals new value, no change needed
else
-- Property doesn't exist yet, add directly
allProperties[property] = value
end
end
end
end
end
end
-- Process all collected properties in one batch
local semanticOutput = SemanticAnnotations.setSemanticProperties(
args,
allProperties,
semanticOptions
)
return semanticOutput
end
-- Register category provider function
-- @param template table The template object
-- @param provider function The provider function
-- @return table The template object (for chaining)
function p.registerCategoryProvider(template, provider)
-- Initialize category providers array if not exists
template._categoryProviders = template._categoryProviders or {}
-- Add provider to array
table.insert(template._categoryProviders, provider)
return template
end
-- Generate categories for template
-- @param template table The template object
-- @param args table The template arguments
-- @return string The generated category HTML
function p.generateCategories(template, args)
if not template.features.categories then
return EMPTY_STRING
end
local configCategories = {}
if template.config.categories and template.config.categories.base and
type(template.config.categories.base) == "table" then
configCategories = template.config.categories.base
elseif template.config.categories and type(template.config.categories) == "table" then
if #template.config.categories > 0 then
configCategories = template.config.categories
end
end
if #configCategories == 0 and
(not template._categoryProviders or #template._categoryProviders == 0) then
return EMPTY_STRING
end
-- Use a seen table for deduplication
local seen = {}
local uniqueCategories = {}
local categoryCount = 0
-- Add config categories with deduplication
for i = 1, #configCategories do
local category = configCategories[i]
if category and category ~= "" and not seen[category] then
seen[category] = true
categoryCount = categoryCount + 1
uniqueCategories[categoryCount] = category
end
end
-- Process provider categories with deduplication
if template._categoryProviders then
for _, provider in ipairs(template._categoryProviders) do
local providerCategories = p.protectedExecute(
template,
'CategoryProvider',
function() return provider(template, args) end,
{},
template,
args
)
if providerCategories then
for _, category in ipairs(providerCategories) do
if category and category ~= "" and not seen[category] then
seen[category] = true
categoryCount = categoryCount + 1
uniqueCategories[categoryCount] = category
end
end
end
end
end
-- Generate HTML for unique categories
local categoryHtml = {}
for i = 1, categoryCount do
categoryHtml[i] = CATEGORY_PREFIX .. uniqueCategories[i] .. CATEGORY_SUFFIX
end
return table.concat(categoryHtml, NEWLINE)
end
-- ========== Template Rendering ==========
-- Main rendering function for templates
-- @param template table The template object
-- @param frame Frame The MediaWiki frame object
-- @return string The rendered template HTML
function p.renderTemplate(template, frame)
if not template._errorContext then
template._errorContext = p.createErrorContext(template)
end
if not template.config.meta then
p.initializeConfig(template)
end
local args = frame:getParent().args or {}
args = TemplateHelpers.normalizeArgumentCase(args)
args = p.runPreprocessors(template, args)
local tableClass = DEFAULT_TABLE_CLASS
if template.config.constants and template.config.constants.tableClass then
tableClass = template.config.constants.tableClass
end
local structureConfig = {
tableClass = tableClass,
blocks = {}
}
local renderingSequence = p.buildRenderingSequence(template)
if renderingSequence._length == 0 then
return EMPTY_STRING
end
for i = 1, renderingSequence._length do
table.insert(structureConfig.blocks, function(a)
return renderingSequence[i](a)
end)
end
return TemplateStructure.render(args, structureConfig, template._errorContext)
end
-- Return the module
-- Initialize elements after module load
p.initializeElements()
return p