util.debug: Remove 'white' from boundary style (leave at default colour)
[prosody.git] / util / template.lua
1
2 local st = require "util.stanza";
3 local lxp = require "lxp";
4 local setmetatable = setmetatable;
5 local pairs = pairs;
6 local ipairs = ipairs;
7 local error = error;
8 local loadstring = loadstring;
9 local debug = debug;
10 local t_remove = table.remove;
11
12 module("template")
13
14 local parse_xml = (function()
15         local ns_prefixes = {
16                 ["http://www.w3.org/XML/1998/namespace"] = "xml";
17         };
18         local ns_separator = "\1";
19         local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
20         return function(xml)
21                 local handler = {};
22                 local stanza = st.stanza("root");
23                 function handler:StartElement(tagname, attr)
24                         local curr_ns,name = tagname:match(ns_pattern);
25                         if name == "" then
26                                 curr_ns, name = "", curr_ns;
27                         end
28                         if curr_ns ~= "" then
29                                 attr.xmlns = curr_ns;
30                         end
31                         for i=1,#attr do
32                                 local k = attr[i];
33                                 attr[i] = nil;
34                                 local ns, nm = k:match(ns_pattern);
35                                 if nm ~= "" then
36                                         ns = ns_prefixes[ns]; 
37                                         if ns then 
38                                                 attr[ns..":"..nm] = attr[k];
39                                                 attr[k] = nil;
40                                         end
41                                 end
42                         end
43                         stanza:tag(name, attr);
44                 end
45                 function handler:CharacterData(data)
46                         stanza:text(data);
47                 end
48                 function handler:EndElement(tagname)
49                         stanza:up();
50                 end
51                 local parser = lxp.new(handler, "\1");
52                 local ok, err, line, col = parser:parse(xml);
53                 if ok then ok, err, line, col = parser:parse(); end
54                 --parser:close();
55                 if ok then
56                         return stanza.tags[1];
57                 else
58                         return ok, err.." (line "..line..", col "..col..")";
59                 end
60         end;
61 end)();
62
63 local function trim_xml(stanza)
64         for i=#stanza,1,-1 do
65                 local child = stanza[i];
66                 if child.name then
67                         trim_xml(child);
68                 else
69                         child = child:gsub("^%s*", ""):gsub("%s*$", "");
70                         stanza[i] = child;
71                         if child == "" then t_remove(stanza, i); end
72                 end
73         end
74 end
75
76 local function create_string_string(str)
77         str = ("%q"):format(str);
78         str = str:gsub("{([^}]*)}", function(s)
79                 return '"..(data["'..s..'"]or"").."';
80         end);
81         return str;
82 end
83 local function create_attr_string(attr, xmlns)
84         local str = '{';
85         for name,value in pairs(attr) do
86                 if name ~= "xmlns" or value ~= xmlns then
87                         str = str..("[%q]=%s;"):format(name, create_string_string(value));
88                 end
89         end
90         return str..'}';
91 end
92 local function create_clone_string(stanza, lookup, xmlns)
93         if not lookup[stanza] then
94                 local s = ('setmetatable({name=%q,attr=%s,tags={'):format(stanza.name, create_attr_string(stanza.attr, xmlns));
95                 -- add tags
96                 for i,tag in ipairs(stanza.tags) do
97                         s = s..create_clone_string(tag, lookup, stanza.attr.xmlns)..";";
98                 end
99                 s = s..'};';
100                 -- add children
101                 for i,child in ipairs(stanza) do
102                         if child.name then
103                                 s = s..create_clone_string(child, lookup, stanza.attr.xmlns)..";";
104                         else
105                                 s = s..create_string_string(child)..";"
106                         end
107                 end
108                 s = s..'}, stanza_mt)';
109                 s = s:gsub('%.%.""', ""):gsub('([=;])""%.%.', "%1"):gsub(';"";', ";"); -- strip empty strings
110                 local n = #lookup + 1;
111                 lookup[n] = s;
112                 lookup[stanza] = "_"..n;
113         end
114         return lookup[stanza];
115 end
116 local stanza_mt = st.stanza_mt;
117 local function create_cloner(stanza, chunkname)
118         local lookup = {};
119         local name = create_clone_string(stanza, lookup, "");
120         local f = "local setmetatable,stanza_mt=...;return function(data)";
121         for i=1,#lookup do
122                 f = f.."local _"..i.."="..lookup[i]..";";
123         end
124         f = f.."return "..name..";end";
125         local f,err = loadstring(f, chunkname);
126         if not f then error(err); end
127         return f(setmetatable, stanza_mt);
128 end
129
130 local template_mt = { __tostring = function(t) return t.name end };
131 local function create_template(templates, text)
132         local stanza, err = parse_xml(text);
133         if not stanza then error(err); end
134         trim_xml(stanza);
135
136         local info = debug.getinfo(3, "Sl");
137         info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
138
139         local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt);
140         templates[text] = template;
141         return template;
142 end
143
144 local templates = setmetatable({}, { __mode = 'k', __index = create_template });
145 return function(text)
146         return templates[text];
147 end;