Module:User:Theknightwho/template parser

setmetatable(package.loaded, {	__mode = "v" })

local require = require local assert = assert local concat = table.concat local floor = math.floor local format = string.format local gmatch = string.gmatch local gsub = string.gsub local insert = table.insert local ipairs = ipairs local lower = string.lower local match = string.match local min = math.min local new_title = mw.title.new local nowiki = require("Module:string utilities").nowiki local pcall = pcall local rawset = rawset local remove = table.remove local rep = string.rep local setmetatable = setmetatable local sub = string.sub local tonumber = tonumber local type = type local ulen = mw.ustring.len local ulower = string.ulower local upper = string.upper local uri_encode = mw.uri.encode

mw.loadData = require

local m_parser = require("Module:parser")

local TAGS = { categorytree = true, ce = true, charinsert = true, chem = true, dynamicpagelist = true, gallery = true, graph = true, hiero = true, imagemap = true, indicator = true, inputbox = true, langconvert = true, mapframe = true, maplink = true, math = true, nowiki = true, poem = true, pre = true, ref = true, references = true, score = true, section = true, source = true, syntaxhighlight = true, talkpage = true, templatedata = true, templatestyles = true, thread = true, timeline = true }

local export = {}

-- -- Helper functions --

local function is_space(this) return this == " " or		this == "\t" or		this == "\n" or		this == "\v" or		this == "\f" or		this == "\r" end

-- Standard PHP character escape. local function php_escaped(text) return (gsub(text, "[\"&'<>]", {		["\""] = "&quot;", ["&"] = "&amp;", ["'"] = "&#039;", ["<"] = "&lt;", [">"] = "&gt;", })) end

local function tonumber_loose(text) if type(text) == "string" then local text_lower = lower(text) return text_lower ~= "inf" and text_lower ~= "-inf" and text_lower ~= "nan" and text_lower ~= "-nan" and tonumber(text) or text end return text end

local function tonumber_strict(text) if type(text) == "string" then local num_text = match(text, "^[+%-]?%d+%.?%d*") text = tonumber(num_text) or text end return text end

local function trim(str) if type(str) ~= "string" then return str end local n	for i = 1, #str do		if not is_space(sub(str, i, i)) then n = i			break end end if not n then return "" end for i = #str, n, -1 do		if not is_space(sub(str, i, i)) then return sub(str, n, i)		end end end

-- -- Frame --

local Frame = mw.getCurrentFrame local actual_parent = Frame:getParent

do local function eq(a, b)		return rawequal(a, b) or rawequal(b, Frame) end setmetatable(Frame, {__eq = eq}) local function newCallbackParserValue(callback) local value, cache = {} function value:expand if not cache then cache = callback end return cache end return value end function Frame:getArgument(opt) local name = type(opt) == "table" and opt.name or opt return newCallbackParserValue(function			return self.args[name]		end) end function Frame:getParent return nil end Frame.really_preprocess = Frame.preprocess function Frame:preprocess(opt) return export.parse(opt, self:getTitle):expand end function Frame:newParserValue(opt) local text = type(opt) == "table" and opt.text or opt return newCallbackParserValue(function			return self:preprocess(text)		end) end function Frame:newTemplateParserValue(opt) assert(			type(opt) == "table",			"frame:newTemplateParserValue: the first parameter must be a table"		) assert(			opt.title,			"frame:newTemplateParserValue: a title is required"		) return newCallbackParserValue(function			return self:expandTemplate(opt)		end) end function Frame:argumentPairs return pairs(self.args) end function Frame:newChild(opt) assert(			type(opt) == "table",			"frame:newChild: the first parameter must be a table"		) local title = opt.title and tostring(opt.title) or self:getTitle assert(			not opt.args or type(opt.args) == "table",			"frame:newChild: args must be a table"		) local child = setmetatable({			args = opt.args or {}		}, {			__index = Frame,			__eq = eq		}) function child:getTitle return title end if opt.parent ~= false then function child.getParent return self end end return child end end

local parent_frame local child_frame = Frame:newChild{ title = Frame:getTitle, args = Frame.args, parent = false }

function mw.getCurrentFrame return child_frame end

-- -- Nodes --

local Node = m_parser.Node

function Node:expand_child(key, args, ...) if type(self[key]) == "table" and not self[key].cached then self[key] = self[key]:expand(false, ...) child_type = type(self[key]) pcall(rawset, self[key], "cached", true) end return type(self[key]) == "table" and self[key]:expand(args, ...) or self[key] end

local Wikitext = m_parser.Wikitext

function Wikitext:expand(args) local output, arg = {} for i in ipairs(self) do		arg = self:expand_child(i, args) if type(arg) == "table" then output = false elseif output then insert(output, arg) end end return output and concat(output) or self end

local Tag = Node:new("tag")

function Tag:__tostring local open_tag = {"<", self.name} if self.ignored then return "" elseif self.attributes then for attr, value in pairs(self.attributes) do insert(open_tag, " " .. attr .. "=\"" .. value .. "\"") end end if self.self_closing then insert(open_tag, "/>") return concat(open_tag) end insert(open_tag, ">") return concat(open_tag) .. concat(self) .. "" end

function Tag:expand(args) -- FIXME if self.ignored then return "" end return self:__tostring end

local Argument = Node:new("argument")

function Argument:__tostring if self[2] then local output, i = {"}")		return concat(output)	elseif self[1] then		return ""	else		return "argument"	end end

function Argument:next self.i = self.i + 1 if self.i <= 2 then return self[self.i]	end end

function Argument:expand(args) if args == false then return self end local arg1 = self:expand_child(1, args) if type(arg1) == "string" then arg1 = tonumber_loose(arg1) end if args and args[arg1] then return args[arg1] end local arg2 = self:expand_child(2, args) return arg2 or "" end

local Parameter = Node:new("parameter")

function Parameter:__tostring if self.key then return tostring(self.key) .. "=" .. Node.__tostring(self) end return Node.__tostring(self) end

function Parameter:expand(args, array) if array and self.key then local a, b = self:expand_child("key", args), Wikitext.expand(self, args) if args == false and (type(a) == "table" or type(b) == "table") then return Wikitext:new{a, "=", b}		end return a .. "=" .. b	end return Wikitext.expand(self, args) end

local Template = Node:new("template")

function Template:__tostring if self[2] then local output, n = {"{{", tostring(self[1])}, 2 if self.colon then insert(output, ":") insert(output, tostring(self[3])) n = 3 end for i = n, #self do			insert(output, "|") insert(output, tostring(self[i])) end insert(output, "}}") return concat(output) elseif self[1] then return "" else return "template" end end

function Template:get_params(args) local params, implicit, key, value, n = {}, 0 for i = 2, #self do		if self[i].key then key = self[i]:expand_child("key", args) if type(key) == "string" then key = tonumber_loose(key) end -- We need to retain the parameter object if there's an explicit key. value = self[i]:expand(args) else implicit = implicit + 1 key = implicit value = self:expand_child(i, args) end if args == false and (type(key) == "table" or type(value) == "table") then params = false elseif params then params[key] = value end end return params end

function Template:get_array_params(args, max, max_eval) max = max + 1 or #self max_eval = max_eval and (max_eval + 1) or max local params, value = {} for i = 2, max do		value = i <= max_eval and self:expand_child(i, args, true) or self[i] if args == false and type(value) == "table" then params = false elseif params then params[i - 1] = value end end return params end

function Template:parser_function_error(mw_page, ...) local msg = new_title("MediaWiki:" .. mw_page):getContent for i, v in ipairs(arg) do msg = gsub(msg, "$" .. i, v)	end return export.parse("" .. php_escaped(msg) .. " ", true):expand end

do local getCurrentTitle do local current_title function getCurrentTitle current_title = current_title or mw.title.getCurrentTitle return current_title end end local getContentLanguage do local content_lang function getContentLanguage content_lang = content_lang or mw.getContentLanguage return content_lang end end local getPageLanguage do local page_lang function getPageLanguage page_lang = page_lang or mw.language.new(Frame:really_preprocess("")) return page_lang end end local case_insensitive = {} function case_insensitive:__index(k) local k_upper = upper(k) if type(k) == "string" and case_insensitive[k_upper] then return rawget(self, k_upper) end end for _, k in ipairs{"#BABEL", "#CATEGORYTREE", "#DATEFORMAT", "#EXPR", "#FORMATDATE", "#IF", "#IFEQ", "#IFERROR", "#IFEXIST", "#IFEXPR", "#INVOKE", "#LANGUAGE", "#LQTPAGELIMIT", "#LST", "#LSTH", "#LSTX", "#PROPERTY", "#REL2ABS", "#SECTION", "#SECTION-H", "#SECTION-X", "#SPECIAL", "#SPECIALE", "#STATEMENTS", "#SWITCH", "#TAG", "#TARGET", "#TIME", "#TIMEL", "#TITLEPARTS", "#USELIQUIDTHREADS", "ANCHORENCODE", "ARTICLEPATH", "BIDI", "CANONICALURL", "CANONICALURLE", "FILEPATH", "FORMATNUM", "FULLURL", "FULLURLE", "GENDER", "GRAMMAR", "INT", "LC", "LCFIRST", "LOCALURL", "LOCALURLE", "MSG", "MSGNW", "NOEXTERNALLANGLINKS", "NS", "NSE", "PADLEFT", "PADRIGHT", "PAGEID", "PLURAL", "RAW", "SAFESUBST", "SCRIPTPATH", "SERVER", "SERVERNAME", "STYLEPATH", "SUBST", "UC", "UCFIRST", "URLENCODE"} do		case_insensitive[k] = true end local parser_functions = setmetatable({}, case_insensitive) parser_functions["#BABEL"] = function(self, args) local params = self:get_array_params(args) if params == false then return self end return Frame:callParserFunction("#BABEL", params) end parser_functions["#CATEGORYTREE"] = function(self, args) -- TODO end parser_functions["#EXPR"] = function(self, args) -- TODO end parser_functions["#FORMATDATE"] = function(self, args) end parser_functions["#IF"] = function(self, args) local check = trim(self:expand_child(2, args, true)) if args == false and type(check) == "table" then return self end local n = check ~= "" and 3 or 4 return trim(self:expand_child(n, args, true)) or "" end parser_functions["#IFEQ"] = function(self, args) local check1 = tonumber_loose(trim(self:expand_child(2, args, true))) -- and decode entities local check2 = tonumber_loose(trim(self:expand_child(3, args, true))) -- and decode entities if args == false and (type(check1) == "table" or type(check2) == "table") then return self end local n = check1 == check2 and 4 or 5 return trim(self:expand_child(n, args, true)) or "" end do local tags = {"strong", "span", "p", "div"} parser_functions["#IFERROR"] = function(self, args) local check = trim(self:expand_child(2, args, true)) if args == false and type(check) == "table" then return self end for _, tag in ipairs(tags) do if match(check, "<" .. tag .. "%s[^>]-%f[^%s]class=\"[^\">]-%f[^%s\"]error%f[%s\"][^\">]-\"") then return trim(self:expand_child(3, args, true)) or "" end end if self[4] then return trim(self:expand_child(4, args, true)) end return check end end parser_functions["#IFEXIST"] = function(self, args) local check = trim(self:expand_child(2, args, true)) if args == false and type(check) == "table" then return self end local title = new_title(check) local n = title and title.exists and 3 or 4 return trim(self:expand_child(n, args, true)) or "" end parser_functions["#IFEXPR"] = function(self, args) end parser_functions["#INVOKE"] = function(self, args) self.module = self.module or remove(self, 2) if not self.func then local func = trim(self:expand_child(2, args, true)) if type(func) == "table" then return self end self.func = remove(self, 2) end if args == false then return self end local params = self:get_params(args) local parent_args if args then parent_args = {} for k, v in pairs(args) do				parent_args[k] = v			end end parent_frame = Frame:newChild{ title = self.title.fullText, args = parent_args, parent = false }		local child_args = {} for k, v in pairs(params) do			child_args[k] = v		end child_frame = parent_frame:newChild{ title = "Module:" .. self.module, args = child_args, parent = parent_frame }		return tostring(require("Module:" .. self.module)[self.func](child_frame)) end parser_functions["#LANGUAGE"] = function(self, args) end parser_functions["#LQTPAGELIMIT"] = function(self, args) end parser_functions["#LST"] = function(self, args) end parser_functions["#LSTH"] = function(self, args) end parser_functions["#LSTX"] = function(self, args) end parser_functions["#PROPERTY"] = function(self, args) end parser_functions["#REL2ABS"] = function(self, args) end parser_functions["#SPECIAL"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end return Frame:callParserFunction("#SPECIAL", params) end parser_functions["#SPECIALE"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end return Frame:callParserFunction("#SPECIALE", params) end parser_functions["#STATEMENTS"] = function(self, args) end -- Parsoid keeps expanding grouped keys even after it finds a match in a given group, which means they can still cause errors to be thrown; this does not apply to the final key in the group, however. In, "b" is expanded (despite "a" already having matched), but "c" is ignored. -- When there are duplicate keys, the first takes priority. However, when there are duplicate #defaults, the last takes priority; this include the implied #default in the final parameter if it has no "=" sign. -- #default is not case sensitive, and can also be a key: e.g. if #default and #DEFAULT were both used (in that order), #DEFAULT would be the actual default, but #default could still be treated as a key (requiring an exact match). parser_functions["#SWITCH"] = function(self, args) local check, next_value, default self[2] = trim(self[2]) -- and decode entities for i = 3, #self do			if self[i].key then if next_value then return trim(self:expand_child(i, args)) end check = trim(self[i]:expand_child("key", args)) -- and decode entities if check == self[2] then return trim(self:expand_child(i, args)) elseif lower(check) == "#default" then default = i				end else check = trim(self:expand_child(i, args)) -- and decode entities if check == self[2] then next_value = true elseif i == #self then return check -- decode entities end end end if default then return trim(self:expand_child(default, args)) end return "" end parser_functions["#TAG"] = function(self, args) end parser_functions["#TARGET"] = function(self, args) end parser_functions["#TIME"] = function(self, args) end parser_functions["#TIMEL"] = function(self, args) end parser_functions["#TITLEPARTS"] = function(self, args) end parser_functions["#USELIQUIDTHREADS"] = function(self, args) end parser_functions["ANCHORENCODE"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end return mw.uri.anchorEncode(params[1]) end parser_functions["BASEPAGENAME"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end local title = new_title(params[1]) return title and title.baseText and nowiki(title.baseText) or "" end parser_functions["BASEPAGENAMEE"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end local title = new_title(params[1]) return title and title.baseText and nowiki(uri_encode(title.baseText, "WIKI")) or "" end parser_functions["BIDI"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end return Frame:callParserFunction("BIDI", params) end parser_functions["CANONICALURL"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return tostring(mw.uri.canonicalUrl(params[1], params[2])) end parser_functions["CANONICALURLE"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return uri_encode(tostring(mw.uri.canonicalUrl(params[1], params[2])), "WIKI") end parser_functions["CASCADINGSOURCES"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end local title = new_title(params[1]) return title and title.cascadingProtection and type(title.cascadingProtection.sources) == "table" and concat(title.cascadingProtection.sources, "|") or "" end parser_functions["DEFAULTSORT"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return Frame:callParserFunction("DEFAULTSORT", params) end parser_functions["DISPLAYTITLE"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return Frame:callParserFunction("DISPLAYTITLE", params) end parser_functions["FILEPATH"] = function(self, args) local params = self:get_array_params(args, 3) if params == false then return self end return Frame:callParserFunction("FILEPATH", params) end parser_functions["FORMATNUM"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return getPageLanguage:formatNum(params[1], params[2]) end parser_functions["FULLPAGENAME"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end local title = new_title(params[1]) return title and title.prefixedText and nowiki(title.prefixedText) or "" end parser_functions["FULLPAGENAMEE"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end local title = new_title(params[1]) return title and title.prefixedText and nowiki(uri_encode(title.prefixedText, "WIKI")) or "" end parser_functions["FULLURL"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return tostring(mw.uri.fullUrl(params[1], params[2])) end parser_functions["FULLURLE"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return uri_encode(tostring(mw.uri.fullUrl(params[1], params[2])), "WIKI") end parser_functions["GENDER"] = function(self, args) end parser_functions["GRAMMAR"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return getPageLanguage:grammar(params[1], params[2]) end parser_functions["INT"] = function(self, args) end parser_functions["LC"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end return getContentLanguage:lc(params[1]) end parser_functions["LCFIRST"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end return getContentLanguage:lcfirst(params[1]) end parser_functions["LOCALURL"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return tostring(mw.uri.localUrl(params[1], params[2])) end parser_functions["LOCALURLE"] = function(self, args) local params = self:get_array_params(args, 2) if params == false then return self end return uri_encode(tostring(mw.uri.localUrl(params[1], params[2])), "WIKI") end parser_functions["NAMESPACE"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end local title = new_title(params[1]) return title and title.nsText and nowiki(title.nsText) or "" end parser_functions["NAMESPACEE"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end local title = new_title(params[1]) return title and title.nsText and nowiki(uri_encode(title.nsText, "WIKI")) or "" end parser_functions["NAMESPACENUMBER"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end local title = new_title(params[1]) return title and title.namespace and tostring(title.namespace) or "" end parser_functions["NOEXTERNALLANGLINKS"] = function(self, args) end parser_functions["NS"] = function(self, args) end parser_functions["NSE"] = function(self, args) end parser_functions["NUMBERINGROUP"] = function(self, args) end parser_functions["NUMBEROFACTIVEUSERS"] = function(self, args) end parser_functions["NUMBEROFADMINS"] = function(self, args) end parser_functions["NUMBEROFARTICLES"] = function(self, args) end parser_functions["NUMBEROFEDITS"] = function(self, args) end parser_functions["NUMBEROFFILES"] = function(self, args) end parser_functions["NUMBEROFPAGES"] = function(self, args) end parser_functions["NUMBEROFUSERS"] = function(self, args) end do local function pad(str, len, padding) if not len or padding == "" then return "" end len = tonumber_strict(len) if type(len) ~= "number" or len < 1 then return "" elseif not padding then padding = "0" end local padding_len = ulen(padding) local ret_len = min(len, 500) - ulen(str) if ret_len <= 0 then return "" end return rep(padding, floor(ret_len / padding_len)) .. sub(padding, 1, ret_len % padding_len) end parser_functions["PADLEFT"] = function(self, args) local params = self:get_array_params(args, 3) if params == false then return self end return pad(params[1], params[2], params[3]) .. params[1] end parser_functions["PADRIGHT"] = function(self, args) local params = self:get_array_params(args, 3) if params == false then return self end return params[1] .. pad(params[1], params[2], params[3]) end end parser_functions["PAGEID"] = function(self, args) end parser_functions["PAGENAME"] = function(self, args) end parser_functions["PAGENAMEE"] = function(self, args) end parser_functions["PAGESINCATEGORY"] = function(self, args) end parser_functions["PAGESIZE"] = function(self, args) end parser_functions["PLURAL"] = function(self, args) end parser_functions["PROTECTIONEXPIRY"] = function(self, args) end parser_functions["PROTECTIONLEVEL"] = function(self, args) end parser_functions["REVISIONDAY"] = function(self, args) end parser_functions["REVISIONDAY2"] = function(self, args) end parser_functions["REVISIONID"] = function(self, args) end parser_functions["REVISIONMONTH"] = function(self, args) end parser_functions["REVISIONMONTH1"] = function(self, args) end parser_functions["REVISIONTIMESTAMP"] = function(self, args) end parser_functions["REVISIONUSER"] = function(self, args) end parser_functions["REVISIONYEAR"] = function(self, args) end parser_functions["ROOTPAGENAME"] = function(self, args) end parser_functions["ROOTPAGENAMEE"] = function(self, args) end parser_functions["SUBJECTPAGENAME"] = function(self, args) end parser_functions["SUBJECTPAGENAMEE"] = function(self, args) end parser_functions["SUBJECTSPACE"] = function(self, args) end parser_functions["SUBJECTSPACEE"] = function(self, args) end parser_functions["SUBPAGENAME"] = function(self, args) end parser_functions["SUBPAGENAMEE"] = function(self, args) end parser_functions["TALKPAGENAME"] = function(self, args) end parser_functions["TALKPAGENAMEE"] = function(self, args) end parser_functions["TALKSPACE"] = function(self, args) end parser_functions["TALKSPACEE"] = function(self, args) end parser_functions["UC"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end return getContentLanguage:uc(params[1]) end parser_functions["UCFIRST"] = function(self, args) local params = self:get_array_params(args, 1) if params == false then return self end return getContentLanguage:ucfirst(params[1]) end parser_functions["URLENCODE"] = function(self, args) end parser_functions["#DATEFORMAT"] = parser_functions["#FORMATDATE"] parser_functions["#SECTION"] = parser_functions["#LST"] parser_functions["#SECTION-H"] = parser_functions["#LSTH"] parser_functions["#SECTION-X"] = parser_functions["#LSTX"] parser_functions["ARTICLEPAGENAME"] = parser_functions["SUBJECTPAGENAME"] parser_functions["ARTICLEPAGENAMEE"] = parser_functions["SUBJECTPAGENAMEE"] parser_functions["ARTICLESPACE"] = parser_functions["SUBJECTSPACE"] parser_functions["ARTICLESPACEE"] = parser_functions["SUBJECTSPACEE"] parser_functions["DEFAULTCATEGORYSORT"] = parser_functions["DEFAULTSORT"] parser_functions["DEFAULTSORTKEY"] = parser_functions["DEFAULTSORT"] parser_functions["NUMINGROUP"] = parser_functions["NUMBERINGROUP"] parser_functions["PAGESINCAT"] = parser_functions["PAGESINCATEGORY"] local parser_variables = setmetatable({}, case_insensitive) parser_variables["!"] = function return "|" end parser_variables["="] = function return "=" end parser_variables["ARTICLEPATH"] = function return new_title("$1"):localUrl end parser_variables["BASEPAGENAME"] = function return nowiki(getCurrentTitle.baseText) end parser_variables["BASEPAGENAMEE"] = function return nowiki(uri_encode(getCurrentTitle.baseText, "WIKI")) end parser_variables["CASCADINGSOURCES"] = function return concat(getCurrentTitle.cascadingProtection.sources, "|") end parser_variables["CONTENTLANGUAGE"] = function return getContentLanguage:getCode end parser_variables["CURRENTDAY"] = function return getPageLanguage:formatDate("j") end parser_variables["CURRENTDAY2"] = function return getPageLanguage:formatDate("d") end parser_variables["CURRENTDAYNAME"] = function return getPageLanguage:formatDate("l") end parser_variables["CURRENTDOW"] = function return getPageLanguage:formatDate("w") end parser_variables["CURRENTHOUR"] = function return getPageLanguage:formatDate("H") end parser_variables["CURRENTMONTH"] = function return getPageLanguage:formatDate("m") end parser_variables["CURRENTMONTH1"] = function return getPageLanguage:formatDate("n") end parser_variables["CURRENTMONTHABBREV"] = function return getPageLanguage:formatDate("M") end parser_variables["CURRENTMONTHNAME"] = function return getPageLanguage:formatDate("F") end parser_variables["CURRENTMONTHNAMEGEN"] = function return getPageLanguage:formatDate("xg") end parser_variables["CURRENTTIME"] = function return getPageLanguage:formatDate("H:i") end parser_variables["CURRENTTIMESTAMP"] = function return getPageLanguage:formatDate("YmdHis") end parser_variables["CURRENTVERSION"] = function return mw.site.currentVersion end parser_variables["CURRENTWEEK"] = function return format("%d", getPageLanguage:formatDate("W")) end parser_variables["CURRENTYEAR"] = function return getPageLanguage:formatDate("Y") end parser_variables["DIRECTIONMARK"] = function return getPageLanguage:getDirMark end parser_variables["FULLPAGENAME"] = function return nowiki(getCurrentTitle.prefixedText) end parser_variables["FULLPAGENAMEE"] = function return nowiki(uri_encode(getCurrentTitle.prefixedText, "WIKI")) end parser_variables["LOCALDAY"] = function return getPageLanguage:formatDate("j", nil, true) end parser_variables["LOCALDAY2"] = function return getPageLanguage:formatDate("d", nil, true) end parser_variables["LOCALDAYNAME"] = function return getPageLanguage:formatDate("l", nil, true) end parser_variables["LOCALDOW"] = function return getPageLanguage:formatDate("w", nil, true) end parser_variables["LOCALHOUR"] = function return getPageLanguage:formatDate("H", nil, true) end parser_variables["LOCALMONTH"] = function return getPageLanguage:formatDate("m", nil, true) end parser_variables["LOCALMONTH1"] = function return getPageLanguage:formatDate("n", nil, true) end parser_variables["LOCALMONTHABBREV"] = function return getPageLanguage:formatDate("M", nil, true) end parser_variables["LOCALMONTHNAME"] = function return getPageLanguage:formatDate("F", nil, true) end parser_variables["LOCALMONTHNAMEGEN"] = function return getPageLanguage:formatDate("xg", nil, true) end parser_variables["LOCALTIME"] = function return getPageLanguage:formatDate("H:i", nil, true) end parser_variables["LOCALTIMESTAMP"] = function return getPageLanguage:formatDate("YmdHis", nil, true) end parser_variables["LOCALWEEK"] = function return format("%d", getPageLanguage:formatDate("W", nil, true)) end parser_variables["LOCALYEAR"] = function return getPageLanguage:formatDate("Y", nil, true) end parser_variables["NAMESPACE"] = function return (gsub(getCurrentTitle.nsText, "_", " ")) end parser_variables["NAMESPACEE"] = function return uri_encode(getCurrentTitle.nsText, "WIKI") end parser_variables["NAMESPACENUMBER"] = function return tostring(getCurrentTitle.namespace) end parser_variables["NOEXTERNALLANGLINKS"] = function return Frame:callParserFunction("NOEXTERNALLANGLINKS", "*") end parser_variables["NUMBEROFACTIVEUSERS"] = function return getPageLanguage:formatNum(mw.site.stats.activeUsers) end parser_variables["NUMBEROFADMINS"] = function return getPageLanguage:formatNum(mw.site.stats.admins) end parser_variables["NUMBEROFARTICLES"] = function return getPageLanguage:formatNum(mw.site.stats.articles) end parser_variables["NUMBEROFEDITS"] = function return getPageLanguage:formatNum(mw.site.stats.edits) end parser_variables["NUMBEROFFILES"] = function return getPageLanguage:formatNum(mw.site.stats.files) end parser_variables["NUMBEROFPAGES"] = function return getPageLanguage:formatNum(mw.site.stats.pages) end parser_variables["NUMBEROFUSERS"] = function return getPageLanguage:formatNum(mw.site.stats.users) end parser_variables["PAGEID"] = function return tostring(getCurrentTitle.id) end parser_variables["PAGELANGUAGE"] = function return getPageLanguage:getCode end parser_variables["PAGENAME"] = function return nowiki(getCurrentTitle.text) end parser_variables["PAGENAMEE"] = function return nowiki(uri_encode(getCurrentTitle.text, "WIKI")) end parser_variables["REVISIONDAY"] = function return Frame:really_preprocess("") end parser_variables["REVISIONDAY2"] = function return Frame:really_preprocess("") end parser_variables["REVISIONID"] = function return Frame:really_preprocess("") end parser_variables["REVISIONMONTH"] = function return Frame:really_preprocess("") end parser_variables["REVISIONMONTH1"] = function return Frame:really_preprocess("") end parser_variables["REVISIONSIZE"] = function return Frame:really_preprocess("") end parser_variables["REVISIONTIMESTAMP"] = function return Frame:really_preprocess("") end parser_variables["REVISIONUSER"] = function return Frame:really_preprocess("") end parser_variables["REVISIONYEAR"] = function return Frame:really_preprocess("") end parser_variables["ROOTPAGENAME"] = function return nowiki(getCurrentTitle.rootText) end parser_variables["ROOTPAGENAMEE"] = function return nowiki(uri_encode(getCurrentTitle.rootText, "WIKI")) end parser_variables["SCRIPTPATH"] = function return mw.site.scriptPath end parser_variables["SERVER"] = function return mw.site.server end parser_variables["SERVERNAME"] = function return Frame:really_preprocess("") end parser_variables["SITENAME"] = function return mw.site.siteName end parser_variables["STYLEPATH"] = function return mw.site.stylePath end parser_variables["SUBJECTPAGENAME"] = function return nowiki(getCurrentTitle.subjectPageTitle.fullText) end parser_variables["SUBJECTPAGENAMEE"] = function return nowiki(uri_encode(getCurrentTitle.subjectPageTitle.fullText, "WIKI")) end parser_variables["SUBJECTSPACE"] = function return (gsub(getCurrentTitle.subjectNsText, "_", " ")) end parser_variables["SUBJECTSPACEE"] = function return uri_encode(getCurrentTitle.subjectNsText, "WIKI") end parser_variables["SUBPAGENAME"] = function return nowiki(getCurrentTitle.subpageText) end parser_variables["SUBPAGENAMEE"] = function return nowiki(uri_encode(getCurrentTitle.subpageText, "WIKI")) end parser_variables["TALKPAGENAME"] = function return nowiki(getCurrentTitle.talkPageTitle.fullText) end parser_variables["TALKPAGENAMEE"] = function return nowiki(uri_encode(getCurrentTitle.talkPageTitle.fullText, "WIKI")) end parser_variables["TALKSPACE"] = function return (gsub(mw.site.namespaces[getCurrentTitle.namespace].talk.canonicalName, "_", " ")) end parser_variables["TALKSPACEE"] = function return uri_encode(mw.site.namespaces[getCurrentTitle.namespace].talk.canonicalName, "WIKI") end parser_variables["ARTICLEPAGENAME"] = parser_variables["SUBJECTPAGENAME"] parser_variables["ARTICLEPAGENAMEE"] = parser_variables["SUBJECTPAGENAMEE"] parser_variables["ARTICLESPACE"] = parser_variables["SUBJECTSPACE"] parser_variables["ARTICLESPACEE"] = parser_variables["SUBJECTSPACEE"] parser_variables["CONTENTLANG"] = parser_variables["CONTENTLANGUAGE"] parser_variables["CURRENTMONTH2"] = parser_variables["CURRENTMONTH"] parser_variables["DIRMARK"] = parser_variables["DIRECTIONMARK"] parser_variables["LOCALMONTH2"] = parser_variables["LOCALMONTH"] local transclusion_modifiers = setmetatable({}, case_insensitive) transclusion_modifiers["MSG"] = function(self) end transclusion_modifiers["MSGNW"] = function(self) end transclusion_modifiers["RAW"] = function(self) end transclusion_modifiers["SAFESUBST"] = function(self) end transclusion_modifiers["SUBST"] = function(self) end local templates = {} function Template:expand(args) local name = self:expand_child(1, args) if type(name) == "table" then return self end for snippet, nxt_loc in gmatch(name, "([^:]*):") do			snippet = trim(snippet) if transclusion_modifiers[snippet] then transclusion_modifiers[snippet](self) elseif parser_functions[snippet] then if not self.colon then insert(self, 2, sub(name, nxt_loc)) self.colon = true end return parser_functions[snippet](self, args) end end name = trim(name) if #self == 1 and parser_variables[name] then return parser_variables[name](self, args) end local params = self:get_params(args) local title = new_title(name, 10) title = title.redirectTarget or title local title_text = title.fullText if not templates[title_text] then templates[title_text] = export.parse(title:getContent, title, true):expand(false) pcall(rawset, templates[title_text], "cached", true) end if type(templates[title_text]) ~= "table" then return templates[title_text] elseif params == false then return self end return templates[title_text]:expand(params) end end

-- -- Parser --

local Parser = m_parser.Parser

-- Argument. do local function handle_argument(self, this) if this == "|" then self:emit(Wikitext:new(self:pop_sublayer)) self:push_sublayer elseif this == "}" and self:read(1) == "}" then if self:read(2) == "}" then self:emit(Wikitext:new(self:pop_sublayer)) self:advance(2) return self:pop end return self:fail_route elseif this == "" then return self:fail_route else return self:block_handler(this) end end

function Parser:argument local argument = self:get(handle_argument, self.push_sublayer) if argument == self.bad_route then self:template else if #self:layer == self.emit_pos then local inner = self:remove if type(argument[1]) == "table" then insert(argument[1], 1, inner) else argument[1] = Wikitext:new{inner, argument[1]} end end self.braces = self.braces - 3 self.brace_head = self.brace_head - 3 argument.pos = self.brace_head self:emit(Argument:new(argument)) end end end

-- Template. do local handle_name local handle_parameter function handle_name(self, this) if this == "|" then self:emit(Wikitext:new(self:pop_sublayer)) self.handler = handle_parameter self:push_sublayer elseif this == "}" and self:read(1) == "}" then self:emit(Wikitext:new(self:pop_sublayer)) self:advance return self:pop elseif this == "" then return self:fail_route else return self:block_handler(this) end end function handle_parameter(self, this) if this == "=" and not self.key and (			self:read(1) ~= "=" or			self:read(-1) ~= "\n" and self:read(-1) ~= ""		) then local key = self:pop_sublayer self:push_sublayer rawset(self:layer, "key", Wikitext:new(key)) elseif this == "|" then self:emit(Parameter:new(self:pop_sublayer)) self:push_sublayer elseif this == "}" and self:read(1) == "}" then self:emit(Parameter:new(self:pop_sublayer)) self:advance return self:pop elseif this == "" then return self:fail_route else return self:block_handler(this) end end function Parser:template local template = self:get(handle_name, self.push_sublayer) if template == self.bad_route then self:advance(-1) for _ = 1, self.braces do				self:emit(self.emit_pos, "{") end self.braces = 0 else if #self:layer == self.emit_pos then local inner = self:remove if type(template[1]) == "table" then insert(template[1], 1, inner) else template[1] = Wikitext:new{inner, template[1]} end end self.braces = self.braces - 2 self.brace_head = self.brace_head - 2 template.pos = self.brace_head self:emit(Template:new(template)) end end function Parser:template_or_argument self:advance(2) self.braces = 2 while self:read == "{" do			self:advance self.braces = self.braces + 1 end self.emit_pos = #self:layer + 1 self.brace_head = self.raw_head repeat if self.braces == 1 then self:emit(self.emit_pos, "{") break elseif self.braces == 2 then self:template else self:argument end self:advance until self.braces == 0 self:advance(-1) end end

-- Text not in. function Parser:not_onlyinclude local this, nxt, nxt2 = self:read(0, 1, 2) while not (		this == "" or		this == "<" and nxt == "onlyinclude" and nxt2 == ">"	) do		self:advance this, nxt, nxt2 = nxt, nxt2, self:read(2) end self:advance(2) end

-- Tag. do local function is_ignored_tag(self, check) return self.transcluded and check == "includeonly" or			not self.transcluded and (				check == "noinclude" or				check == "onlyinclude"			) end -- Handlers. local handle_start local handle_ignored_tag_start local handle_ignored_tag local handle_after_tag_name local handle_before_attribute_name local handle_attribute_name local handle_before_attribute_value local handle_quoted_attribute_value local handle_unquoted_attribute_value local handle_after_attribute_value local handle_tag_block local handle_end function handle_start(self, this) if this == "/" then local check = lower(self:read(1)) if is_ignored_tag(self, check) then self.name = check self.ignored = true self:advance self.handler = handle_ignored_tag_start return end return self:fail_route end local check = lower(this) if is_ignored_tag(self, check) then self.name = check self.ignored = true self.handler = handle_ignored_tag_start elseif (			check == "noinclude" and self.transcluded or			check == "includeonly" and not self.transcluded		) then self.name = check self.ignored = true self.handler = handle_after_tag_name elseif TAGS[check] then self.name = check self.handler = handle_after_tag_name else return self:fail_route end end function handle_ignored_tag_start(self, this) if this == ">" then return self:pop elseif this == "/" and self:read(1) == ">" then self.self_closing = true self:advance return self:pop elseif is_space(this) then self.handler = handle_ignored_tag else return self:fail_route end end function handle_ignored_tag(self, this) if this == ">" then return self:pop elseif this == "" then return self:fail_route end end function handle_after_tag_name(self, this) if this == "/" and self:read(1) == ">" then self.self_closing = true self:advance return self:pop elseif this == ">" then self.handler = handle_tag_block elseif is_space(this) then self.handler = handle_before_attribute_name else return self:fail_route end end function handle_before_attribute_name(self, this) if this == "/" and self:read(1) == ">" then self.self_closing = true self:advance return self:pop elseif this == ">" then self.handler = handle_tag_block elseif this ~= "/" and not is_space(this) then self:push_sublayer(handle_attribute_name) return self:consume elseif this == "" then return self:fail_route end end function handle_attribute_name(self, this) if this == "/" or this == ">" or is_space(this) then self:pop_sublayer return self:consume elseif this == "=" then self.attr_name = ulower(concat(self:pop_sublayer)) self.handler = handle_before_attribute_value elseif this == "" then return self:fail_route else self:emit(this) end end function handle_before_attribute_value(self, this) if this == "/" or this == ">" then handle_after_attribute_value(self, "") return self:consume elseif is_space(this) then handle_after_attribute_value(self, "") elseif this == "\"" or this == "'" then			self:push_sublayer(handle_quoted_attribute_value)			rawset(self:layer, "quoter", this)		elseif this == "" then			return self:fail_route		else			self:push_sublayer(handle_unquoted_attribute_value)			return self:consume		end	end	function handle_quoted_attribute_value(self, this)		if this == ">" then			handle_after_attribute_value(self, concat(self:pop_sublayer))			return self:consume		elseif this == self.quoter then			handle_after_attribute_value(self, concat(self:pop_sublayer))		elseif this == "" then			return self:fail_route		else			self:emit(this)		end	end	function handle_unquoted_attribute_value(self, this)		if this == "/" or this == ">" then			handle_after_attribute_value(self, concat(self:pop_sublayer))			return self:consume		elseif is_space(this) then			handle_after_attribute_value(self, concat(self:pop_sublayer))		elseif this == "" then return self:fail_route else self:emit(this) end end function handle_after_attribute_value(self, attr_value) self.attributes = self.attributes or {} self.attributes[self.attr_name] = attr_value self.attr_name = nil self.handler = handle_before_attribute_name end function handle_tag_block(self, this) if (			this == "<" and			self:read(1) == "/" and			lower(self:read(2)) == self.name		) then local tag_end = self:get(handle_end, self.advance, 3) if tag_end == self.bad_route then self:emit("<") else return self:pop end elseif this == "" then return self:fail_route else self:emit(this) end end function handle_end(self, this) if this == ">" then return self:pop elseif not is_space(this) then return self:fail_route end end function Parser:tag local tag = self:get(handle_start, self.advance) if tag == self.bad_route then self:emit("<") else self:emit(Tag:new(tag)) end end end

-- Block handlers. do local function handle_heading_block(self, this) if this == "\n" then self:emit("\n") return self:pop else return self:block_handler(this) end end local function handle_language_conversion_block(self, this) if this == "}" and self:read(1) == "-" then self:advance self:emit("}", "-") return self:pop else return self:block_handler(this) end end local function handle_wikilink_block(self, this) if this == "]" and self:read(1) == "]" then self:advance self:emit("]", "]") return self:pop else return self:block_handler(this) end end function Parser:block_handler(this) if this == "-" and self:read(1) == "{" then self:advance self:emit("-") if self:read(1) == "{" then self:template_or_argument else self:emit_tokens(self:get(handle_language_conversion_block)) end elseif this == "=" and (			self:read(-1) == "\n" or			self:read(-1) == ""		) then self:advance self:emit("=") self:emit_tokens(self:get(handle_heading_block)) elseif this == "[" and self:read(1) == "[" then self:advance self:emit("[") self:emit_tokens(self:get(handle_wikilink_block)) else return self:main_handler(this) end end end

function Parser:main_handler(this) if this == "<" then if (			self:read(1) == "!" and			self:read(2) == "-" and			self:read(3) == "-"		 ) then self:advance(4) local this, nxt, nxt2 = self:read(0, 1, 2) while not (				this == "" or				this == "-" and nxt == "-" and nxt2 == ">"			) do				self:advance this, nxt, nxt2 = nxt, nxt2, self:read(2) end self:advance(2) elseif (		 	self.onlyinclude and		 	self:read(1) == "/" and		 	self:read(2) == "onlyinclude" and		 	self:read(3) == ">"		) then self:advance(4) self:not_onlyinclude else self:tag end elseif this == "{" and self:read(1) == "{" then self:template_or_argument elseif this == "" then return self:pop else self:emit(this) end end

do local function do_parse(self, str, title, transcluded) rawset(self, "title", title) if transcluded then rawset(self, "transcluded", true) if match(str, " ") and match(str, " ") then rawset(self, "onlyinclude", true) self:not_onlyinclude self:advance end end end function export.parse(str, title, transcluded) local text = {} for chunk, char in gmatch(str, "([^%s!\"'%-/<=>%[%]{|}]*)(.?)") do			if #chunk > 0 then				insert(text, chunk)			end			if #char > 0 then				insert(text, char)			end		end		local tokens = Parser:parse(			text,			Parser.main_handler,			do_parse,			str,			title,			transcluded		)		return tokens	end end

function export.parse_page(title) local title = type(title) == "string" and title or title.args[1] mw.title.getCurrentTitle = function return mw.title.new(title) end return export.parse(mw.title.getCurrentTitle:getContent):expand end

return export