aboutsummaryrefslogtreecommitdiffstats
path: root/teal-src/prosody/util/xtemplate.tl
blob: 9b6b678b7b82526f451df573a1106239f615b189 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
-- render(template, stanza) --> string
-- {path} --> stanza:find(path)
-- {{ns}name/child|each({ns}name){sub-template}}

--[[
template ::= "{" path ("|" name ("(" args ")")? (template)? )* "}"
path ::= defined by util.stanza
name ::= %w+
args ::= anything with balanced ( ) pairs
]]

local s_gsub = string.gsub;
local s_match = string.match;
local s_sub = string.sub;
local t_concat = table.concat;

local st = require "prosody.util.stanza";

local type escape_t = function (string) : string
local type filter_t = function (string, string | st.stanza_t, string) : string | st.stanza_t, boolean
local type filter_coll = { string : filter_t }

local function render(template : string, root : st.stanza_t, escape : escape_t, filters : filter_coll) : string
	escape = escape or st.xml_escape;

	return (s_gsub(template, "(%s*)(%b{})(%s*)", function(pre_blank : string, block : string, post_blank : string) : string
		local inner = s_sub(block, 2, -2);
		if inner:sub(1, 1) == "-" then
			pre_blank = "";
			inner = inner:sub(2);
		end
		if inner:sub(-1, -1) == "-" then
			post_blank = "";
			inner = inner:sub(1, -2);
		end
		local path, pipe, pos = s_match(inner, "^([^|]+)(|?)()");
		if not path is string then return end
		local value : string | st.stanza_t
		if path == "." then
			value = root;
		elseif path == "#" then
			value = root:get_text();
		else
			value = root:find(path);
		end
		local is_escaped = false;

		while pipe == "|" do
			local func, args, tmpl, p = s_match(inner, "^(%w+)(%b())(%b{})()", pos as integer);
			if not func then func, args, p = s_match(inner, "^(%w+)(%b())()", pos as integer); end
			if not func then func, tmpl, p = s_match(inner, "^(%w+)(%b{})()", pos as integer); end
			if not func then func, p = s_match(inner, "^(%w+)()", pos as integer); end
			if not func then break end
			if tmpl then tmpl = s_sub(tmpl, 2, -2); end
			if args then args = s_sub(args, 2, -2); end

			if func == "each" and tmpl and st.is_stanza(value) then
				if not args then value, args = root, path; end
				local ns, name = s_match(args, "^(%b{})(.*)$");
				if ns then ns = s_sub(ns, 2, -2); else name, ns = args, nil; end
				if ns == "" then ns = nil; end
				if name == "" then name = nil; end
				local out, i = {}, 1;
				for c in (value as st.stanza_t):childtags(name, ns) do
					out[i], i = render(tmpl, c, escape, filters), i + 1;
				end
				value = t_concat(out);
				is_escaped = true;
			elseif func == "and" and tmpl then
				local condition = value;
				if args then condition = root:find(args); end
				if condition then
					value = render(tmpl, root, escape, filters);
					is_escaped = true;
				end
			elseif func == "or" and tmpl then
				local condition = value;
				if args then condition = root:find(args); end
				if not condition then
					value = render(tmpl, root, escape, filters);
					is_escaped = true;
				end
			elseif filters and filters[func] then
				local f = filters[func];
				if args == nil then
					value, is_escaped = f(value, tmpl);
				else
					value, is_escaped = f(args, value, tmpl);
				end
			else
				error("No such filter function: " .. func);
			end
			pipe, pos = s_match(inner, "^(|?)()", p as integer);
		end

		if value is string then
			if not is_escaped then value = escape(value); end
			return pre_blank .. value .. post_blank;
		elseif st.is_stanza(value) then
			value = value:get_text();
			if value then
				return pre_blank .. escape(value) .. post_blank;
			end
		end
		return pre_blank .. post_blank;
	end));
end

return { render = render };