Module:User:Theknightwho/template parser
- This module lacks a documentation subpage. You may create it.
- Useful links: root page • root page’s subpages • links • transclusions • testcases • user page • user talk page • userspace
This is a private module sandbox of Theknightwho, for their own experimentation. Items in this module may be added and removed at Theknightwho's discretion; do not rely on this module's stability.
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, "[\"&'<>]", {
["\""] = """, ["&"] = "&", ["'"] = "'",
["<"] = "<", [">"] = ">",
}))
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) .. "</" .. self.name .. ">"
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 = {"{{{", tostring(self[1])}, 2
while self[i] do
insert(output, "|")
insert(output, tostring(self[i]))
i = i + 1
end
insert(output, "}}}")
return concat(output)
elseif self[1] then
return "{{{" .. tostring(self[1]) .. "}}}"
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 "{{{" .. arg1 .. "}}}"
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 "{{" .. tostring(self[1]) .. "}}"
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("<strong class=\"error\">" .. php_escaped(msg) .. "</strong>", 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("{{PAGELANGUAGE}}"))
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 {{#switch:a|a|b|c=d}}, "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("{{REVISIONDAY}}")
end
parser_variables["REVISIONDAY2"] = function()
return Frame:really_preprocess("{{REVISIONDAY2}}")
end
parser_variables["REVISIONID"] = function()
return Frame:really_preprocess("{{REVISIONID}}")
end
parser_variables["REVISIONMONTH"] = function()
return Frame:really_preprocess("{{REVISIONMONTH}}")
end
parser_variables["REVISIONMONTH1"] = function()
return Frame:really_preprocess("{{REVISIONMONTH1}}")
end
parser_variables["REVISIONSIZE"] = function()
return Frame:really_preprocess("{{REVISIONSIZE}}")
end
parser_variables["REVISIONTIMESTAMP"] = function()
return Frame:really_preprocess("{{REVISIONTIMESTAMP}}")
end
parser_variables["REVISIONUSER"] = function()
return Frame:really_preprocess("{{REVISIONUSER}}")
end
parser_variables["REVISIONYEAR"] = function()
return Frame:really_preprocess("{{REVISIONYEAR}}")
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("{{SERVERNAME}}")
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 <onlyinclude></onlyinclude>.
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, "<onlyinclude>") and match(str, "</onlyinclude>") 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