<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mindpowe.red/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3ATemplate_test_case</id>
	<title>Module:Template test case - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mindpowe.red/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3ATemplate_test_case"/>
	<link rel="alternate" type="text/html" href="https://mindpowe.red/wiki/index.php?title=Module:Template_test_case&amp;action=history"/>
	<updated>2026-04-06T14:24:08Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.34.2</generator>
	<entry>
		<id>https://mindpowe.red/wiki/index.php?title=Module:Template_test_case&amp;diff=913&amp;oldid=prev</id>
		<title>imported&gt;SUM1: Further tweak</title>
		<link rel="alternate" type="text/html" href="https://mindpowe.red/wiki/index.php?title=Module:Template_test_case&amp;diff=913&amp;oldid=prev"/>
		<updated>2020-03-09T03:27:02Z</updated>

		<summary type="html">&lt;p&gt;Further tweak&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;--[[&lt;br /&gt;
   A module for generating test case templates.&lt;br /&gt;
&lt;br /&gt;
   This module incorporates code from the English Wikipedia's &amp;quot;Testcase table&amp;quot;&lt;br /&gt;
   module,[1] written by Frietjes [2] with contributions by Mr. Stradivarius [3]&lt;br /&gt;
   and Jackmcbarn,[4] and the English Wikipedia's &amp;quot;Testcase rows&amp;quot; module,[5]&lt;br /&gt;
   written by Mr. Stradivarius.&lt;br /&gt;
&lt;br /&gt;
   The &amp;quot;Testcase table&amp;quot; and &amp;quot;Testcase rows&amp;quot; modules are released under the&lt;br /&gt;
   CC BY-SA 3.0 License [6] and the GFDL.[7]&lt;br /&gt;
&lt;br /&gt;
   License: CC BY-SA 3.0 and the GFDL&lt;br /&gt;
   Author: Mr. Stradivarius&lt;br /&gt;
&lt;br /&gt;
   [1] https://en.wikipedia.org/wiki/Module:Testcase_table&lt;br /&gt;
   [2] https://en.wikipedia.org/wiki/User:Frietjes&lt;br /&gt;
   [3] https://en.wikipedia.org/wiki/User:Mr._Stradivarius&lt;br /&gt;
   [4] https://en.wikipedia.org/wiki/User:Jackmcbarn&lt;br /&gt;
   [5] https://en.wikipedia.org/wiki/Module:Testcase_rows&lt;br /&gt;
   [6] https://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License&lt;br /&gt;
   [7] https://en.wikipedia.org/wiki/Wikipedia:Text_of_the_GNU_Free_Documentation_License&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Load required modules&lt;br /&gt;
local yesno = require('Module:Yesno')&lt;br /&gt;
&lt;br /&gt;
-- Set constants&lt;br /&gt;
local DATA_MODULE = 'Module:Template test case/data'&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Shared methods&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function message(self, key, ...)&lt;br /&gt;
	-- This method is added to classes that need to deal with messages from the&lt;br /&gt;
	-- config module.&lt;br /&gt;
	local msg = self.cfg.msg[key]&lt;br /&gt;
	if select(1, ...) then&lt;br /&gt;
		return mw.message.newRawMessage(msg, ...):plain()&lt;br /&gt;
	else&lt;br /&gt;
		return msg&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Template class&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local Template = {}&lt;br /&gt;
&lt;br /&gt;
Template.memoizedMethods = {&lt;br /&gt;
	-- Names of methods to be memoized in each object. This table should only&lt;br /&gt;
	-- hold methods with no parameters.&lt;br /&gt;
	getFullPage = true,&lt;br /&gt;
	getName = true,&lt;br /&gt;
	makeHeader = true,&lt;br /&gt;
	getOutput = true&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function Template.new(invocationObj, options)&lt;br /&gt;
	local obj = {}&lt;br /&gt;
&lt;br /&gt;
	-- Set input&lt;br /&gt;
	for k, v in pairs(options or {}) do&lt;br /&gt;
		if not Template[k] then&lt;br /&gt;
			obj[k] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	obj._invocation = invocationObj&lt;br /&gt;
&lt;br /&gt;
	-- Validate input&lt;br /&gt;
	if not obj.template and not obj.title then&lt;br /&gt;
		error('no template or title specified', 2)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Memoize expensive method calls&lt;br /&gt;
	local memoFuncs = {}&lt;br /&gt;
	return setmetatable(obj, {&lt;br /&gt;
		__index = function (t, key)&lt;br /&gt;
			if Template.memoizedMethods[key] then&lt;br /&gt;
				local func = memoFuncs[key]&lt;br /&gt;
				if not func then&lt;br /&gt;
					local val = Template[key](t)&lt;br /&gt;
					func = function () return val end&lt;br /&gt;
					memoFuncs[key] = func&lt;br /&gt;
				end&lt;br /&gt;
				return func&lt;br /&gt;
			else&lt;br /&gt;
				return Template[key]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Template:getFullPage()&lt;br /&gt;
	if self.template then&lt;br /&gt;
		local strippedTemplate, hasColon = self.template:gsub('^:', '', 1)&lt;br /&gt;
		hasColon = hasColon &amp;gt; 0&lt;br /&gt;
		local ns = strippedTemplate:match('^(.-):')&lt;br /&gt;
		ns = ns and mw.site.namespaces[ns]&lt;br /&gt;
		if ns then&lt;br /&gt;
			return strippedTemplate&lt;br /&gt;
		elseif hasColon then&lt;br /&gt;
			return strippedTemplate -- Main namespace&lt;br /&gt;
		else&lt;br /&gt;
			return mw.site.namespaces[10].name .. ':' .. strippedTemplate&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		return self.title.prefixedText&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Template:getName()&lt;br /&gt;
	if self.template then&lt;br /&gt;
		return self.template&lt;br /&gt;
	else&lt;br /&gt;
		return require('Module:Template invocation').name(self.title)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Template:makeLink(display)&lt;br /&gt;
	if display then&lt;br /&gt;
		return string.format('[[:%s|%s]]', self:getFullPage(), display)&lt;br /&gt;
	else&lt;br /&gt;
		return string.format('[[:%s]]', self:getFullPage())&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Template:makeBraceLink(display)&lt;br /&gt;
	display = display or self:getName()&lt;br /&gt;
	local link = self:makeLink(display)&lt;br /&gt;
	return mw.text.nowiki('{{') .. link .. mw.text.nowiki('}}')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Template:makeHeader()&lt;br /&gt;
	return self.heading or self:makeBraceLink()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Template:getInvocation(format)&lt;br /&gt;
	local invocation = self._invocation:getInvocation{&lt;br /&gt;
		template = self:getName(),&lt;br /&gt;
		requireMagicWord = self.requireMagicWord,&lt;br /&gt;
	}&lt;br /&gt;
	if format == 'code' then&lt;br /&gt;
		invocation = '&amp;lt;code&amp;gt;' .. mw.text.nowiki(invocation) .. '&amp;lt;/code&amp;gt;'&lt;br /&gt;
	elseif format == 'kbd' then&lt;br /&gt;
		invocation = '&amp;lt;kbd&amp;gt;' .. mw.text.nowiki(invocation) .. '&amp;lt;/kbd&amp;gt;'&lt;br /&gt;
	elseif format == 'plain' then&lt;br /&gt;
		invocation = mw.text.nowiki(invocation)&lt;br /&gt;
	else&lt;br /&gt;
		-- Default is pre tags&lt;br /&gt;
		invocation = mw.text.encode(invocation, '&amp;amp;')&lt;br /&gt;
		invocation = '&amp;lt;pre style=&amp;quot;white-space: pre-wrap;&amp;quot;&amp;gt;' .. invocation .. '&amp;lt;/pre&amp;gt;'&lt;br /&gt;
		invocation = mw.getCurrentFrame():preprocess(invocation)&lt;br /&gt;
	end&lt;br /&gt;
	return invocation&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Template:getOutput()&lt;br /&gt;
	local protect = require('Module:Protect')&lt;br /&gt;
	-- calling self._invocation:getOutput{...}&lt;br /&gt;
	return protect(self._invocation.getOutput)(self._invocation, {&lt;br /&gt;
		template = self:getName(),&lt;br /&gt;
		requireMagicWord = self.requireMagicWord,&lt;br /&gt;
	})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- TestCase class&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local TestCase = {}&lt;br /&gt;
TestCase.__index = TestCase&lt;br /&gt;
TestCase.message = message -- add the message method&lt;br /&gt;
&lt;br /&gt;
TestCase.renderMethods = {&lt;br /&gt;
	-- Keys in this table are values of the &amp;quot;format&amp;quot; option, values are the&lt;br /&gt;
	-- method for rendering that format.&lt;br /&gt;
	columns = 'renderColumns',&lt;br /&gt;
	rows = 'renderRows',&lt;br /&gt;
	tablerows = 'renderRows',&lt;br /&gt;
	inline = 'renderInline',&lt;br /&gt;
	cells = 'renderCells',&lt;br /&gt;
	default = 'renderDefault'&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function TestCase.new(invocationObj, options, cfg)&lt;br /&gt;
	local obj = setmetatable({}, TestCase)&lt;br /&gt;
	obj.cfg = cfg&lt;br /&gt;
&lt;br /&gt;
	-- Separate general options from template options. Template options are&lt;br /&gt;
	-- numbered, whereas general options are not.&lt;br /&gt;
	local generalOptions, templateOptions = {}, {}&lt;br /&gt;
	for k, v in pairs(options) do&lt;br /&gt;
		local prefix, num&lt;br /&gt;
		if type(k) == 'string' then&lt;br /&gt;
			prefix, num = k:match('^(.-)([1-9][0-9]*)$')&lt;br /&gt;
		end&lt;br /&gt;
		if prefix then&lt;br /&gt;
			num = tonumber(num)&lt;br /&gt;
			templateOptions[num] = templateOptions[num] or {}&lt;br /&gt;
			templateOptions[num][prefix] = v&lt;br /&gt;
		else&lt;br /&gt;
			generalOptions[k] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Set general options&lt;br /&gt;
	generalOptions.showcode = yesno(generalOptions.showcode)&lt;br /&gt;
	generalOptions.showheader = yesno(generalOptions.showheader) ~= false&lt;br /&gt;
	generalOptions.showcaption = yesno(generalOptions.showcaption) ~= false&lt;br /&gt;
	generalOptions.collapsible = yesno(generalOptions.collapsible)&lt;br /&gt;
	generalOptions.notcollapsed = yesno(generalOptions.notcollapsed)&lt;br /&gt;
	generalOptions.wantdiff = yesno(generalOptions.wantdiff) &lt;br /&gt;
	obj.options = generalOptions&lt;br /&gt;
&lt;br /&gt;
	-- Preprocess template args&lt;br /&gt;
	for num, t in pairs(templateOptions) do&lt;br /&gt;
		if t.showtemplate ~= nil then&lt;br /&gt;
			t.showtemplate = yesno(t.showtemplate)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Set up first two template options tables, so that if only the&lt;br /&gt;
	-- &amp;quot;template3&amp;quot; is specified it isn't made the first template when the&lt;br /&gt;
	-- the table options array is compressed.&lt;br /&gt;
	templateOptions[1] = templateOptions[1] or {}&lt;br /&gt;
	templateOptions[2] = templateOptions[2] or {}&lt;br /&gt;
&lt;br /&gt;
	-- Allow the &amp;quot;template&amp;quot; option to override the &amp;quot;template1&amp;quot; option for&lt;br /&gt;
	-- backwards compatibility with [[Module:Testcase table]].&lt;br /&gt;
	if generalOptions.template then&lt;br /&gt;
		templateOptions[1].template = generalOptions.template&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Add default template options&lt;br /&gt;
	if templateOptions[1].template and not templateOptions[2].template then&lt;br /&gt;
		templateOptions[2].template = templateOptions[1].template ..&lt;br /&gt;
			'/' .. obj.cfg.sandboxSubpage&lt;br /&gt;
	end&lt;br /&gt;
	if not templateOptions[1].template then&lt;br /&gt;
		templateOptions[1].title = mw.title.getCurrentTitle().basePageTitle&lt;br /&gt;
	end&lt;br /&gt;
	if not templateOptions[2].template then&lt;br /&gt;
		templateOptions[2].title = templateOptions[1].title:subPageTitle(&lt;br /&gt;
			obj.cfg.sandboxSubpage&lt;br /&gt;
		)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Remove template options for any templates where the showtemplate&lt;br /&gt;
	-- argument is false. This prevents any output for that template.&lt;br /&gt;
	for num, t in pairs(templateOptions) do&lt;br /&gt;
		if t.showtemplate == false then&lt;br /&gt;
			templateOptions[num] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Check for missing template names.&lt;br /&gt;
	for num, t in pairs(templateOptions) do&lt;br /&gt;
		if not t.template and not t.title then&lt;br /&gt;
			error(obj:message(&lt;br /&gt;
				'missing-template-option-error',&lt;br /&gt;
				num, num&lt;br /&gt;
			), 2)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Compress templateOptions table so we can iterate over it with ipairs.&lt;br /&gt;
	templateOptions = (function (t)&lt;br /&gt;
		local nums = {}&lt;br /&gt;
		for num in pairs(t) do&lt;br /&gt;
			nums[#nums + 1] = num&lt;br /&gt;
		end&lt;br /&gt;
		table.sort(nums)&lt;br /&gt;
		local ret = {}&lt;br /&gt;
		for i, num in ipairs(nums) do&lt;br /&gt;
			ret[i] = t[num]&lt;br /&gt;
		end&lt;br /&gt;
		return ret&lt;br /&gt;
	end)(templateOptions)&lt;br /&gt;
&lt;br /&gt;
	-- Don't require the __TEMPLATENAME__ magic word for nowiki invocations if&lt;br /&gt;
	-- there is only one template being output.&lt;br /&gt;
	if #templateOptions &amp;lt;= 1 then&lt;br /&gt;
		templateOptions[1].requireMagicWord = false&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	mw.logObject(templateOptions)&lt;br /&gt;
&lt;br /&gt;
	-- Make the template objects&lt;br /&gt;
	obj.templates = {}&lt;br /&gt;
	for i, options in ipairs(templateOptions) do&lt;br /&gt;
		table.insert(obj.templates, Template.new(invocationObj, options))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Add tracking categories. At the moment we are only tracking templates&lt;br /&gt;
	-- that use any &amp;quot;heading&amp;quot; parameters or an &amp;quot;output&amp;quot; parameter.&lt;br /&gt;
	obj.categories = {}&lt;br /&gt;
	for k, v in pairs(options) do&lt;br /&gt;
		if type(k) == 'string' and k:find('heading') then&lt;br /&gt;
			obj.categories['Test cases using heading parameters'] = true&lt;br /&gt;
		elseif k == 'output' then&lt;br /&gt;
			obj.categories['Test cases using output parameter'] = true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return obj&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:getTemplateOutput(templateObj)&lt;br /&gt;
	local output = templateObj:getOutput()&lt;br /&gt;
	if self.options.resetRefs then&lt;br /&gt;
		mw.getCurrentFrame():extensionTag('references')&lt;br /&gt;
	end&lt;br /&gt;
	return output&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:templateOutputIsEqual()&lt;br /&gt;
	-- Returns a boolean showing whether all of the template outputs are equal.&lt;br /&gt;
	-- The random parts of strip markers (see [[Help:Strip markers]]) are&lt;br /&gt;
	-- removed before comparison. This means a strip marker can contain anything&lt;br /&gt;
	-- and still be treated as equal, but it solves the problem of otherwise&lt;br /&gt;
	-- identical wikitext not returning as exactly equal.&lt;br /&gt;
	local function normaliseOutput(obj)&lt;br /&gt;
		local out = obj:getOutput()&lt;br /&gt;
		-- Remove the random parts from strip markers.&lt;br /&gt;
		out = out:gsub('(\127\'&amp;quot;`UNIQ.-)%-%x+%-(QINU`&amp;quot;\'\127)', '%1%2')&lt;br /&gt;
		return out&lt;br /&gt;
	end&lt;br /&gt;
	local firstOutput = normaliseOutput(self.templates[1])&lt;br /&gt;
	for i = 2, #self.templates do&lt;br /&gt;
		local output = normaliseOutput(self.templates[i])&lt;br /&gt;
		if output ~= firstOutput then&lt;br /&gt;
			return false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:makeCollapsible(s)&lt;br /&gt;
	local title = self.options.title or self.templates[1]:makeHeader()&lt;br /&gt;
	if self.options.titlecode then&lt;br /&gt;
		title = self.templates[1]:getInvocation('kbd')&lt;br /&gt;
	end&lt;br /&gt;
	local isEqual = self:templateOutputIsEqual()&lt;br /&gt;
	local root = mw.html.create('table')&lt;br /&gt;
	if self.options.wantdiff then&lt;br /&gt;
	root&lt;br /&gt;
		:addClass('mw-collapsible')&lt;br /&gt;
	if self.options.notcollapsed == false then&lt;br /&gt;
		root&lt;br /&gt;
			:addClass('mw-collapsed')&lt;br /&gt;
	end&lt;br /&gt;
	root&lt;br /&gt;
		:css('background-color', 'transparent')&lt;br /&gt;
		:css('width', '100%')&lt;br /&gt;
		:css('border', 'solid silver 1px')&lt;br /&gt;
		:tag('tr')&lt;br /&gt;
			:tag('th')&lt;br /&gt;
				:css('background-color', isEqual and 'yellow' or '#90a8ee')&lt;br /&gt;
				:wikitext(title)&lt;br /&gt;
				:done()&lt;br /&gt;
			:done()&lt;br /&gt;
		:tag('tr')&lt;br /&gt;
			:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(s)&lt;br /&gt;
				:newline()&lt;br /&gt;
	else&lt;br /&gt;
		root&lt;br /&gt;
		:addClass('mw-collapsible')&lt;br /&gt;
		if self.options.notcollapsed == false then&lt;br /&gt;
			root&lt;br /&gt;
				:addClass('mw-collapsed')&lt;br /&gt;
		end&lt;br /&gt;
		if self.options.notcollapsed ~= true or false then&lt;br /&gt;
			root&lt;br /&gt;
				:addClass(isEqual and 'mw-collapsed' or nil)&lt;br /&gt;
		end&lt;br /&gt;
		root&lt;br /&gt;
		:css('background-color', 'transparent')&lt;br /&gt;
		:css('width', '100%')&lt;br /&gt;
		:css('border', 'solid silver 1px')&lt;br /&gt;
		:tag('tr')&lt;br /&gt;
			:tag('th')&lt;br /&gt;
				:css('background-color', isEqual and 'lightgreen' or 'yellow')&lt;br /&gt;
				:wikitext(title)&lt;br /&gt;
				:done()&lt;br /&gt;
			:done()&lt;br /&gt;
		:tag('tr')&lt;br /&gt;
			:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(s)&lt;br /&gt;
				:newline()&lt;br /&gt;
	 end&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:renderColumns()&lt;br /&gt;
	local root = mw.html.create()&lt;br /&gt;
	if self.options.showcode then&lt;br /&gt;
		root&lt;br /&gt;
			:wikitext(self.templates[1]:getInvocation())&lt;br /&gt;
			:newline()&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local tableroot = root:tag('table')&lt;br /&gt;
&lt;br /&gt;
	if self.options.showheader then&lt;br /&gt;
		-- Caption&lt;br /&gt;
		if self.options.showcaption then&lt;br /&gt;
			tableroot&lt;br /&gt;
				:addClass(self.options.class)&lt;br /&gt;
				:cssText(self.options.style)&lt;br /&gt;
				:tag('caption')&lt;br /&gt;
					:wikitext(self.options.caption or self:message('columns-header'))&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		-- Headers&lt;br /&gt;
		local headerRow = tableroot:tag('tr')&lt;br /&gt;
		if self.options.rowheader then&lt;br /&gt;
			-- rowheader is correct here. We need to add another th cell if&lt;br /&gt;
			-- rowheader is set further down, even if heading0 is missing.&lt;br /&gt;
			headerRow:tag('th'):wikitext(self.options.heading0)&lt;br /&gt;
		end&lt;br /&gt;
		local width&lt;br /&gt;
		if #self.templates &amp;gt; 0 then&lt;br /&gt;
			width = tostring(math.floor(100 / #self.templates)) .. '%'&lt;br /&gt;
		else&lt;br /&gt;
			width = '100%'&lt;br /&gt;
		end&lt;br /&gt;
		for i, obj in ipairs(self.templates) do&lt;br /&gt;
			headerRow&lt;br /&gt;
				:tag('th')&lt;br /&gt;
					:css('width', width)&lt;br /&gt;
					:wikitext(obj:makeHeader())&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Row header&lt;br /&gt;
	local dataRow = tableroot:tag('tr'):css('vertical-align', 'top')&lt;br /&gt;
	if self.options.rowheader then&lt;br /&gt;
		dataRow:tag('th')&lt;br /&gt;
			:attr('scope', 'row')&lt;br /&gt;
			:wikitext(self.options.rowheader)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Template output&lt;br /&gt;
	for i, obj in ipairs(self.templates) do&lt;br /&gt;
		if self.options.output == 'nowiki+' then&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(self.options.before)&lt;br /&gt;
				:wikitext(self:getTemplateOutput(obj))&lt;br /&gt;
				:wikitext(self.options.after)&lt;br /&gt;
				:wikitext('&amp;lt;pre style=&amp;quot;white-space: pre-wrap;&amp;quot;&amp;gt;')&lt;br /&gt;
				:wikitext(mw.text.nowiki(self.options.before or &amp;quot;&amp;quot;))&lt;br /&gt;
				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))&lt;br /&gt;
				:wikitext(mw.text.nowiki(self.options.after or &amp;quot;&amp;quot;))&lt;br /&gt;
				:wikitext('&amp;lt;/pre&amp;gt;')&lt;br /&gt;
		elseif self.options.output == 'nowiki' then&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(mw.text.nowiki(self.options.before or &amp;quot;&amp;quot;))&lt;br /&gt;
				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))&lt;br /&gt;
				:wikitext(mw.text.nowiki(self.options.after or &amp;quot;&amp;quot;))&lt;br /&gt;
		else&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(self.options.before)&lt;br /&gt;
				:wikitext(self:getTemplateOutput(obj))&lt;br /&gt;
				:wikitext(self.options.after)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:renderRows()&lt;br /&gt;
	local root = mw.html.create()&lt;br /&gt;
	if self.options.showcode then&lt;br /&gt;
		root&lt;br /&gt;
			:wikitext(self.templates[1]:getInvocation())&lt;br /&gt;
			:newline()&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local tableroot = root:tag('table')&lt;br /&gt;
	tableroot&lt;br /&gt;
		:addClass(self.options.class)&lt;br /&gt;
		:cssText(self.options.style)&lt;br /&gt;
&lt;br /&gt;
	if self.options.caption then&lt;br /&gt;
		tableroot&lt;br /&gt;
			:tag('caption')&lt;br /&gt;
				:wikitext(self.options.caption)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	for _, obj in ipairs(self.templates) do&lt;br /&gt;
		local dataRow = tableroot:tag('tr')&lt;br /&gt;
		&lt;br /&gt;
		-- Header&lt;br /&gt;
		if self.options.showheader then&lt;br /&gt;
			if self.options.format == 'tablerows' then&lt;br /&gt;
				dataRow:tag('th')&lt;br /&gt;
					:attr('scope', 'row')&lt;br /&gt;
					:css('vertical-align', 'top')&lt;br /&gt;
					:css('text-align', 'left')&lt;br /&gt;
					:wikitext(obj:makeHeader())&lt;br /&gt;
				dataRow:tag('td')&lt;br /&gt;
					:css('vertical-align', 'top')&lt;br /&gt;
					:css('padding', '0 1em')&lt;br /&gt;
					:wikitext('→')&lt;br /&gt;
			else&lt;br /&gt;
				dataRow:tag('td')&lt;br /&gt;
					:css('text-align', 'center')&lt;br /&gt;
					:css('font-weight', 'bold')&lt;br /&gt;
					:wikitext(obj:makeHeader())&lt;br /&gt;
				dataRow = tableroot:tag('tr')&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		-- Template output&lt;br /&gt;
		if self.options.output == 'nowiki+' then&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(self:getTemplateOutput(obj))&lt;br /&gt;
				:wikitext('&amp;lt;pre style=&amp;quot;white-space: pre-wrap;&amp;quot;&amp;gt;')&lt;br /&gt;
				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))&lt;br /&gt;
				:wikitext('&amp;lt;/pre&amp;gt;')&lt;br /&gt;
		elseif self.options.output == 'nowiki' then&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))&lt;br /&gt;
		else&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(self:getTemplateOutput(obj))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:renderInline()&lt;br /&gt;
	local arrow = mw.language.getContentLanguage():getArrow('forwards')&lt;br /&gt;
	local ret = {}&lt;br /&gt;
	for i, obj in ipairs(self.templates) do&lt;br /&gt;
		local line = {}&lt;br /&gt;
		line[#line + 1] = self.options.prefix or '* '&lt;br /&gt;
		if self.options.showcode then&lt;br /&gt;
			line[#line + 1] = obj:getInvocation('code')&lt;br /&gt;
			line[#line + 1] = ' '&lt;br /&gt;
			line[#line + 1] = arrow&lt;br /&gt;
			line[#line + 1] = ' '&lt;br /&gt;
		end&lt;br /&gt;
		if self.options.output == 'nowiki+' then&lt;br /&gt;
			line[#line + 1] = self:getTemplateOutput(obj)&lt;br /&gt;
			line[#line + 1] = '&amp;lt;pre style=&amp;quot;white-space: pre-wrap;&amp;quot;&amp;gt;'&lt;br /&gt;
			line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj))&lt;br /&gt;
			line[#line + 1] = '&amp;lt;/pre&amp;gt;'&lt;br /&gt;
		elseif self.options.output == 'nowiki' then&lt;br /&gt;
			line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj))&lt;br /&gt;
		else&lt;br /&gt;
			line[#line + 1] = self:getTemplateOutput(obj)&lt;br /&gt;
		end&lt;br /&gt;
		ret[#ret + 1] = table.concat(line)&lt;br /&gt;
	end&lt;br /&gt;
	if self.options.addline then&lt;br /&gt;
		local line = {}&lt;br /&gt;
		line[#line + 1] = self.options.prefix or '* '&lt;br /&gt;
		line[#line + 1] = self.options.addline&lt;br /&gt;
		ret[#ret + 1] = table.concat(line)&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(ret, '\n')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:renderCells()&lt;br /&gt;
	local root = mw.html.create()&lt;br /&gt;
	local dataRow = root:tag('tr')&lt;br /&gt;
	dataRow&lt;br /&gt;
		:css('vertical-align', 'top')&lt;br /&gt;
		:addClass(self.options.class)&lt;br /&gt;
		:cssText(self.options.style)&lt;br /&gt;
&lt;br /&gt;
	-- Row header&lt;br /&gt;
	if self.options.rowheader then&lt;br /&gt;
		dataRow:tag('th')&lt;br /&gt;
			:attr('scope', 'row')&lt;br /&gt;
			:newline()&lt;br /&gt;
			:wikitext(self.options.rowheader or self:message('row-header'))&lt;br /&gt;
	end&lt;br /&gt;
	-- Caption&lt;br /&gt;
	if self.options.showcaption then&lt;br /&gt;
		dataRow:tag('th')&lt;br /&gt;
			:attr('scope', 'row')&lt;br /&gt;
			:newline()&lt;br /&gt;
			:wikitext(self.options.caption or self:message('columns-header'))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Show code&lt;br /&gt;
	if self.options.showcode then&lt;br /&gt;
		dataRow:tag('td')&lt;br /&gt;
			:newline()&lt;br /&gt;
			:wikitext(self:getInvocation('code'))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Template output&lt;br /&gt;
	for i, obj in ipairs(self.templates) do&lt;br /&gt;
		if self.options.output == 'nowiki+' then&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(self.options.before)&lt;br /&gt;
				:wikitext(self:getTemplateOutput(obj))&lt;br /&gt;
				:wikitext(self.options.after)&lt;br /&gt;
				:wikitext('&amp;lt;pre style=&amp;quot;white-space: pre-wrap;&amp;quot;&amp;gt;')&lt;br /&gt;
				:wikitext(mw.text.nowiki(self.options.before or &amp;quot;&amp;quot;))&lt;br /&gt;
				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))&lt;br /&gt;
				:wikitext(mw.text.nowiki(self.options.after or &amp;quot;&amp;quot;))&lt;br /&gt;
				:wikitext('&amp;lt;/pre&amp;gt;')&lt;br /&gt;
		elseif self.options.output == 'nowiki' then&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(mw.text.nowiki(self.options.before or &amp;quot;&amp;quot;))&lt;br /&gt;
				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))&lt;br /&gt;
				:wikitext(mw.text.nowiki(self.options.after or &amp;quot;&amp;quot;))&lt;br /&gt;
		else&lt;br /&gt;
			dataRow:tag('td')&lt;br /&gt;
				:newline()&lt;br /&gt;
				:wikitext(self.options.before)&lt;br /&gt;
				:wikitext(self:getTemplateOutput(obj))&lt;br /&gt;
				:wikitext(self.options.after)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:renderDefault()&lt;br /&gt;
	local ret = {}&lt;br /&gt;
	if self.options.showcode then&lt;br /&gt;
		ret[#ret + 1] = self.templates[1]:getInvocation()&lt;br /&gt;
	end&lt;br /&gt;
	for i, obj in ipairs(self.templates) do&lt;br /&gt;
		ret[#ret + 1] = '&amp;lt;div style=&amp;quot;clear: both;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;'&lt;br /&gt;
		if self.options.showheader then&lt;br /&gt;
			ret[#ret + 1] = obj:makeHeader()&lt;br /&gt;
		end&lt;br /&gt;
		if self.options.output == 'nowiki+' then&lt;br /&gt;
			ret[#ret + 1] = self:getTemplateOutput(obj) .. '&amp;lt;pre style=&amp;quot;white-space: pre-wrap;&amp;quot;&amp;gt;' .. mw.text.nowiki(self:getTemplateOutput(obj)) .. '&amp;lt;/pre&amp;gt;'&lt;br /&gt;
		elseif self.options.output == 'nowiki' then&lt;br /&gt;
			ret[#ret + 1] = mw.text.nowiki(self:getTemplateOutput(obj))&lt;br /&gt;
		else&lt;br /&gt;
			ret[#ret + 1] = self:getTemplateOutput(obj)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(ret, '\n\n')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TestCase:__tostring()&lt;br /&gt;
	local format = self.options.format&lt;br /&gt;
	local method = format and TestCase.renderMethods[format] or 'renderDefault'&lt;br /&gt;
	local ret = self[method](self)&lt;br /&gt;
	if self.options.collapsible then&lt;br /&gt;
		ret = self:makeCollapsible(ret)&lt;br /&gt;
	end&lt;br /&gt;
	for cat in pairs(self.categories) do&lt;br /&gt;
		ret = ret .. string.format('[[Category:%s]]', cat)&lt;br /&gt;
	end&lt;br /&gt;
	return ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Nowiki invocation class&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local NowikiInvocation = {}&lt;br /&gt;
NowikiInvocation.__index = NowikiInvocation&lt;br /&gt;
NowikiInvocation.message = message -- Add the message method&lt;br /&gt;
&lt;br /&gt;
function NowikiInvocation.new(invocation, cfg)&lt;br /&gt;
	local obj = setmetatable({}, NowikiInvocation)&lt;br /&gt;
	obj.cfg = cfg&lt;br /&gt;
	invocation = mw.text.unstrip(invocation)&lt;br /&gt;
	-- Decode HTML entities for &amp;lt;, &amp;gt;, and &amp;quot;. This means that HTML entities in&lt;br /&gt;
	-- the original code must be escaped as e.g. &amp;amp;amp;lt;, which is unfortunate,&lt;br /&gt;
	-- but it is the best we can do as the distinction between &amp;lt;, &amp;gt;, &amp;quot; and &amp;amp;lt;,&lt;br /&gt;
	-- &amp;amp;gt;, &amp;amp;quot; is lost during the original nowiki operation.&lt;br /&gt;
	invocation = invocation:gsub('&amp;amp;lt;', '&amp;lt;')&lt;br /&gt;
	invocation = invocation:gsub('&amp;amp;gt;', '&amp;gt;')&lt;br /&gt;
	invocation = invocation:gsub('&amp;amp;quot;', '&amp;quot;')&lt;br /&gt;
	obj.invocation = invocation&lt;br /&gt;
	return obj&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function NowikiInvocation:getInvocation(options)&lt;br /&gt;
	local template = options.template:gsub('%%', '%%%%') -- Escape &amp;quot;%&amp;quot; with &amp;quot;%%&amp;quot;&lt;br /&gt;
	local invocation, count = self.invocation:gsub(&lt;br /&gt;
		self.cfg.templateNameMagicWordPattern,&lt;br /&gt;
		template&lt;br /&gt;
	)&lt;br /&gt;
	if options.requireMagicWord ~= false and count &amp;lt; 1 then&lt;br /&gt;
		error(self:message(&lt;br /&gt;
			'nowiki-magic-word-error',&lt;br /&gt;
			self.cfg.templateNameMagicWord&lt;br /&gt;
		))&lt;br /&gt;
	end&lt;br /&gt;
	return invocation&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function NowikiInvocation:getOutput(options)&lt;br /&gt;
	local invocation = self:getInvocation(options)&lt;br /&gt;
	return mw.getCurrentFrame():preprocess(invocation)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Table invocation class&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local TableInvocation = {}&lt;br /&gt;
TableInvocation.__index = TableInvocation&lt;br /&gt;
TableInvocation.message = message -- Add the message method&lt;br /&gt;
&lt;br /&gt;
function TableInvocation.new(invokeArgs, nowikiCode, cfg)&lt;br /&gt;
	local obj = setmetatable({}, TableInvocation)&lt;br /&gt;
	obj.cfg = cfg&lt;br /&gt;
	obj.invokeArgs = invokeArgs&lt;br /&gt;
	obj.code = nowikiCode&lt;br /&gt;
	return obj&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TableInvocation:getInvocation(options)&lt;br /&gt;
	if self.code then&lt;br /&gt;
		local nowikiObj = NowikiInvocation.new(self.code, self.cfg)&lt;br /&gt;
		return nowikiObj:getInvocation(options)&lt;br /&gt;
	else&lt;br /&gt;
		return require('Module:Template invocation').invocation(&lt;br /&gt;
			options.template,&lt;br /&gt;
			self.invokeArgs&lt;br /&gt;
		)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TableInvocation:getOutput(options)&lt;br /&gt;
	return mw.getCurrentFrame():expandTemplate{&lt;br /&gt;
		title = options.template,&lt;br /&gt;
		args = self.invokeArgs&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Bridge functions&lt;br /&gt;
--&lt;br /&gt;
-- These functions translate template arguments into forms that can be accepted&lt;br /&gt;
-- by the different classes, and return the results.&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local bridge = {}&lt;br /&gt;
&lt;br /&gt;
function bridge.table(args, cfg)&lt;br /&gt;
	cfg = cfg or mw.loadData(DATA_MODULE)&lt;br /&gt;
&lt;br /&gt;
	local options, invokeArgs = {}, {}&lt;br /&gt;
	for k, v in pairs(args) do&lt;br /&gt;
		local optionKey = type(k) == 'string' and k:match('^_(.*)$')&lt;br /&gt;
		if optionKey then&lt;br /&gt;
			if type(v) == 'string' then&lt;br /&gt;
				v = v:match('^%s*(.-)%s*$') -- trim whitespace&lt;br /&gt;
			end&lt;br /&gt;
			if v ~= '' then&lt;br /&gt;
				options[optionKey] = v&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			invokeArgs[k] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Allow passing a nowiki invocation as an option. While this means users&lt;br /&gt;
	-- have to pass in the code twice, whitespace is preserved and &amp;amp;lt; etc.&lt;br /&gt;
	-- will work as intended.&lt;br /&gt;
	local nowikiCode = options.code&lt;br /&gt;
	options.code = nil&lt;br /&gt;
&lt;br /&gt;
	local invocationObj = TableInvocation.new(invokeArgs, nowikiCode, cfg)&lt;br /&gt;
	local testCaseObj = TestCase.new(invocationObj, options, cfg)&lt;br /&gt;
	return tostring(testCaseObj)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function bridge.nowiki(args, cfg)&lt;br /&gt;
	cfg = cfg or mw.loadData(DATA_MODULE)&lt;br /&gt;
&lt;br /&gt;
	local code = args.code or args[1]&lt;br /&gt;
	local invocationObj = NowikiInvocation.new(code, cfg)&lt;br /&gt;
	args.code = nil&lt;br /&gt;
	args[1] = nil&lt;br /&gt;
	-- Assume we want to see the code as we already passed it in.&lt;br /&gt;
	args.showcode = args.showcode or true&lt;br /&gt;
	local testCaseObj = TestCase.new(invocationObj, args, cfg)&lt;br /&gt;
	return tostring(testCaseObj)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Exports&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
function p.main(frame, cfg)&lt;br /&gt;
	cfg = cfg or mw.loadData(DATA_MODULE)&lt;br /&gt;
&lt;br /&gt;
	-- Load the wrapper config, if any.&lt;br /&gt;
	local wrapperConfig&lt;br /&gt;
	if frame.getParent then&lt;br /&gt;
		local title = frame:getParent():getTitle()&lt;br /&gt;
		local template = title:gsub(cfg.sandboxSubpagePattern, '')&lt;br /&gt;
		wrapperConfig = cfg.wrappers[template]&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Work out the function we will call, use it to generate the config for&lt;br /&gt;
	-- Module:Arguments, and use Module:Arguments to find the arguments passed&lt;br /&gt;
	-- by the user.&lt;br /&gt;
	local func = wrapperConfig and wrapperConfig.func or 'table'&lt;br /&gt;
	local userArgs = require('Module:Arguments').getArgs(frame, {&lt;br /&gt;
		parentOnly = wrapperConfig,&lt;br /&gt;
		frameOnly = not wrapperConfig,&lt;br /&gt;
		trim = func ~= 'table',&lt;br /&gt;
		removeBlanks = func ~= 'table'&lt;br /&gt;
	})&lt;br /&gt;
&lt;br /&gt;
	-- Get default args and build the args table. User-specified args overwrite&lt;br /&gt;
	-- default args.&lt;br /&gt;
	local defaultArgs = wrapperConfig and wrapperConfig.args or {}&lt;br /&gt;
	local args = {}&lt;br /&gt;
	for k, v in pairs(defaultArgs) do&lt;br /&gt;
		args[k] = v&lt;br /&gt;
	end&lt;br /&gt;
	for k, v in pairs(userArgs) do&lt;br /&gt;
		args[k] = v&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return bridge[func](args, cfg)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p._exportClasses() -- For testing&lt;br /&gt;
	return {&lt;br /&gt;
		Template = Template,&lt;br /&gt;
		TestCase = TestCase,&lt;br /&gt;
		NowikiInvocation = NowikiInvocation,&lt;br /&gt;
		TableInvocation = TableInvocation&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>imported&gt;SUM1</name></author>
		
	</entry>
</feed>