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
|
local t_insert = table.insert;
local st = require "util.stanza";
local lxp = require "lxp";
local setmetatable = setmetatable;
local pairs = pairs;
local error = error;
local s_gsub = string.gsub;
local print = print;
module("template")
local function process_stanza(stanza, ops)
-- process attrs
for key, val in pairs(stanza.attr) do
if val:match("{[^}]*}") then
t_insert(ops, {stanza.attr, key, val});
end
end
-- process children
local i = 1;
while i <= #stanza do
local child = stanza[i];
if child.name then
process_stanza(child, ops);
elseif child:match("^{[^}]*}$") then -- text
t_insert(ops, {stanza, i, child:match("^{([^}]*)}$"), true});
elseif child:match("{[^}]*}") then -- text
t_insert(ops, {stanza, i, child});
end
i = i + 1;
end
end
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)
data = data:gsub("^%s*", ""):gsub("%s*$", "");
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 stanza_mt = st.stanza_mt;
local function fast_st_clone(stanza, lookup)
local stanza_attr = stanza.attr;
local stanza_tags = stanza.tags;
local tags, attr = {}, {};
local clone = { name = stanza.name, attr = attr, tags = tags, last_add = {} };
for k,v in pairs(stanza_attr) do attr[k] = v; end
lookup[stanza_attr] = attr;
for i=1,#stanza_tags do
local child = stanza_tags[i];
local new = fast_st_clone(child, lookup);
tags[i] = new;
lookup[child] = new;
end
for i=1,#stanza do
local child = stanza[i];
clone[i] = lookup[child] or child;
end
return setmetatable(clone, stanza_mt);
end
local function create_template(text)
local stanza, err = parse_xml(text);
if not stanza then error(err); end
local ops = {};
process_stanza(stanza, ops);
local template = {};
local lookup = {};
function template.apply(data)
local newstanza = fast_st_clone(stanza, lookup);
for i=1,#ops do
local op = ops[i];
local t, k, v, g = op[1], op[2], op[3], op[4];
if g then
lookup[t][k] = data[v];
else
lookup[t][k] = s_gsub(v, "{([^}]*)}", data);
end
end
return newstanza;
end
return template;
end
local templates = setmetatable({}, { __mode = 'k' });
return function(text)
local template = templates[text];
if not template then
template = create_template(text);
templates[text] = template;
end
return template;
end;
|