local loaded = package.loaded
local loader = package.loaders
local function sentinel()
end
function require(modname)
assert(
type(modname) == "string",
("bad argument #1 to 'require' (string expected, got %s)"):format(type(modname))
)
local p = loaded
if p then -- is it there?
if p == sentinel then
error(("loop or previous error loading module '%s'"):format(modname))
end
return p -- package is already loaded
end
local init = loader(modname)
assert(
init,
("module '%s' not found"):format(modname)
)
loaded = sentinel
local actual_arg = _G.arg
_G.arg = {modname}
local res = init(modname)
if res then
loaded = res
end
_G.arg = actual_arg
if loaded == sentinel then
loaded = true
end
return loaded
end
mw.loadData = require
setmetatable(loaded, {
__mode = "v"
})
local Parser = {}
Parser.__index = Parser
------------------------------------------------------------------------------------
--
-- Submodules
--
------------------------------------------------------------------------------------
local Tokenizer = require("Module:User:Theknightwho/parser/tokenizer")
------------------------------------------------------------------------------------
--
-- Cache
--
------------------------------------------------------------------------------------
local cached_nodes = {}
local called_nodes = {}
local content_lang = mw.getContentLanguage()
local current_title = mw.title.getCurrentTitle()
local parsed_nodes = {}
local template_calls = {}
local template_trees = {}
local titles = {}
------------------------------------------------------------------------------------
--
-- Utility functions
--
------------------------------------------------------------------------------------
local ulen = mw.ustring.len
local function capturing_split(str, pattern)
local ret, start = {}, 1
pattern = "(.-)(" .. pattern .. ")()"
repeat
if start > #str then
return ret
end
local m1, m2, new_start = str:match(pattern, start)
if not m1 then
table.insert(ret, str:sub(start))
return ret
end
if m1 ~= "" then
table.insert(ret, m1)
end
start = new_start
table.insert(ret, m2)
until false
end
local function text_split(str, pattern)
local ret, start = {}, 1
pattern = "(.-)" .. pattern .. "()"
repeat
local m1, new_start = str:match(pattern, start)
if not m1 then
table.insert(ret, str:sub(start))
return ret
end
table.insert(ret, m1)
start = new_start
until false
end
local function comma_value(n)
local k
n = tostring(n)
repeat
n, k = n:gsub("^(-?%d+)(%d%d%d)", '%1,%2')
until k == 0
return n
end
local function frame_arg_key(key)
-- Parameter keys which are non-decimal integers with no leading zeros between -2^53 and 2^53 that are unsigned when positive (and not -0) are converted to numbers by PHP.
if key == "0" or key:find("^-?%d*$") then
local num_key = tonumber(key)
if (
num_key >= -9007199254740992 and
num_key <= 9007199254740992 and
-- Treated as equal to +/-9007199254740992 due to floating-point rounding errors.
key ~= "9007199254740993" and
key ~= "-9007199254740993"
) then
key = num_key
end
end
return key
end
local function len_event(t)
if #t == 0 then
local mt = getmetatable(t)
if mt and mt.__len then
return mt.__len()
end
end
return #t
end
-- Standard PHP character escape.
local function php_escaped(text)
return (text:gsub("", {
= """, = "&", = "'",
= "<", = ">",
}))
end
-- Almost identical to mw.text.nowiki, but with minor changes to match the PHP equivalent: ";" always escapes, and colons in certain protocols only escape after regex \b.
local function php_wfEscapeWikiText(text)
return (text
:gsub("{|};]", {
= """, = "&", = "'",
= "<", = "=", = ">",
= "[", "] = "]", = "{",
= "|", = "}", = ";"
})
:gsub("%f", {
= "#", = "*", = ":",
= " ", = " ", = " ",
= "	"
})
:gsub("(%f)%-(%-%-%-)", "%1-%2")
:gsub("__", "__")
:gsub("://", "://")
:gsub("(?)()", function(m1, m2)
if m1 == "ISBN" or m1 == "RFC" or m1 == "PMID" then
return m1 .. m2:gsub(".", {
= "	", = " ", = "",
= " ", = " "
})
end
end)
:gsub("+:", {
= "bitcoin:", = "geo:", = "magnet:",
= "mailto:", = "matrix:", = "news:",
= "sip:", = "sips:", = "sms:",
= "tel:", = "urn:", = "xmpp:"
}))
end
local function reverse_table(t)
local new_t = {}
local new_t_i = 1
for i = #t, 1, -1 do
new_t = t
new_t_i = new_t_i + 1
end
return new_t
end
local function shallowcopy(t)
local ret = {}
for k, v in pairs(t) do
ret = v
end
return ret
end
local function tonumber_loose(text)
if type(text) == "string" then
local text_lower = text:lower()
if not (
text_lower == "inf" or
text_lower == "-inf" or
text_lower == "nan" or
text_lower == "-nan"
) then
text = tonumber(text) or text
end
end
return text
end
local function tonumber_strict(text)
if type(text) == "string" then
local num_text = text:match("^?%d+%.?%d*")
text = tonumber(num_text) or text
end
return text
end
------------------------------------------------------------------------------------
--
-- Errors
--
------------------------------------------------------------------------------------
local errors = {}
for _, k in ipairs{"BadRoute", "DisallowedModifier", "MissedCloseToken", "Unresolved"} do
errors = {}
end
------------------------------------------------------------------------------------
--
-- Frame
--
------------------------------------------------------------------------------------
local Frame = mw.getCurrentFrame()
local actual_parent = Frame:getParent()
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
end
)
end
function Frame:getParent()
return nil
end
Frame.really_preprocess = Frame.preprocess
function Frame:preprocess(opt)
return Parser:parse(opt)
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 args = opt.args or {}
local parent = opt.parent ~= false and self
local child = setmetatable({}, {
__index = Frame,
__eq = eq
})
function child:getParent()
return parent
end
function child:getTitle()
return title
end
child.args = args
return child
end
local parent_frame, child_frame
if actual_frame then
parent_frame = Frame:newChild{title = actual_parent:getTitle(), args = actual_parent.args, parent = false}
child_frame = parent_frame:newChild{title = Frame:getTitle(), args = Frame.args}
else
child_frame = Frame:newChild{title = Frame:getTitle(), args = Frame.args, parent = false}
end
function mw.getCurrentFrame()
return child_frame
end
------------------------------------------------------------------------------------
--
-- Tags
--
------------------------------------------------------------------------------------
local tags = {}
for _, k in ipairs{"categorytree", "ce", "chem", "gallery", "graph", "hiero", "imagemap", "inputbox", "math", "nowiki", "pre", "score", "section", "source", "syntaxhighlight", "templatedata", "timeline"} do
tags = true
end
local tag_captures = {}
for tag in pairs(tags) do
tag = tag:gsub(".", function(m)
return ""
end)
table.insert(tag_captures, "(<" .. tag .. ".->)")
table.insert(tag_captures, "(<" .. "/" .. tag .. "%s->)")
end
for _, tag in ipairs{"includeonly", "noinclude", "onlyinclude"} do
tag = tag:gsub(".", function(m)
return ""
end)
table.insert(tag_captures, "(<" .. tag .. ".->)")
table.insert(tag_captures, "(<" .. "/" .. tag .. ".->)")
end
table.insert(tag_captures, "(<!%-%-)")
table.insert(tag_captures, "(%-%->)")
local iferror_tags = {}
for _, k in ipairs{"div", "p", "span", "strong"} do
iferror_tags = true
end
------------------------------------------------------------------------------------
--
-- Nodes
--
------------------------------------------------------------------------------------
local Wikitext = {}
Wikitext.__index = Wikitext
function Wikitext:new(t, type)
local node = select(2, xpcall(
function()
return setmetatable(t, self)
end,
function()
return setmetatable({t}, self)
end
))
cached_nodes = type
return node
end
function Wikitext:unresolved_handler(func)
return function(err)
if err == errors.Unresolved then
return func()
else
if self.title then
-- TODO: implement template traceback
error("Error parsing " .. current_title .. ": " .. debug.traceback(err), 2)
else
error(debug.traceback(err), 2)
end
end
end
end
function Wikitext:resolve()
for k in ipairs(self) do
self = self:get_child(self, true)
end
return select(2, xpcall(
function()
return table.concat(self)
end,
function()
return self
end
))
end
function Wikitext:try_resolve()
self = select(2, xpcall(
function()
return self:resolve()
end,
self:unresolved_handler(function()
return self
end)
))
return self
end
function Wikitext:get_child(node, no_trim)
if called_nodes then
return called_nodes
end
if type(node) == "table" then
node = node:try_resolve()
end
if type(node) == "string" and not no_trim then
local node_old = node
node = node:match("^*(.-)*$")
called_nodes = node
called_nodes = node
end
return node
end
function Wikitext:unresolved()
error(errors.Unresolved)
end
function Wikitext:get_arg()
return nil
end
function Wikitext:prepare_frames()
return nil
end
function Wikitext:get_instance(cached_node, name, args)
if type(cached_node) ~= "table" then
return cached_node
end
local node = {}
if cached_nodes then
function node:try_resolve()
if not parsed_nodes then
cached_node = self:get_child(cached_node)
parsed_nodes = true
end
if type(cached_node) == "string" then
return cached_node
end
self = select(2, xpcall(
function()
return self:resolve()
end,
self:unresolved_handler(function()
return self
end)
))
return self
end
function node:get_arg(arg)
return args and args
end
if cached_nodes == "Template" then
function node:prepare_frames(mod)
local parent_args = args and {}
for k, v in pairs(args) do
local k_new = frame_arg_key(k)
parent_args = args
end
parent_frame = Frame:newChild{title = name, args = parent_args, parent = false}
for k in pairs(self.params) do
self.params = self:get_child(self.params)
end
local child_args = self.params and shallowcopy(self.params)
child_frame = parent_frame:newChild{title = mod, args = child_args}
return true
end
end
end
return setmetatable(node, {
__index = function(t, k)
if type(rawget(cached_node, k)) == "table" then
t = self:get_instance(cached_node, name, args)
return t
end
return cached_node
end,
__ipairs = function(t)
return function(t, i)
i = i + 1
local v = t
if v then
return i, v
end
end, t, 0
end,
__pairs = function(t)
local done, mt = {}
return function(t, k)
if not mt then
k = next(t, k)
local v = k and t
if v then
done = true
return k, v
end
mt = true
k = next(cached_node)
end
while k and done do
k = next(cached_node, k)
end
local v = k and t
if v then
done = true
return k, v
end
end, t
end,
__len = function()
return #cached_node
end
})
end
local Argument = Wikitext:new{}
Argument.__index = Argument
function Argument:resolve()
self = self:get_child(self)
if type(self) == "table" or cached_nodes then
self:unresolved()
elseif self:get_arg(self) then
return self:get_arg(self)
end
self = self:get_child(self)
if type(self) == "table" then
self:unresolved()
else
return self or "{{{" .. self .. "}}}"
end
end
local Template = Wikitext:new{}
Template.__index = Template
function Template:parser_function_error(mw_page, ...)
local msg = mw.title.new("MediaWiki:" .. mw_page):getContent()
for i, arg in ipairs{...} do
msg = msg:gsub("$" .. i, arg)
end
return Parser:parse("<strong class=\"error\">" .. php_escaped(msg) .. "</strong>")
end
local expr = {}
for v, k in ipairs{"NEGATIVE", "POSITIVE", "PLUS", "MINUS", "TIMES", "DIVIDE", "MOD", "OPEN", "CLOSE", "AND", "OR", "NOT", "EQUALITY", "LESS", "GREATER", "LESSEQ", "GREATEREQ", "NOTEQ", "ROUND", "EXPONENT", "SINE", "COSINE", "TANGENS", "ARCSINE", "ARCCOS", "ARCTAN", "EXP", "LN", "ABS", "FLOOR", "TRUNC", "CEIL", "POW", "PI", "FMOD", "SQRT"} do
expr = v
end
local expr_white_class = {}
for _, k in ipairs{" ", "\t", "\r", "\n"} do
expr_white_class = true
end
local expr_number_class = {}
for _, k in ipairs{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."} do
expr_number_class = true
end
local expr_alpha_class = {}
for _, k in ipairs{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"} do
expr_alpha_class = true
end
local expr_precedence = {
= 10, = 10, = 10,
= 9, = 9, = 9,
= 9, = 9, = 9,
= 9, = 9, = 9,
= 9, = 9, = 9,
= 9, = 9, = 8,
= 7, = 7, = 7,
= 7, = 6, = 6,
= 5, = 4, = 4,
= 4, = 4, = 4,
= 4, = 3, = 2,
= 0, = -1, = -1,
}
local expr_names = {
= "-", = "+", = "not",
= "*", = "/", = "mod",
= "fmod", = "+", = "-",
= "round", = "=", = "<",
= ">", = "<=", = ">=",
= "<>", = "and", = "or",
= "e", = "sin", = "cos",
= "tan", = "asin", = "acos",
= "atan", = "ln", = "exp",
= "abs", = "floor", = "trunc",
= "ceil", = "^", = "pi", = "sqrt",
}
local expr_signs = {
= expr.LESSEQ, = expr.GREATEREQ, = expr.NOTEQ,
= expr.NOTEQ, = expr.TIMES, = expr.DIVIDE,
= expr.POW, = expr.EQUALITY, = expr.LESS,
= expr.GREATER
}
local expr_stack_min = {
= 1, = 1, = 2,
= 2, = 2, = 2,
= 2, = 2, = 2,
= 2, = 2, = 1,
= 2, = 2, = 2,
= 2, = 2, = 2,
= 2, = 1, = 1,
= 1, = 1, = 1,
= 1, = 1, = 1,
= 1, = 1, = 1,
= 1, = 2, = 1
}
local expr_unary = {}
for _, k in ipairs{"NOT", "SINE", "COSINE", "TANGENS", "ARCSINE", "ARCCOS", "ARCTAN", "EXP", "LN", "ABS", "FLOOR", "TRUNC", "CEIL", "SQRT"} do
expr_unary] = true
end
local expr_words = {
= expr.MOD, = expr.FMOD, = expr.AND,
= expr.OR, = expr.NOT, = expr.ROUND,
= expr.DIVIDE, = expr.EXPONENT, = expr.SINE,
= expr.COSINE, = expr.TANGENS, = expr.ARCSINE,
= expr.ARCCOS, = expr.ARCTAN, = expr.EXP,
= expr.LN, = expr.ABS, = expr.TRUNC,
= expr.FLOOR, = expr.CEIL, = expr.PI,
= expr.SQRT
}
local expr_operations = {
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, arg * -1)
end,
= function(op, stack)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left * right)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
if right == 0 then
self:parser_function_error("pfunc_expr_division_by_zero")
end
table.insert(stack, left / right)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
if right == 0 then
self:parser_function_error("pfunc_expr_division_by_zero")
end
table.insert(stack, left % right)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
if right == 0 then
self:parser_function_error("pfunc_expr_division_by_zero")
end
table.insert(stack, math.fmod(left, right))
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left + right)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left - right)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left and right and 1 or 0)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, (left or right) and 1 or 0)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left == right and 1 or 0)
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, (not arg) and 1 or 0)
end,
= function(op, stack)
local mult = 10^(table.remove(stack) or 0)
local value = table.remove(stack)
table.insert(stack, math.floor(value * mult + 0.5) / mult)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left < right and 1 or 0)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left > right and 1 or 0)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left <= right and 1 or 0)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left >= right and 1 or 0)
end,
= function(op, stack)
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, left ~= right and 1 or 0)
end,
= function(op, stack) -- TODO
local right = table.remove(stack)
local left = table.remove(stack)
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, math.sin(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, math.cos(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, math.tan(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
if arg < -1 or arg > 1 then
self:parser_function_error("pfunc_expr_invalid_argument")
end
table.insert(stack, math.asin(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
if arg < -1 or arg > 1 then
self:parser_function_error("pfunc_expr_invalid_argument")
end
table.insert(stack, math.acos(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, math.atan(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, math.exp(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
if arg <= 0 then
self:parser_function_error("pfunc_expr_invalid_argument_ln")
end
table.insert(stack, math.log(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, math.abs(arg))
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, math.floor(arg))
end,
= function(op, stack) -- TODO
local arg = table.remove(stack)
end,
= function(op, stack)
local arg = table.remove(stack)
table.insert(stack, math.ceil(arg))
end,
= function(op, stack) -- TODO
local right = table.remove(stack)
local left = table.remove(stack)
table.insert(stack, math.pow(left, right))
end,
= function(op, stack)
local arg = table.remove(stack)
local ret = math.sqrt(arg)
--if ret == tonumber("NaN") then -- TODO
self:parser_function_error("pfunc_expr_not_a_number")
--end
table.insert(stack, ret)
end
}
-- Implements #EXPR and #IFEXPR.
function Template:expression(str)
local operands, operators = {}, {}
for k, v in pairs{
= "<", = ">", = "-", = "-"
} do
str = str:gsub(k, v)
end
local p, fin, expecting, name, op = 1, str:len(), "expression"
while p <= fin do
repeat
if #operands > 100 or #operators > 100 then
return self:parser_function_error("pfunc_expr_stack_exhausted")
end
local char = str:sub(p, p)
local char2 = str:sub(p + 1, p + 2)
if expr_white_class then
while expr_white_class do
p = p + 1
char = str:sub(p, p)
end
break
elseif expr_number_class then
if expecting ~= "expression" then
return self:parser_function_error("pfunc_expr_unexpected_number")
end
local len = 0
while expr_number_class do
len = len + 1
p = p + 1
char = str:sub(p, p)
end
table.insert(operands, tonumber_loose(str:sub(p - len, p - 1)))
expecting = "operator"
break
elseif expr_alpha_class then
local remaining = str:sub(p)
local word = remaining:match("^%a*")
if not word then
return self:parser_function_error("pfunc_expr_preg_match_failure")
end
word = word:lower()
p = p + word:len()
if not expr_words then
return self:parser_function_error("pfunc_expr_unrecognised_word", word)
end
op = expr_words
if op == expr.EXPONENT or op == expr.PI then
if expecting ~= "expression" then
break
end
local v = op == expr.EXPONENT and math.exp(1) or math.pi
table.insert(operands, v)
expecting = "operator"
break
elseif expr_unary then
if expecting ~= "expression" then
return self:parser_function_error("pfunc_expr_unexpected_operator", word)
end
tabe.insert(operators, op)
break
end
name = word
elseif expr_signs then
name = char2
op = expr_signs
p = p + 2
elseif char == "+" then
p = p + 1
if expecting == "expression" then
table.insert(operators, expr.POSITIVE)
break
end
op = expr.PLUS
elseif char == "-" then
p = p + 1
if expecting == "expression" then
table.insert(operators, expr.NEGATIVE)
break
end
op = expr.MINUS
elseif char == "(" then
if expecting == "operator" then
return self:parser_function_error("pfunc_expr_unexpected_operator", "(")
end
table.insert(operators, expr.OPEN)
p = p + 1
break
elseif char == ")" then
local last_op = operators
repeat
local last_op = table.remove(operators)
until last_op == expr.OPEN or not last_op
if not last_op then
return self:parser_function_error("pfunc_expr_unexpected_closing_bracket")
end
expecting = "operator"
p = p + 1
break
elseif expr_signs then
name = char
op = expr_signs
p = p + 1
else
--local utf_expr =
end
if execting == "expression" then
return self:parser_function_error("pfunc_expr_unexpected_operator", name)
end
local last_op = operators
while last_op and expr_precedence <= expr_precedence do
if #operands < expr_stack_min then
self:parser_function_error("pfunc_expr_missing_operand", expr_names)
end
expr_operations(last_op, operands)
table.remove(operators)
last_op = operators
end
table.insert(operators, op)
expecting = "expression"
until true
end
while #operators > 0 and op == table.remove(operators) do
if op == expr.OPEN then
return self:parser_function_error("pfunc_expr_unclosed_bracket", name)
elseif expr_operations then
expr_operations(op, operands)
else
self:parser_function_error("pfunc_expr_unknown_error")
end
end
return table.concat(operands, "<br />\n")
end
-- Implements PADLEFT and PADRIGHT.
function Template:pad(left, str, len, pad_str)
len = len and tonumber_strict(len)
if (
not len or
pad_str == "" or
type(len) ~= "number"
or len < 1
) then
return str, ""
elseif not pad_str then
pad_str = "0"
end
local param3_len = ulen(pad_str)
local ret_len = math.min(len, 500) - ulen(str)
if ret_len <= 0 then
return str, ""
end
local reps = math.floor(ret_len / param3_len)
local rem = ret_len % param3_len
if left then
return pad_str:rep(reps) .. pad_str:sub(1, rem) .. str
end
return str .. pad_str:rep(reps) .. pad_str:sub(1, rem)
end
local case_insensitive = {}
case_insensitive.__index = function(t, k)
local k_upper = k:upper()
if type(k) == "string" and case_insensitive then
return rawget(t, 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 = true
end
Template.parser_functions = {
= function(self)
self:load_array_params()
return Frame:callParserFunction(
"#BABEL",
self.params
)
end,
= function(self) -- TODO
end,
= function(self)
self:load_array_params(1)
return self:expression(self.params)
end,
= function(self) -- TODO
-- self:load_array_params(2) ?
end,
= function(self)
self:load_array_params(3, 1)
local n = self.params ~= "" and 2 or 3
self.params = self.params and self:get_child(self.params)
return self.params or ""
end,
= function(self)
self:load_array_params(4, 2)
for i = 1, 2 do
self.params = tonumber_loose(self.params)
end
local n = self.params == self.params and 3 or 4
self.params = self.params and self:get_child(self.params)
return self.params or ""
end,
= function(self)
self:load_array_params(3, 1)
local n = 3
for tag in self.params:gmatch("<(+)-%fclass=\"-%ferror%f-\"") do
if iferror_tags then
n = 2
break
end
end
self.params = self.params and self:get_child(self.params)
return (
self.params or
n == 2 and "" or
n == 3 and self.params
)
end,
= function(self)
self:load_array_params(3, 1)
local title = mw.title.new(self.params)
local n = title and title.exists and 2 or 3
self.params = self.params and self:get_child(self.params)
return self.params or ""
end,
= function(self)
self:load_array_params(3, 1)
local t = Frame:callParserFunction(
"#IFEXPR",
self.params,
"2", "3"
)
local n = t == "2" and 2 or t == "3" and 3
if not n then
return t
end
self.params = self.params and self:get_child(self.params)
return self.params or ""
end,
= function(self)
if self then
self.params = self.params or {}
local params, added, offset, unresolved = self, {}, 0
if not params then
self = params
offset = offset + 1
params = true
end
if not params then
error("Module error: You must specify a function to call.")
end
if not params then
if params then
self = Wikitext:new({params, "=", params}, cached_nodes and "Wikitext")
else
self = params
offset = offset + 1
end
params = true
end
for i = 3, len_event(params) do
repeat
if not params then
if params then
params = self:get_child(params)
if type(params) == "table" then
unresolved = true
break
end
params = frame_arg_key(params)
else
params = params - offset
end
params = true
end
if not added] then
self.params] = params
added] = true
end
until true
end
if unresolved then
self:unresolved()
end
self = nil
end
self = self:get_child(self)
self = self:get_child(self)
if type(self) == "table" or type(self) == "table" then
self:unresolved()
end
local mod = "Module:" .. self
if not self:prepare_frames(mod) then
self:unresolved()
end
if not require(mod)] then error(mw.dumpObject(self)) end
return tostring(require(mod)](child_frame))
end,
= function(self) -- TODO
-- self:load_array_params(2) ?
end,
= function(self) -- TODO
end,
= function(self) -- TODO
end,
= function(self) -- TODO
end,
= function(self) -- TODO
end,
= function(self) -- TODO
end,
= function(self)
self:load_array_params(2)
local from = self.params
if from == "" then
from = current_title.prefixedText
end
local to = self.params
:reverse()
:gsub("^*", "")
:reverse()
if to == "" or to == "." then
return from
end
if self.params:find("^%.?%.?/?$") then
from = ""
end
local full_path = from .. "/" .. to
local exploded, i = text_split(full_path, "/"), 1
repeat
if exploded == "" or exploded == "." then
table.remove(exploded, i)
elseif exploded == ".." then
if i == 1 then
return self:parser_function_error("pfunc_rel2abs_invalid_depth", full_path)
end
table.remove(exploded, i)
table.remove(exploded, i - 1)
i = i - 1
else
i = i + 1
end
until not exploded
return table.concat(exploded, "/")
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"#SPECIAL",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"#SPECIALE",
self.params
)
end,
= function(self) -- TODO
end,
= function(self)
-- `added` keeps track of whether a key has already been added to self.params in the current run. If present, the new value is ignored. We can't do this by simply checking whether it's already been added to self.params or not, because key X might be a variable during caching (so needs to be evaluated each call), while key Y might be static. In situations where X == Y, Y should be ignored. However, because it's static, it will already be present in the cached template's self.params table, so checking self.params after evaluating X in a template call would result in X's value being discarded.
-- `add_keys` stores any shortcut keys (e.g. a and b in a|b|c=d).
-- The parameter `false` stores the default value, given by #default (case insensitive). These can also act as keys (e.g. if #dEfault and #defaulT are given, #defaulT (the second) would give the actual default value as it's the last instance, but an exact input of #dEfault simply treats the first as an ordinary key and matches it. This default is overridden if any values without keys are given at the end.
if self then
self.params = self.params or {}
local params, added, add_keys, unresolved = self, {}, {}
self.params = params
for i = 2, len_event(params) do
repeat
if params then
if not params then
params = self:get_child(params)
if type(params) == "table" then
unresolved = true
add_keys = {}
break
end
params = true
end
if not added] then
self.params] = params
added] = true
end
for _, k in ipairs(add_keys) do
params = self:get_child(params)
if type(params) == "table" then
unresolved = true
break
end
if not added] then
self.params] = params
added] = true
end
end
add_keys = {}
if params == nil then
params = params:lower() == "#defaultsort"
end
if params then
self.params = params
end
else
table.insert(add_keys, i)
end
until true
end
-- Default value is always the final parameter if it doesn't have an equals sign.
if len_event(add_keys) > 0 then
local k = add_keys
self.params = params
end
if unresolved then
self:unresolved()
end
self = nil
end
self.params = self:get_child(self.params)
if type(self.params) == "table" then
self:unresolved()
end
local n = self.params] and self.params or false
self.params = self:get_child(self.params)
return self.params or ""
end,
= function(self) -- TODO
end,
= function(self) -- TODO
end,
= function(self) -- TODO
end,
= function(self) -- TODO
end,
= function(self)
self:load_array_params(3)
return Frame:callParserFunction(
"#TITLEPARTS",
self.params
)
end,
= function(self) -- TODO
end,
= function(self)
self:load_array_params(1)
return mw.uri.anchorEncode(self.params)
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.baseText and php_wfEscapeWikiText(title.baseText) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.baseText and php_wfEscapeWikiText(mw.uri.encode(title.baseText, "WIKI")) or ""
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"BIDI",
self.params
)
end,
= function(self)
self:load_array_params(2)
return tostring(mw.uri.canonicalUrl(
self.params,
self.params
))
end,
= function(self)
self:load_array_params(2)
return mw.uri.encode(tostring(mw.uri.canonicalUrl(
self.params,
self.params
)), "WIKI")
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.cascadingProtection and type(title.cascadingProtection.sources) == "table" and table.concat(title.cascadingProtection.sources, "|") or ""
end,
= function(self)
self:load_array_params(2)
return Frame:callParserFunction(
"DEFAULTSORT",
self.params
)
end,
= function(self)
self:load_array_params(2)
return Frame:callParserFunction(
"DISPLAYTITLE",
self.params
)
end,
= function(self)
self:load_array_params(3)
return Frame:callParserFunction(
"FILEPATH",
self.params
)
end,
= function(self)
self:load_array_params(2)
return content_lang:formatNum(
self.params,
self.params
)
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.prefixedText and php_wfEscapeWikiText(title.prefixedText) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.prefixedText and php_wfEscapeWikiText(mw.uri.encode(title.prefixedText, "WIKI")) or ""
end,
= function(self)
self:load_array_params(2)
return tostring(mw.uri.fullUrl(
self.params,
self.params
))
end,
= function(self)
self:load_array_params(2)
return mw.uri.encode(tostring(mw.uri.fullUrl(
self.params,
self.params
)), "WIKI")
end,
= function(self)
self:load_array_params(4, 1)
local t = Frame:callParserFunction(
"GENDER",
self.params,
"2", "3", "4"
)
local n = (
t == "3" and self.params and 3 or
t == "4" and self.params and 4 or
2
)
self.params = self.params and self:get_child(self.params)
return self.params or ""
end,
= function(self)
self:load_array_params(2)
return content_lang:grammar(
self.params,
self.params
)
end,
= function(self) -- TODO
end,
= function(self)
self:load_array_params(1)
return content_lang:lc(self.params)
end,
= function(self)
self:load_array_params(1)
return content_lang:lcfirst(self.params)
end,
= function(self)
self:load_array_params(2)
return tostring(mw.uri.localUrl(
self.params,
self.params
))
end,
= function(self)
self:load_array_params(2)
return mw.uri.encode(tostring(mw.uri.localUrl(
self.params,
self.params
)), "WIKI")
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.nsText and php_wfEscapeWikiText(title.nsText) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.nsText and php_wfEscapeWikiText(mw.uri.encode(title.nsText, "WIKI")) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.namespace and tostring(title.namespace) or ""
end,
= function(self) -- TODO
end,
= function(self)
self:load_array_params(1)
local ns = tonumber(self.params)
return mw.site.namespaces and mw.site.namespaces.name or ""
end,
= function(self)
self:load_array_params(1)
local ns = tonumber(self.params)
return mw.site.namespaces and mw.uri.encode(mw.site.namespaces.name, "WIKI") or ""
end,
= function(self)
self:load_array_params(2)
local num = mw.site.stats.usersInGroup(self.params)
return self.params == "R" and num or comma_value(num)
end,
= function(self)
self:load_array_params(1)
local num = mw.site.stats.activeUsers
return self.params == "R" and num or comma_value(num)
end,
= function(self)
self:load_array_params(1)
local num = mw.site.stats.admins
return self.params == "R" and num or comma_value(num)
end,
= function(self)
self:load_array_params(1)
local num = mw.site.stats.articles
return self.params == "R" and num or comma_value(num)
end,
= function(self)
self:load_array_params(1)
local num = mw.site.stats.edits
return self.params == "R" and num or comma_value(num)
end,
= function(self)
self:load_array_params(1)
local num = mw.site.stats.files
return self.params == "R" and num or comma_value(num)
end,
= function(self)
self:load_array_params(1)
local num = mw.site.stats.pages
return self.params == "R" and num or comma_value(num)
end,
= function(self)
self:load_array_params(1)
local num = mw.site.stats.users
return self.params == "R" and num or comma_value(num)
end,
= function(self)
self:load_array_params(3)
return self:pad(true, unpack(self.params))
end,
= function(self)
self:load_array_params(3)
return self:pad(false, unpack(self.params))
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.id and tostring(title.id) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.text and php_wfEscapeWikiText(title.text) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.text and php_wfEscapeWikiText(mw.uri.encode(title.text, "WIKI")) or ""
end,
= function(self)
self:load_array_params(3)
if self.params then
self.params = self.params:lower()
if not (
self.params == "all" or
self.params == "subcats" or
self.params == "files" or
self.params == "pages"
) then
self.params = nil
end
end
local num = mw.site.stats.pagesInCategory(self.params, self.params)
return self.params == "R" and num or comma_value(num)
end,
= function(self) -- TODO
-- self:load_array_params(2) ?
end,
= function(self)
if self then
self.params = self.params or {}
local params, added, unresolved = self, {}
self.params = params
for i = 2, len_event(params) do
repeat
if params then
if not params then
params = self:get_child(params)
if type(params) == "table" then
unresolved = true
break
end
params = true
end
if not added] then
self.params] = params
added] = true
end
else
if params == 2 then
self.params = params
elseif params == 3 then
self.params = params
end
end
until true
end
if unresolved then
self:unresolved()
end
self = nil
end
self.params = self:get_child(self.params)
if type(self.params) == "table" then
self:unresolved()
end
local v = tostring(tonumber_strict(self.params))
local n = (
self.params and v or
self and v ~= "1" and v ~= "-1" and true or
false
)
self.params = self:get_child(self.params)
return self.params or ""
end,
= function(self)
self:load_array_params(2)
return Frame:callParserFunction(
"PROTECTIONEXPIRY",
self.params
)
end,
= function(self) -- TODO
self:load_array_params(2)
return Frame:callParserFunction(
"PROTECTIONLEVEL",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"REVISIONDAY",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"REVISIONDAY2",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"REVISIONID",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"REVISIONMONTH",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"REVISIONMONTH1",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"REVISIONTIMESTAMP",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"REVISIONUSER",
self.params
)
end,
= function(self)
self:load_array_params(1)
return Frame:callParserFunction(
"REVISIONYEAR",
self.params
)
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.rootText and php_wfEscapeWikiText(title.rootText) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.rootText and php_wfEscapeWikiText(mw.uri.encode(title.rootText, "WIKI")) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.subjectPageTitle and title.subjectPageTitle.fullText and php_wfEscapeWikiText(title.subjectPageTitle.fullText) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.subjectPageTitle and title.subjectPageTitle.fullText and php_wfEscapeWikiText(mw.uri.encode(title.subjectPageTitle.fullText, "WIKI")) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.subjectNsText and php_wfEscapeWikiText(title.subjectNsText) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.subjectNsText and php_wfEscapeWikiText(mw.uri.encode(title.subjectNsText, "WIKI")) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.subpageText and php_wfEscapeWikiText(title.subpageText) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.subpageText and php_wfEscapeWikiText(mw.uri.encode(title.subpageText, "WIKI")) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.talkPageTitle and title.talkPageTitle.fullText and php_wfEscapeWikiText(title.talkPageTitle.fullText) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.talkPageTitle and title.talkPageTitle.fullText and php_wfEscapeWikiText(mw.uri.encode(title.talkPageTitle.fullText, "WIKI")) or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.namespace and mw.site.namespaces and mw.site.namespaces.talk and mw.site.namespaces.talk.canonicalName or ""
end,
= function(self)
self:load_array_params(1)
local title = mw.title.new(self.params)
return title and title.namespace and mw.site.namespaces and mw.site.namespaces.talk and mw.site.namespaces.talk.canonicalName and mw.uri.encode(mw.site.namespaces.talk.canonicalName, "WIKI") or ""
end,
= function(self)
self:load_array_params(1)
return content_lang:uc(self.params)
end,
= function(self)
self:load_array_params(1)
return content_lang:ucfirst(self.params)
end,
= function(self)
self:load_array_params(2)
return mw.uri.encode(
self.params,
self.params
)
end
}
for k, v in pairs{
= "#FORMATDATE",
= "#LST",
= "#LSTH",
= "#LSTX",
= "DEFAULTSORT",
= "DEFAULTSORT",
= "NUMBERINGROUP",
= "PAGESINCATEGORY",
= "SUBJECTPAGENAME",
= "SUBJECTPAGENAMEE",
= "SUBJECTSPACE",
= "SUBJECTSPACEE"
} do
Template.parser_functions = Template.parser_functions
end
setmetatable(Template.parser_functions, case_insensitive)
Template.parser_variables = {
= "|",
= "="
}
local parser_variables = {
= "SUBJECTPAGENAME",
= "SUBJECTPAGENAMEE",
= function()
return mw.title.new("$1"):localUrl()
end,
= "SUBJECTSPACE",
= "SUBJECTSPACEE",
= function()
return php_wfEscapeWikiText(current_title.baseText)
end,
= function()
return php_wfEscapeWikiText(mw.uri.encode(current_title.baseText, "WIKI"))
end,
= function()
return table.concat(current_title.cascadingProtection.sources, "|")
end,
= "CONTENTLANGUAGE",
= function()
return content_lang:getCode()
end,
= function()
return ("%d"):format(os.date("!%d"))
end,
= function()
return os.date("!%d")
end,
= function()
return os.date("!%A")
end,
= function()
return os.date("!%w")
end,
= function()
return os.date("!%H")
end,
= function()
return os.date("!%m")
end,
= function()
return ("%d"):format(os.date("!%m"))
end,
= "CURRENTMONTH",
= function()
return os.date("!%b")
end,
= function()
return os.date("!%B")
end,
= "CURRENTMONTHNAME",
= function()
return os.date("!%R")
end,
= function()
return os.date("!%Y%m%d%H%M%S")
end,
= function()
return mw.site.currentVersion
end,
= function()
return ("%02d"):format(
(os.date("!%U%w"):gsub("(..)(.)", function(m1, m2)
return m2 == "0" and (m1 - 1) or m1
end) - 1) % 52 + 1
)
end,
= function()
return os.date("!%Y")
end,
= function()
return content_lang:getDirMark()
end,
= "DIRECTIONMARK",
= function()
return php_wfEscapeWikiText(current_title.prefixedText)
end,
= function()
return php_wfEscapeWikiText(mw.uri.encode(current_title.prefixedText, "WIKI"))
end,
= function()
return ("%d"):format(os.date("%d"))
end,
= function()
return os.date("%d")
end,
= function()
return os.date("%A")
end,
= function()
return os.date("%w")
end,
= function()
return os.date("%H")
end,
= function()
return os.date("%m")
end,
= function()
return ("%d"):format(os.date("%m"))
end,
= "LOCALMONTH",
= function()
return os.date("%b")
end,
= function()
return os.date("%B")
end,
= "LOCALMONTHNAME",
= function()
return os.date("%R")
end,
= function()
return os.date("%Y%m%d%H%M%S")
end,
= function()
return ("%02d"):format(
(os.date("%U%w"):gsub("(..)(.)", function(m1, m2)
return m2 == "0" and (m1 - 1) or m1
end) - 1) % 52 + 1
)
end,
= function()
return os.date("%Y")
end,
= function()
return current_title.nsText
end,
= function()
return mw.uri.encode(current_title.nsText, "WIKI")
end,
= function()
return tostring(current_title.namespace)
end,
= function()
return Frame:callParserFunction(
"NOEXTERNALLANGLINKS",
"*"
)
end,
= function()
return comma_value(mw.site.stats.activeUsers)
end,
= function()
return comma_value(mw.site.stats.admins)
end,
= function()
return comma_value(mw.site.stats.articles)
end,
= function()
return comma_value(mw.site.stats.edits)
end,
= function()
return comma_value(mw.site.stats.files)
end,
= function()
return comma_value(mw.site.stats.pages)
end,
= function()
return comma_value(mw.site.stats.users)
end,
= function()
return tostring(current_title.id)
end,
= function()
return Frame:really_preprocess("{{PAGELANGUAGE}}")
end,
= function()
return php_wfEscapeWikiText(current_title.text)
end,
= function()
return php_wfEscapeWikiText(mw.uri.encode(current_title.text, "WIKI"))
end,
= function()
return Frame:callParserFunction(
"REVISIONDAY",
current_title.fullText
)
end,
= function()
return Frame:callParserFunction(
"REVISIONDAY2",
current_title.fullText
)
end,
= function()
return Frame:callParserFunction(
"REVISIONID",
current_title.fullText
)
end,
= function()
return Frame:callParserFunction(
"REVISIONMONTH",
current_title.fullText
)
end,
= function()
return Frame:callParserFunction(
"REVISIONMONTH1",
current_title.fullText
)
end,
= function()
return Frame:really_preprocess("{{REVISIONSIZE}}")
end,
= function()
return Frame:callParserFunction(
"REVISIONTIMESTAMP",
current_title.fullText
)
end,
= function()
return Frame:callParserFunction(
"REVISIONUSER",
current_title.fullText
)
end,
= function()
return Frame:callParserFunction(
"REVISIONYEAR",
current_title.fullText
)
end,
= function()
return php_wfEscapeWikiText(current_title.rootText)
end,
= function()
return php_wfEscapeWikiText(mw.uri.encode(current_title.rootText, "WIKI"))
end,
= function()
return mw.site.scriptPath
end,
= function()
return mw.site.server
end,
= function()
return Frame:really_preprocess("{{SERVERNAME}}")
end,
= function()
return mw.site.siteName
end,
= function()
return mw.site.stylePath
end,
= function()
return php_wfEscapeWikiText(current_title.subjectPageTitle.fullText)
end,
= function()
return php_wfEscapeWikiText(mw.uri.encode(current_title.subjectPageTitle.fullText, "WIKI"))
end,
= function()
return current_title.subjectNsText
end,
= function()
return mw.uri.encode(current_title.subjectNsText, "WIKI")
end,
= function()
return php_wfEscapeWikiText(current_title.subpageText)
end,
= function()
return php_wfEscapeWikiText(mw.uri.encode(current_title.subpageText, "WIKI"))
end,
= function()
return php_wfEscapeWikiText(current_title.talkPageTitle.fullText)
end,
= function()
return php_wfEscapeWikiText(mw.uri.encode(current_title.talkPageTitle.fullText, "WIKI"))
end,
= function()
return mw.site.namespaces.talk.canonicalName
end,
= function()
return mw.uri.encode(mw.site.namespaces.talk.canonicalName, "WIKI")
end
}
parser_variables.__index = function(t, k)
local k_upper = k:upper()
if type(k) == "string" and case_insensitive then
k = k_upper
end
if type(parser_variables) == "string" then
k = parser_variables
end
if parser_variables then
t = parser_variables()
parser_variables = nil
end
return rawget(t, k)
end
setmetatable(Template.parser_variables, parser_variables)
Template.transclusion_modifiers = {
= function(self)
if self.modifiers_context < 2 then
-- ...
self.modifiers_context = 2
end
error(errors.DisallowedModifier)
end,
= function(self)
if self.modifiers_context < 2 then
-- ...
self.modifiers_context = 2
end
error(errors.DisallowedModifier)
end,
= function(self)
if self.modifiers_context < 3 then
-- ...
self.modifiers_context = 3
end
error(errors.DisallowedModifier)
end,
= function(self)
if self.modifiers_context < 1 then
self.modifiers_context = 1
return
end
error(errors.DisallowedModifier)
end,
= function(self)
if self.modifiers_context < 1 then
self:fail()
end
error(errors.DisallowedModifier)
end
}
setmetatable(Template.transclusion_modifiers, case_insensitive)
function Template:remove_param(pos, abs)
local params, param = self
if type(pos) ~= "number" then
for i, p in ipairs(params) do
if p == pos then
return table.remove(params, i)
end
end
elseif abs then
param = table.remove(self, pos)
if param and not param then
for i = pos, len_event(params) do
if not params then
params = params - 1
end
end
end
return param
end
local removed, show_key
for i, p in ipairs(params) do
if removed and not (show_key or p) then
p = p - 1
elseif p == pos then
param = table.remove(params, i)
removed, show_key = true, param
end
end
return param
end
function Template:load_table_params()
if not self then
return
end
self.params = self.params or {}
local params, unresolved = self
for i, param in ipairs(params) do
repeat
if not param then
if param then
param = self:get_child(param)
end
param = self:get_child(param)
if (
type(param) == "table" or
type(param) == "table"
) then
unresolved = true
break
end
param = true
end
self.params] = param
until true
end
if unresolved then
self:unresolved()
end
self = nil
end
function Template:load_array_params(max, eval)
if not self then
return
end
self.params = self.params or {}
local params, unresolved = self
for i, param in ipairs(params) do
repeat
if not (
param or
(max and i > max)
) then
if param then
param = {i, Wikitext:new({param, "=", param}, cached_nodes and "Wikitext")}
end
if not (eval and i > eval) then
param = self:get_child(param)
if type(param) == "table" then
unresolved = true
break
end
end
param = true
end
self.params = param
until true
end
if unresolved then
self:unresolved()
end
self = nil
end
function Template:resolve()
self = self:get_child(self)
if type(self) == "table" then
self:unresolved()
end
if template_calls] then
return template_calls](self)
end
self.modifiers_context = 0
local name = text_split(self, ":")
for i, snippet in ipairs(name) do
repeat
if pcall(self.transclusion_modifiers, self) then
break
elseif (
i < #name and
self.parser_functions
) then
if not template_calls then
template_calls = function(self, param_1)
if self then
for i, p in ipairs(self) do
self = p
end
if not (self and self) then
for i, param in ipairs(self) do
if not param then
param = param + 1
end
end
param_1 = param_1 or self:match(snippet .. ":(.*)")
param_1 = { = true, 1, Wikitext:new(param_1, cached_nodes and "Wikitext")}
table.insert(self, 1, param_1)
end
end
self = snippet
return self.parser_functions](self)
end
end
local param_1 = table.concat(name, ":", i + 1)
template_calls] = template_calls
return template_calls(self, param_1)
elseif(
i == #name and
len_event(self) == 0 and
self.parser_variables
) then
if not template_calls then
template_calls = function(self)
self = snippet
return self.parser_variables]
end
end
template_calls] = template_calls
return template_calls(self)
else
name = table.concat(name, ":", i)
if not template_calls then
template_calls = function(self)
self = name
self:load_table_params()
if template_trees == nil then
local title = mw.title.new(name, 10)
title = title.redirectTarget or title
local canonical_name = ":" .. title.fullText
if template_trees == nil then
local content = title:getContent()
if content then
template_trees = Parser:parse(content, true, title)
else
template_trees = false
end
titles = titles or title.fullText
end
template_trees = template_trees
titles = titles
end
local template = self:get_instance(template_trees, titles, self.params)
return self:get_child(template, true)
end
end
template_calls] = template_calls
return template_calls(self)
end
until true
end
end
------------------------------------------------------------------------------------
--
-- Builder
--
------------------------------------------------------------------------------------
local Builder = {
nodes = {
Argument = Argument,
Template = Template,
Wikitext = Wikitext
},
stack = {}
}
setmetatable(errors.MissedCloseToken , {
__call = function(t, handler, title)
errors.MissedCloseToken.handler = handler
errors.MissedCloseToken.title = title
error(errors.MissedCloseToken)
end
})
function Builder:push(n)
table.insert(self.stack, {})
if n and n > 1 then
self:push(n - 1)
end
end
function Builder:pop(node)
local stack = table.remove(self.stack)
if node ~= false then
while type(stack) == "table" do
if node then
stack = self.nodes:new(stack, self.transcluded and node)
break
elseif getmetatable(stack) then
break
elseif #stack == 1 then
stack = stack
else
local ok, ret = pcall(table.concat, stack)
stack = ok and ret or stack
if not ok then
stack = Wikitext:new(stack, self.transcluded and "Wikitext")
break
end
end
end
end
return stack
end
function Builder:read()
return self.stack
end
function Builder:write(...)
table.insert(self.stack, ...)
end
function Builder:handle_other_token(token)
self:write(self:handle_token(token))
end
function Builder:handle_template_name()
self:push(2)
while #self.tokens > 0 do
local token = table.remove(self.tokens)
if (
token == tokens.TemplateParamSeparator or
token == tokens.TemplateClose
) then
table.insert(self.tokens, token)
return self:write(self:pop())
else
self:handle_other_token(token)
end
end
errors.MissedCloseToken("handle_template_name", self.title)
end
function Builder:handle_parameter(default)
self:push(2)
while #self.tokens > 0 do
local token = table.remove(self.tokens)
if token == tokens.TemplateParamEquals then
self:write(self:pop())
self:push()
elseif (
token == tokens.TemplateParamSeparator or
token == tokens.TemplateClose
) then
table.insert(self.tokens, token)
self:write(self:pop())
if #self:read() == 1 then
self:write(1, tostring(default))
default = default + 1
else
self:write(true)
end
self:write(self:pop(false))
return default
else
self:handle_other_token(token)
end
end
errors.MissedCloseToken("handle_parameter", self.title)
end
function Builder:handle_template()
local default = 1
self:handle_template_name()
self:push()
while #self.tokens > 0 do
local token = table.remove(self.tokens)
if token == tokens.TemplateParamSeparator then
default = self:handle_parameter(default)
elseif token == tokens.TemplateClose then
self:write(self:pop(false))
return self:pop("Template")
else
self:handle_other_token(token)
end
end
errors.MissedCloseToken("handle_template", self.title)
end
function Builder:handle_argument()
self:push(2)
while #self.tokens > 0 do
local token = table.remove(self.tokens)
if token == tokens.ArgumentSeparator then
self:write(self:pop())
self:push()
elseif token == tokens.ArgumentClose then
self:write(self:pop())
return self:pop("Argument")
else
self:handle_other_token(token)
end
end
errors.MissedCloseToken("handle_argument", self.title)
end
Builder.handlers = {
= Builder.handle_template,
= Builder.handle_argument
}
function Builder:handle_token(token)
return select(2, xpcall(
function()
if not tokens then
return token
end
if not self.handlers then
error("Builder:handle_token() got unexpected " .. tostring(token) .. ".")
else
return self.handlers(self)
end
end,
function(err)
if err == errors.MissedCloseToken then
error(debug.traceback("Builder:" .. errors.MissedCloseToken.handler .. "() missed a close token building " .. errors.MissedCloseToken.title.fullText .. "."))
else
error("Error building " .. self.title.fullText .. ": " .. debug.traceback(err), 2)
end
end
))
end
function Builder:build(tokenlist, transcluded, title)
self.tokens = reverse_table(tokenlist)
self.title = title or Parser.title or current_title
self.transcluded = transcluded
self:push()
while #self.tokens > 0 do
local node = self:handle_token(
table.remove(self.tokens)
)
self:write(node)
end
return self:pop()
end
------------------------------------------------------------------------------------
--
-- Parser
--
------------------------------------------------------------------------------------
function Parser.sort_tags(a, b)
return a > b
end
function Parser:split_by_tags(text)
local ret = {}
local next_tags = {}
local start = 1
for _, tag in ipairs(tag_captures) do
local m = {text:match("()" .. tag .. "()", start)}
if #m > 0 then
table.insert(m, tag)
table.insert(next_tags, m)
end
end
table.sort(next_tags, self.sort_tags)
while #next_tags > 0 do
local next_tag = table.remove(next_tags)
local inter = text:sub(start, next_tag - 1)
if inter ~= "" then
table.insert(ret, inter)
end
table.insert(ret, next_tag)
local new_start = next_tag
local new_tags = {next_tag}
for i, tag in ipairs(next_tags) do
if tag < new_start then
table.remove(next_tags, i)
table.insert(new_tags, tag)
end
end
start = new_start
for _, tag in ipairs(new_tags) do
local m = {text:match("()" .. tag .. "()", start)}
if #m > 0 then
table.insert(m, tag)
table.insert(next_tags, m)
end
end
table.sort(next_tags, self.sort_tags)
end
local inter = text:sub(start)
if inter ~= "" then
table.insert(ret, text:sub(start))
end
return ret
end
function Parser:handle_tags(text, transcluded)
text = self:split_by_tags(text)
if transcluded then
local new_text, include = {}
local open, close
for _, this in ipairs(text) do
if (
not include and
this == "<onlyinclude>"
) then
open = true
include = true
elseif include then
if this == "</onlyinclude>" then
close = true
include = false
else
table.insert(new_text, this)
end
end
end
if open and close and #new_text > 0 then
text = new_text
end
end
local new_text = {}
local current, current_tag = {}
local include = true
for _, this in ipairs(text) do
if include and this == "<!--" then
current_tag = this
include = false
elseif this == "-->" and current_tag == "<!--" then
current_tag = nil
include = true
else
local tag = (
this:match("^<(.-)%s.->$") or
this:match("^<(.-)>$")
)
tag = tag and tag:lower()
if not current_tag then
if include and tags then
current_tag = tag
table.insert(current, this)
elseif tag == "includeonly" then
if not transcluded then
include = false
end
elseif tag == "noinclude" then
if transcluded then
include = false
end
elseif (
tag == "onlyinclude" or
tag == "/onlyinclude"
) then
if transcluded then
table.insert(new_text, this)
end
elseif tag == "/includeonly" then
if not transcluded then
if not include then
include = true
else
table.insert(new_text, this)
end
end
elseif tag == "/noinclude" then
if transcluded then
if not include then
include = true
else
table.insert(new_text, this)
end
end
elseif include then
table.insert(new_text, this)
end
elseif tag and include and tag:sub(2) == current_tag then
table.insert(current, this)
table.insert(
new_text,
Frame:really_preprocess(table.concat(current))
)
current, current_tag = {}
elseif include then
table.insert(current, this)
end
end
end
if #current > 0 then
table.insert(
new_text,
table.concat(current)
)
end
return table.concat(new_text)
end
function Parser:parse(text, transcluded, title)
self.title = current_title
text = self:handle_tags(text, transcluded)
text = Tokenizer:tokenize(text, transcluded, title)
text = Builder:build(text, transcluded, title)
return Wikitext:get_child(text, true)
end
return setmetatable({}, Parser)