Module:WikitextProcessor: Difference between revisions

// via Wikitext Extension for VSCode
 
// via Wikitext Extension for VSCode
 
(28 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Module:WikitextProcessor
--[[
-- Processes JSON content with wikitext formatting for frontend display
* Name: WikitextProcessor
-- Handles placeholder replacement, wiki link conversion to HTML, and content normalization
* Author: Mark W. Datysgeld
-- Extracted from T-Campaign.lua for reusability across templates
* Description: Generalized content processor for wikitext formatting and frontend display, regardless of source (JSON, XML, database, user input, etc.)
* Notes: Error handling; three wiki link patterns for page links, page links with custom text, and anchor links; placeholder replacement with $VARIABLE$ syntax; content normalization and whitespace cleanup; JavaScript escape hatch factory for NoticeHandler.js gadget integration to work around Scribunto/Lua environment-specific bugs
]]


local p = {}
local p = {}
Line 9: Line 11:
local ErrorHandling = require('Module:ErrorHandling')
local ErrorHandling = require('Module:ErrorHandling')


-- Module-level cache for processed content
-- Constants for performance
local contentCache = {}
local CONTEXT_NAME = 'wikitextProcessor'
local ERROR_MESSAGES = {
    urlFailed = 'URL generation failed for wiki link',
    encodeFailed = 'Text encoding failed for wiki link',
    formatFailed = 'HTML formatting failed for wiki link'
}
 
-- Clean error handling
local function handleError(errorContext, operation, fallbackValue)
    if errorContext then
        ErrorHandling.addStatus(errorContext, CONTEXT_NAME, ERROR_MESSAGES[operation] or ERROR_MESSAGES.urlFailed, nil)
    end
    return fallbackValue
end


-- Constants as upvalues for performance
-- Constants as upvalues for performance
Line 19: Line 34:
         processor = function(pageName, errorContext)
         processor = function(pageName, errorContext)
             local spacesReplaced = (pageName:gsub(' ', '_'))
             local spacesReplaced = (pageName:gsub(' ', '_'))
             local success1, pageUrl = pcall(function()
             local success, pageUrl = pcall(function()
                 return tostring(mw.uri.fullUrl(spacesReplaced))
                 return tostring(mw.uri.fullUrl(spacesReplaced))
             end)
             end)
           
             if not success then
             if not success1 then
                 return handleError(errorContext, 'urlFailed', '[[' .. pageName .. ']]')
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'mw.uri.fullUrl failed for page link', 'Page: ' .. pageName .. ', Error: ' .. tostring(pageUrl))
                end
                return '[[' .. pageName .. ']]'
             end
             end
              
              
             local success2, encodedName = pcall(function()
             local pageNameStr = type(pageName) == "string" and pageName or tostring(pageName)
                return mw.text.encode(pageName)
            success, pageNameStr = pcall(mw.text.encode, pageNameStr)
            end)
             if not success then
           
                 return handleError(errorContext, 'encodeFailed', '[[' .. pageName .. ']]')
             if not success2 then
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'mw.text.encode failed for page link', 'Page: ' .. pageName .. ', Error: ' .. tostring(encodedName))
                end
                return '[[' .. pageName .. ']]'
             end
             end
              
              
             local success3, result = pcall(function()
             success, pageUrl = pcall(string.format, '<a href="%s">%s</a>', pageUrl, pageNameStr)
                return string.format('<a href="%s">%s</a>', pageUrl, encodedName)
             if not success then
            end)
                 return handleError(errorContext, 'formatFailed', '[[' .. pageName .. ']]')
           
             if not success3 then
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'string.format failed for page link', 'Page: ' .. pageName .. ', Error: ' .. tostring(result))
                end
                return '[[' .. pageName .. ']]'
             end
             end
              
              
             return result
             return pageUrl
         end
         end
     },
     },
Line 60: Line 60:
         pattern = '%[%[([^#|%]]+)|([^%]]+)%]%]',
         pattern = '%[%[([^#|%]]+)|([^%]]+)%]%]',
         processor = function(pageName, text, errorContext)
         processor = function(pageName, text, errorContext)
             local success1, pageUrl = pcall(function()
             local success, pageUrl = pcall(function()
                 return tostring(mw.uri.fullUrl((pageName:gsub(' ', '_'))))
                 return tostring(mw.uri.fullUrl((pageName:gsub(' ', '_'))))
             end)
             end)
           
             if not success then
             if not success1 then
                 return handleError(errorContext, 'urlFailed', '[[' .. pageName .. '|' .. text .. ']]')
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'Pattern 2 mw.uri.fullUrl failed', 'Error: ' .. tostring(pageUrl))
                end
                return '[[' .. pageName .. '|' .. text .. ']]'
             end
             end
              
              
             local success2, encodedText = pcall(function()
             local textStr = type(text) == "string" and text or tostring(text)
                return mw.text.encode(text)
            success, textStr = pcall(mw.text.encode, textStr)
            end)
             if not success then
           
                 return handleError(errorContext, 'encodeFailed', '[[' .. pageName .. '|' .. text .. ']]')
             if not success2 then
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'Pattern 2 mw.text.encode failed', 'Error: ' .. tostring(encodedText))
                end
                return '[[' .. pageName .. '|' .. text .. ']]'
             end
             end
              
              
             local success3, result = pcall(function()
             success, pageUrl = pcall(string.format, '<a href="%s">%s</a>', pageUrl, textStr)
                return string.format('<a href="%s">%s</a>', pageUrl, encodedText)
             if not success then
            end)
                 return handleError(errorContext, 'formatFailed', '[[' .. pageName .. '|' .. text .. ']]')
           
             if not success3 then
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'Pattern 2 string.format failed', 'Error: ' .. tostring(result))
                end
                return '[[' .. pageName .. '|' .. text .. ']]'
             end
             end
              
              
             return result
             return pageUrl
         end
         end
     },
     },
Line 101: Line 86:
         pattern = '%[%[#([^|%]]+)|([^%]]+)%]%]',
         pattern = '%[%[#([^|%]]+)|([^%]]+)%]%]',
         processor = function(anchor, text, errorContext)
         processor = function(anchor, text, errorContext)
             local success1, encodedAnchor = pcall(function()
             local success, encodedAnchor = pcall(mw.uri.anchorEncode, anchor)
                return mw.uri.anchorEncode(anchor)
             if not success then
            end)
                 return handleError(errorContext, 'urlFailed', '[[#' .. anchor .. '|' .. text .. ']]')
           
             if not success1 then
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'Pattern 3 mw.uri.anchorEncode failed', 'Error: ' .. tostring(encodedAnchor))
                end
                return '[[#' .. anchor .. '|' .. text .. ']]'
             end
             end
              
              
             local success2, encodedText = pcall(function()
             local textStr = type(text) == "string" and text or tostring(text)
                return mw.text.encode(text)
            success, textStr = pcall(mw.text.encode, textStr)
            end)
             if not success then
           
                 return handleError(errorContext, 'encodeFailed', '[[#' .. anchor .. '|' .. text .. ']]')
             if not success2 then
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'Pattern 3 mw.text.encode failed', 'Error: ' .. tostring(encodedText))
                end
                return '[[#' .. anchor .. '|' .. text .. ']]'
             end
             end
              
              
             local success3, result = pcall(function()
             success, encodedAnchor = pcall(string.format, '<a href="#%s">%s</a>', encodedAnchor, textStr)
                return string.format('<a href="#%s">%s</a>', encodedAnchor, encodedText)
             if not success then
            end)
                 return handleError(errorContext, 'formatFailed', '[[#' .. anchor .. '|' .. text .. ']]')
           
             if not success3 then
                 if errorContext then
                    ErrorHandling.addStatus(errorContext, 'wikiLinkProcessor', 'Pattern 3 string.format failed', 'Error: ' .. tostring(result))
                end
                return '[[#' .. anchor .. '|' .. text .. ']]'
             end
             end
              
              
             return result
             return encodedAnchor
         end
         end
     }
     }
}
}
-- Use TemplateHelpers caching functions for consistency
local TemplateHelpers = require('Module:TemplateHelpers')


-- Normalizes content string by cleaning up whitespace
-- Normalizes content string by cleaning up whitespace
Line 148: Line 113:
     end
     end
      
      
     local cacheKey = TemplateHelpers.generateCacheKey("normalizeContentString", content)
     -- Apply string normalization exactly like the original T-Campaign code
   
    return content:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "")
    return TemplateHelpers.withCache(cacheKey, function()
        -- Apply string normalization - consolidate whitespace operations
        -- Handle multiple return values from gsub properly
        local result = (content:gsub("%s+", " "))
        result = (result:gsub("^%s+", ""))
        result = (result:gsub("%s+$", ""))
        return result
    end)
end
end


Line 166: Line 123:
     end
     end
      
      
     local cacheKey = TemplateHelpers.generateCacheKey("replacePlaceholders", content, tostring(placeholderMap))
     local result = content
      
      
     return TemplateHelpers.withCache(cacheKey, function()
     -- Apply placeholder replacement exactly like the original T-Campaign code
        local result = content
    for key, value in pairs(placeholderMap) do
       
        if value and value ~= "" then
        -- Apply placeholder replacement - handle multiple return values
            result = result:gsub("%$" .. key .. "%$", value)
        for key, value in pairs(placeholderMap) do
            if value and value ~= "" then
                result = (result:gsub("%$" .. key .. "%$", value))
            end
         end
         end
       
    end
        -- Clean up any remaining unfilled placeholders
   
        result = (result:gsub("%$[A-Z_]+%$", ""))
    -- Clean up any remaining unfilled placeholders (TemplateStarter's removeEmptyPlaceholders logic)
       
    result = result:gsub("%$[A-Z_]+%$", "")
        -- Final normalization after placeholder replacement
    result = result:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "")
        result = (result:gsub("%s+", " "))
   
        result = (result:gsub("^%s+", ""))
    return result
        result = (result:gsub("%s+$", ""))
        return result
    end)
end
end


Line 195: Line 145:
     end
     end
      
      
     local cacheKey = TemplateHelpers.generateCacheKey("processWikiLinksToHTML", content)
    -- UNIFIED PIPELINE: Process ALL wiki links with custom patterns (no frame:preprocess)
    local originalContent = content
     local result = content
   
    -- Process each wiki link pattern in sequence exactly like the original T-Campaign code
    for _, patternInfo in ipairs(WIKI_LINK_PATTERNS) do
        result = result:gsub(patternInfo.pattern, function(...)
            return patternInfo.processor(..., errorContext)
        end)
    end
      
      
     return TemplateHelpers.withCache(cacheKey, function()
     return result
        local result = content
       
        -- Process each wiki link pattern in sequence
        for _, patternInfo in ipairs(WIKI_LINK_PATTERNS) do
            -- Handle multiple return values from gsub properly
            result = (result:gsub(patternInfo.pattern, function(...)
                return patternInfo.processor(..., errorContext)
            end))
        end
       
        return result
    end)
end
end


Line 222: Line 169:
     end
     end
      
      
     -- Create cache key including all parameters
     -- Step 1: Normalize content string
     local placeholdersKey = placeholders and tostring(placeholders) or "nil"
     local processedContent = p.normalizeContentString(content)
     local cacheKey = TemplateHelpers.generateCacheKey("processContentForFrontend", content, placeholdersKey)
   
    -- Step 2: Replace placeholders if provided
    if placeholders then
        processedContent = p.replacePlaceholders(processedContent, placeholders)
     end
   
    -- Step 3: Process wiki links to HTML
    processedContent = p.processWikiLinksToHTML(processedContent, errorContext)
      
      
     return TemplateHelpers.withCache(cacheKey, function()
     return processedContent
         -- Step 1: Normalize content string
end
         local processedContent = p.normalizeContentString(content)
 
          
--[[
        -- Step 2: Replace placeholders if provided
    The "JavaScript Escape Hatch" Factory
         if placeholders then
 
            processedContent = p.replacePlaceholders(processedContent, placeholders)
    This function creates a data-only div intended to be processed by the
         end
    NoticeHandler.js gadget. It serves as a workaround for a complex,
          
    environment-specific bug in Scribunto/Lua where string values would
         -- Step 3: Process wiki links to HTML
    mysteriously disappear when passed through certain table operations.
         processedContent = p.processWikiLinksToHTML(processedContent, errorContext)
 
       
    How it works:
         return processedContent
    1.  Problem: Direct string processing and placeholder replacement in Lua
        was failing unpredictably. Byte-level analysis confirmed the string
        data was valid, but it would be lost during processing.
    2.  Solution: Instead of processing the content in Lua, we "escape" from
        the Lua environment. This function packages the raw, unprocessed
        content and any necessary parameters (like a title) into data-*
         attributes on an HTML element.
    3.  Handoff: This HTML element is then passed to the client-side, where
        the NoticeHandler.js gadget picks it up.
    4.  Execution: The JavaScript, running in the user's browser, reads the
        data attributes, performs the string replacements and wikitext
         processing, and injects the final HTML into the page.
 
    Architectural Note:
    This function is deliberately self-contained and does NOT call any other
    functions within this module (like processContentForFrontend). This is
    critical to prevent circular dependencies, as this function may be called
    by modules that are themselves dependencies of this one. It is a pure
         utility for generating the required HTML structure.
--]]
function p.createNoticeForJS(options)
    options = options or {}
    local noticeData = {
         type = options.type or "notice",
        position = options.position or "top",
         content = options.content or "",
         title = options.title or "",
         cssClass = options.cssClass or "notice-box"
    }
 
    local success, result = pcall(function()
         return string.format(
            '<div style="display:none" class="notice-data" data-notice-type="%s" data-notice-position="%s" data-banner-template="%s" data-banner-title="%s" data-notice-css="%s"></div>',
            mw.text.encode(noticeData.type),
            mw.text.encode(noticeData.position),
            mw.text.encode(noticeData.content),
            mw.text.encode(noticeData.title),
            mw.text.encode(noticeData.cssClass)
         )
     end)
     end)
    if success then
        return result
    else
        -- In case of error, return a simple error message.
        return '<span class="error">Error creating notice.</span>'
    end
end
end


return p
return p