可在模組:templateparser/doc建立此模組的說明文件

local export = {}

-- local function strip_whitespace(text)
--	local text, _ = mw.ustring.gsub(text, "%s*(.*)%s*", "%1")
--	return text
-- end
local strip_whitespace = mw.text.trim

local parseTemplateSearchPattern = "(([{|}%[%]=<]).?)"
local parseTemplateSearchPatternNoEq = "(([{|}%[%]<]).?)"

local function invalidSyntax()
	error("Invalid syntax detected!")
end

function export.parseTemplate(text)
	local text, ok = text:gsub("^{{(.+)}}$", "%1")
	if ok == 0 then
		return nil
	end
	
	local prev = 1
	local pos = 1, mend, found, f1
	local in_template = 0
	local in_table = 0
	local in_link = 0
	local search = parseTemplateSearchPattern
	local has_key = false
	local eq_index = 1
	
	local name = nil
	local args = {}
	local next_i = 1
	local function add_param(x, has_key, eq)
		if name == nil then
			name = strip_whitespace(x)
			return
		end

		if has_key then
			local key = strip_whitespace(x:sub(1, eq))
			local value = strip_whitespace(x:sub(eq + 2))
			local num = tonumber(key, 10)
			if num ~= nil and num > 0 then
				key = num
			end
			args[key] = value
		else
			args[next_i] = x
			next_i = next_i + 1
		end
	end

	while true do
		pos, mend, found, f1 = text:find(search, pos)
		if pos == nil then break end

		if found == "{{" then
			-- start of subtemplate
			in_template = in_template + 1
			pos = pos + 2
		elseif found == "{|" then
			-- start of a table
			in_table = in_table + 1
			pos = pos + 2
		elseif found == "[[" then
			-- start of link
			in_link = in_link + 1
			pos = pos + 2
		elseif found == "<n" and text:sub(pos, pos + 7) == "<nowiki>" then
			pos = text:find("</nowiki>", pos)
			if pos == nil then return nil end
			pos = pos + 8
		elseif found == "<-" and text:sub(pos, pos + 3) == "<!--" then
			pos = text:find("-->", pos + 4)
			if pos == nil then return nil end
			pos = pos + 3
		--elseif found == "}}" and in_template == 0 then
		--	invalidSyntax()
		elseif in_template == 0 and in_table == 0 and in_link == 0 then
			if f1 == "|" then
				-- parameter separator
				add_param(text:sub(prev, pos - 1), has_key, eq_index - prev)
				has_key = false
				search = parseTemplateSearchPattern -- allow equals sign again
				prev = pos + 1
			elseif f1 == "=" and not has_key then
				-- parameter key/value separator
				eq_index = pos
				has_key = true
				search = parseTemplateSearchPatternNoEq -- do not allow further equals signs
			end
			pos = pos + 1
		elseif found == "}}" then
			-- end of subtemplate
			if in_template > 0 then in_template = in_template - 1 end
			pos = pos + 2
		elseif found == "|}" then
			-- end of table
			if in_table > 0 then
				in_table = in_table - 1
				pos = pos + 2
			-- end of subtemplate with final pipe
			elseif text:sub(pos + 2, pos + 2) == "}" and in_template > 0 then
				in_template = in_template - 1
				pos = pos + 3
			end
		elseif found == "]]" then
			-- end of link
			if in_link > 0 then in_link = in_link - 1 end
			pos = pos + 2
		else
			pos = pos + 1
		end
	end

	if in_template ~= 0 or in_table ~= 0 or in_link ~= 0 then
		invalidSyntax()
	end
	
	add_param(text:sub(prev), has_key, eq_index - prev)

	return name, args
end

function export.findTemplates(text)
	-- Remove HTML comments.
	text = text
		:gsub("<!%-%-.-%-%->", "")
		:gsub("<!%-%-.*", "")
	
	local next = 1
	local function findNextTemplate()
		local pos, mend, found
		local in_template, in_argument = 0, 0
		local temp_start = 1
		pos = next
		while true do
			pos, mend, found = text:find("([{}<][{}n!])", pos)
			if pos == nil then break end
			if found == "{{" then
				if text:sub(pos, pos + 2) == "{{{" then
					pos = pos + 3
					in_argument = in_argument + 1
				else
					if in_template == 0 then
						temp_start = pos
					end
					pos = pos + 2
					in_template = in_template + 1
				end
			elseif found == "}}" then
				if text:sub(pos, pos + 2) == "}}}" and in_argument > 0 then
					in_argument = in_argument - 1
					pos = pos + 3
				else
					if in_template > 0 then
						in_template = in_template - 1
						if in_template == 0 then
							next = pos + 2
							local src = text:sub(temp_start, pos + 1)
							local ok, name, args = pcall(export.parseTemplate, src)
							if ok and name ~= nil then
								local modifier, type, func
								for prefix in name:gmatch("%s*(.-):") do
									if prefix == "subst" or prefix == "safesubst" then
										view = prefix
										name = name:gsub(prefix .. ":", "", 1)
									elseif prefix:match("^#") then
										if prefix == "#invoke" then
											type = "module"
											func = args[1]
											for i = 2, #args do
												args[i-1] = args[i]
											end
											args[#args] = nil
										else
											type = "parser function"
										end
										name = name:gsub(prefix .. ":", "", 1)
									end
								end
								name = name:gsub("^%s*(.*)%s*$", "%1")
								view = view or "transclude"
								type = type or "template"
								return name, args, src, temp_start, view, type, func
							end
						end
					end
					pos = pos + 2
				end
			elseif found == "<n" and text:sub(pos, pos + 7) == "<nowiki>" then
				pos = text:find("</nowiki>", pos)
				if pos == nil then break end
				pos = pos + 8
			elseif found == "<!" and text:sub(pos, pos + 3) == "<!--" then
				pos = text:find("-->", pos + 4)
				if pos == nil then break end
				pos = pos + 3
			else
				pos = pos + 1
			end
		end
	end

	return findNextTemplate
end

return export