Merge 0.6->0.7
[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
11 module("template")
12
13 local parse_xml = (function()
14         local ns_prefixes = {
15                 ["http://www.w3.org/XML/1998/namespace"] = "xml";
16         };
17         local ns_separator = "\1";
18         local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
19         return function(xml)
20                 local handler = {};
21                 local stanza = st.stanza("root");
22                 function handler:StartElement(tagname, attr)
23                         local curr_ns,name = tagname:match(ns_pattern);
24                         if name == "" then
25                                 curr_ns, name = "", curr_ns;
26                         end
27                         if curr_ns ~= "" then
28                                 attr.xmlns = curr_ns;
29                         end
30                         for i=1,#attr do
31                                 local k = attr[i];
32                                 attr[i] = nil;
33                                 local ns, nm = k:match(ns_pattern);
34                                 if nm ~= "" then
35                                         ns = ns_prefixes[ns]; 
36                                         if ns then 
37                                                 attr[ns..":"..nm] = attr[k];
38                                                 attr[k] = nil;
39                                         end
40                                 end
41                         end
42                         stanza:tag(name, attr);
43                 end
44                 function handler:CharacterData(data)
45                         data = data:gsub("^%s*", ""):gsub("%s*$", "");
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 create_string_string(str)
64         str = ("%q"):format(str);
65         str = str:gsub("{([^}]*)}", function(s)
66                 return '"..(data["'..s..'"]or"").."';
67         end);
68         return str;
69 end
70 local function create_attr_string(attr, xmlns)
71         local str = '{';
72         for name,value in pairs(attr) do
73                 if name ~= "xmlns" or value ~= xmlns then
74                         str = str..("[%q]=%s;"):format(name, create_string_string(value));
75                 end
76         end
77         return str..'}';
78 end
79 local function create_clone_string(stanza, lookup, xmlns)
80         if not lookup[stanza] then
81                 local s = ('setmetatable({name=%q,attr=%s,tags={'):format(stanza.name, create_attr_string(stanza.attr, xmlns));
82                 -- add tags
83                 for i,tag in ipairs(stanza.tags) do
84                         s = s..create_clone_string(tag, lookup, stanza.attr.xmlns)..";";
85                 end
86                 s = s..'};';
87                 -- add children
88                 for i,child in ipairs(stanza) do
89                         if child.name then
90                                 s = s..create_clone_string(child, lookup, stanza.attr.xmlns)..";";
91                         else
92                                 s = s..create_string_string(child)..";"
93                         end
94                 end
95                 s = s..'}, stanza_mt)';
96                 s = s:gsub('%.%.""', ""):gsub('([=;])""%.%.', "%1"):gsub(';"";', ";"); -- strip empty strings
97                 local n = #lookup + 1;
98                 lookup[n] = s;
99                 lookup[stanza] = "_"..n;
100         end
101         return lookup[stanza];
102 end
103 local stanza_mt = st.stanza_mt;
104 local function create_cloner(stanza, chunkname)
105         local lookup = {};
106         local name = create_clone_string(stanza, lookup, "");
107         local f = "local setmetatable,stanza_mt=...;return function(data)";
108         for i=1,#lookup do
109                 f = f.."local _"..i.."="..lookup[i]..";";
110         end
111         f = f.."return "..name..";end";
112         local f,err = loadstring(f, chunkname);
113         if not f then error(err); end
114         return f(setmetatable, stanza_mt);
115 end
116
117 local template_mt = { __tostring = function(t) return t.name end };
118 local function create_template(templates, text)
119         local stanza, err = parse_xml(text);
120         if not stanza then error(err); end
121
122         local info = debug.getinfo(3, "Sl");
123         info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
124
125         local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt);
126         templates[text] = template;
127         return template;
128 end
129
130 local templates = setmetatable({}, { __mode = 'k', __index = create_template });
131 return function(text)
132         return templates[text];
133 end;