<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://the-democratika.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AWikitext_Parsing</id>
	<title>Module:Wikitext Parsing - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://the-democratika.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AWikitext_Parsing"/>
	<link rel="alternate" type="text/html" href="https://the-democratika.com/wiki/index.php?title=Module:Wikitext_Parsing&amp;action=history"/>
	<updated>2026-04-04T13:02:58Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://the-democratika.com/wiki/index.php?title=Module:Wikitext_Parsing&amp;diff=5911&amp;oldid=prev</id>
		<title>&gt;Pppery: Changed protection settings for &quot;Module:Wikitext Parsing&quot;: High-risk template or module: This is, among other things, an indirect dependency of Module:Pagetype and hence Template:Short description, and hence used on 26% of all pages ([Edit=Require administrator access] (indefinite) [Move=Require administrator access] (indefinite))</title>
		<link rel="alternate" type="text/html" href="https://the-democratika.com/wiki/index.php?title=Module:Wikitext_Parsing&amp;diff=5911&amp;oldid=prev"/>
		<updated>2024-02-22T04:42:24Z</updated>

		<summary type="html">&lt;p&gt;Changed protection settings for &amp;quot;&lt;a href=&quot;/wiki/index.php/Module:Wikitext_Parsing&quot; title=&quot;Module:Wikitext Parsing&quot;&gt;Module:Wikitext Parsing&lt;/a&gt;&amp;quot;: &lt;a href=&quot;/wiki/index.php?title=WP:High-risk_templates&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;WP:High-risk templates (page does not exist)&quot;&gt;High-risk template or module&lt;/a&gt;: This is, among other things, an indirect dependency of &lt;a href=&quot;/wiki/index.php/Module:Pagetype&quot; title=&quot;Module:Pagetype&quot;&gt;Module:Pagetype&lt;/a&gt; and hence &lt;a href=&quot;/wiki/index.php/Template:Short_description&quot; title=&quot;Template:Short description&quot;&gt;Template:Short description&lt;/a&gt;, and hence used on 26% of all pages ([Edit=Require administrator access] (indefinite) [Move=Require administrator access] (indefinite))&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;require(&amp;quot;strict&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
--Helper functions&lt;br /&gt;
local function startswith(text, subtext)&lt;br /&gt;
	return string.sub(text, 1, #subtext) == subtext&lt;br /&gt;
end&lt;br /&gt;
local function endswith(text, subtext)&lt;br /&gt;
	return string.sub(text, -#subtext, -1) == subtext&lt;br /&gt;
end&lt;br /&gt;
local function allcases(s)&lt;br /&gt;
	return s:gsub(&amp;quot;%a&amp;quot;, function(c) &lt;br /&gt;
		return &amp;quot;[&amp;quot;..c:upper()..c:lower()..&amp;quot;]&amp;quot;&lt;br /&gt;
	end)&lt;br /&gt;
end&lt;br /&gt;
local trimcache = {}&lt;br /&gt;
local whitespace = {[&amp;quot; &amp;quot;]=1, [&amp;quot;\n&amp;quot;]=1, [&amp;quot;\t&amp;quot;]=1, [&amp;quot;\r&amp;quot;]=1}&lt;br /&gt;
local function cheaptrim(str) --mw.text.trim is surprisingly expensive, so here&amp;#039;s an alternative approach&lt;br /&gt;
	local quick = trimcache[str]&lt;br /&gt;
	if quick then&lt;br /&gt;
		return quick&lt;br /&gt;
	else&lt;br /&gt;
		-- local out = string.gsub(str, &amp;quot;^%s*(.-)%s*$&amp;quot;, &amp;quot;%1&amp;quot;)&lt;br /&gt;
		local lowEnd&lt;br /&gt;
		for i = 1,#str do&lt;br /&gt;
			if not whitespace[string.sub(str, i, i)] then&lt;br /&gt;
				lowEnd = i&lt;br /&gt;
				break&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if not lowEnd then&lt;br /&gt;
			trimcache[str] = &amp;quot;&amp;quot;&lt;br /&gt;
			return &amp;quot;&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		for i = #str,1,-1 do&lt;br /&gt;
			if not whitespace[string.sub(str, i, i)] then&lt;br /&gt;
				local out = string.sub(str, lowEnd, i)&lt;br /&gt;
				trimcache[str] = out&lt;br /&gt;
				return out&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[=[ Implementation notes&lt;br /&gt;
---- NORMAL HTML TAGS ----&lt;br /&gt;
Tags are very strict on how they want to start, but loose on how they end.&lt;br /&gt;
The start must strictly follow &amp;lt;[tAgNaMe](%s|&amp;gt;) with no room for whitespace in&lt;br /&gt;
the tag&amp;#039;s name, but may then flow as they want afterwards, making&lt;br /&gt;
&amp;lt;div\nclass\n=\n&amp;quot;\nerror\n&amp;quot;\n&amp;gt; valid&lt;br /&gt;
&lt;br /&gt;
There&amp;#039;s no sense of escaping &amp;lt; or &amp;gt;&lt;br /&gt;
E.g.&lt;br /&gt;
 &amp;lt;div class=&amp;quot;error\&amp;gt;&amp;quot;&amp;gt; will end at \&amp;gt; despite it being inside a quote&lt;br /&gt;
 &amp;lt;div class=&amp;quot;&amp;lt;span class=&amp;quot;error&amp;quot;&amp;gt;error&amp;lt;/span&amp;gt;&amp;quot;&amp;gt; will not process the larger div&lt;br /&gt;
&lt;br /&gt;
If a tag has no end, it will consume all text instead of not processing&lt;br /&gt;
&lt;br /&gt;
---- NOPROCESSING TAGS (nowiki, pre, syntaxhighlight, source, etc.) ----&lt;br /&gt;
(In most comments, &amp;lt;source&amp;gt; will not be mentioned. This is because it is the&lt;br /&gt;
deprecated version of &amp;lt;syntaxhighlight&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
No-Processing tags have some interesting differences to the above rules.&lt;br /&gt;
For example, their syntax is a lot stricter. While an opening tag appears to&lt;br /&gt;
follow the same set of rules, A closing tag can&amp;#039;t have any sort of extra&lt;br /&gt;
formatting period. While &amp;lt;/div a/a&amp;gt; is valid, &amp;lt;/nowiki a/a&amp;gt; isn&amp;#039;t - only&lt;br /&gt;
newlines and spaces/tabs are allowed in closing tags.&lt;br /&gt;
Note that, even though &amp;lt;pre&amp;gt; tags cause a visual change when the ending tag has&lt;br /&gt;
extra formatting, it won&amp;#039;t cause the no-processing effects. For some reason, the&lt;br /&gt;
format must be strict for that to apply.&lt;br /&gt;
&lt;br /&gt;
Both the content inside the tag pair and the content inside each side of the&lt;br /&gt;
pair is not processed. E.g. &amp;lt;nowiki |}}&amp;gt;|}}&amp;lt;/nowiki&amp;gt; would have both of the |}}&lt;br /&gt;
escaped in practice.&lt;br /&gt;
&lt;br /&gt;
When something in the code is referenced to as a &amp;quot;Nowiki Tag&amp;quot;, it means a tag&lt;br /&gt;
which causes wiki text to not be processed, which includes &amp;lt;nowiki&amp;gt;, &amp;lt;pre&amp;gt;,&lt;br /&gt;
and &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since we only care about these tags, we can ignore the idea of an intercepting&lt;br /&gt;
tag preventing processing, and just go straight for the first ending we can find&lt;br /&gt;
If there is no ending to find, the tag will NOT consume the rest of the text in&lt;br /&gt;
terms of processing behaviour (though &amp;lt;pre&amp;gt; will appear to have an effect).&lt;br /&gt;
Even if there is no end of the tag, the content inside the opening half will&lt;br /&gt;
still be unprocessed, meaning {{X20|&amp;lt;nowiki }}&amp;gt;}} wouldn&amp;#039;t end at the first }}&lt;br /&gt;
despite there being no ending to the tag.&lt;br /&gt;
&lt;br /&gt;
Note that there are some tags, like &amp;lt;math&amp;gt;, which also function like &amp;lt;nowiki&amp;gt;&lt;br /&gt;
which are included in this aswell. Some other tags, like &amp;lt;ref&amp;gt;, have far too&lt;br /&gt;
unpredictable behaviour to be handled currently (they&amp;#039;d have to be split and&lt;br /&gt;
processed as something seperate - its complicated, but maybe not impossible.)&lt;br /&gt;
I suspect that every tag listed in [[Special:Version]] may behave somewhat like&lt;br /&gt;
this, but that&amp;#039;s far too many cases worth checking for rarely used tags that may&lt;br /&gt;
not even have a good reason to contain {{ or }} anyways, so we leave them alone.&lt;br /&gt;
&lt;br /&gt;
---- HTML COMMENTS AND INCLUDEONLY ----&lt;br /&gt;
HTML Comments are about as basic as it could get for this&lt;br /&gt;
Start at &amp;lt;!--, end at --&amp;gt;, no extra conditions. Simple enough&lt;br /&gt;
If a comment has no end, it will eat all text instead of not being processed&lt;br /&gt;
&lt;br /&gt;
includeonly tags function mostly like a regular nowiki tag, with the exception&lt;br /&gt;
that the tag will actually consume all future text if not given an ending as&lt;br /&gt;
opposed to simply giving up and not changing anything. Due to complications and&lt;br /&gt;
the fact that this is far less likely to be present on a page, aswell as being&lt;br /&gt;
something that may not want to be escaped, includeonly tags are ignored during&lt;br /&gt;
our processing&lt;br /&gt;
--]=]&lt;br /&gt;
local validtags = {nowiki=1, pre=1, syntaxhighlight=1, source=1, math=1}&lt;br /&gt;
--This function expects the string to start with the tag&lt;br /&gt;
local function TestForNowikiTag(text, scanPosition)&lt;br /&gt;
	local tagName = (string.match(text, &amp;quot;^&amp;lt;([^\n /&amp;gt;]+)&amp;quot;, scanPosition) or &amp;quot;&amp;quot;):lower()&lt;br /&gt;
	if not validtags[tagName] then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	local nextOpener = string.find(text, &amp;quot;&amp;lt;&amp;quot;, scanPosition+1) or -1&lt;br /&gt;
	local nextCloser = string.find(text, &amp;quot;&amp;gt;&amp;quot;, scanPosition+1) or -1&lt;br /&gt;
	if nextCloser &amp;gt; -1 and (nextOpener == -1 or nextCloser &amp;lt; nextOpener) then&lt;br /&gt;
		local startingTag = string.sub(text, scanPosition, nextCloser)&lt;br /&gt;
		--We have our starting tag (E.g. &amp;#039;&amp;lt;pre style=&amp;quot;color:red&amp;quot;&amp;gt;&amp;#039;)&lt;br /&gt;
		--Now find our ending...&lt;br /&gt;
		if endswith(startingTag, &amp;quot;/&amp;gt;&amp;quot;) then --self-closing tag (we are our own ending)&lt;br /&gt;
			return {&lt;br /&gt;
				Tag = tagName,&lt;br /&gt;
				Start = startingTag,&lt;br /&gt;
				Content = &amp;quot;&amp;quot;, End = &amp;quot;&amp;quot;,&lt;br /&gt;
				Length = #startingTag&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
		else&lt;br /&gt;
			local endingTagStart, endingTagEnd = string.find(text, &amp;quot;&amp;lt;/&amp;quot;..allcases(tagName)..&amp;quot;[ \t\n]*&amp;gt;&amp;quot;, scanPosition)&lt;br /&gt;
			if endingTagStart then --Regular tag formation&lt;br /&gt;
				local endingTag = string.sub(text, endingTagStart, endingTagEnd)&lt;br /&gt;
				local tagContent = string.sub(text, nextCloser+1, endingTagStart-1)&lt;br /&gt;
				return {&lt;br /&gt;
					Tag = tagName,&lt;br /&gt;
					Start = startingTag,&lt;br /&gt;
					Content = tagContent,&lt;br /&gt;
					End = endingTag,&lt;br /&gt;
					Length = #startingTag + #tagContent + #endingTag&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
			else --Content inside still needs escaping (also linter error!)&lt;br /&gt;
				return {&lt;br /&gt;
					Tag = tagName,&lt;br /&gt;
					Start = startingTag,&lt;br /&gt;
					Content = &amp;quot;&amp;quot;, End = &amp;quot;&amp;quot;,&lt;br /&gt;
					Length = #startingTag&lt;br /&gt;
				}&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
local function TestForComment(text, scanPosition) --Like TestForNowikiTag but for &amp;lt;!-- --&amp;gt;&lt;br /&gt;
	if string.match(text, &amp;quot;^&amp;lt;!%-%-&amp;quot;, scanPosition) then&lt;br /&gt;
		local commentEnd = string.find(text, &amp;quot;--&amp;gt;&amp;quot;, scanPosition+4, true)&lt;br /&gt;
		if commentEnd then&lt;br /&gt;
			return {&lt;br /&gt;
				Start = &amp;quot;&amp;lt;!--&amp;quot;, End = &amp;quot;--&amp;gt;&amp;quot;,&lt;br /&gt;
				Content = string.sub(text, scanPosition+4, commentEnd-1),&lt;br /&gt;
				Length = commentEnd-scanPosition+3&lt;br /&gt;
			}&lt;br /&gt;
		else --Consumes all text if not given an ending&lt;br /&gt;
			return {&lt;br /&gt;
				Start = &amp;quot;&amp;lt;!--&amp;quot;, End = &amp;quot;&amp;quot;,&lt;br /&gt;
				Content = string.sub(text, scanPosition+4),&lt;br /&gt;
				Length = #text-scanPosition+1&lt;br /&gt;
			}&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[ Implementation notes&lt;br /&gt;
The goal of this function is to escape all text that wouldn&amp;#039;t be parsed if it&lt;br /&gt;
was preprocessed (see above implementation notes).&lt;br /&gt;
&lt;br /&gt;
Using keepComments will keep all HTML comments instead of removing them. They&lt;br /&gt;
will still be escaped regardless to avoid processing errors&lt;br /&gt;
--]]&lt;br /&gt;
local function PrepareText(text, keepComments)&lt;br /&gt;
	local newtext = {}&lt;br /&gt;
	local scanPosition = 1&lt;br /&gt;
	while true do&lt;br /&gt;
		local NextCheck = string.find(text, &amp;quot;&amp;lt;[NnSsPpMm!]&amp;quot;, scanPosition) --Advance to the next potential tag we care about&lt;br /&gt;
		if not NextCheck then --Done&lt;br /&gt;
			newtext[#newtext+1] =  string.sub(text,scanPosition)&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
		newtext[#newtext+1] = string.sub(text,scanPosition,NextCheck-1)&lt;br /&gt;
		scanPosition = NextCheck&lt;br /&gt;
		local Comment = TestForComment(text, scanPosition)&lt;br /&gt;
		if Comment then&lt;br /&gt;
			if keepComments then&lt;br /&gt;
				newtext[#newtext+1] = Comment.Start .. mw.text.nowiki(Comment.Content) .. Comment.End&lt;br /&gt;
			end&lt;br /&gt;
			scanPosition = scanPosition + Comment.Length&lt;br /&gt;
		else&lt;br /&gt;
			local Tag = TestForNowikiTag(text, scanPosition)&lt;br /&gt;
			if Tag then&lt;br /&gt;
				local newTagStart = &amp;quot;&amp;lt;&amp;quot; .. mw.text.nowiki(string.sub(Tag.Start,2,-2)) .. &amp;quot;&amp;gt;&amp;quot;&lt;br /&gt;
				local newTagEnd = &lt;br /&gt;
					Tag.End == &amp;quot;&amp;quot; and &amp;quot;&amp;quot; or --Respect no tag ending&lt;br /&gt;
					&amp;quot;&amp;lt;/&amp;quot; .. mw.text.nowiki(string.sub(Tag.End,3,-2)) .. &amp;quot;&amp;gt;&amp;quot;&lt;br /&gt;
				local newContent = mw.text.nowiki(Tag.Content)&lt;br /&gt;
				newtext[#newtext+1] = newTagStart .. newContent .. newTagEnd&lt;br /&gt;
				scanPosition = scanPosition + Tag.Length&lt;br /&gt;
			else --Nothing special, move on...&lt;br /&gt;
				newtext[#newtext+1] = string.sub(text, scanPosition, scanPosition)&lt;br /&gt;
				scanPosition = scanPosition + 1&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(newtext, &amp;quot;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[=[ Implementation notes&lt;br /&gt;
This function is an alternative to Transcluder&amp;#039;s getParameters which considers&lt;br /&gt;
the potential for a singular { or } or other odd syntax that %b doesn&amp;#039;t like to&lt;br /&gt;
be in a parameter&amp;#039;s value.&lt;br /&gt;
&lt;br /&gt;
When handling the difference between {{ and {{{, mediawiki will attempt to match&lt;br /&gt;
as many sequences of {{{ as possible before matching a {{&lt;br /&gt;
E.g.&lt;br /&gt;
 {{{{A}}}} -&amp;gt; { {{{A}}} }&lt;br /&gt;
 {{{{{{{{Text|A}}}}}}}} -&amp;gt; {{ {{{ {{{Text|A}}} }}} }}&lt;br /&gt;
If there aren&amp;#039;t enough triple braces on both sides, the parser will compromise&lt;br /&gt;
for a template interpretation.&lt;br /&gt;
E.g.&lt;br /&gt;
 {{{{A}} }} -&amp;gt; {{ {{ A }} }}&lt;br /&gt;
&lt;br /&gt;
While there are technically concerns about things such as wikilinks breaking&lt;br /&gt;
template processing (E.g. {{[[}}]]}} doesn&amp;#039;t stop at the first }}), it shouldn&amp;#039;t&lt;br /&gt;
be our job to process inputs perfectly when the input has garbage ({ / } isn&amp;#039;t&lt;br /&gt;
legal in titles anyways, so if something&amp;#039;s unmatched in a wikilink, it&amp;#039;s&lt;br /&gt;
guaranteed GIGO)&lt;br /&gt;
&lt;br /&gt;
Setting dontEscape will prevent running the input text through EET. Avoid&lt;br /&gt;
setting this to true if you don&amp;#039;t have to set it.&lt;br /&gt;
&lt;br /&gt;
Returned values:&lt;br /&gt;
A table of all templates. Template data goes as follows:&lt;br /&gt;
 Text: The raw text of the template&lt;br /&gt;
 Name: The name of the template&lt;br /&gt;
 Args: A list of arguments&lt;br /&gt;
 Children: A list of immediate template children&lt;br /&gt;
--]=]&lt;br /&gt;
--Helper functions&lt;br /&gt;
local function boundlen(pair)&lt;br /&gt;
	return pair.End-pair.Start+1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--Main function&lt;br /&gt;
local function ParseTemplates(InputText, dontEscape)&lt;br /&gt;
	--Setup&lt;br /&gt;
	if not dontEscape then&lt;br /&gt;
		InputText = PrepareText(InputText)&lt;br /&gt;
	end&lt;br /&gt;
	local function finalise(text)&lt;br /&gt;
		if not dontEscape then&lt;br /&gt;
			return mw.text.decode(text)&lt;br /&gt;
		else&lt;br /&gt;
			return text&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local function CreateContainerObj(Container)&lt;br /&gt;
		Container.Text = {}&lt;br /&gt;
		Container.Args = {}&lt;br /&gt;
		Container.ArgOrder = {}&lt;br /&gt;
		Container.Children = {}&lt;br /&gt;
		-- Container.Name = nil&lt;br /&gt;
		-- Container.Value = nil&lt;br /&gt;
		-- Container.Key = nil&lt;br /&gt;
		Container.BeyondStart = false&lt;br /&gt;
		Container.LastIndex = 1&lt;br /&gt;
		Container.finalise = finalise&lt;br /&gt;
		function Container:HandleArgInput(character, internalcall)&lt;br /&gt;
			if not internalcall then&lt;br /&gt;
				self.Text[#self.Text+1] = character&lt;br /&gt;
			end&lt;br /&gt;
			if character == &amp;quot;=&amp;quot; then&lt;br /&gt;
				if self.Key then&lt;br /&gt;
					self.Value[#self.Value+1] = character&lt;br /&gt;
				else&lt;br /&gt;
					self.Key = cheaptrim(self.Value and table.concat(self.Value, &amp;quot;&amp;quot;) or &amp;quot;&amp;quot;)&lt;br /&gt;
					self.Value = {}&lt;br /&gt;
				end&lt;br /&gt;
			else --&amp;quot;|&amp;quot; or &amp;quot;}&amp;quot;&lt;br /&gt;
				if not self.Name then&lt;br /&gt;
					self.Name = cheaptrim(self.Value and table.concat(self.Value, &amp;quot;&amp;quot;) or &amp;quot;&amp;quot;)&lt;br /&gt;
					self.Value = nil&lt;br /&gt;
				else&lt;br /&gt;
					self.Value = self.finalise(self.Value and table.concat(self.Value, &amp;quot;&amp;quot;) or &amp;quot;&amp;quot;)&lt;br /&gt;
					if self.Key then&lt;br /&gt;
						self.Key = self.finalise(self.Key)&lt;br /&gt;
						self.Args[self.Key] = cheaptrim(self.Value)&lt;br /&gt;
						self.ArgOrder[#self.ArgOrder+1] = self.Key&lt;br /&gt;
					else&lt;br /&gt;
						local Key = tostring(self.LastIndex)&lt;br /&gt;
						self.Args[Key] = self.Value&lt;br /&gt;
						self.ArgOrder[#self.ArgOrder+1] = Key&lt;br /&gt;
						self.LastIndex = self.LastIndex + 1&lt;br /&gt;
					end&lt;br /&gt;
					self.Key = nil&lt;br /&gt;
					self.Value = nil&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		function Container:AppendText(text, ftext)&lt;br /&gt;
			self.Text[#self.Text+1] = (ftext or text)&lt;br /&gt;
			if not self.Value then&lt;br /&gt;
				self.Value = {}&lt;br /&gt;
			end&lt;br /&gt;
			self.BeyondStart = self.BeyondStart or (#table.concat(self.Text, &amp;quot;&amp;quot;) &amp;gt; 2)&lt;br /&gt;
			if self.BeyondStart then&lt;br /&gt;
				self.Value[#self.Value+1] = text&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		function Container:Clean(IsTemplate)&lt;br /&gt;
			self.Text = table.concat(self.Text, &amp;quot;&amp;quot;)&lt;br /&gt;
			if self.Value and IsTemplate then&lt;br /&gt;
				self.Value = {string.sub(table.concat(self.Value, &amp;quot;&amp;quot;), 1, -3)} --Trim ending }}&lt;br /&gt;
				self:HandleArgInput(&amp;quot;|&amp;quot;, true) --Simulate ending&lt;br /&gt;
			end&lt;br /&gt;
			self.Value = nil&lt;br /&gt;
			self.Key = nil&lt;br /&gt;
			self.BeyondStart = nil&lt;br /&gt;
			self.LastIndex = nil&lt;br /&gt;
			self.finalise = nil&lt;br /&gt;
			self.HandleArgInput = nil&lt;br /&gt;
			self.AppendText = nil&lt;br /&gt;
			self.Clean = nil&lt;br /&gt;
		end&lt;br /&gt;
		return Container&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	--Step 1: Find and escape the content of all wikilinks on the page, which are stronger than templates (see implementation notes)&lt;br /&gt;
	local scannerPosition = 1&lt;br /&gt;
	local wikilinks = {}&lt;br /&gt;
	local openWikilinks = {}&lt;br /&gt;
	while true do&lt;br /&gt;
		local Position, _, Character = string.find(InputText, &amp;quot;([%[%]])%1&amp;quot;, scannerPosition)&lt;br /&gt;
		if not Position then --Done&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		scannerPosition = Position+2 --+2 to pass the [[ / ]]&lt;br /&gt;
		if Character == &amp;quot;[&amp;quot; then --Add a [[ to the pending wikilink queue&lt;br /&gt;
			openWikilinks[#openWikilinks+1] = Position&lt;br /&gt;
		else --Pair up the ]] to any available [[&lt;br /&gt;
			if #openWikilinks &amp;gt;= 1 then&lt;br /&gt;
				local start = table.remove(openWikilinks) --Pop the latest [[&lt;br /&gt;
				wikilinks[start] = {Start=start, End=Position+1, Type=&amp;quot;Wikilink&amp;quot;} --Note the pair&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	--Step 2: Find the bounds of every valid template and variable ({{ and {{{)&lt;br /&gt;
	local scannerPosition = 1&lt;br /&gt;
	local templates = {}&lt;br /&gt;
	local variables = {}&lt;br /&gt;
	local openBrackets = {}&lt;br /&gt;
	while true do&lt;br /&gt;
		local Start, _, Character = string.find(InputText, &amp;quot;([{}])%1&amp;quot;, scannerPosition)&lt;br /&gt;
		if not Start then --Done (both 9e9)&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
		local _, End = string.find(InputText, &amp;quot;^&amp;quot;..Character..&amp;quot;+&amp;quot;, Start)&lt;br /&gt;
&lt;br /&gt;
		scannerPosition = Start --Get to the {{ / }} set&lt;br /&gt;
		if Character == &amp;quot;{&amp;quot; then --Add the {{+ set to the queue&lt;br /&gt;
			openBrackets[#openBrackets+1] = {Start=Start, End=End}&lt;br /&gt;
&lt;br /&gt;
		else --Pair up the }} to any available {{, accounting for {{{ / }}}&lt;br /&gt;
			local BracketCount = End-Start+1&lt;br /&gt;
			while BracketCount &amp;gt;= 2 and #openBrackets &amp;gt;= 1 do&lt;br /&gt;
				local OpenSet = table.remove(openBrackets)&lt;br /&gt;
				if boundlen(OpenSet) &amp;gt;= 3 and BracketCount &amp;gt;= 3 then --We have a {{{variable}}} (both sides have 3 spare)&lt;br /&gt;
					variables[OpenSet.End-2] = {Start=OpenSet.End-2, End=scannerPosition+2, Type=&amp;quot;Variable&amp;quot;} --Done like this to ensure chronological order&lt;br /&gt;
					BracketCount = BracketCount - 3&lt;br /&gt;
					OpenSet.End = OpenSet.End - 3&lt;br /&gt;
					scannerPosition = scannerPosition + 3&lt;br /&gt;
&lt;br /&gt;
				else --We have a {{template}} (both sides have 2 spare, but at least one side doesn&amp;#039;t have 3 spare)&lt;br /&gt;
					templates[OpenSet.End-1] = {Start=OpenSet.End-1, End=scannerPosition+1, Type=&amp;quot;Template&amp;quot;} --Done like this to ensure chronological order&lt;br /&gt;
					BracketCount = BracketCount - 2&lt;br /&gt;
					OpenSet.End = OpenSet.End - 2&lt;br /&gt;
					scannerPosition = scannerPosition + 2&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
				if boundlen(OpenSet) &amp;gt;= 2 then --Still has enough data left, leave it in&lt;br /&gt;
					openBrackets[#openBrackets+1] = OpenSet&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		scannerPosition = End --Now move past the bracket set&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	--Step 3: Re-trace every object using their known bounds, collecting our parameters with (slight) ease&lt;br /&gt;
	local scannerPosition = 1&lt;br /&gt;
	local activeObjects = {}&lt;br /&gt;
	local finalObjects = {}&lt;br /&gt;
	while true do&lt;br /&gt;
		local LatestObject = activeObjects[#activeObjects] --Commonly needed object&lt;br /&gt;
		local NNC, _, Character --NNC = NextNotableCharacter&lt;br /&gt;
		if LatestObject then&lt;br /&gt;
			NNC, _, Character = string.find(InputText, &amp;quot;([{}%[%]|=])&amp;quot;, scannerPosition)&lt;br /&gt;
		else&lt;br /&gt;
			NNC, _, Character = string.find(InputText, &amp;quot;([{}])&amp;quot;, scannerPosition) --We are only after templates right now&lt;br /&gt;
		end&lt;br /&gt;
		if not NNC then&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
		if NNC &amp;gt; scannerPosition and LatestObject then&lt;br /&gt;
			local scannedContent = string.sub(InputText, scannerPosition, NNC-1)&lt;br /&gt;
			LatestObject:AppendText(scannedContent, finalise(scannedContent))&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		scannerPosition = NNC+1&lt;br /&gt;
		if Character == &amp;quot;{&amp;quot; or Character == &amp;quot;[&amp;quot; then&lt;br /&gt;
			local Container = templates[NNC] or variables[NNC] or wikilinks[NNC]&lt;br /&gt;
			if Container then&lt;br /&gt;
				CreateContainerObj(Container)&lt;br /&gt;
				if Container.Type == &amp;quot;Template&amp;quot; then&lt;br /&gt;
					Container:AppendText(&amp;quot;{{&amp;quot;)&lt;br /&gt;
					scannerPosition = NNC+2&lt;br /&gt;
				elseif Container.Type == &amp;quot;Variable&amp;quot; then&lt;br /&gt;
					Container:AppendText(&amp;quot;{{{&amp;quot;)&lt;br /&gt;
					scannerPosition = NNC+3&lt;br /&gt;
				else --Wikilink&lt;br /&gt;
					Container:AppendText(&amp;quot;[[&amp;quot;)&lt;br /&gt;
					scannerPosition = NNC+2&lt;br /&gt;
				end&lt;br /&gt;
				if LatestObject and Container.Type == &amp;quot;Template&amp;quot; then --Only templates count as children&lt;br /&gt;
					LatestObject.Children[#LatestObject.Children+1] = Container&lt;br /&gt;
				end&lt;br /&gt;
				activeObjects[#activeObjects+1] = Container&lt;br /&gt;
			elseif LatestObject then&lt;br /&gt;
				LatestObject:AppendText(Character)&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
		elseif Character == &amp;quot;}&amp;quot; or Character == &amp;quot;]&amp;quot; then&lt;br /&gt;
			if LatestObject then&lt;br /&gt;
				LatestObject:AppendText(Character)&lt;br /&gt;
				if LatestObject.End == NNC then&lt;br /&gt;
					if LatestObject.Type == &amp;quot;Template&amp;quot; then&lt;br /&gt;
						LatestObject:Clean(true)&lt;br /&gt;
						finalObjects[#finalObjects+1] = LatestObject&lt;br /&gt;
					else&lt;br /&gt;
						LatestObject:Clean(false)&lt;br /&gt;
					end&lt;br /&gt;
					activeObjects[#activeObjects] = nil&lt;br /&gt;
					local NewLatest = activeObjects[#activeObjects]&lt;br /&gt;
					if NewLatest then&lt;br /&gt;
						NewLatest:AppendText(LatestObject.Text) --Append to new latest&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
		else --| or =&lt;br /&gt;
			if LatestObject then&lt;br /&gt;
				LatestObject:HandleArgInput(Character)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	--Step 4: Fix the order&lt;br /&gt;
	local FixedOrder = {}&lt;br /&gt;
	local SortableReference = {}&lt;br /&gt;
	for _,Object in next,finalObjects do&lt;br /&gt;
		SortableReference[#SortableReference+1] = Object.Start&lt;br /&gt;
	end&lt;br /&gt;
	table.sort(SortableReference)&lt;br /&gt;
	for i = 1,#SortableReference do&lt;br /&gt;
		local start = SortableReference[i]&lt;br /&gt;
		for n,Object in next,finalObjects do&lt;br /&gt;
			if Object.Start == start then&lt;br /&gt;
				finalObjects[n] = nil&lt;br /&gt;
				Object.Start = nil --Final cleanup&lt;br /&gt;
				Object.End = nil&lt;br /&gt;
				Object.Type = nil&lt;br /&gt;
				FixedOrder[#FixedOrder+1] = Object&lt;br /&gt;
				break&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	--Finished, return&lt;br /&gt;
	return FixedOrder&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
--Main entry points&lt;br /&gt;
p.PrepareText = PrepareText&lt;br /&gt;
p.ParseTemplates = ParseTemplates&lt;br /&gt;
--Extra entry points, not really required&lt;br /&gt;
p.TestForNowikiTag = TestForNowikiTag&lt;br /&gt;
p.TestForComment = TestForComment&lt;br /&gt;
&lt;br /&gt;
return p&lt;br /&gt;
&lt;br /&gt;
--[==[ console tests&lt;br /&gt;
&lt;br /&gt;
local s = [=[Hey!{{Text|&amp;lt;nowiki | ||&amp;gt;&lt;br /&gt;
Hey! }}&lt;br /&gt;
A&amp;lt;/nowiki&amp;gt;|&amp;lt;!--AAAAA|AAA--&amp;gt;Should see|Shouldn&amp;#039;t see}}]=]&lt;br /&gt;
local out = p.PrepareText(s)&lt;br /&gt;
mw.logObject(out)&lt;br /&gt;
&lt;br /&gt;
local s = [=[B&amp;lt;!--&lt;br /&gt;
Hey!&lt;br /&gt;
--&amp;gt;A]=]&lt;br /&gt;
local out = p.TestForComment(s, 2)&lt;br /&gt;
mw.logObject(out); mw.log(string.sub(s, 2, out.Length))&lt;br /&gt;
&lt;br /&gt;
local a = p.ParseTemplates([=[&lt;br /&gt;
{{User:Aidan9382/templates/dummy&lt;br /&gt;
|A|B|C {{{A|B}}} { } } {&lt;br /&gt;
|&amp;lt;nowiki&amp;gt;D&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|&amp;lt;pre&amp;gt;E&lt;br /&gt;
|F&amp;lt;/pre&amp;gt;&lt;br /&gt;
|G|=|a=|A  =  [[{{PAGENAME}}|A=B]]{{Text|1==&amp;lt;nowiki&amp;gt;}}&amp;lt;/nowiki&amp;gt;}}|A B=Success}}&lt;br /&gt;
]=])&lt;br /&gt;
mw.logObject(a)&lt;br /&gt;
&lt;br /&gt;
]==]&lt;/div&gt;</summary>
		<author><name>&gt;Pppery</name></author>
	</entry>
</feed>