Module:template parser: Difference between revisions

Jump to navigation Jump to search
Incorporate Module:title/makeTitle + some changes to Module:title/newTitle, and replace is_internal_title() with is_title() + a check for the isExternal key.
No edit summary
(Incorporate Module:title/makeTitle + some changes to Module:title/newTitle, and replace is_internal_title() with is_title() + a check for the isExternal key.)
Line 14: Line 14:
local parser_extension_tags_data_module = "Module:data/parser extension tags"
local parser_extension_tags_data_module = "Module:data/parser extension tags"
local parser_module = "Module:parser"
local parser_module = "Module:parser"
local scribunto_module = "Module:Scribunto"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_replacement_escape_module = "Module:string/replacementEscape"
local string_utilities_module = "Module:string utilities"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local table_length_module = "Module:table/length"
local table_shallow_copy_module = "Module:table/shallowCopy"
local table_sorted_pairs_module = "Module:table/sortedPairs"
local title_is_title_module = "Module:title/isTitle"
local title_make_title_module = "Module:title/makeTitle"
local title_new_title_module = "Module:title/newTitle"
local title_redirect_target_module = "Module:title/redirectTarget"


local require = require
local require = require
Line 38: Line 47:
local is_node = m_parser.is_node
local is_node = m_parser.is_node
local lower = string.lower
local lower = string.lower
local make_title = mw_title.makeTitle -- unconditionally adds the specified namespace prefix
local match = string.match
local match = string.match
local new_title = mw_title.new -- specified namespace prefix is only added if the input doesn't contain one
local next = next
local next = next
local pairs = pairs
local pairs = pairs
Line 47: Line 54:
local pcall = pcall
local pcall = pcall
local rep = string.rep
local rep = string.rep
local reverse = string.reverse
local select = select
local select = select
local sub = string.sub
local sub = string.sub
Line 57: Line 63:
--[==[
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function decode_entities(...)
local function decode_entities(...)
decode_entities = require(string_utilities_module).decode_entities
decode_entities = require(string_utilities_module).decode_entities
return decode_entities(...)
return decode_entities(...)
end
end
 
local function encode_entities(...)
local function encode_entities(...)
encode_entities = require(string_utilities_module).encode_entities
encode_entities = require(string_utilities_module).encode_entities
return encode_entities(...)
return encode_entities(...)
end
end
 
local function get_namespace_shortcut(...)
local function get_link_target(...)
get_namespace_shortcut = require(pages_module).get_namespace_shortcut
get_link_target = require(pages_module).get_link_target
return get_namespace_shortcut(...)
return get_link_target(...)
end
end
 
local function is_internal_title(...)
local function is_title(...)
is_internal_title = require(pages_module).is_internal_title
is_title = require(title_is_title_module)
return is_internal_title(...)
return is_title(...)
end
end
 
local function load_data(...)
local function load_data(...)
load_data = require(load_module).load_data
load_data = require(load_module).load_data
return load_data(...)
return load_data(...)
end
end
 
local function pattern_escape(...)
local function make_title(...)
pattern_escape = require(string_utilities_module).pattern_escape
make_title = require(title_make_title_module)
return pattern_escape(...)
return make_title(...)
end
end
 
local function php_trim(...)
local function new_title(...)
php_trim = require(string_utilities_module).php_trim
new_title = require(title_new_title_module)
return php_trim(...)
return new_title(...)
end
end
 
local function replacement_escape(...)
local function pattern_escape(...)
replacement_escape = require(string_utilities_module).replacement_escape
pattern_escape = require(string_pattern_escape_module)
return replacement_escape(...)
return pattern_escape(...)
end
end
 
local function scribunto_param_key(...)
local function php_htmlspecialchars(...)
scribunto_param_key = require(string_utilities_module).scribunto_param_key
php_htmlspecialchars = require(scribunto_module).php_htmlspecialchars
return scribunto_param_key(...)
return php_htmlspecialchars(...)
end
end
 
local function shallow_copy(...)
local function php_ltrim(...)
shallow_copy = require(table_module).shallowCopy
php_ltrim = require(scribunto_module).php_ltrim
return shallow_copy(...)
return php_ltrim(...)
end
end
 
local function sorted_pairs(...)
local function php_trim(...)
sorted_pairs = require(table_module).sortedPairs
php_trim = require(scribunto_module).php_trim
return sorted_pairs(...)
return php_trim(...)
end
end
 
local function split(...)
local function redirect_target(...)
split = require(string_utilities_module).split
redirect_target = require(title_redirect_target_module)
return split(...)
return redirect_target(...)
end
end
 
local function table_len(...)
local function replacement_escape(...)
table_len = require(table_module).length
replacement_escape = require(string_replacement_escape_module)
return table_len(...)
return replacement_escape(...)
end
end
 
local function uupper(...)
local function scribunto_parameter_key(...)
uupper = require(string_utilities_module).upper
scribunto_parameter_key = require(scribunto_module).scribunto_parameter_key
return uupper(...)
return scribunto_parameter_key(...)
end
end
 
local function shallow_copy(...)
shallow_copy = require(table_shallow_copy_module)
return shallow_copy(...)
end
 
local function sorted_pairs(...)
sorted_pairs = require(table_sorted_pairs_module)
return sorted_pairs(...)
end
 
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
 
local function table_len(...)
table_len = require(table_length_module)
return table_len(...)
end
 
local function uupper(...)
uupper = require(string_utilities_module).upper
return uupper(...)
end


--[==[
--[==[
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
local data
local data
local function get_data()
local function get_data()
data, get_data = load_data(data_module), nil
data, get_data = load_data(data_module), nil
return data
return data
end
end
 
local frame
local frame
local function get_frame()
local function get_frame()
frame, get_frame = mw.getCurrentFrame(), nil
frame, get_frame = mw.getCurrentFrame(), nil
return frame
return frame
end
end
 
local magic_words
local magic_words
local function get_magic_words()
local function get_magic_words()
magic_words, get_magic_words = load_data(magic_words_data_module), nil
magic_words, get_magic_words = load_data(magic_words_data_module), nil
return magic_words
return magic_words
end
end
local parser_extension_tags
local function get_parser_extension_tags()
parser_extension_tags, get_parser_extension_tags = load_data(parser_extension_tags_data_module), nil
return parser_extension_tags
end


local Parser, Node = m_parser.new()
local parser_extension_tags
local function get_parser_extension_tags()
parser_extension_tags, get_parser_extension_tags = load_data(parser_extension_tags_data_module), nil
return parser_extension_tags
end


------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
Line 161: Line 190:
------------------------------------------------------------------------------------
------------------------------------------------------------------------------------


Node.keys_to_remove = {"handler", "head", "pattern", "route", "step"}
local Node = m_parser.node()
local new_node = Node.new


local function expand(obj, frame_args)
local function expand(obj, frame_args)
Line 178: Line 208:
local Wikitext = Node:new_class("wikitext")
local Wikitext = Node:new_class("wikitext")


-- force_node ensures the output will always be a node.
-- force_node ensures the output will always be a Wikitext node.
function Wikitext:new(this, force_node)
function Wikitext:new(this, force_node)
if type(this) ~= "table" then
if type(this) ~= "table" then
return force_node and Node.new(self, {this}) or this
return force_node and new_node(self, {this}) or this
elseif #this == 1 then
elseif #this == 1 then
local this1 = this[1]
local this1 = this[1]
return force_node and not is_node(this1) and Node.new(self, this) or this1
return force_node and class_else_type(this1) ~= "wikitext" and new_node(self, this) or this1
end
end
local success, str = pcall(concat, this)
local success, str = pcall(concat, this)
if success then
if success then
return force_node and Node.new(self, {str}) or str
return force_node and new_node(self, {str}) or str
end
end
return Node.new(self, this)
return new_node(self, this)
end
end


Line 209: Line 239:
this = {this[1], this2}
this = {this[1], this2}
end
end
return Node.new(self, this)
return new_node(self, this)
end
end


Line 221: Line 251:


function Parameter:get_name(frame_args)
function Parameter:get_name(frame_args)
return scribunto_param_key(expand(self[1], frame_args))
return scribunto_parameter_key(expand(self[1], frame_args))
end
end


Line 237: Line 267:
end
end
local name = expand(self[1], frame_args)
local name = expand(self[1], frame_args)
local val = frame_args[scribunto_param_key(name)] -- Parameter in use.
local val = frame_args[scribunto_parameter_key(name)] -- Parameter in use.
if val ~= nil then
if val ~= nil then
return val
return val
Line 249: Line 279:


local Argument = Node:new_class("argument")
local Argument = Node:new_class("argument")
function Argument:new(this)
local key = this._parse_data.key
this = Wikitext:new(this)
if key == nil then
return this
end
return new_node(self, {Wikitext:new(key), this})
end


function Argument:__tostring()
function Argument:__tostring()
Line 272: Line 311:
-- FIXME: Some parser functions have special argument handling (e.g. {{#SWITCH:}}).
-- FIXME: Some parser functions have special argument handling (e.g. {{#SWITCH:}}).
do
do
local page_title = mw_title.getCurrentTitle()
local templates, parser_variables, parser_functions = {}, {}, {}
local namespace_has_subpages = mw.site.namespaces[page_title.namespace].hasSubpages
local raw_pagename = page_title.fullText
local templates = {}
local parser_variables = {}
local parser_functions = {}
local function retrieve_magic_word_data(chunk)
local function retrieve_magic_word_data(chunk)
Line 290: Line 323:
return mgw_data
return mgw_data
end
end
end
local function get_prefixed_invocation_name(title, namespace, shortcut)
return shortcut and get_namespace_shortcut(title) .. ":" .. title.text or
namespace == 0 and ":" .. title.prefixedText or
title.prefixedText
end
end
Line 303: Line 330:
-- exists (e.g. "Template:PAGENAME" becomes "T:PAGENAME").
-- exists (e.g. "Template:PAGENAME" becomes "T:PAGENAME").
local function get_template_invocation_name(title, shortcut)
local function get_template_invocation_name(title, shortcut)
if not (title and is_internal_title(title)) then
if not (is_title(title) and not title.isExternal) then
error("Template invocations require a valid page title, which cannot contain an interwiki prefix.")
error("Template invocations require a valid page title, which cannot contain an interwiki prefix.")
end
end
Line 310: Line 337:
-- mainspace).
-- mainspace).
if namespace ~= 10 then
if namespace ~= 10 then
return get_prefixed_invocation_name(title, namespace, shortcut)
return get_link_target(title, shortcut)
end
end
-- If in the template namespace and it shares a name with a magic word,
-- If in the template namespace and it shares a name with a magic word,
-- it needs the prefix "Template:".
-- it needs the prefix "Template:".
local text = title.text
local text, fragment = title.text, title.fragment
if fragment and fragment ~= "" then
text = text .. "#" .. fragment
end
local colon = find(text, ":", nil, true)
local colon = find(text, ":", nil, true)
if not colon then
if not colon then
local mgw_data = retrieve_magic_word_data(text)
local mgw_data = retrieve_magic_word_data(text)
return mgw_data and mgw_data.parser_variable and get_prefixed_invocation_name(title, namespace, shortcut) or text
return mgw_data and mgw_data.parser_variable and get_link_target(title, shortcut) or text
end
end
local mgw_data = retrieve_magic_word_data(sub(text, 1, colon - 1))
local mgw_data = retrieve_magic_word_data(sub(text, 1, colon - 1))
if mgw_data and (mgw_data.parser_function or mgw_data.transclusion_modifier) then
if mgw_data and (mgw_data.parser_function or mgw_data.transclusion_modifier) then
return get_prefixed_invocation_name(title, namespace, shortcut)
return get_link_target(title, shortcut)
end
end
-- Also if "Template:" is necessary for disambiguation (e.g.
-- Also if "Template:" is necessary for disambiguation (e.g.
-- "Template:Category:Foo" can't be called with "Category:Foo").
-- "Template:Category:Foo" can't be called with "Category:Foo").
local check = new_title(text, namespace)
local check = new_title(text, namespace)
return check and title_equals(title, check) and text or get_prefixed_invocation_name(title, namespace, shortcut)
return check and title_equals(title, check) and text or get_link_target(title, shortcut)
end
end
export.getTemplateInvocationName = get_template_invocation_name
export.getTemplateInvocationName = get_template_invocation_name
-- Returns whether a title is a redirect or not. Structured like this to
-- allow the use of pcall, since it will throw an error if the expensive
-- parser function limit has been reached.
local function is_redirect(title)
return title.isRedirect
end
function parse_template_name(name, has_args, fragment, force_transclusion)
function parse_template_name(name, has_args, fragment, force_transclusion)
local chunks, colon, start, n, p = {}, find(name, ":", nil, true), 1, 0, 0
local chunks, colon, start, n, p = {}, find(name, ":", nil, true), 1, 0, 0
while colon do
while colon do
-- Pattern applies PHP ltrim.
local mgw_data = retrieve_magic_word_data(php_ltrim(sub(name, start, colon - 1)))
local mgw_data = retrieve_magic_word_data(match(sub(name, start, colon - 1), "[^%z\t-\v\r ].*") or "")
if not mgw_data then
if not mgw_data then
break
break
Line 375: Line 397:
end
end
end
end
-- Handle relative template names.
-- Get the template title with the custom new_title() function in
if namespace_has_subpages then
-- [[Module:title/newTitle]], with `allowOnlyFragment` set to false
-- If the name starts with "/", it's treated as a subpage of the
-- (e.g. "{{#foo}}" is invalid) and `allowRelative` set to true, for
-- current page. Final slashes are trimmed, but this can't affect
-- relative links for namespaces with subpages (e.g. "{{/foo}}").
-- the intervening slash (e.g. {{///}} refers to "{{PAGENAME}}/").
local title = new_title(name, 10, false, true)
local initial = sub(name, 1, 1)
if not (title and not title.isExternal) then
if initial == "/" then
name = raw_pagename .. (match(name, "^/.*[^/]") or "/")
-- If it starts with "../", trim it and any that follow, and go up
-- that many subpage levels. Then, treat any additional text as
-- a subpage of that page; final slashes are trimmed.
elseif initial == "." and sub(name, 2, 3) == "./" then
local n = 4
while sub(name, n, n + 2) == "../" do
n = n + 3
end
-- Retain an initial "/".
name = sub(name, n - 1)
-- Trim the relevant number of subpages from the pagename.
local pagename, i = reverse(raw_pagename), 0
for _ = 1, (n - 1) / 3 do
i = find(pagename, "/", i + 1, true)
-- Fail if there aren't enough slashes.
if not i then
return nil
end
end
-- Add the subpage text; since the intervening "/" is retained
-- in `name`, it can be trimmed along with any other final
-- slashes (e.g. {{..///}} refers to "{{BASEPAGENAME}}".)
name = reverse(sub(pagename, i + 1)) .. (match(name, "^.*[^/]") or "")
end
end
local title = new_title(name, 10)
if not (title and is_internal_title(title)) then
return nil
return nil
end
end
-- If `fragment` is set, save the original title's fragment, since it
-- Resolve any redirects. If the redirect target is an interwiki link,
-- won't carry through to any redirect targets.
-- the template won't fail, but the redirect does not get resolved (i.e.
if fragment then
-- the redirect page itself gets transcluded, so the template name
fragment = title.fragment
-- should not be normalized to the target).
end
local redirect = redirect_target(title, force_transclusion)
-- Resolve any redirects. Note that is_internal_title treats interwiki
if redirect and not redirect.isExternal then
-- titles as invalid, which is correct in this case: if the redirect
title = redirect
-- target is an interwiki link, the template won't fail, but the
-- redirect does not get resolved (i.e. the redirect page itself gets
-- transcluded, so the template name should not be normalized to the
-- target). It also treats titles that only have fragments as invalid
-- (e.g. "#foo"), but these can't be used as redirects anyway.
-- title.redirectTarget increments the expensive parser function count,
-- but avoids extraneous transclusions polluting template lists and the
-- performance hit caused by indiscriminately grabbing redirectTarget.
-- However, if the expensive parser function limit has already been hit,
-- redirectTarget is used as a fallback. force_transclusion forces the
-- use of the fallback.
local redirect = true
if not force_transclusion then
local success, resolved = pcall(is_redirect, title)
if success and not resolved then
redirect = false
end
end
if redirect then
redirect = title.redirectTarget
if redirect and is_internal_title(redirect) then
title = redirect
end
end
end
local chunk = get_template_invocation_name(title)
-- If `fragment` is not true, unset it from the title object to prevent
-- Set the fragment (if applicable).
-- it from being included by get_template_invocation_name.
if fragment then
if not fragment then
chunk = chunk .. "#" .. fragment
title.fragment = ""
end
end
chunks[n + 1] = chunk
chunks[n + 1] = get_template_invocation_name(title)
return chunks, "template"
return chunks, "template"
end
end
Line 532: Line 502:
local arg = self[i]
local arg = self[i]
if class_else_type(arg) == "argument" then
if class_else_type(arg) == "argument" then
template_args[scribunto_param_key(expand(arg[1], frame_args))] = php_trim(expand(arg[2], frame_args))
template_args[scribunto_parameter_key(expand(arg[1], frame_args))] = php_trim((expand(arg[2], frame_args)))
else
else
implicit = implicit + 1
implicit = implicit + 1
Line 540: Line 510:
return template_args
return template_args
end
end
end


-- BIG TODO: manual template expansion.
-- BIG TODO: manual template expansion.
function Template:expand()
function Template:expand(frame_args)
return (frame or get_frame()):preprocess(tostring(self))
local name, subclass, pf_arg1 = process_name(self, frame_args)
if name == nil then
local output = {}
for i = 1, #self do
output[i] = expand(self[i], frame_args)
end
return "{{" .. concat(output, "|") .. "}}"
elseif subclass == "parser variable" then
return (frame or get_frame()):preprocess("{{" .. name .. "}}")
elseif subclass == "parser function" then
local f = frame or get_frame()
if frame_args ~= nil then
local success, new_f = pcall(f.newChild, f, {args = frame_args})
if success then
f = new_f
end
end
return f:preprocess(tostring(self))
end
local output = {}
for i = 1, #self do
output[i] = expand(self[i], frame_args)
end
return (frame or get_frame()):preprocess("{{" .. concat(output, "|") .. "}}")
end
end
end


local Tag = Node:new_class("tag")
local Tag = Node:new_class("tag")


do
function Tag:__tostring()
local php_htmlspecialchars_data
local open_tag, attributes, n = {"<", self.name}, self:get_attributes(), 2
for attr, value in next, attributes do
local function get_php_htmlspecialchars_data()
n = n + 1
php_htmlspecialchars_data, get_php_htmlspecialchars_data = (data or get_data()).php_htmlspecialchars, nil
open_tag[n] = " " .. php_htmlspecialchars(attr) .. "=\"" .. php_htmlspecialchars(value, "compat") .. "\""
return php_htmlspecialchars_data
end
end
if self.self_closing then
local function php_htmlspecialchars(str, compat)
return concat(open_tag) .. "/>"
return (gsub(str, compat and "[&\"<>]" or "[&\"'<>]", php_htmlspecialchars_data or get_php_htmlspecialchars_data()))
end
end
return concat(open_tag) .. ">" .. concat(self) .. "</" .. self.name .. ">"
function Tag:__tostring()
end
local open_tag, attributes, n = {"<", self.name}, self:get_attributes(), 2
 
for attr, value in next, attributes do
do
n = n + 1
open_tag[n] = " " .. php_htmlspecialchars(attr) .. "=\"" .. php_htmlspecialchars(value, true) .. "\""
end
if self.self_closing then
return concat(open_tag) .. "/>"
end
return concat(open_tag) .. ">" .. concat(self) .. "</" .. self.name .. ">"
end
local valid_attribute_name
local valid_attribute_name
local function get_valid_attribute_name()
local function get_valid_attribute_name()
valid_attribute_name, get_valid_attribute_name = (data or get_data()).valid_attribute_name, nil
valid_attribute_name, get_valid_attribute_name = (data or get_data()).valid_attribute_name, nil
Line 638: Line 619:
local success, str = pcall(concat, this)
local success, str = pcall(concat, this)
if success then
if success then
return Node.new(self, {
return new_node(self, {
str,
str,
level = this.level,
level = this.level,
Line 646: Line 627:
end
end
end
end
return Node.new(self, this)
return new_node(self, this)
end
end


function Heading:__tostring()
do
local eq = rep("=", self.level)
local node_tostring = Node.__tostring
return eq .. Node.__tostring(self) .. eq
 
function Heading:__tostring()
local eq = rep("=", self.level)
return eq .. node_tostring(self) .. eq
end
end
end


do
do
local expand_node = Node.expand
local expand_node = Node.expand
 
-- Expanded heading names can contain "\n" (e.g. inside nowiki tags), which
-- Expanded heading names can contain "\n" (e.g. inside nowiki tags), which
-- causes any heading containing them to fail. However, in such cases, the
-- causes any heading containing them to fail. However, in such cases, the
Line 692: Line 677:
------------------------------------------------------------------------------------
------------------------------------------------------------------------------------


function Parser:read(i, j)
local Parser = m_parser.string_parser()
local head, i = self.head, i or 0
return sub(self.text, head + i, head + (j or i))
end
 
function Parser:advance(n)
self.head = self.head + (n or self[-1].step or 1)
end
 
function Parser:jump(head)
self.head = head
self[-1].nxt = nil
end
 
function Parser:set_pattern(pattern)
local layer = self[-1]
layer.pattern = pattern
layer.nxt = nil
end
 
function Parser:consume()
local layer = self[-1]
local this = layer.nxt
if this then
layer.nxt = nil
else
local text, head = self.text, self.head
local loc1, loc2 = find(text, layer.pattern, head)
if loc1 == head or not loc1 then
this = sub(text, head, loc2)
else
this = sub(text, head, loc1 - 1)
layer.nxt = sub(text, loc1, loc2)
end
end
layer.step = #this
return layer.handler(self, this)
end


-- Template or parameter.
-- Template or parameter.
Line 740: Line 688:
local handle_name
local handle_name
local handle_argument
local handle_argument
local handle_value
local function do_template_or_parameter(self, inner_node)
local function do_template_or_parameter(self, inner_node)
Line 752: Line 701:
end
end
end
end
 
local function pipe(self)
self:emit(Wikitext:new(self:pop_sublayer()))
self:push_sublayer(handle_argument)
self:set_pattern("[\n<=[{|}]")
end
 
local function rbrace(self, this)
if self:read(1) == "}" then
self:emit(Wikitext:new(self:pop_sublayer()))
return self:pop()
end
self:emit(this)
end
 
function handle_name(self, ...)
function handle_name(self, ...)
handle_name = self:switch(handle_name, {
handle_name = self:switch(handle_name, {
Line 759: Line 722:
["["] = Parser.wikilink_block,
["["] = Parser.wikilink_block,
["{"] = Parser.braces,
["{"] = Parser.braces,
["|"] = pipe,
["|"] = function(self)
["}"] = rbrace,
self:emit(Wikitext:new(self:pop_sublayer()))
self:push_sublayer(handle_argument)
self:set_pattern("[\n<=[{|}]")
end,
["}"] = function(self)
if self:read(1) == "}" then
self:emit(Wikitext:new(self:pop_sublayer()))
return self:pop()
end
self:emit("}")
end,
[""] = Parser.fail_route,
[""] = Parser.fail_route,
[false] = Parser.emit
[false] = Parser.emit
Line 779: Line 729:
return handle_name(self, ...)
return handle_name(self, ...)
end
end
 
function handle_argument(self, ...)
function handle_argument(self, ...)
local function emit_argument(self)
local arg = Wikitext:new(self:pop_sublayer())
local layer = self[-1]
local key = layer.key
if key then
arg = Argument:new{key, arg}
layer.key = nil
end
self:emit(arg)
end
handle_argument = self:switch(handle_argument, {
handle_argument = self:switch(handle_argument, {
["\n"] = function(self)
["\n"] = function(self, this)
return self:heading_block("\n", self[-1].key and "=" or "==")
return self:heading_block(this, "==")
end,
end,
 
["<"] = Parser.tag,
["<"] = Parser.tag,
 
["="] = function(self)
["="] = function(self)
local key = Wikitext:new(self:pop_sublayer())
local key = self:pop_sublayer()
self[-1].key = key
self:push_sublayer(handle_value)
self:push_sublayer(handle_argument)
self:set_pattern("[\n<[{|}]")
self:set_pattern("[\n<[{|}]")
self.current_layer._parse_data.key = key
end,
end,
 
["["] = Parser.wikilink_block,
["{"] = Parser.braces,
["|"] = pipe,
["}"] = rbrace,
[""] = Parser.fail_route,
[false] = Parser.emit
})
return handle_argument(self, ...)
end
 
function handle_value(self, ...)
handle_value = self:switch(handle_value, {
["\n"] = Parser.heading_block,
["<"] = Parser.tag,
["["] = Parser.wikilink_block,
["["] = Parser.wikilink_block,
["{"] = Parser.braces,
["{"] = Parser.braces,
 
["|"] = function(self)
["|"] = function(self)
emit_argument(self)
self:emit(Argument:new(self:pop_sublayer()))
self:push_sublayer(handle_argument)
self:push_sublayer(handle_argument)
self:set_pattern("[\n<=[{|}]")
self:set_pattern("[\n<=[{|}]")
end,
end,
 
["}"] = function(self)
["}"] = function(self, this)
if self:read(1) == "}" then
if self:read(1) == "}" then
emit_argument(self)
self:emit(Argument:new(self:pop_sublayer()))
return self:pop()
return self:pop()
end
end
self:emit("}")
self:emit(this)
end,
end,
 
[""] = Parser.fail_route,
[""] = Parser.fail_route,
[false] = Parser.emit
[false] = Parser.emit
})
})
return handle_argument(self, ...)
return handle_value(self, ...)
end
end
Line 888: Line 841:
local function do_tag(self)
local function do_tag(self)
local layer = self[-1]
local layer = self.current_layer
layer.handler, layer.index = handle_start, self.head
layer._parse_data.handler, layer.index = handle_start, self.head
self:set_pattern("[%s/>]")
self:set_pattern("[%s/>]")
self:advance()
self:advance()
Line 933: Line 886:
return self:fail_route()
return self:fail_route()
end
end
local layer = self[-1]
local layer = self.current_layer
local text, head = self.text, self.head + layer.step
local pdata = layer._parse_data
local text, head = self.text, self.head + pdata.step
if match(text, "^/[^>]", head) then
if match(text, "^/[^>]", head) then
return self:fail_route()
return self:fail_route()
Line 947: Line 901:
layer.raw_name = raw_name
layer.raw_name = raw_name
end
end
layer.name, layer.handler, layer.end_tag_pattern = this, handle_tag, end_tag_pattern
layer.name, pdata.handler, pdata.end_tag_pattern = this, handle_tag, end_tag_pattern
self:set_pattern(">")
self:set_pattern(">")
end
end
Line 954: Line 908:
if this == "" then
if this == "" then
return self:fail_route()
return self:fail_route()
elseif this ~= ">" then
end
self[-1].attributes = this
local layer = self.current_layer
if this ~= ">" then
layer.attributes = this
return
return
elseif self:read(-1) == "/" then
elseif self:read(-1) == "/" then
self[-1].self_closing = true
layer.self_closing = true
return self:pop()
return self:pop()
end
end
local text, head, layer = self.text, self.head + 1, self[-1]
local text, head = self.text, self.head + 1
local loc1, loc2 = find(text, layer.end_tag_pattern, head)
local loc1, loc2 = find(text, layer._parse_data.end_tag_pattern, head)
if loc1 then
if loc1 then
if loc1 > head then
if loc1 > head then
Line 999: Line 955:
self:emit("<")
self:emit("<")
elseif not tag.ignored then
elseif not tag.ignored then
tag.end_tag_pattern = nil
self:emit(Tag:new(tag))
self:emit(Tag:new(tag))
end
end
Line 1,015: Line 970:
local function do_heading(self)
local function do_heading(self)
local layer, head = self[-1], self.head
local layer, head = self.current_layer, self.head
layer.handler, layer.index = handle_start, head
layer._parse_data.handler, layer.index = handle_start, head
self:set_pattern("[\t\n ]")
self:set_pattern("[\t\n ]")
-- Comments/tags interrupt the equals count.
-- Comments/tags interrupt the equals count.
Line 1,025: Line 980:
local function do_heading_possible_end(self)
local function do_heading_possible_end(self)
local layer = self[-1]
self.current_layer._parse_data.handler = handle_possible_end
layer.handler = handle_possible_end
self:set_pattern("[\n<]")
self:set_pattern("[\n<]")
end
end
Line 1,033: Line 987:
-- ===== is "=" as an L2; ======== is "==" as an L3 etc.
-- ===== is "=" as an L2; ======== is "==" as an L3 etc.
local function newline(self)
local function newline(self)
local layer = self[-1]
local layer = self.current_layer
local eq = layer.level
local eq = layer.level
if eq <= 2 then
if eq <= 2 then
Line 1,051: Line 1,005:
if success then
if success then
self:emit(Wikitext:new(possible_end))
self:emit(Wikitext:new(possible_end))
local layer = self[-1]
self.current_layer._parse_data.handler = handle_body
layer.handler = handle_body
self:set_pattern("[\n<=[{]")
self:set_pattern("[\n<=[{]")
return self:consume()
return self:consume()
Line 1,067: Line 1,020:
[false] = function(self)
[false] = function(self)
-- Emit any excess = signs once we know it's a conventional heading. Up till now, we couldn't know if the heading is just a string of = signs (e.g. ========), so it wasn't guaranteed that the heading text starts after the 6th.
-- Emit any excess = signs once we know it's a conventional heading. Up till now, we couldn't know if the heading is just a string of = signs (e.g. ========), so it wasn't guaranteed that the heading text starts after the 6th.
local layer = self[-1]
local layer = self.current_layer
local eq = layer.level
local eq = layer.level
if eq > 6 then
if eq > 6 then
Line 1,073: Line 1,026:
layer.level = 6
layer.level = 6
end
end
layer.handler = handle_body
layer._parse_data.handler = handle_body
self:set_pattern("[\n<=[{]")
self:set_pattern("[\n<=[{]")
return self:consume()
return self:consume()
Line 1,097: Line 1,050:
return self:consume()
return self:consume()
end
end
local layer = self[-1]
local layer = self.current_layer
local level = layer.level
local level = layer.level
if eq_len > level then
if eq_len > level then
Line 1,111: Line 1,064:
["{"] = function(self, this)
["{"] = function(self, this)
return self:braces("{", true)
return self:braces(this, true)
end,
end,
Line 1,187: Line 1,140:
local function do_language_conversion_block(self)
local function do_language_conversion_block(self)
local layer = self[-1]
self.current_layer._parse_data.handler = handle_language_conversion_block
layer.handler = handle_language_conversion_block
self:set_pattern("[\n<[{}]")
self:set_pattern("[\n<[{}]")
end
end
Line 1,199: Line 1,151:
["{"] = Parser.braces,
["{"] = Parser.braces,
["}"] = function(self)
["}"] = function(self, this)
if self:read(1) == "-" then
if self:read(1) == "-" then
self:emit("}-")
self:emit("}-")
Line 1,205: Line 1,157:
return self:pop()
return self:pop()
end
end
self:emit("}")
self:emit(this)
end,
end,
Line 1,251: Line 1,203:
local function do_heading_block(self)
local function do_heading_block(self)
local layer = self[-1]
self.current_layer._parse_data.handler = handle_heading_block
layer.handler = handle_heading_block
self:set_pattern("[\n<[{]")
self:set_pattern("[\n<[{]")
end
end
Line 1,290: Line 1,241:
local function do_wikilink_block(self)
local function do_wikilink_block(self)
local layer = self[-1]
self.current_layer._parse_data.handler = handle_wikilink_block
layer.handler = handle_wikilink_block
self:set_pattern("[\n<[%]{]")
self:set_pattern("[\n<[%]{]")
end
end
Line 1,301: Line 1,251:
["["] = Parser.wikilink_block,
["["] = Parser.wikilink_block,
["]"] = function(self)
["]"] = function(self, this)
if self:read(1) == "]" then
if self:read(1) == "]" then
self:emit("]]")
self:emit("]]")
Line 1,307: Line 1,257:
return self:pop()
return self:pop()
end
end
self:emit("]")
self:emit(this)
end,
end,
Line 1,370: Line 1,320:
-- blocks.
-- blocks.
local function do_parse(self, transcluded)
local function do_parse(self, transcluded)
local layer = self[-1]
self.current_layer._parse_data.handler = handle_start
layer.handler = handle_start
self:set_pattern(".")
self:set_pattern(".")
self.section = 0
self.section = 0
Line 1,390: Line 1,339:
-- If the first character is "=", try parsing it as a heading.
-- If the first character is "=", try parsing it as a heading.
function handle_start(self, this)
function handle_start(self, this)
local layer = self[-1]
self.current_layer._parse_data.handler = main_handler
layer.handler = main_handler
self:set_pattern("[\n<{]")
self:set_pattern("[\n<{]")
if this == "=" then
if this == "=" then
Line 1,411: Line 1,359:
["<"] = Parser.tag,
["<"] = Parser.tag,
["{"] = function(self)
["{"] = function(self, this)
if self:read(1) == "{" then
if self:read(1) == "{" then
self:template_or_parameter()
self:template_or_parameter()
return self:consume()
return self:consume()
end
end
self:emit("{")
self:emit(this)
end,
end,
Line 1,438: Line 1,386:
end
end


do
function export.find_templates(text, not_transcluded)
local function is_template(v)
return parse(text, not not_transcluded):iterate_nodes("template")
return class_else_type(v) == "template"
end
function export.find_templates(text, not_transcluded)
return parse(text, not not_transcluded):iterate(is_template)
end
end
end


Line 1,484: Line 1,426:
-- "Module:Template:foo", and "Module:foo" gives "Module:Module:foo").
-- "Module:Template:foo", and "Module:foo" gives "Module:Module:foo").
-- However, this isn't possible with mainspace (namespace 0), so prefixes
-- However, this isn't possible with mainspace (namespace 0), so prefixes
-- are respected. make_title handles all of this automatically.
-- are respected. make_title() handles all of this automatically.
local function finalize_arg(pagename, namespace)
local function finalize_arg(pagename, namespace)
if namespace == nil then
if namespace == nil then
Line 1,490: Line 1,432:
end
end
local title = make_title(namespace, pagename)
local title = make_title(namespace, pagename)
return title and is_internal_title(title) and link_page(title, pagename) or pagename
return title and not title.isExternal and link_page(title, pagename) or pagename
end
end
Line 1,629: Line 1,571:


do
do
local function is_parameter(v)
return class_else_type(v) == "parameter"
end
function export.find_parameters(text, not_transcluded)
function export.find_parameters(text, not_transcluded)
return parse(text, not not_transcluded):iterate(is_parameter)
return parse(text, not not_transcluded):iterate_nodes("parameter")
end
end
Line 1,653: Line 1,591:
end
end
return level
return level
end
local function is_heading(v)
return class_else_type(v) == "heading"
end
end
Line 1,678: Line 1,612:
local parsed = parse(text)
local parsed = parse(text)
if i == nil and j == nil then
if i == nil and j == nil then
return parsed:iterate(is_heading)
return parse(text):iterate_nodes("heading")
end
end
i = i and check_level(i) or 1
i = i and check_level(i) or 1
Anonymous user

Navigation menu