From Wikipedia, the free encyclopedia

require("strict")



--Helper functions

local function startswith(text, subtext)

	return string.sub(text, 1, #subtext) == subtext

end

local function endswith(text, subtext)

	return string.sub(text, -#subtext, -1) == subtext

end

local function allcases(s)

	return s:gsub("%a", function(c) 

		return "["..c:upper()..c:lower().."]"

	end)

end

local trimcache = {}

local whitespace = {[" "=1, "\n"=1, "\t"=1, "\r"=1}

local function cheaptrim(str) --mw.text.trim is surprisingly expensive, so here's an alternative approach

	local quick = trimcachestr

	if quick then

		return quick

	else

		-- local out = string.gsub(str, "^%s*(.-)%s*$", "%1")

		local lowEnd

		for i = 1,#str do

			if not whitespacestring.sub(str, i, i)] then

				lowEnd = i

				break

			end

		end

		if not lowEnd then

			trimcachestr = ""

			return ""

		end

		for i = #str,1,-1 do

			if not whitespacestring.sub(str, i, i)] then

				local out = string.sub(str, lowEnd, i)

				trimcachestr = out

				return out

			end

		end

	end

end



--[=[ Implementation notes

---- NORMAL HTML TAGS ----

Tags are very strict on how they want to start, but loose on how they end.

The start must strictly follow <[tAgNaMe](%s|>) with no room for whitespace in

the tag's name, but may then flow as they want afterwards, making

<div\nclass\n=\n"\nerror\n"\n> valid



There's no sense of escaping < or >

E.g.

 <div class="error\>"> will end at \> despite it being inside a quote

 <div class="<span class="error">error</span>"> will not process the larger div



If a tag has no end, it will consume all text instead of not processing



---- NOPROCESSING TAGS (nowiki, pre, syntaxhighlight, source, etc.) ----

(In most comments, <source> will not be mentioned. This is because it is the

deprecated version of <syntaxhighlight>)



No-Processing tags have some interesting differences to the above rules.

For example, their syntax is a lot stricter. While an opening tag appears to

follow the same set of rules, A closing tag can't have any sort of extra

formatting period. While </div a/a> is valid, </nowiki a/a> isn't - only

newlines and spaces/tabs are allowed in closing tags.

Note that, even though <pre> tags cause a visual change when the ending tag has

extra formatting, it won't cause the no-processing effects. For some reason, the

format must be strict for that to apply.



Both the content inside the tag pair and the content inside each side of the

pair is not processed. E.g. <nowiki |}}>|}}</nowiki> would have both of the |}}

escaped in practice.



When something in the code is referenced to as a "Nowiki Tag", it means a tag

which causes wiki text to not be processed, which includes <nowiki>, <pre>,

and <syntaxhighlight>



Since we only care about these tags, we can ignore the idea of an intercepting

tag preventing processing, and just go straight for the first ending we can find

If there is no ending to find, the tag will NOT consume the rest of the text in

terms of processing behaviour (though <pre> will appear to have an effect).

Even if there is no end of the tag, the content inside the opening half will

still be unprocessed, meaning {{X20|<nowiki }}>}} wouldn't end at the first }}

despite there being no ending to the tag.



Note that there are some tags, like <math>, which also function like <nowiki>

which are included in this aswell. Some other tags, like <ref>, have far too

unpredictable behaviour to be handled currently (they'd have to be split and

processed as something seperate - its complicated, but maybe not impossible.)

I suspect that every tag listed in [[Special:Version]] may behave somewhat like

this, but that's far too many cases worth checking for rarely used tags that may

not even have a good reason to contain {{ or }} anyways, so we leave them alone.



---- HTML COMMENTS AND INCLUDEONLY ----

HTML Comments are about as basic as it could get for this

Start at <!--, end at -->, no extra conditions. Simple enough

If a comment has no end, it will eat all text instead of not being processed



includeonly tags function mostly like a regular nowiki tag, with the exception

that the tag will actually consume all future text if not given an ending as

opposed to simply giving up and not changing anything. Due to complications and

the fact that this is far less likely to be present on a page, aswell as being

something that may not want to be escaped, includeonly tags are ignored during

our processing

--]=]

local validtags = {nowiki=1, pre=1, syntaxhighlight=1, source=1, math=1}

--This function expects the string to start with the tag

local function TestForNowikiTag(text, scanPosition)

	local tagName = (string.match(text, "^<([^\n />]+)", scanPosition) or ""):lower()

	if not validtagstagName then

		return nil

	end

	local nextOpener = string.find(text, "<", scanPosition+1) or -1

	local nextCloser = string.find(text, ">", scanPosition+1) or -1

	if nextCloser > -1 and (nextOpener == -1 or nextCloser < nextOpener) then

		local startingTag = string.sub(text, scanPosition, nextCloser)

		--We have our starting tag (E.g. '<pre style="color:red">')

		--Now find our ending...

		if endswith(startingTag, "/>") then --self-closing tag (we are our own ending)

			return {

				Tag = tagName,

				Start = startingTag,

				Content = "", End = "",

				Length = #startingTag

			}



		else

			local endingTagStart, endingTagEnd = string.find(text, "</"..allcases(tagName).."[ \t\n]*>", scanPosition)

			if endingTagStart then --Regular tag formation

				local endingTag = string.sub(text, endingTagStart, endingTagEnd)

				local tagContent = string.sub(text, nextCloser+1, endingTagStart-1)

				return {

					Tag = tagName,

					Start = startingTag,

					Content = tagContent,

					End = endingTag,

					Length = #startingTag + #tagContent + #endingTag

				}



			else --Content inside still needs escaping (also linter error!)

				return {

					Tag = tagName,

					Start = startingTag,

					Content = "", End = "",

					Length = #startingTag

				}

			end

		end

	end

	return nil

end

local function TestForComment(text, scanPosition) --Like TestForNowikiTag but for <!-- -->

	if string.match(text, "^<!%-%-", scanPosition) then

		local commentEnd = string.find(text, "-->", scanPosition+4, true)

		if commentEnd then

			return {

				Start = "<!--", End = "-->",

				Content = string.sub(text, scanPosition+4, commentEnd-1),

				Length = commentEnd-scanPosition+3

			}

		else --Consumes all text if not given an ending

			return {

				Start = "<!--", End = "",

				Content = string.sub(text, scanPosition+4),

				Length = #text-scanPosition+1

			}

		end

	end

	return nil

end



--[[ Implementation notes

The goal of this function is to escape all text that wouldn't be parsed if it

was preprocessed (see above implementation notes).



Using keepComments will keep all HTML comments instead of removing them. They

will still be escaped regardless to avoid processing errors

--]]

local function PrepareText(text, keepComments)

	local newtext = {}

	local scanPosition = 1

	while true do

		local NextCheck = string.find(text, "<[NnSsPpMm!]", scanPosition) --Advance to the next potential tag we care about

		if not NextCheck then --Done

			newtext#newtext+1 =  string.sub(text,scanPosition)

			break

		end

		newtext#newtext+1 = string.sub(text,scanPosition,NextCheck-1)

		scanPosition = NextCheck

		local Comment = TestForComment(text, scanPosition)

		if Comment then

			if keepComments then

				newtext#newtext+1 = Comment.Start .. mw.text.nowiki(Comment.Content) .. Comment.End

			end

			scanPosition = scanPosition + Comment.Length

		else

			local Tag = TestForNowikiTag(text, scanPosition)

			if Tag then

				local newTagStart = "<" .. mw.text.nowiki(string.sub(Tag.Start,2,-2)) .. ">"

				local newTagEnd = 

					Tag.End == "" and "" or --Respect no tag ending

					"</" .. mw.text.nowiki(string.sub(Tag.End,3,-2)) .. ">"

				local newContent = mw.text.nowiki(Tag.Content)

				newtext#newtext+1 = newTagStart .. newContent .. newTagEnd

				scanPosition = scanPosition + Tag.Length

			else --Nothing special, move on...

				newtext#newtext+1 = string.sub(text, scanPosition, scanPosition)

				scanPosition = scanPosition + 1

			end

		end

	end

	return table.concat(newtext, "")

end



--[=[ Implementation notes

This function is an alternative to Transcluder's getParameters which considers

the potential for a singular { or } or other odd syntax that %b doesn't like to

be in a parameter's value.



When handling the difference between {{ and {{{, mediawiki will attempt to match

as many sequences of {{{ as possible before matching a {{

E.g.

 {{{{A}}}} -> { {{{A}}} }

 {{{{{{{{Text|A}}}}}}}} -> {{ {{{ {{{Text|A}}} }}} }}

If there aren't enough triple braces on both sides, the parser will compromise

for a template interpretation.

E.g.

 {{{{A}} }} -> {{ {{ A }} }}



While there are technically concerns about things such as wikilinks breaking

template processing (E.g. {{[[}}]]}} doesn't stop at the first }}), it shouldn't

be our job to process inputs perfectly when the input has garbage ({ / } isn't

legal in titles anyways, so if something's unmatched in a wikilink, it's

guaranteed GIGO)



Setting dontEscape will prevent running the input text through EET. Avoid

setting this to true if you don't have to set it.



Returned values:

A table of all templates. Template data goes as follows:

 Text: The raw text of the template

 Name: The name of the template

 Args: A list of arguments

 Children: A list of immediate template children

--]=]

--Helper functions

local function boundlen(pair)

	return pair.End-pair.Start+1

end



--Main function

local function ParseTemplates(InputText, dontEscape)

	--Setup

	if not dontEscape then

		InputText = PrepareText(InputText)

	end

	local function finalise(text)

		if not dontEscape then

			return mw.text.decode(text)

		else

			return text

		end

	end

	local function CreateContainerObj(Container)

		Container.Text = {}

		Container.Args = {}

		Container.ArgOrder = {}

		Container.Children = {}

		-- Container.Name = nil

		-- Container.Value = nil

		-- Container.Key = nil

		Container.BeyondStart = false

		Container.LastIndex = 1

		Container.finalise = finalise

		function Container:HandleArgInput(character, internalcall)

			if not internalcall then

				self.Text#self.Text+1 = character

			end

			if character == "=" then

				if self.Key then

					self.Value#self.Value+1 = character

				else

					self.Key = cheaptrim(self.Value and table.concat(self.Value, "") or "")

					self.Value = {}

				end

			else --"|" or "}"

				if not self.Name then

					self.Name = cheaptrim(self.Value and table.concat(self.Value, "") or "")

					self.Value = nil

				else

					self.Value = self.finalise(self.Value and table.concat(self.Value, "") or "")

					if self.Key then

						self.Key = self.finalise(self.Key)

						self.Argsself.Key = cheaptrim(self.Value)

						self.ArgOrder#self.ArgOrder+1 = self.Key

					else

						local Key = tostring(self.LastIndex)

						self.ArgsKey = self.Value

						self.ArgOrder#self.ArgOrder+1 = Key

						self.LastIndex = self.LastIndex + 1

					end

					self.Key = nil

					self.Value = nil

				end

			end

		end

		function Container:AppendText(text, ftext)

			self.Text#self.Text+1 = (ftext or text)

			if not self.Value then

				self.Value = {}

			end

			self.BeyondStart = self.BeyondStart or (#table.concat(self.Text, "") > 2)

			if self.BeyondStart then

				self.Value#self.Value+1 = text

			end

		end

		function Container:Clean(IsTemplate)

			self.Text = table.concat(self.Text, "")

			if self.Value and IsTemplate then

				self.Value = {string.sub(table.concat(self.Value, ""), 1, -3)} --Trim ending }}

				self:HandleArgInput("|", true) --Simulate ending

			end

			self.Value = nil

			self.Key = nil

			self.BeyondStart = nil

			self.LastIndex = nil

			self.finalise = nil

			self.HandleArgInput = nil

			self.AppendText = nil

			self.Clean = nil

		end

		return Container

	end

	

	--Step 1: Find and escape the content of all wikilinks on the page, which are stronger than templates (see implementation notes)

	local scannerPosition = 1

	local wikilinks = {}

	local openWikilinks = {}

	while true do

		local Position, _, Character = string.find(InputText, "([%[%]])%1", scannerPosition)

		if not Position then --Done

			break

		end



		scannerPosition = Position+2 --+2 to pass the [[ / ]]

		if Character == "[" then --Add a [[ to the pending wikilink queue

			openWikilinks#openWikilinks+1 = Position

		else --Pair up the ]] to any available [[

			if #openWikilinks >= 1 then

				local start = table.remove(openWikilinks) --Pop the latest [[

				wikilinksstart = {Start=start, End=Position+1, Type="Wikilink"} --Note the pair

			end

		end

	end

	

	--Step 2: Find the bounds of every valid template and variable ({{ and {{{)

	local scannerPosition = 1

	local templates = {}

	local variables = {}

	local openBrackets = {}

	while true do

		local Start, _, Character = string.find(InputText, "([{}])%1", scannerPosition)

		if not Start then --Done (both 9e9)

			break

		end

		local _, End = string.find(InputText, "^"..Character.."+", Start)



		scannerPosition = Start --Get to the {{ / }} set

		if Character == "{" then --Add the {{+ set to the queue

			openBrackets#openBrackets+1 = {Start=Start, End=End}



		else --Pair up the }} to any available {{, accounting for {{{ / }}}

			local BracketCount = End-Start+1

			while BracketCount >= 2 and #openBrackets >= 1 do

				local OpenSet = table.remove(openBrackets)

				if boundlen(OpenSet) >= 3 and BracketCount >= 3 then --We have a {{{variable}}} (both sides have 3 spare)

					variablesOpenSet.End-2 = {Start=OpenSet.End-2, End=scannerPosition+2, Type="Variable"} --Done like this to ensure chronological order

					BracketCount = BracketCount - 3

					OpenSet.End = OpenSet.End - 3

					scannerPosition = scannerPosition + 3



				else --We have a {{template}} (both sides have 2 spare, but at least one side doesn't have 3 spare)

					templatesOpenSet.End-1 = {Start=OpenSet.End-1, End=scannerPosition+1, Type="Template"} --Done like this to ensure chronological order

					BracketCount = BracketCount - 2

					OpenSet.End = OpenSet.End - 2

					scannerPosition = scannerPosition + 2

				end



				if boundlen(OpenSet) >= 2 then --Still has enough data left, leave it in

					openBrackets#openBrackets+1 = OpenSet

				end

			end

		end

		scannerPosition = End --Now move past the bracket set

	end

	

	--Step 3: Re-trace every object using their known bounds, collecting our parameters with (slight) ease

	local scannerPosition = 1

	local activeObjects = {}

	local finalObjects = {}

	while true do

		local LatestObject = activeObjects#activeObjects --Commonly needed object

		local NNC, _, Character --NNC = NextNotableCharacter

		if LatestObject then

			NNC, _, Character = string.find(InputText, "([{}%[%]|=])", scannerPosition)

		else

			NNC, _, Character = string.find(InputText, "([{}])", scannerPosition) --We are only after templates right now

		end

		if not NNC then

			break

		end

		if NNC > scannerPosition and LatestObject then

			local scannedContent = string.sub(InputText, scannerPosition, NNC-1)

			LatestObject:AppendText(scannedContent, finalise(scannedContent))

		end



		scannerPosition = NNC+1

		if Character == "{" or Character == "[" then

			local Container = templatesNNC or variablesNNC or wikilinksNNC

			if Container then

				CreateContainerObj(Container)

				if Container.Type == "Template" then

					Container:AppendText("{{")

					scannerPosition = NNC+2

				elseif Container.Type == "Variable" then

					Container:AppendText("{{{")

					scannerPosition = NNC+3

				else --Wikilink

					Container:AppendText("[[")

					scannerPosition = NNC+2

				end

				if LatestObject and Container.Type == "Template" then --Only templates count as children

					LatestObject.Children#LatestObject.Children+1 = Container

				end

				activeObjects#activeObjects+1 = Container

			elseif LatestObject then

				LatestObject:AppendText(Character)

			end



		elseif Character == "}" or Character == "]" then

			if LatestObject then

				LatestObject:AppendText(Character)

				if LatestObject.End == NNC then

					if LatestObject.Type == "Template" then

						LatestObject:Clean(true)

						finalObjects#finalObjects+1 = LatestObject

					else

						LatestObject:Clean(false)

					end

					activeObjects#activeObjects = nil

					local NewLatest = activeObjects#activeObjects

					if NewLatest then

						NewLatest:AppendText(LatestObject.Text) --Append to new latest

					end

				end

			end



		else --| or =

			if LatestObject then

				LatestObject:HandleArgInput(Character)

			end

		end

	end

	

	--Step 4: Fix the order

	local FixedOrder = {}

	local SortableReference = {}

	for _,Object in next,finalObjects do

		SortableReference#SortableReference+1 = Object.Start

	end

	table.sort(SortableReference)

	for i = 1,#SortableReference do

		local start = SortableReferencei

		for n,Object in next,finalObjects do

			if Object.Start == start then

				finalObjectsn = nil

				Object.Start = nil --Final cleanup

				Object.End = nil

				Object.Type = nil

				FixedOrder#FixedOrder+1 = Object

				break

			end

		end

	end

	

	--Finished, return

	return FixedOrder

end



local p = {}

--Main entry points

p.PrepareText = PrepareText

p.ParseTemplates = ParseTemplates

--Extra entry points, not really required

p.TestForNowikiTag = TestForNowikiTag

p.TestForComment = TestForComment



return p



--[==[ console tests



local s = [=[Hey!{{Text|<nowiki | ||>

Hey! }}

A</nowiki>|<!--AAAAA|AAA-->Should see|Shouldn't see}}]=]

local out = p.PrepareText(s)

mw.logObject(out)



local s = [=[B<!--

Hey!

-->A]=]

local out = p.TestForComment(s, 2)

mw.logObject(out); mw.log(string.sub(s, 2, out.Length))



local a = p.ParseTemplates([=[

{{User:Aidan9382/templates/dummy

|A|B|C {{{A|B}}} { } } {

|<nowiki>D</nowiki>

|<pre>E

|F</pre>

|G|=|a=|A  =  [[{{PAGENAME}}|A=B]]{{Text|1==<nowiki>}}</nowiki>}}|A B=Success}}

]=])

mw.logObject(a)



]==]
From Wikipedia, the free encyclopedia

require("strict")



--Helper functions

local function startswith(text, subtext)

	return string.sub(text, 1, #subtext) == subtext

end

local function endswith(text, subtext)

	return string.sub(text, -#subtext, -1) == subtext

end

local function allcases(s)

	return s:gsub("%a", function(c) 

		return "["..c:upper()..c:lower().."]"

	end)

end

local trimcache = {}

local whitespace = {[" "=1, "\n"=1, "\t"=1, "\r"=1}

local function cheaptrim(str) --mw.text.trim is surprisingly expensive, so here's an alternative approach

	local quick = trimcachestr

	if quick then

		return quick

	else

		-- local out = string.gsub(str, "^%s*(.-)%s*$", "%1")

		local lowEnd

		for i = 1,#str do

			if not whitespacestring.sub(str, i, i)] then

				lowEnd = i

				break

			end

		end

		if not lowEnd then

			trimcachestr = ""

			return ""

		end

		for i = #str,1,-1 do

			if not whitespacestring.sub(str, i, i)] then

				local out = string.sub(str, lowEnd, i)

				trimcachestr = out

				return out

			end

		end

	end

end



--[=[ Implementation notes

---- NORMAL HTML TAGS ----

Tags are very strict on how they want to start, but loose on how they end.

The start must strictly follow <[tAgNaMe](%s|>) with no room for whitespace in

the tag's name, but may then flow as they want afterwards, making

<div\nclass\n=\n"\nerror\n"\n> valid



There's no sense of escaping < or >

E.g.

 <div class="error\>"> will end at \> despite it being inside a quote

 <div class="<span class="error">error</span>"> will not process the larger div



If a tag has no end, it will consume all text instead of not processing



---- NOPROCESSING TAGS (nowiki, pre, syntaxhighlight, source, etc.) ----

(In most comments, <source> will not be mentioned. This is because it is the

deprecated version of <syntaxhighlight>)



No-Processing tags have some interesting differences to the above rules.

For example, their syntax is a lot stricter. While an opening tag appears to

follow the same set of rules, A closing tag can't have any sort of extra

formatting period. While </div a/a> is valid, </nowiki a/a> isn't - only

newlines and spaces/tabs are allowed in closing tags.

Note that, even though <pre> tags cause a visual change when the ending tag has

extra formatting, it won't cause the no-processing effects. For some reason, the

format must be strict for that to apply.



Both the content inside the tag pair and the content inside each side of the

pair is not processed. E.g. <nowiki |}}>|}}</nowiki> would have both of the |}}

escaped in practice.



When something in the code is referenced to as a "Nowiki Tag", it means a tag

which causes wiki text to not be processed, which includes <nowiki>, <pre>,

and <syntaxhighlight>



Since we only care about these tags, we can ignore the idea of an intercepting

tag preventing processing, and just go straight for the first ending we can find

If there is no ending to find, the tag will NOT consume the rest of the text in

terms of processing behaviour (though <pre> will appear to have an effect).

Even if there is no end of the tag, the content inside the opening half will

still be unprocessed, meaning {{X20|<nowiki }}>}} wouldn't end at the first }}

despite there being no ending to the tag.



Note that there are some tags, like <math>, which also function like <nowiki>

which are included in this aswell. Some other tags, like <ref>, have far too

unpredictable behaviour to be handled currently (they'd have to be split and

processed as something seperate - its complicated, but maybe not impossible.)

I suspect that every tag listed in [[Special:Version]] may behave somewhat like

this, but that's far too many cases worth checking for rarely used tags that may

not even have a good reason to contain {{ or }} anyways, so we leave them alone.



---- HTML COMMENTS AND INCLUDEONLY ----

HTML Comments are about as basic as it could get for this

Start at <!--, end at -->, no extra conditions. Simple enough

If a comment has no end, it will eat all text instead of not being processed



includeonly tags function mostly like a regular nowiki tag, with the exception

that the tag will actually consume all future text if not given an ending as

opposed to simply giving up and not changing anything. Due to complications and

the fact that this is far less likely to be present on a page, aswell as being

something that may not want to be escaped, includeonly tags are ignored during

our processing

--]=]

local validtags = {nowiki=1, pre=1, syntaxhighlight=1, source=1, math=1}

--This function expects the string to start with the tag

local function TestForNowikiTag(text, scanPosition)

	local tagName = (string.match(text, "^<([^\n />]+)", scanPosition) or ""):lower()

	if not validtagstagName then

		return nil

	end

	local nextOpener = string.find(text, "<", scanPosition+1) or -1

	local nextCloser = string.find(text, ">", scanPosition+1) or -1

	if nextCloser > -1 and (nextOpener == -1 or nextCloser < nextOpener) then

		local startingTag = string.sub(text, scanPosition, nextCloser)

		--We have our starting tag (E.g. '<pre style="color:red">')

		--Now find our ending...

		if endswith(startingTag, "/>") then --self-closing tag (we are our own ending)

			return {

				Tag = tagName,

				Start = startingTag,

				Content = "", End = "",

				Length = #startingTag

			}



		else

			local endingTagStart, endingTagEnd = string.find(text, "</"..allcases(tagName).."[ \t\n]*>", scanPosition)

			if endingTagStart then --Regular tag formation

				local endingTag = string.sub(text, endingTagStart, endingTagEnd)

				local tagContent = string.sub(text, nextCloser+1, endingTagStart-1)

				return {

					Tag = tagName,

					Start = startingTag,

					Content = tagContent,

					End = endingTag,

					Length = #startingTag + #tagContent + #endingTag

				}



			else --Content inside still needs escaping (also linter error!)

				return {

					Tag = tagName,

					Start = startingTag,

					Content = "", End = "",

					Length = #startingTag

				}

			end

		end

	end

	return nil

end

local function TestForComment(text, scanPosition) --Like TestForNowikiTag but for <!-- -->

	if string.match(text, "^<!%-%-", scanPosition) then

		local commentEnd = string.find(text, "-->", scanPosition+4, true)

		if commentEnd then

			return {

				Start = "<!--", End = "-->",

				Content = string.sub(text, scanPosition+4, commentEnd-1),

				Length = commentEnd-scanPosition+3

			}

		else --Consumes all text if not given an ending

			return {

				Start = "<!--", End = "",

				Content = string.sub(text, scanPosition+4),

				Length = #text-scanPosition+1

			}

		end

	end

	return nil

end



--[[ Implementation notes

The goal of this function is to escape all text that wouldn't be parsed if it

was preprocessed (see above implementation notes).



Using keepComments will keep all HTML comments instead of removing them. They

will still be escaped regardless to avoid processing errors

--]]

local function PrepareText(text, keepComments)

	local newtext = {}

	local scanPosition = 1

	while true do

		local NextCheck = string.find(text, "<[NnSsPpMm!]", scanPosition) --Advance to the next potential tag we care about

		if not NextCheck then --Done

			newtext#newtext+1 =  string.sub(text,scanPosition)

			break

		end

		newtext#newtext+1 = string.sub(text,scanPosition,NextCheck-1)

		scanPosition = NextCheck

		local Comment = TestForComment(text, scanPosition)

		if Comment then

			if keepComments then

				newtext#newtext+1 = Comment.Start .. mw.text.nowiki(Comment.Content) .. Comment.End

			end

			scanPosition = scanPosition + Comment.Length

		else

			local Tag = TestForNowikiTag(text, scanPosition)

			if Tag then

				local newTagStart = "<" .. mw.text.nowiki(string.sub(Tag.Start,2,-2)) .. ">"

				local newTagEnd = 

					Tag.End == "" and "" or --Respect no tag ending

					"</" .. mw.text.nowiki(string.sub(Tag.End,3,-2)) .. ">"

				local newContent = mw.text.nowiki(Tag.Content)

				newtext#newtext+1 = newTagStart .. newContent .. newTagEnd

				scanPosition = scanPosition + Tag.Length

			else --Nothing special, move on...

				newtext#newtext+1 = string.sub(text, scanPosition, scanPosition)

				scanPosition = scanPosition + 1

			end

		end

	end

	return table.concat(newtext, "")

end



--[=[ Implementation notes

This function is an alternative to Transcluder's getParameters which considers

the potential for a singular { or } or other odd syntax that %b doesn't like to

be in a parameter's value.



When handling the difference between {{ and {{{, mediawiki will attempt to match

as many sequences of {{{ as possible before matching a {{

E.g.

 {{{{A}}}} -> { {{{A}}} }

 {{{{{{{{Text|A}}}}}}}} -> {{ {{{ {{{Text|A}}} }}} }}

If there aren't enough triple braces on both sides, the parser will compromise

for a template interpretation.

E.g.

 {{{{A}} }} -> {{ {{ A }} }}



While there are technically concerns about things such as wikilinks breaking

template processing (E.g. {{[[}}]]}} doesn't stop at the first }}), it shouldn't

be our job to process inputs perfectly when the input has garbage ({ / } isn't

legal in titles anyways, so if something's unmatched in a wikilink, it's

guaranteed GIGO)



Setting dontEscape will prevent running the input text through EET. Avoid

setting this to true if you don't have to set it.



Returned values:

A table of all templates. Template data goes as follows:

 Text: The raw text of the template

 Name: The name of the template

 Args: A list of arguments

 Children: A list of immediate template children

--]=]

--Helper functions

local function boundlen(pair)

	return pair.End-pair.Start+1

end



--Main function

local function ParseTemplates(InputText, dontEscape)

	--Setup

	if not dontEscape then

		InputText = PrepareText(InputText)

	end

	local function finalise(text)

		if not dontEscape then

			return mw.text.decode(text)

		else

			return text

		end

	end

	local function CreateContainerObj(Container)

		Container.Text = {}

		Container.Args = {}

		Container.ArgOrder = {}

		Container.Children = {}

		-- Container.Name = nil

		-- Container.Value = nil

		-- Container.Key = nil

		Container.BeyondStart = false

		Container.LastIndex = 1

		Container.finalise = finalise

		function Container:HandleArgInput(character, internalcall)

			if not internalcall then

				self.Text#self.Text+1 = character

			end

			if character == "=" then

				if self.Key then

					self.Value#self.Value+1 = character

				else

					self.Key = cheaptrim(self.Value and table.concat(self.Value, "") or "")

					self.Value = {}

				end

			else --"|" or "}"

				if not self.Name then

					self.Name = cheaptrim(self.Value and table.concat(self.Value, "") or "")

					self.Value = nil

				else

					self.Value = self.finalise(self.Value and table.concat(self.Value, "") or "")

					if self.Key then

						self.Key = self.finalise(self.Key)

						self.Argsself.Key = cheaptrim(self.Value)

						self.ArgOrder#self.ArgOrder+1 = self.Key

					else

						local Key = tostring(self.LastIndex)

						self.ArgsKey = self.Value

						self.ArgOrder#self.ArgOrder+1 = Key

						self.LastIndex = self.LastIndex + 1

					end

					self.Key = nil

					self.Value = nil

				end

			end

		end

		function Container:AppendText(text, ftext)

			self.Text#self.Text+1 = (ftext or text)

			if not self.Value then

				self.Value = {}

			end

			self.BeyondStart = self.BeyondStart or (#table.concat(self.Text, "") > 2)

			if self.BeyondStart then

				self.Value#self.Value+1 = text

			end

		end

		function Container:Clean(IsTemplate)

			self.Text = table.concat(self.Text, "")

			if self.Value and IsTemplate then

				self.Value = {string.sub(table.concat(self.Value, ""), 1, -3)} --Trim ending }}

				self:HandleArgInput("|", true) --Simulate ending

			end

			self.Value = nil

			self.Key = nil

			self.BeyondStart = nil

			self.LastIndex = nil

			self.finalise = nil

			self.HandleArgInput = nil

			self.AppendText = nil

			self.Clean = nil

		end

		return Container

	end

	

	--Step 1: Find and escape the content of all wikilinks on the page, which are stronger than templates (see implementation notes)

	local scannerPosition = 1

	local wikilinks = {}

	local openWikilinks = {}

	while true do

		local Position, _, Character = string.find(InputText, "([%[%]])%1", scannerPosition)

		if not Position then --Done

			break

		end



		scannerPosition = Position+2 --+2 to pass the [[ / ]]

		if Character == "[" then --Add a [[ to the pending wikilink queue

			openWikilinks#openWikilinks+1 = Position

		else --Pair up the ]] to any available [[

			if #openWikilinks >= 1 then

				local start = table.remove(openWikilinks) --Pop the latest [[

				wikilinksstart = {Start=start, End=Position+1, Type="Wikilink"} --Note the pair

			end

		end

	end

	

	--Step 2: Find the bounds of every valid template and variable ({{ and {{{)

	local scannerPosition = 1

	local templates = {}

	local variables = {}

	local openBrackets = {}

	while true do

		local Start, _, Character = string.find(InputText, "([{}])%1", scannerPosition)

		if not Start then --Done (both 9e9)

			break

		end

		local _, End = string.find(InputText, "^"..Character.."+", Start)



		scannerPosition = Start --Get to the {{ / }} set

		if Character == "{" then --Add the {{+ set to the queue

			openBrackets#openBrackets+1 = {Start=Start, End=End}



		else --Pair up the }} to any available {{, accounting for {{{ / }}}

			local BracketCount = End-Start+1

			while BracketCount >= 2 and #openBrackets >= 1 do

				local OpenSet = table.remove(openBrackets)

				if boundlen(OpenSet) >= 3 and BracketCount >= 3 then --We have a {{{variable}}} (both sides have 3 spare)

					variablesOpenSet.End-2 = {Start=OpenSet.End-2, End=scannerPosition+2, Type="Variable"} --Done like this to ensure chronological order

					BracketCount = BracketCount - 3

					OpenSet.End = OpenSet.End - 3

					scannerPosition = scannerPosition + 3



				else --We have a {{template}} (both sides have 2 spare, but at least one side doesn't have 3 spare)

					templatesOpenSet.End-1 = {Start=OpenSet.End-1, End=scannerPosition+1, Type="Template"} --Done like this to ensure chronological order

					BracketCount = BracketCount - 2

					OpenSet.End = OpenSet.End - 2

					scannerPosition = scannerPosition + 2

				end



				if boundlen(OpenSet) >= 2 then --Still has enough data left, leave it in

					openBrackets#openBrackets+1 = OpenSet

				end

			end

		end

		scannerPosition = End --Now move past the bracket set

	end

	

	--Step 3: Re-trace every object using their known bounds, collecting our parameters with (slight) ease

	local scannerPosition = 1

	local activeObjects = {}

	local finalObjects = {}

	while true do

		local LatestObject = activeObjects#activeObjects --Commonly needed object

		local NNC, _, Character --NNC = NextNotableCharacter

		if LatestObject then

			NNC, _, Character = string.find(InputText, "([{}%[%]|=])", scannerPosition)

		else

			NNC, _, Character = string.find(InputText, "([{}])", scannerPosition) --We are only after templates right now

		end

		if not NNC then

			break

		end

		if NNC > scannerPosition and LatestObject then

			local scannedContent = string.sub(InputText, scannerPosition, NNC-1)

			LatestObject:AppendText(scannedContent, finalise(scannedContent))

		end



		scannerPosition = NNC+1

		if Character == "{" or Character == "[" then

			local Container = templatesNNC or variablesNNC or wikilinksNNC

			if Container then

				CreateContainerObj(Container)

				if Container.Type == "Template" then

					Container:AppendText("{{")

					scannerPosition = NNC+2

				elseif Container.Type == "Variable" then

					Container:AppendText("{{{")

					scannerPosition = NNC+3

				else --Wikilink

					Container:AppendText("[[")

					scannerPosition = NNC+2

				end

				if LatestObject and Container.Type == "Template" then --Only templates count as children

					LatestObject.Children#LatestObject.Children+1 = Container

				end

				activeObjects#activeObjects+1 = Container

			elseif LatestObject then

				LatestObject:AppendText(Character)

			end



		elseif Character == "}" or Character == "]" then

			if LatestObject then

				LatestObject:AppendText(Character)

				if LatestObject.End == NNC then

					if LatestObject.Type == "Template" then

						LatestObject:Clean(true)

						finalObjects#finalObjects+1 = LatestObject

					else

						LatestObject:Clean(false)

					end

					activeObjects#activeObjects = nil

					local NewLatest = activeObjects#activeObjects

					if NewLatest then

						NewLatest:AppendText(LatestObject.Text) --Append to new latest

					end

				end

			end



		else --| or =

			if LatestObject then

				LatestObject:HandleArgInput(Character)

			end

		end

	end

	

	--Step 4: Fix the order

	local FixedOrder = {}

	local SortableReference = {}

	for _,Object in next,finalObjects do

		SortableReference#SortableReference+1 = Object.Start

	end

	table.sort(SortableReference)

	for i = 1,#SortableReference do

		local start = SortableReferencei

		for n,Object in next,finalObjects do

			if Object.Start == start then

				finalObjectsn = nil

				Object.Start = nil --Final cleanup

				Object.End = nil

				Object.Type = nil

				FixedOrder#FixedOrder+1 = Object

				break

			end

		end

	end

	

	--Finished, return

	return FixedOrder

end



local p = {}

--Main entry points

p.PrepareText = PrepareText

p.ParseTemplates = ParseTemplates

--Extra entry points, not really required

p.TestForNowikiTag = TestForNowikiTag

p.TestForComment = TestForComment



return p



--[==[ console tests



local s = [=[Hey!{{Text|<nowiki | ||>

Hey! }}

A</nowiki>|<!--AAAAA|AAA-->Should see|Shouldn't see}}]=]

local out = p.PrepareText(s)

mw.logObject(out)



local s = [=[B<!--

Hey!

-->A]=]

local out = p.TestForComment(s, 2)

mw.logObject(out); mw.log(string.sub(s, 2, out.Length))



local a = p.ParseTemplates([=[

{{User:Aidan9382/templates/dummy

|A|B|C {{{A|B}}} { } } {

|<nowiki>D</nowiki>

|<pre>E

|F</pre>

|G|=|a=|A  =  [[{{PAGENAME}}|A=B]]{{Text|1==<nowiki>}}</nowiki>}}|A B=Success}}

]=])

mw.logObject(a)



]==]

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook