Software /
code /
prosody
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 }; |