Comparison

teal-src/util/xtemplate.tl @ 12213:dc9d63166488

util.xtemplate: Yet another string template library This one takes a stanza as input Roughly based on util.interpolation
author Kim Alvefur <zash@zash.se>
date Mon, 24 Jan 2022 23:54:32 +0100
comparison
equal deleted inserted replaced
12212:bc6fc1cb04ae 12213:dc9d63166488
1 -- render(template, stanza) --> string
2 -- {path} --> stanza:find(path)
3 -- {{ns}name/child|each({ns}name){sub-template}}
4
5 --[[
6 template ::= "{" path ("|" name ("(" args ")")? (template)? )* "}"
7 path ::= defined by util.stanza
8 name ::= %w+
9 args ::= anything with balanced ( ) pairs
10 ]]
11
12 local s_gsub = string.gsub;
13 local s_match = string.match;
14 local s_sub = string.sub;
15 local t_concat = table.concat;
16
17 local st = require "util.stanza";
18
19 local type escape_t = function (string) : string
20 local type filter_t = function (string, string | st.stanza_t, string) : string | st.stanza_t, boolean
21 local type filter_coll = { string : filter_t }
22
23 local function render(template : string, root : st.stanza_t, escape : escape_t, filters : filter_coll) : string
24 escape = escape or st.xml_escape;
25
26 return (s_gsub(template, "%b{}", function(block : string) : string
27 local inner = s_sub(block, 2, -2);
28 local path, pipe, pos = s_match(inner, "^([^|]+)(|?)()");
29 if not path is string then return end
30 local value : string | st.stanza_t
31 if path == "." then
32 value = root;
33 elseif path == "#" then
34 value = root:get_text();
35 else
36 value = root:find(path);
37 end
38 local is_escaped = false;
39
40 while pipe == "|" do
41 local func, args, tmpl, p = s_match(inner, "^(%w+)(%b())(%b{})()", pos as integer);
42 if not func then func, args, p = s_match(inner, "^(%w+)(%b())()", pos as integer); end
43 if not func then func, tmpl, p = s_match(inner, "^(%w+)(%b{})()", pos as integer); end
44 if not func then func, p = s_match(inner, "^(%w+)()", pos as integer); end
45 if not func then break end
46 if tmpl then tmpl = s_sub(tmpl, 2, -2); end
47 if args then args = s_sub(args, 2, -2); end
48
49 if func == "each" and tmpl and st.is_stanza(value) then
50 if not args then value, args = root, path; end
51 local ns, name = s_match(args, "^(%b{})(.*)$");
52 if ns then ns = s_sub(ns, 2, -2); else name, ns = args, nil; end
53 if ns == "" then ns = nil; end
54 if name == "" then name = nil; end
55 local out, i = {}, 1;
56 for c in (value as st.stanza_t):childtags(name, ns) do
57 out[i], i = render(tmpl, c, escape, filters), i + 1;
58 end
59 value = t_concat(out);
60 is_escaped = true;
61 elseif func == "and" and tmpl then
62 local condition = value;
63 if args then condition = root:find(args); end
64 if condition then
65 value = render(tmpl, root, escape, filters);
66 is_escaped = true;
67 end
68 elseif func == "or" and tmpl then
69 local condition = value;
70 if args then condition = root:find(args); end
71 if not condition then
72 value = render(tmpl, root, escape, filters);
73 is_escaped = true;
74 end
75 elseif filters and filters[func] then
76 local f = filters[func];
77 if args == nil then
78 value, is_escaped = f(value, tmpl);
79 else
80 value, is_escaped = f(args, value, tmpl);
81 end
82 else
83 error("No such filter function: " .. func);
84 end
85 pipe, pos = s_match(inner, "^(|?)()", p as integer);
86 end
87
88 if value is string then
89 if not is_escaped then value = escape(value); end
90 return value;
91 elseif st.is_stanza(value) then
92 value = value:get_text();
93 if value then
94 return escape(value);
95 end
96 end
97 return "";
98 end));
99 end
100
101 return { render = render };