This module provides functions for finding and parsing template invocations found in wikitext, though it can also be used for other purposes.
find_bracket(str, brackets, p_start)
'%b'
, but accepts multi-character tokens as brackets.
str
: the string to be searchedbrackets
: the left and right brackets to be searched for, given as a hash table with the left brackets being keys and the right being values. They are interpreted as Lua patterns:{
= '}}',
= ']]',
}
p_start
: where in str
to start the searchnil
.gfind_bracket(str, brackets)
brackets
found in str
.
find_bracket()
.find_ignoring_brackets(str, brackets, pat, init, ...)
str
using string.find()
, but ignore all text inside brackets
.
pat, init, ...
: the same parameters for string.find()
find_bracket()
.string.find()
.gsplit_ignoring_brackets
str
into substrings at boundaries that match the pattern sep
and are not in any brackets
, and iterates through them.
sep
: the pattern matching the boundaries. If it matches the empty string, s will be split into individual characters.find_bracket()
.parse_temp(str)
str
as a template invocation and returns the result table.
str
: the string of a template invocation
and the parameters as a table
; otherwise, returns nil
.iter_num(t)
t
: a table containing the template name as a string
and the parameters as a table
glue_temp(t)
parse_temp(str)
.
t
: the same as in iter_num(t)
local export = {}
export.brackets_temp = { = '}}' }
export.brackets_temp_and_link = { = '}}', = ']]' }
function export.find_bracket(str, brackets, p_start)
local function find_left(pos_start)
local p1_result, cap_result, right_result = str:len() + 1
local cap_this
for k, v in pairs(brackets) do
cap_this = {str:find(k, pos_start)}
if cap_this and cap_this < p1_result then
p1_result, cap_result, right_result = cap_this, cap_this, v
end
end
if not cap_result then return nil end
local p2_result = cap_result
cap_result = str:sub(p1_result, p2_result)
local t = type(right_result)
if t == 'string' then
return p1_result, p2_result, right_result:gsub('%%(.)', function(m1)
if m1:match'%d' then
return cap_result
else return m1 end
end)
end
local repl
if t == 'function' then
cap_result = cap_result or cap_result
repl = right_result(select(3, unpack(cap_result)))
elseif t == 'table' then
repl = right_result or cap_result]
else error('bad right bracket type: ' .. t) end
if not repl then repl = cap_result
elseif type(repl) == 'number' then repl = tostring(repl) end
return p1_result, p2_result, repl
end
local p_init, p0, str_brac = find_left(p_start)
if p_init == nil then return nil end
local nest = {str_brac}
local p1, p2, p3, p4
repeat
p0 = p0 + 1
p1, p2 = str:find(str_brac, p0)
if p1 == nil then return nil end
if p1 > p2 then error'Any bracket must not have zero length.' end
p3, p4, str_brac = find_left(p0)
if p3 == nil then
local n = #nest - 1
while n > 0 do
p1, p2 = str:find(nest, p2 + 1)
if p1 == nil then return nil end
n = n - 1
end
p0 = p2
break
else
if p3 > p4 then error'Any bracket must not have zero length.' end
if p3 < p1 then
table.insert(nest, str_brac)
p0 = p4
else
table.remove(nest)
str_brac = nest
p0 = p2
end
end
until #nest == 0
return p_init, p0, str:sub(p_init, p0)
end
function export.gfind_bracket(str, brackets)
local p0 = 0
return function()
p0 = p0 + 1
local p1, p2, text_b = export.find_bracket(str, brackets, p0)
p0 = p2
return p1, p2, text_b
end
end
function export.find_ignoring_brackets(str, brackets, pat, init, ...)
local find_result = {str:find(pat, init, ...)}
local p1, p2 = find_result, find_result
if p1 == nil then return nil end
local p3, p4 = export.find_bracket(str, brackets)
while p4 and p4 <= p2 do p3, p4 = export.find_bracket(str, brackets, p4 + 1) end
while p3 and p3 <= p2 do
find_result = {str:find(pat, p4 + 1, ...)}
p1, p2 = find_result, find_result
if p1 == nil then return nil end
while p4 and p4 <= p2 do p3, p4 = export.find_bracket(str, brackets, p4 + 1) end
end
return unpack(find_result)
end
function export.gsplit_ignoring_brackets(str, brackets, sep)
local p0 = 0
local empty = 0
return function()
if p0 == nil then return nil end
p0 = p0 + empty
if p0 > str:len() then return nil end
p0 = p0 + 1
local p1, p2 = export.find_ignoring_brackets(str, brackets, sep, p0)
p0, p2 = p2, p0 - empty
if p1 then
empty = p1 > p0 and 1 or 0
return str:sub(p2, p1 - 1)
else return str:sub(p2) end
end
end
function export.parse_temp(str)
if str:sub(1, 2) ~= '{{' or str:sub(-2) ~= '}}' then return nil end
str = str:sub(3, -3)
local p_title_end = export.find_ignoring_brackets(str, export.brackets_temp_and_link, '|')
if not p_title_end then return { title = str, args = {} }
end
local args = {}
local count = 0
for arg in export.gsplit_ignoring_brackets(str:sub(p_title_end + 1), export.brackets_temp_and_link, '|') do
local p_eqsign = export.find_ignoring_brackets(arg, export.brackets_temp_and_link, '=')
if p_eqsign then
local arg_name = arg:sub(1, p_eqsign - 1)
local arg_name_num = tonumber(arg_name)
if arg_name_num and arg_name_num > 0 and arg_name_num == math.floor(arg_name_num) then
arg_name = arg_name_num
else
arg_name = arg_name:match'^%s*(%S-)%s*$'
end
args = arg:sub(p_eqsign + 1):match'^%s*(%S-)%s*$'
else
count = count + 1
args = arg
end
end
return { title = str:sub(1, p_title_end - 1), args = args }
end
function export.iter_num(t)
local i = 0
local index_max = 0
for k, _ in pairs(t.args) do
if type(k) == 'number' and index_max < k then
index_max = k
end
end
return function()
local v
repeat
i = i + 1
if i > index_max then return nil end
v = t.args
until v
return i, v
end
end
function export.glue_temp(t)
local content = { t.title }
for i, v in export.iter_num(t) do
if i == #content then
table.insert(content, v)
else
table.insert(content, i .. '=' .. v)
end
end
for k, v in pairs(t.args) do
if type(k) == 'string' then
table.insert(content, k .. '=' .. v)
end
end
return '{{' .. table.concat(content, '|') .. '}}'
end
return export