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