Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:Format

From Wikipedia, the free encyclopedia

--- 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, '"', '&amp;quot;' ) .. '"'
			else
				outTable[currArg] = k .. '=' .. '"' .. mw.ustring.gsub( v, '"', '&amp;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('&nbsp;', ' ') --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
})