Jump to content

Module:TemplateStructure: Difference between revisions

No edit summary
Tag: Reverted
// via Wikitext Extension for VSCode
 
(28 intermediate revisions by the same user not shown)
Line 1: Line 1:
--[[
* Name: TemplateStructure
* Author: Mark W. Datysgeld
* Description: Block-based template rendering module with centralized error reporting integration
* Notes: Block rendering functions for templates; title blocks; divider blocks; field tables; configurable CSS classes and attributes; ARIA support; container tag support for div wrappers; error handling integration
]]
local ErrorHandling = require('Module:ErrorHandling')
local NormalizationText = require('Module:NormalizationText')
local p = {}
local p = {}
-- Local trim function: removes leading and trailing whitespace.
-- Now delegates to NormalizationText
local function trim(s)
    return NormalizationText.trim(s)
end


--[[
--[[
     p.render(args, config)
     p.render(args, config, errorContext)
   
 
    Renders a table using a modular, block-based approach via the HTML builder API.
    Instead of returning wiki table markup (which is later reinterpreted and escaped),
    this version builds and returns an mw.html object so that the output is treated as raw HTML.
   
     Parameters:
     Parameters:
       args   - Table of template parameters (passed to each block function).
       args         - Template parameters (passed to each block function)
       config - Table containing configuration options:
       config       - Configuration options:
           tableClass: (string) CSS class for the table.
           tableClass: CSS class for the table (default: "template-table")
                      Default: "template-table"
           tableAttrs: Additional table attributes (default: 'cellpadding="2"')
           tableAttrs: (table) A table of additional attributes for the table tag.
           blocks:    Functions that generate table rows
                      Default: { cellpadding = "2" }
                       Each accepts (args, config) and returns a string
           blocks:    (array) An ordered list of functions (blocks) that generate table rows.
          continueOnError: Continue rendering if a block fails (default: true)
                       Each block function should accept (args, config) and return an HTML fragment.
      errorContext - Optional error context from ErrorHandling.createContext()
                    If provided, errors will be reported to this context
      
      
     Returns:
     Returns:
       An mw.html object representing the complete table.
       Wikitext markup for the complete table
   
    NOTE:
      This approach expects that the block functions produce HTML (or HTML fragments) rather than
      wiki markup. For example, instead of returning "{| ..." rows and "|}" markers, block functions
      should generate <tr>, <td>, or <th> elements. This change moves layout control into Lua so that
      MediaWiki’s parser won’t rewrap or escape the output.
]]
]]
function p.render(args, config)
function p.render(args, config, errorContext)
     config = config or {}
     config = config or {}
    -- ARIA support
    local ariaLabelledBy = config.ariaLabelledBy and string.format('aria-labelledby="%s"', config.ariaLabelledBy) or ''
    -- Support fullPage mode: render blocks inside a <div> wrapper when requested
    if config.containerTag == 'div' then
        local out = {}
        local tag = config.containerTag
        -- open container without default template-table class when empty
        local cls = config.tableClass or ''
        local attr = {ariaLabelledBy}
        if cls ~= '' then
            table.insert(attr, 1, string.format('class="%s"', cls))
        end
       
        table.insert(out, string.format('<%s %s>', tag, table.concat(attr, ' ')))
        for i, block in ipairs(config.blocks or {}) do
            local ok, blk = pcall(block, args, config)
            if ok and blk and blk ~= "" then
                table.insert(out, trim(blk))
            elseif not ok and errorContext then
                ErrorHandling.addError(
                    errorContext,
                    "TemplateStructure",
                    "Block #" .. i .. " execution failed",
                    tostring(blk),
                    false
                )
            end
        end
        -- close container
        table.insert(out, string.format('</%s>', tag))
        return table.concat(out, "\n")
    end
     local tableClass = config.tableClass or "template-table"
     local tableClass = config.tableClass or "template-table"
     local tableAttrs = config.tableAttrs or { cellpadding = "2" }
     local tableAttrs = config.tableAttrs or 'cellpadding="2"'
     local blocks = config.blocks or {}
     local blocks = config.blocks or {}
    local continueOnError = config.continueOnError
    if continueOnError == nil then continueOnError = true end -- Default to true


     -- Create a table element using the HTML builder API.
     -- Begin the table markup
     local tableEl = mw.html.create("table", tableAttrs)
     local result = {}
    tableEl:attr("class", tableClass)
    table.insert(result, string.format('{| class="%s" %s %s', tableClass, tableAttrs, ariaLabelledBy))
      
      
     -- Iterate over each block function, and insert its output as wikitext (which here is assumed to be safe HTML).
     -- Process each block function in the supplied order
     for i, block in ipairs(blocks) do
     for i, block in ipairs(blocks) do
         if type(block) == "function" then
         if type(block) == "function" then
             local blockOutput = block(args, config)
            -- Protect block execution to avoid template failure
             if blockOutput and blockOutput ~= "" then
             local success, blockOutput = pcall(function()
                 tableEl:wikitext(blockOutput)
                return block(args, config)  
            end)
           
             if success then
                blockOutput = trim(blockOutput)
                if blockOutput ~= "" then
                    table.insert(result, blockOutput)
                end
            else
                -- Get error message
                 local errorMsg = tostring(blockOutput)
               
                -- Report to error context if available
                if errorContext then
                    ErrorHandling.addError(
                        errorContext,
                        "TemplateStructure",
                        "Block #" .. i .. " execution failed",
                        errorMsg,
                        false
                    )
                end
               
                -- If we shouldn't continue on error, break the loop
                if not continueOnError then
                    break
                end
             end
             end
         else
         else
             mw.log("Warning: Block #" .. i .. " is not a function and will be skipped.")
             -- Report non-function blocks to error context if available
            if errorContext then
                ErrorHandling.addError(
                    errorContext,
                    "TemplateStructure",
                    "Block #" .. i .. " is not a function",
                    "Expected function, got " .. type(block),
                    false
                )
            end
           
         end
         end
     end
     end
   
    -- Close the table
    table.insert(result, "|}")
    local finalOutput = table.concat(result, "\n")
    return trim(finalOutput)
end
-- Renders a standard title block with configurable class and text (basic version)
function p.renderTitleBlock(args, titleClass, titleText, titleId)
    titleClass = titleClass or "template-title"
    -- ARIA title
    local idAttr = titleId and string.format('id="%s"', titleId) or ''
    local titleSpan = string.format('<span %s>%s</span>', idAttr, titleText)
    return string.format('|-\n! colspan="2" class="%s" | %s', titleClass, titleSpan)
end
-- Renders a standard divider block with optional label
function p.renderDividerBlock(label)
    if label and label ~= "" then
        return string.format('|-\n| colspan="2" class="template-divider" |\n|-\n| colspan="2" class="icannwiki-centered" | <span class="icannwiki-bold">%s</span>', label)
    else
        return '|-\n| colspan="2" class="template-divider" |'
    end
end


     -- Return the mw.html object so that its output is not re-escaped.
--[[
     return tableEl
    Renders a table of fields with labels and values using TemplateStructure.
   
    Parameters:
      fields      - Array of field objects, each with:
                    - label: The field label to display
                    - value: The field value to display
                    - class: Optional CSS class for the row
      options      - Optional configuration:
                    - tableClass: CSS class for the table (default: "template-field-table")
                    - tableAttrs: Additional table attributes
                    - fieldFormat: Format string for field rows (default: uses FIELD_FORMAT)
                    - errorContext: Optional error context for error handling
   
    Returns:
      Wikitext markup for the field table
]]
function p.renderFieldTable(fields, options)
    -- Early return for empty fields
    if not fields or #fields == 0 then
        return ""
    end
   
    options = options or {}
   
    -- Define the field table rendering operation
     local function renderFieldTableOperation(fields, options)
        -- Create a config for the render function with optimized defaults
        local config = {
            tableClass = options.tableClass or "template-field-table",
            tableAttrs = options.tableAttrs or 'cellpadding="2"',
            blocks = {}
        }
       
        -- Pre-allocate blocks array based on field count
        local blocks = {}
       
        -- Get the field format from options or use a default
        local fieldFormat = options.fieldFormat or '|- class="template-data-row"\n| class="template-label-cell" | <span class="template-label-style">%s</span>\n| class="template-value-cell" | %s'
       
        -- Create a block function for each field with direct index assignment
        for i = 1, #fields do
            local field = fields[i]
            blocks[i] = function()
                -- Combine the field's class with the template-data-row class
                local fieldClass = field.class and field.class or ""
               
                -- Create the row with proper classes
                local row = '|- class="template-data-row' .. (fieldClass ~= "" and ' ' .. fieldClass or '') .. '"'
               
                -- Format the cells using the field format but replace the row start
                local cellsFormat = fieldFormat:gsub("^[^|]+", "")
               
                -- Return the complete row
                return row .. string.format(cellsFormat, field.label, field.value)
            end
        end
       
        -- Assign blocks to config
        config.blocks = blocks
       
        -- Use TemplateStructure's render function
        return p.render({}, config, options.errorContext)
    end
   
    -- Use error handling if provided
    if options.errorContext then
        return ErrorHandling.protect(
            options.errorContext,
            "renderFieldTable",
            renderFieldTableOperation,
            "", -- Empty string fallback
            fields, options
        )
     else
        -- Direct execution without error handling
        return renderFieldTableOperation(fields, options)
    end
end
end


return p
return p

Latest revision as of 03:13, 25 August 2025

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

--[[
* Name: TemplateStructure
* Author: Mark W. Datysgeld
* Description: Block-based template rendering module with centralized error reporting integration
* Notes: Block rendering functions for templates; title blocks; divider blocks; field tables; configurable CSS classes and attributes; ARIA support; container tag support for div wrappers; error handling integration
]]

local ErrorHandling = require('Module:ErrorHandling')
local NormalizationText = require('Module:NormalizationText')
local p = {}

-- Local trim function: removes leading and trailing whitespace.
-- Now delegates to NormalizationText
local function trim(s)
    return NormalizationText.trim(s)
end

--[[
    p.render(args, config, errorContext)

    Parameters:
      args         - Template parameters (passed to each block function)
      config       - Configuration options:
          tableClass: CSS class for the table (default: "template-table")
          tableAttrs: Additional table attributes (default: 'cellpadding="2"')
          blocks:     Functions that generate table rows
                      Each accepts (args, config) and returns a string
          continueOnError: Continue rendering if a block fails (default: true)
      errorContext - Optional error context from ErrorHandling.createContext()
                     If provided, errors will be reported to this context
    
    Returns:
      Wikitext markup for the complete table
]]
function p.render(args, config, errorContext)
    config = config or {}
    -- ARIA support
    local ariaLabelledBy = config.ariaLabelledBy and string.format('aria-labelledby="%s"', config.ariaLabelledBy) or ''

    -- Support fullPage mode: render blocks inside a <div> wrapper when requested
    if config.containerTag == 'div' then
        local out = {}
        local tag = config.containerTag
        -- open container without default template-table class when empty
        local cls = config.tableClass or ''
        local attr = {ariaLabelledBy}
        if cls ~= '' then
            table.insert(attr, 1, string.format('class="%s"', cls))
        end
        
        table.insert(out, string.format('<%s %s>', tag, table.concat(attr, ' ')))
        for i, block in ipairs(config.blocks or {}) do
            local ok, blk = pcall(block, args, config)
            if ok and blk and blk ~= "" then
                table.insert(out, trim(blk))
            elseif not ok and errorContext then
                ErrorHandling.addError(
                    errorContext,
                    "TemplateStructure",
                    "Block #" .. i .. " execution failed",
                    tostring(blk),
                    false
                )
            end
        end
        -- close container
        table.insert(out, string.format('</%s>', tag))
        return table.concat(out, "\n")
    end
    local tableClass = config.tableClass or "template-table"
    local tableAttrs = config.tableAttrs or 'cellpadding="2"'
    local blocks = config.blocks or {}
    local continueOnError = config.continueOnError
    if continueOnError == nil then continueOnError = true end -- Default to true

    -- Begin the table markup
    local result = {}
    table.insert(result, string.format('{| class="%s" %s %s', tableClass, tableAttrs, ariaLabelledBy))
    
    -- Process each block function in the supplied order
    for i, block in ipairs(blocks) do
        if type(block) == "function" then
            -- Protect block execution to avoid template failure
            local success, blockOutput = pcall(function() 
                return block(args, config) 
            end)
            
            if success then
                blockOutput = trim(blockOutput)
                if blockOutput ~= "" then
                    table.insert(result, blockOutput)
                end
            else
                -- Get error message
                local errorMsg = tostring(blockOutput)
                
                -- Report to error context if available
                if errorContext then
                    ErrorHandling.addError(
                        errorContext,
                        "TemplateStructure",
                        "Block #" .. i .. " execution failed",
                        errorMsg,
                        false
                    )
                end
                
                -- If we shouldn't continue on error, break the loop
                if not continueOnError then
                    break
                end
            end
        else
            -- Report non-function blocks to error context if available
            if errorContext then
                ErrorHandling.addError(
                    errorContext,
                    "TemplateStructure",
                    "Block #" .. i .. " is not a function",
                    "Expected function, got " .. type(block),
                    false
                )
            end
            
        end
    end
    
    -- Close the table
    table.insert(result, "|}")
    local finalOutput = table.concat(result, "\n")
    return trim(finalOutput)
end

-- Renders a standard title block with configurable class and text (basic version)
function p.renderTitleBlock(args, titleClass, titleText, titleId)
    titleClass = titleClass or "template-title"
    -- ARIA title
    local idAttr = titleId and string.format('id="%s"', titleId) or ''
    local titleSpan = string.format('<span %s>%s</span>', idAttr, titleText)
    return string.format('|-\n! colspan="2" class="%s" | %s', titleClass, titleSpan)
end

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

--[[
    Renders a table of fields with labels and values using TemplateStructure.
    
    Parameters:
      fields       - Array of field objects, each with:
                     - label: The field label to display
                     - value: The field value to display
                     - class: Optional CSS class for the row
      options      - Optional configuration:
                     - tableClass: CSS class for the table (default: "template-field-table")
                     - tableAttrs: Additional table attributes
                     - fieldFormat: Format string for field rows (default: uses FIELD_FORMAT)
                     - errorContext: Optional error context for error handling
    
    Returns:
      Wikitext markup for the field table
]]
function p.renderFieldTable(fields, options)
    -- Early return for empty fields
    if not fields or #fields == 0 then
        return ""
    end
    
    options = options or {}
    
    -- Define the field table rendering operation
    local function renderFieldTableOperation(fields, options)
        -- Create a config for the render function with optimized defaults
        local config = {
            tableClass = options.tableClass or "template-field-table",
            tableAttrs = options.tableAttrs or 'cellpadding="2"',
            blocks = {}
        }
        
        -- Pre-allocate blocks array based on field count
        local blocks = {}
        
        -- Get the field format from options or use a default
        local fieldFormat = options.fieldFormat or '|- class="template-data-row"\n| class="template-label-cell" | <span class="template-label-style">%s</span>\n| class="template-value-cell" | %s'
        
        -- Create a block function for each field with direct index assignment
        for i = 1, #fields do
            local field = fields[i]
            blocks[i] = function()
                -- Combine the field's class with the template-data-row class
                local fieldClass = field.class and field.class or ""
                
                -- Create the row with proper classes
                local row = '|- class="template-data-row' .. (fieldClass ~= "" and ' ' .. fieldClass or '') .. '"'
                
                -- Format the cells using the field format but replace the row start
                local cellsFormat = fieldFormat:gsub("^[^|]+", "")
                
                -- Return the complete row
                return row .. string.format(cellsFormat, field.label, field.value)
            end
        end
        
        -- Assign blocks to config
        config.blocks = blocks
        
        -- Use TemplateStructure's render function
        return p.render({}, config, options.errorContext)
    end
    
    -- Use error handling if provided
    if options.errorContext then
        return ErrorHandling.protect(
            options.errorContext,
            "renderFieldTable",
            renderFieldTableOperation,
            "", -- Empty string fallback
            fields, options
        )
    else
        -- Direct execution without error handling
        return renderFieldTableOperation(fields, options)
    end
end

return p