2 local st = require "util.stanza";
3 local lxp = require "lxp";
4 local setmetatable = setmetatable;
8 local loadstring = loadstring;
13 local parse_xml = (function()
15 ["http://www.w3.org/XML/1998/namespace"] = "xml";
17 local ns_separator = "\1";
18 local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
21 local stanza = st.stanza("root");
22 function handler:StartElement(tagname, attr)
23 local curr_ns,name = tagname:match(ns_pattern);
25 curr_ns, name = "", curr_ns;
33 local ns, nm = k:match(ns_pattern);
37 attr[ns..":"..nm] = attr[k];
42 stanza:tag(name, attr);
44 function handler:CharacterData(data)
45 data = data:gsub("^%s*", ""):gsub("%s*$", "");
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 create_string_string(str)
64 str = ("%q"):format(str);
65 str = str:gsub("{([^}]*)}", function(s)
66 return '"..(data["'..s..'"]or"").."';
70 local function create_attr_string(attr, xmlns)
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));
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));
83 for i,tag in ipairs(stanza.tags) do
84 s = s..create_clone_string(tag, lookup, stanza.attr.xmlns)..";";
88 for i,child in ipairs(stanza) do
90 s = s..create_clone_string(child, lookup, stanza.attr.xmlns)..";";
92 s = s..create_string_string(child)..";"
95 s = s..'}, stanza_mt)';
96 s = s:gsub('%.%.""', ""):gsub('([=;])""%.%.', "%1"):gsub(';"";', ";"); -- strip empty strings
97 local n = #lookup + 1;
99 lookup[stanza] = "_"..n;
101 return lookup[stanza];
103 local stanza_mt = st.stanza_mt;
104 local function create_cloner(stanza, chunkname)
106 local name = create_clone_string(stanza, lookup, "");
107 local f = "local setmetatable,stanza_mt=...;return function(data)";
109 f = f.."local _"..i.."="..lookup[i]..";";
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);
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
122 local info = debug.getinfo(3, "Sl");
123 info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
125 local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt);
126 templates[text] = template;
130 local templates = setmetatable({}, { __mode = 'k', __index = create_template });
131 return function(text)
132 return templates[text];