Merge 0.10->trunk
[prosody.git] / util / template.lua
1 -- luacheck: ignore 213/i
2 local stanza_mt = require "util.stanza".stanza_mt;
3 local setmetatable = setmetatable;
4 local pairs = pairs;
5 local ipairs = ipairs;
6 local error = error;
7 local loadstring = loadstring;
8 local debug = debug;
9 local t_remove = table.remove;
10 local parse_xml = require "util.xml".parse;
11
12 local _ENV = nil;
13
14 local function trim_xml(stanza)
15         for i=#stanza,1,-1 do
16                 local child = stanza[i];
17                 if child.name then
18                         trim_xml(child);
19                 else
20                         child = child:gsub("^%s*", ""):gsub("%s*$", "");
21                         stanza[i] = child;
22                         if child == "" then t_remove(stanza, i); end
23                 end
24         end
25 end
26
27 local function create_string_string(str)
28         str = ("%q"):format(str);
29         str = str:gsub("{([^}]*)}", function(s)
30                 return '"..(data["'..s..'"]or"").."';
31         end);
32         return str;
33 end
34 local function create_attr_string(attr, xmlns)
35         local str = '{';
36         for name,value in pairs(attr) do
37                 if name ~= "xmlns" or value ~= xmlns then
38                         str = str..("[%q]=%s;"):format(name, create_string_string(value));
39                 end
40         end
41         return str..'}';
42 end
43 local function create_clone_string(stanza, lookup, xmlns)
44         if not lookup[stanza] then
45                 local s = ('setmetatable({name=%q,attr=%s,tags={'):format(stanza.name, create_attr_string(stanza.attr, xmlns));
46                 -- add tags
47                 for i,tag in ipairs(stanza.tags) do
48                         s = s..create_clone_string(tag, lookup, stanza.attr.xmlns)..";";
49                 end
50                 s = s..'};';
51                 -- add children
52                 for i,child in ipairs(stanza) do
53                         if child.name then
54                                 s = s..create_clone_string(child, lookup, stanza.attr.xmlns)..";";
55                         else
56                                 s = s..create_string_string(child)..";"
57                         end
58                 end
59                 s = s..'}, stanza_mt)';
60                 s = s:gsub('%.%.""', ""):gsub('([=;])""%.%.', "%1"):gsub(';"";', ";"); -- strip empty strings
61                 local n = #lookup + 1;
62                 lookup[n] = s;
63                 lookup[stanza] = "_"..n;
64         end
65         return lookup[stanza];
66 end
67 local function create_cloner(stanza, chunkname)
68         local lookup = {};
69         local name = create_clone_string(stanza, lookup, "");
70         local src = "local setmetatable,stanza_mt=...;return function(data)";
71         for i=1,#lookup do
72                 src = src.."local _"..i.."="..lookup[i]..";";
73         end
74         src = src.."return "..name..";end";
75         local f,err = loadstring(src, chunkname);
76         if not f then error(err); end
77         return f(setmetatable, stanza_mt);
78 end
79
80 local template_mt = { __tostring = function(t) return t.name end };
81 local function create_template(templates, text)
82         local stanza, err = parse_xml(text);
83         if not stanza then error(err); end
84         trim_xml(stanza);
85
86         local info = debug.getinfo(3, "Sl");
87         info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
88
89         local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt);
90         templates[text] = template;
91         return template;
92 end
93
94 local templates = setmetatable({}, { __mode = 'k', __index = create_template });
95 return function(text)
96         return templates[text];
97 end;