Module:Format
Appearance
| This module is rated as alpha. It is ready for limited use and third-party feedback. It may be used on a small number of pages, but should be monitored closely. Suggestions for new features or adjustments to input and output are welcome. |
| This module depends on the following other modules: |
| Description | This module provides a powerful formatting engine for template links, module links, wikilinks, external links, etc. |
|---|---|
| Status | Alpha |
| Updated | June 30, 2026 (27 days ago) |
| Dependencies | Module:Yesno |
This module provides a powerful formatting engine for template links, module links, wikilinks, external links, etc.
Documentation
Package items
format.templateLink( args )(function)- Generates a template link code
- Parameter:
argsArguments (table) - Returns: Wikitext for the template link (string)
- Usage:
{{#invoke:Format|templateLink|template or parser function name'|...subsequent template args...}} format.wikilink( args )(function)- Generates a wikilink code
- Parameter:
argsArguments (table) - Returns: Wikitext for the wikilink (string)
- Usage:
{{#invoke:Format|wikilink|page name|link display|...other link arguments...}} format.exlink( args )(function)- Generates an external link code
- Parameter:
argsArguments (table) - Returns: Wikitext for the external link (string)
- Usage:
{{#invoke:Format|exlink|URL|display}} format.tag( args )(function)- Generates a tag code
- Parameter:
argsArguments (table) - Returns: Wikitext for tag link (string)
- Usage:
{{#invoke:Format|tag|tagName|tagContent|...tag arguments...}}
Other items
(table)- Metatable in case linker is not explicitly defined.
See also
Module:Template link with magic
Documentation above is auto generated
--- This module provides a powerful formatting engine for
-- template links, module links, wikilinks, external links, etc.
--
-- @module format
-- @alias p
-- @release alpha
-- @require Module:Yesno
-- @see [[Module:Template link with magic]]
local p = {}
local yn = require( "Module:Yesno" )
local magic = mw.loadData( "Module:Format/magiclinks" )
--- Global flags
local link = true
local wrapperTags = {}
function wrapperTags:wrap( text )
local open = ''
local close = ''
for _,v in ipairs( self ) do
open = open .. '<' .. v .. '>'
close = '</' .. mw.text.split( v, ' ', true )[ 1 ] .. '>'
end
return open .. text .. close
end
local Linker = { linkedParamNumber = nil, namespace = 0, numberedParamOffset = math.huge, namedargs = false, forcens = false }
Linker.__index = Linker
function Linker:construct( linkedParamNumber, namespace, numberedParamOffset, namedargs, forcens )
local o = setmetatable({}, Linker)
o.linkedParamNumber = linkedParamNumber
o.namespace = namespace or self.namespace
o.numberedParamOffset = numberedParamOffset or self.numberedParamOffset
o.namedargs = namedargs or self.namedargs
o.forcens = forcens or self.forcens
return o
end
function Linker:run( args )
local title = mw.title.new( ( self.forcens and mw.site.namespaces[ self.namespace ].name .. ':' or '')
.. args[ self.linkedParamNumber ], mw.site.namespaces[ self.namespace ].id )
local outTable = {}
local currArg = 1
while args[currArg] ~= nil do
if ( currArg == self.linkedParamNumber and link ) then
outTable[currArg] = '[[:' .. title.fullText .. '|' .. args[currArg] .. ']]'
else
outTable[currArg] = args[currArg]
end
currArg = currArg + 1
end
if self.namedargs then
local maxArg = currArg
for k,v in pairs(args) do
if tonumber(k) and (( tonumber(k) >= 1 + self.numberedParamOffset and tonumber(k) < maxArg ) or ( tonumber(k) <= self.linkedParamNumber and tonumber(k) >= 1)) then
else
if tonumber(k) and tonumber(k) >= self.numberedParamOffset then
outTable[currArg] = tonumber(k) - self.numberedParamOffset .. ' = ' .. v
else
outTable[currArg] = k .. ' = ' .. v
end
currArg = currArg + 1
end
end
end
return outTable
end
--- Pagename linker function
--
-- @param {table} args Module arguments
-- @return {table} Resulting table
local pagenameLinker = Linker:construct( 1 )
local tagLinker = Linker:construct( 1 )
function tagLinker:run( args )
local tag = args[ 1 ]
local tagContent = args[ 2 ]
local tagLink = magic.xmlTags[ tag ] or magic.htmltags[ tag ]
local outTable = {}
local currArg = 2
outTable[1] = tagLink and link and '[[:' .. tagLink .. '|' .. args[currArg] .. ']]' or args[currArg]
if tagContent then
outTable[2] = tagContent
currArg = 3
end
for k,v in pairs( args ) do
if tonumber(k) and (tonumber(k) <= 2 and tonumber(k) >= 1) then
else
if tonumber(k) and tonumber(k) > 2 then
outTable[currArg] = (tonumber(k) - 2) .. ' = ' .. v
else
outTable[currArg] = k .. ' = ' .. v
end
currArg = currArg + 1
end
end
return outTable
end
local templateLinker = Linker:construct( 1, 10, 1, true )
local plainLinker = Linker:construct( 1, 0, 1, true )
local lsthLinker = Linker:construct( 1 )
function lsthLinker:run( args )
local title = mw.title.new( args[ 1 ], 0 )
local outTable = {}
local currArg = 1
while args[currArg] ~= nil do
if ( currArg == 1 and link ) then
outTable[currArg] = '[[:' .. title.fullText .. '|' .. args[currArg] .. ']]'
elseif ( currArg == 2 and link ) then
outTable[currArg] = '[[:' .. title.fullText .. '#' .. args[currArg] .. '|' .. args[currArg] .. ']]'
else
outTable[currArg] = args[currArg]
end
currArg = currArg + 1
end
return outTable
end
--- Linkers for various parser functions
--
-- @table linkers
local linkers = {
["#invoke"] = Linker:construct( 1, 'Module', 2, true ),
["#lst"] = pagenameLinker,
["#lsth"] = lsthLinker,
["#lstx"] = pagenameLinker,
["int"] = Linker:construct( 1, 8 ),
["GENDER"] = Linker:construct( 1, 2 ),
["PROTECTIONLEVEL"] = Linker:construct( 2 ),
["#contentmodel"] = Linker:construct( 2 ),
["PAGESINCATEGORY"] = Linker:construct( 1, 14 ),
["PAGESINCAT"] = Linker:construct( 1, 14 ),
["FULLPAGENAME"] = pagenameLinker,
["PAGENAME"] = pagenameLinker,
["BASEPAGENAME"] = pagenameLinker,
["SUBPAGENAME"] = pagenameLinker,
["SUBJECTPAGENAME"] = pagenameLinker,
["ARTICLEPAGENAME"] = pagenameLinker,
["TALKPAGENAME"] = pagenameLinker,
["ROOTPAGENAME"] = pagenameLinker,
["NAMESPACE"] = pagenameLinker,
["SUBJECTSPACE"] = pagenameLinker,
["TALKSPACE"] = pagenameLinker,
["FULLPAGENAMEE"] = pagenameLinker,
["PAGENAMEE"] = pagenameLinker,
["BASEPAGENAMEE"] = pagenameLinker,
["SUBPAGENAMEE"] = pagenameLinker,
["SUBJECTPAGENAMEE"] = pagenameLinker,
["ARTICLEPAGENAMEE"] = pagenameLinker,
["TALKPAGENAMEE"] = pagenameLinker,
["ROOTPAGENAMEE"] = pagenameLinker,
["NAMESPACEE"] = pagenameLinker,
["SUBJECTSPACEE"] = pagenameLinker,
["TALKSPACEE"] = pagenameLinker,
["localurl"] = pagenameLinker,
["localurle"] = pagenameLinker,
["fullurl"] = pagenameLinker,
["fullurle"] = pagenameLinker,
["canonicalurl"] = pagenameLinker,
["canonicalurle"] = pagenameLinker,
["filepath"] = Linker:construct( 1, 6, 1 ),
["PAGEID"] = pagenameLinker,
["PAGESIZE"] = pagenameLinker,
["CASCADINGSOURCES"] = pagenameLinker,
["REVISIONID"] = pagenameLinker,
["REVISIONDAY"] = pagenameLinker,
["REVISIONDAY2"] = pagenameLinker,
["REVISIONMONTH"] = pagenameLinker,
["REVISIONMONTH1"] = pagenameLinker,
["REVISIONYEAR"] = pagenameLinker,
["REVISIONTIMESTAMP"] = pagenameLinker,
["REVISIONUSER"] = pagenameLinker,
["#ifexist"] = pagenameLinker,
["#tag"] = tagLinker
}
p.linkers = linkers -- debugging
--- Metatable in case linker is not explicitly defined.
setmetatable(linkers, {
__index = function( tbl, key )
local altKey = mw.text.split( key, ':', true )
if (linkers[altKey][1]) then return linkers[altKey] end
local default = Linker:construct( 1 )
function default:run( args )
--- Default builder for magic words and parser functions, etc.
local outTable = {}
local currArg = 1
while args[currArg] ~= nil do
outTable[currArg] = args[currArg]
currArg = currArg + 1
end
local maxArg = currArg
for k,v in pairs(args) do
if tonumber(k) and tonumber(k) >= 1 and tonumber(k) < maxArg then
else
if tonumber(k) then
outTable[currArg] = tonumber(k) .. ' = ' .. v
else
outTable[currArg] = k .. ' = ' .. v
end
currArg = currArg + 1
end
end
return outTable
end
return default
end
})
--- Gets the magic word or parser function specified if it exists
--
-- @param {string} word argument to test
-- @return Magic word if it exists
local function getMagicWordOrParserFunction( word )
local func = mw.text.split( word, ':', true )[1]
local lowerFunc = mw.ustring.lower( func )
if magic.parserFunctions[func] or magic.variables_about_other_pages[func] or magic.variables[func]
or magic.parserFunctions[lowerFunc] or magic.variables_about_other_pages[lowerFunc] or magic.variables[lowerFunc] then
return func, ( magic.parserFunctions[func] or ( magic.variables_about_other_pages[func] and not magic.variables[func] ) )
or ( magic.parserFunctions[lowerFunc] or ( magic.variables_about_other_pages[lowerFunc] and not magic.variables[lowerFunc] ) )
end
local success, _ = pcall( function( w )
return mw.getCurrentFrame():callParserFunction( w )
end, func )
success, _ = success, _ or pcall( function( w )
return mw.getCurrentFrame():callParserFunction( w )
end, mw.ustring.lower( func ) )
if success then return func, false else return nil, false end
end
--- Checks if a transclusion modifier is present
--
-- @param {string} arg Argument
-- @param {string} name Name of modifier
-- @return The name of the modifier, whether the modifier is present, and the new value of the argument
local function hasTransclusionModifier( arg, name )
local modifier = mw.ustring.sub( arg, 1, mw.ustring.len( name .. ":" ))
if ( mw.ustring.lower( modifier ) == name .. ':' ) then
return modifier, true, mw.ustring.sub( arg, mw.ustring.len( name .. ":" ) + 1 )
else
return nil, false, arg
end
end
local function hasOtherArgs( fn, args )
for k,v in pairs( args ) do
if tonumber(k) ~= 1 then
return true
else
if mw.ustring.sub( v, mw.ustring.len( fn ) + 1, mw.ustring.len( fn ) + 1 ) == ":" then
return true
end
end
end
return false
end
--- Left shifts argument names which begin with an underscore
--
-- @param {table} args Module arguments
-- @param {string} argName argument name
-- @return {table} New arguments
local function leftShiftUnderscore( args, argName )
local us = '_'
while args[ us .. '_' .. argName ] ~= nil do
args[ us .. argName ] = nil
args[ us .. argName ] = args[ us .. '_' .. argName ]
us = us .. '_'
mw.logObject( args )
end
args[ us .. argName ] = nil
return args
end
--- Generates a template link code
--
-- @param {table} args Arguments
-- @return {string} Wikitext for the template link
-- @usage {{#invoke:Format|templateLink|''template or parser function name'''|...subsequent template args...}}
function p.templateLink( args )
local templateName = args[1]
link = yn( args['_link'] ~= nil and args['_link'] or true )
args = leftShiftUnderscore( args, 'link' )
if not templateName then error( "No template or magic word specified", 0 ) end
local transclusionModifiers = {
{ "subst", "safesubst" },
{ "msg", "msgnw" },
{ "raw" }
}
local modifiersToApply = {}
for k,v in pairs( transclusionModifiers ) do
local modifier, hasModifier
for l,w in pairs( v ) do
modifier, hasModifier, args[1] = hasTransclusionModifier( args[1], w )
if ( hasModifier ) then
table.insert( modifiersToApply, modifier )
break
end
end
end
local parserFunction, needsColon = getMagicWordOrParserFunction( args[1] )
local lowerParserFunction = parserFunction ~= nil and mw.ustring.lower( parserFunction )
local colon = parserFunction ~= nil and mw.ustring.sub( args[1], mw.ustring.len( parserFunction ) + 1, mw.ustring.len( parserFunction ) + 1 ) == ':'
if parserFunction and colon and hasOtherArgs( parserFunction, args ) then
-- A parser function must always have a colon following
local parserFunctionLink = link and ( magic.parserFunctions[ parserFunction ] or magic.variables_about_other_pages[ parserFunction ] or magic.parserFunctions[ lowerParserFunction ] or magic.variables_about_other_pages[ lowerParserFunction ] or nil ) or nil
args[1] = mw.ustring.sub( args[1], mw.ustring.len( parserFunction ) + 2 )
local outTable = type( linkers[ parserFunction ] == "table" ) and linkers[ parserFunction ]:run( args ) or linkers[ parserFunction ]( args )
return mw.getCurrentFrame():preprocess('<nowiki>{{</nowiki>')
.. table.concat( modifiersToApply )
.. (parserFunctionLink and '[[' .. parserFunctionLink .. '|' .. parserFunction .. ']]' or parserFunction)
.. (colon and ':' or '')
.. table.concat( outTable, '|' ) .. mw.getCurrentFrame():preprocess('<nowiki>}}</nowiki>')
elseif parserFunction and not colon and not needsColon and not hasOtherArgs( parserFunction, args ) then
-- A static variable does not have a colon and does not have any other parameters
local parserFunctionLink = link and ( magic.variables[ parserFunction ] or magic.variables[ lowerParserFunction ] or nil ) or nil
return mw.getCurrentFrame():preprocess('<nowiki>{{</nowiki>')
.. table.concat( modifiersToApply )
.. (parserFunctionLink and '[[' .. parserFunctionLink .. '|' .. parserFunction .. ']]' or parserFunction)
.. (colon and ':' or '')
.. mw.getCurrentFrame():preprocess('<nowiki>}}</nowiki>')
else
-- Treat like template
local outTable = templateLinker:run( args )
return mw.getCurrentFrame():preprocess('<nowiki>{{</nowiki>')
.. table.concat( modifiersToApply )
.. table.concat( outTable, "|" ) .. mw.getCurrentFrame():preprocess('<nowiki>}}</nowiki>')
end
end
--- Generates a wikilink code
--
-- @param {table} args Arguments
-- @return {string} Wikitext for the wikilink
-- @usage {{#invoke:Format|wikilink|''page name''|''link display''|...other link arguments...}}
function p.wikilink( args )
local linkTitle = mw.title.new( args[1] )
if not linkTitle then error( "No link target specified", 0 ) end
link = yn( args['_link'] ~= nil and args['_link'] or true )
args = leftShiftUnderscore( args, 'link' )
local i = 1
while args[ '_wrapperTag' .. i ] ~= nil do
table.insert( wrapperTags, args[ '_wrapperTag' .. i ] )
args = leftShiftUnderscore( args, 'wrapperTag' .. i )
i = i + 1
end
local outTable = plainLinker:run( args )
return mw.getCurrentFrame():preprocess('<nowiki>[[</nowiki>') .. table.concat(outTable, "|") .. mw.getCurrentFrame():preprocess('<nowiki>]]</nowiki>')
end
--- Generates an external link code
--
-- @param {table} args Arguments
-- @return {string} Wikitext for the external link
-- @usage {{#invoke:Format|exlink|''URL''|''display''}}
function p.exlink( args )
local linkUrl = args[1]
local linkDisplay = args[2]
link = yn( args['_link'] ~= nil and args['_link'] or true )
local i = 1
while args[ '_wrapperTag' .. i ] ~= nil do
table.insert( wrapperTags, args[ '_wrapperTag' .. i ] )
i = i + 1
end
local outTable = {}
if not linkUrl then error( "No link target specified", 0 ) end
if link then
if yn( args.plainlinks ) then
table.insert( outTable, '<span class="plainlinks">' .. linkUrl .. '</span>' )
else
table.insert( outTable, linkUrl )
end
else
table.insert( outTable, mw.getCurrentFrame():preprocess( '<nowiki>' .. linkUrl .. '</nowiki>' ) )
end
if linkDisplay then
table.insert( outTable, mw.getCurrentFrame():preprocess( '<nowiki>' .. linkDisplay .. '</nowiki>' ) )
end
return mw.getCurrentFrame():preprocess('<nowiki>[</nowiki>') .. table.concat(outTable, ' ') .. mw.getCurrentFrame():preprocess('<nowiki>]</nowiki>')
end
--- Generates a tag code
--
-- @param {table} args Arguments
-- @return {string} Wikitext for tag link
-- @usage {{#invoke:Format|tag|''tagName''|''tagContent''|...tag arguments...}}
function p.tag( args )
local tag = args[1] or error("No tag specified", 0)
local tagContent = args[2]
link = yn( args['_link'] ~= nil and args['_link'] or true )
args = leftShiftUnderscore( args, 'link' )
local i = 1
while args[ '_wrapperTag' .. i ] ~= nil do
table.insert( wrapperTags, args[ '_wrapperTag' .. i ] )
args = leftShiftUnderscore( args, 'wrapperTag' .. i )
i = i + 1
end
local splitFirstArg = mw.text.split( tag, " " )
tag = splitFirstArg[1]
local props = table.concat( splitFirstArg, " ", 2 )
local tagLink = magic.xmlTags[ tag ] or magic.htmltags[ tag ]
local outTable = {}
local currArg = 1
if mw.ustring.sub( args[1], 1, 3 ) == "!--" and mw.ustring.sub( args[1], -2 ) == "--" then
return mw.getCurrentFrame():preprocess('<nowiki><</nowiki>') .. args[1] .. mw.getCurrentFrame():preprocess('<nowiki>></nowiki>')
end
for k,v in pairs( args ) do
if tonumber(k) and tonumber(k) - 2 < 1 then
else
if tonumber(k) and tonumber(k) >= 3 then
outTable[currArg] = (tonumber(k) - 2) .. '=' .. '"' .. mw.ustring.gsub( v, '"', '&quot;' ) .. '"'
else
outTable[currArg] = k .. '=' .. '"' .. mw.ustring.gsub( v, '"', '&quot;' ) .. '"'
end
currArg = currArg + 1
end
end
if tagContent ~= nil then
return mw.getCurrentFrame():preprocess('<nowiki><</nowiki>')
.. ((link and tagLink) and ('[[' .. tagLink .. '|' .. tag .. ']]') or tag)
.. (props ~= nil and props ~= '' and ' ' .. props or '')
.. (#outTable >= 1 and ' ' or '') .. table.concat( outTable, " " ) .. mw.getCurrentFrame():preprocess('<nowiki>></nowiki>') .. tagContent .. mw.getCurrentFrame():preprocess('<nowiki></</nowiki>') .. tag .. mw.getCurrentFrame():preprocess('<nowiki>></nowiki>')
else
return mw.getCurrentFrame():preprocess('<nowiki><</nowiki>')
.. ((link and tagLink) and ('[[' .. tagLink .. '|' .. tag .. ']]') or tag)
.. (props ~= nil and props ~= '' and ' ' .. props or '')
.. (#outTable >= 1 and ' ' or '') .. table.concat( outTable, " " ) .. mw.getCurrentFrame():preprocess('<nowiki>/></nowiki>')
end
end
local function main( args, fn )
--- Handle wrapper tags
local i = 1
while args[ '_wrapperTag' .. i ] ~= nil do
table.insert( wrapperTags, args[ '_wrapperTag' .. i ] )
args = leftShiftUnderscore( args, 'wrapperTag' .. i )
i = i + 1
end
--- As well as evaluation
local showResult = yn( args[ '_y_' ] or args[ '_show_result' ] )
args = leftShiftUnderscore( args, 'show_result' )
args = leftShiftUnderscore( args, 'y_' )
local syntaxhighlight = yn(args[ '_syntax_highlight' ])
args = leftShiftUnderscore( args, "syntax_highlight" )
local code = p[ fn ]( args )
local output
if showResult then
-- based on [[Module:Plain text]]
output = mw.getCurrentFrame():preprocess(
mw.text.unstripNoWiki( code
:gsub(' ', ' ') --replace nbsp spaces with regular spaces
:gsub('<br ?/?>', '\n') --replace br with line breaks
:gsub('<span.->(.-)</span>', '%1') --remove spans while keeping text inside
:gsub('<i.->(.-)</i>', '%1') --remove italics while keeping text inside
:gsub('<b.->(.-)</b>', '%1') --remove bold while keeping text inside
:gsub('<em.->(.-)</em>', '%1') --remove emphasis while keeping text inside
:gsub('<strong.->(.-)</strong>', '%1') --remove strong while keeping text inside
:gsub('<sub.->(.-)</sub>', '%1') --remove subscript markup; retain contents
:gsub('<sup.->(.-)</sup>', '%1') --remove superscript markup; retain contents
:gsub('<u.->(.-)</u>', '%1') --remove underline markup; retain contents
:gsub('<.->.-<.->', '') --strip out remaining tags and the text inside
:gsub('<.->', '') --remove any other tag markup
:gsub('%[%[%s*[Ff][Ii][Ll][Ee]%s*:.-%]%]', '') --strip out files
:gsub('%[%[%s*[Ii][Mm][Aa][Gg][Ee]%s*:.-%]%]', '') --strip out use of image:
:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:.-%]%]', '') --strip out categories
:gsub('%[%[[^%]]-|', '') --strip out piped link text
:gsub('([^%[])%[[^%[%]][^%]]-%s', '%1') --strip out external link text
:gsub('^%[[^%[%]][^%]]-%s', '') --strip out external link text
:gsub('[%[%]]', '') --then strip out remaining [ and ]
:gsub("'''''", "") --strip out bold italic markup
:gsub("'''?", "") --not stripping out '''' gives correct output for bolded text in quotes
:gsub('%-%-%-%-+', '') --remove ---- lines
:gsub("^%s+", "") --strip leading
:gsub("%s+$", "") --and trailing spaces
:gsub("%s+", " ") --strip redundant spaces
)
)
end
if syntaxhighlight then
return mw.getCurrentFrame():extensionTag( "syntaxhighlight", code, { lang="wikitext", "inline" } ) .. ( output and ' → ' .. output or '' )
end
return wrapperTags:wrap( code ) .. ( output and ' → ' .. output or '' )
end
return setmetatable( {
[''] = main
}, {
__index = function( _, tblKey )
if type( p[ tblKey ] ) ~= "function" then
return p[ tblKey ]
end
return function( frame )
local args = require( "Module:Arguments" ).getArgs( frame, {
parentFirst = true,
valueFunc = function (key, value)
if type(value) == 'string' then
value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
if value ~= '' then
return value
else
return nil
end
else
return value
end
end
} )
return main( args, tblKey )
end
end
})