2 local st = require "util.stanza";
3 local lxp = require "lxp";
4 local setmetatable = setmetatable;
8 local loadstring = loadstring;
10 local t_remove = table.remove;
14 local parse_xml = (function()
16 ["http://www.w3.org/XML/1998/namespace"] = "xml";
18 local ns_separator = "\1";
19 local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
22 local stanza = st.stanza("root");
23 function handler:StartElement(tagname, attr)
24 local curr_ns,name = tagname:match(ns_pattern);
26 curr_ns, name = "", curr_ns;
34 local ns, nm = k:match(ns_pattern);
38 attr[ns..":"..nm] = attr[k];
43 stanza:tag(name, attr);
45 function handler:CharacterData(data)
48 function handler:EndElement(tagname)
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
56 return stanza.tags[1];
58 return ok, err.." (line "..line..", col "..col..")";
63 local function trim_xml(stanza)
65 local child = stanza[i];
69 child = child:gsub("^%s*", ""):gsub("%s*$", "");
71 if child == "" then t_remove(stanza, i); end
76 local function create_string_string(str)
77 str = ("%q"):format(str);
78 str = str:gsub("{([^}]*)}", function(s)
79 return '"..(data["'..s..'"]or"").."';
83 local function create_attr_string(attr, xmlns)
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));
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));
96 for i,tag in ipairs(stanza.tags) do
97 s = s..create_clone_string(tag, lookup, stanza.attr.xmlns)..";";
101 for i,child in ipairs(stanza) do
103 s = s..create_clone_string(child, lookup, stanza.attr.xmlns)..";";
105 s = s..create_string_string(child)..";"
108 s = s..'}, stanza_mt)';
109 s = s:gsub('%.%.""', ""):gsub('([=;])""%.%.', "%1"):gsub(';"";', ";"); -- strip empty strings
110 local n = #lookup + 1;
112 lookup[stanza] = "_"..n;
114 return lookup[stanza];
116 local stanza_mt = st.stanza_mt;
117 local function create_cloner(stanza, chunkname)
119 local name = create_clone_string(stanza, lookup, "");
120 local f = "local setmetatable,stanza_mt=...;return function(data)";
122 f = f.."local _"..i.."="..lookup[i]..";";
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);
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
136 local info = debug.getinfo(3, "Sl");
137 info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
139 local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt);
140 templates[text] = template;
144 local templates = setmetatable({}, { __mode = 'k', __index = create_template });
145 return function(text)
146 return templates[text];