Jump to content

Module:template utilities

From Wiktionary, the free dictionary

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)
Finds a substring with balanced brackets. This means that, if one reads the string from left to right, counting +1 for a left bracket and -1 for a right bracket, the ending right bracket is the first right bracket where the count reaches 0. This function is similar to Lua search pattern '%b', but accepts multi-character tokens as brackets.
  • str: the string to be searched
  • brackets: 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 search
  • Return value: If it finds a match, returns indices of where this bracket starts and ends, and the whole bracket substring (including the brackets); otherwise returns nil.
gfind_bracket(str, brackets)
Iterates through all top-level brackets found in str.
  • The parameters are the same as in find_bracket().
  • Return value: Returns an iterator function. It yields indices of where this bracket starts and ends, and the whole bracket substring.
find_ignoring_brackets(str, brackets, pat, init, ...)
Searches str using string.find(), but ignore all text inside brackets.
  • pat, init, ...: the same parameters for string.find()
  • Other parameters are the same as in find_bracket().
  • Return value: Returns the result of string.find().
gsplit_ignoring_brackets
Splits 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.
  • Other parameters are the same as in find_bracket().
  • Return value: Returns an iterator function. It yields each section substring.
parse_temp(str)
Parses str as a template invocation and returns the result table.
  • str: the string of a template invocation
  • Return value: If success, returns a table containing the template name as a string ['title'] and the parameters as a table ['args']; otherwise, returns nil.
iter_num(t)
Iterates through all extant numbered parameters of a template. Named parameters are ignored. Nonexistent numbered parameters are skipped.
  • t: a table containing the template name as a string ['title'] and the parameters as a table ['args']
  • Return value: Returns an iterator function. It yields the index and values of all number-indexed non-nil content of the table in order.
glue_temp(t)
Serializes a template information table to template invocation wikitext. The inverse operation of parse_temp(str).
  • t: the same as in iter_num(t)
  • Return value: Returns a string of template invocation wikitext. Raises errors when it fails.
A user has added this module to requests for deletion(+).
Please see that page for discussion and justifications. You may continue to edit this module while the discussion proceeds, but please mention significant edits at the RFD discussion and ensure that the intention of votes already cast is not left unclear. Do not remove the {{rfd}} until the debate has finished.

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[1] and cap_this[1] < p1_result then
				p1_result, cap_result, right_result = cap_this[1], cap_this, v
			end
		end
		if not cap_result then return nil end
		
		local p2_result = cap_result[2]
		cap_result[2] = 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[m1 + 2]
				else return m1 end
			end)
		end
		
		local repl
		if t == 'function' then
			cap_result[3] = cap_result[3] or cap_result[2]
			repl = right_result(select(3, unpack(cap_result)))
		elseif t == 'table' then
			repl = right_result[cap_result[3] or cap_result[2]]
		else error('bad right bracket type: ' .. t) end
		
		if not repl then repl = cap_result[2]
		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[n], 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[#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[1], find_result[2]
    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[1], find_result[2]
        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_name] = arg:sub(p_eqsign + 1):match'^%s*(%S-)%s*$'
        else
			count = count + 1
            args[count] = 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[i]
		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