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: | ||
-- | --[[ | ||
* Name: WikitextProcessor | |||
* Author: Mark W. Datysgeld | |||
* 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') | ||
-- | -- Constants for performance | ||
local | 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 | 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 | return handleError(errorContext, 'urlFailed', '[[' .. pageName .. ']]') | ||
end | end | ||
local | local pageNameStr = type(pageName) == "string" and pageName or tostring(pageName) | ||
success, pageNameStr = pcall(mw.text.encode, pageNameStr) | |||
if not success then | |||
return handleError(errorContext, 'encodeFailed', '[[' .. pageName .. ']]') | |||
if not | |||
end | end | ||
success, pageUrl = pcall(string.format, '<a href="%s">%s</a>', pageUrl, pageNameStr) | |||
if not success then | |||
return handleError(errorContext, 'formatFailed', '[[' .. pageName .. ']]') | |||
if not | |||
end | end | ||
return | return pageUrl | ||
end | end | ||
}, | }, | ||
| Line 60: | Line 60: | ||
pattern = '%[%[([^#|%]]+)|([^%]]+)%]%]', | pattern = '%[%[([^#|%]]+)|([^%]]+)%]%]', | ||
processor = function(pageName, text, errorContext) | processor = function(pageName, text, errorContext) | ||
local | 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 | return handleError(errorContext, 'urlFailed', '[[' .. pageName .. '|' .. text .. ']]') | ||
end | end | ||
local | local textStr = type(text) == "string" and text or tostring(text) | ||
success, textStr = pcall(mw.text.encode, textStr) | |||
if not success then | |||
return handleError(errorContext, 'encodeFailed', '[[' .. pageName .. '|' .. text .. ']]') | |||
if not | |||
end | end | ||
success, pageUrl = pcall(string.format, '<a href="%s">%s</a>', pageUrl, textStr) | |||
if not success then | |||
return handleError(errorContext, 'formatFailed', '[[' .. pageName .. '|' .. text .. ']]') | |||
if not | |||
end | end | ||
return | return pageUrl | ||
end | end | ||
}, | }, | ||
| Line 101: | Line 86: | ||
pattern = '%[%[#([^|%]]+)|([^%]]+)%]%]', | pattern = '%[%[#([^|%]]+)|([^%]]+)%]%]', | ||
processor = function(anchor, text, errorContext) | processor = function(anchor, text, errorContext) | ||
local | local success, encodedAnchor = pcall(mw.uri.anchorEncode, anchor) | ||
if not success then | |||
return handleError(errorContext, 'urlFailed', '[[#' .. anchor .. '|' .. text .. ']]') | |||
if not | |||
end | end | ||
local | local textStr = type(text) == "string" and text or tostring(text) | ||
success, textStr = pcall(mw.text.encode, textStr) | |||
if not success then | |||
return handleError(errorContext, 'encodeFailed', '[[#' .. anchor .. '|' .. text .. ']]') | |||
if not | |||
end | end | ||
success, encodedAnchor = pcall(string.format, '<a href="#%s">%s</a>', encodedAnchor, textStr) | |||
if not success then | |||
return handleError(errorContext, 'formatFailed', '[[#' .. anchor .. '|' .. text .. ']]') | |||
if not | |||
end | end | ||
return | return encodedAnchor | ||
end | end | ||
} | } | ||
} | } | ||
-- Normalizes content string by cleaning up whitespace | -- Normalizes content string by cleaning up whitespace | ||
| Line 148: | Line 113: | ||
end | end | ||
-- Apply string normalization exactly like the original T-Campaign code | |||
return content:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "") | |||
end | end | ||
| Line 166: | Line 123: | ||
end | end | ||
local | local result = content | ||
-- Apply placeholder replacement exactly like the original T-Campaign code | |||
for key, value in pairs(placeholderMap) do | |||
if value and value ~= "" then | |||
result = result:gsub("%$" .. key .. "%$", value) | |||
end | end | ||
end | |||
-- Clean up any remaining unfilled placeholders (TemplateStarter's removeEmptyPlaceholders logic) | |||
result = result:gsub("%$[A-Z_]+%$", "") | |||
result = result:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "") | |||
return result | |||
end | end | ||
| Line 195: | Line 145: | ||
end | end | ||
local | -- 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 | return result | ||
end | end | ||
| Line 222: | Line 169: | ||
end | end | ||
-- | -- Step 1: Normalize content string | ||
local | local processedContent = p.normalizeContentString(content) | ||
-- 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 | return processedContent | ||
- | end | ||
--[[ | |||
The "JavaScript Escape Hatch" Factory | |||
This function creates a data-only div intended to be processed by the | |||
NoticeHandler.js gadget. It serves as a workaround for a complex, | |||
environment-specific bug in Scribunto/Lua where string values would | |||
- | mysteriously disappear when passed through certain table operations. | ||
How it works: | |||
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 | ||