Comparison

util/template.lua @ 3637:bd491def3efb

util.template: Rewritten to be much faster than the util.stanza stanza building API.
author Waqas Hussain <waqas20@gmail.com>
date Thu, 25 Nov 2010 08:38:26 +0500
parent 3546:cb1600dea3ad
child 3640:4bc88bb748d1
comparison
equal deleted inserted replaced
3636:88e4397e39a9 3637:bd491def3efb
1 1
2 local t_insert = table.insert;
3 local st = require "util.stanza"; 2 local st = require "util.stanza";
4 local lxp = require "lxp"; 3 local lxp = require "lxp";
5 local setmetatable = setmetatable; 4 local setmetatable = setmetatable;
6 local pairs = pairs; 5 local pairs = pairs;
6 local ipairs = ipairs;
7 local error = error; 7 local error = error;
8 local s_gsub = string.gsub; 8 local loadstring = loadstring;
9 9 local debug = debug;
10 local print = print;
11 10
12 module("template") 11 module("template")
13
14 local function process_stanza(stanza, ops)
15 -- process attrs
16 for key, val in pairs(stanza.attr) do
17 if val:match("{[^}]*}") then
18 t_insert(ops, {stanza.attr, key, val});
19 end
20 end
21 -- process children
22 local i = 1;
23 while i <= #stanza do
24 local child = stanza[i];
25 if child.name then
26 process_stanza(child, ops);
27 elseif child:match("^{[^}]*}$") then -- text
28 t_insert(ops, {stanza, i, child:match("^{([^}]*)}$"), true});
29 elseif child:match("{[^}]*}") then -- text
30 t_insert(ops, {stanza, i, child});
31 end
32 i = i + 1;
33 end
34 end
35 12
36 local parse_xml = (function() 13 local parse_xml = (function()
37 local ns_prefixes = { 14 local ns_prefixes = {
38 ["http://www.w3.org/XML/1998/namespace"] = "xml"; 15 ["http://www.w3.org/XML/1998/namespace"] = "xml";
39 }; 16 };
81 return ok, err.." (line "..line..", col "..col..")"; 58 return ok, err.." (line "..line..", col "..col..")";
82 end 59 end
83 end; 60 end;
84 end)(); 61 end)();
85 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,last_add={},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
86 local stanza_mt = st.stanza_mt; 103 local stanza_mt = st.stanza_mt;
87 local function fast_st_clone(stanza, lookup) 104 local function create_cloner(stanza, chunkname)
88 local stanza_attr = stanza.attr; 105 local lookup = {};
89 local stanza_tags = stanza.tags; 106 local name = create_clone_string(stanza, lookup, "");
90 local tags, attr = {}, {}; 107 local f = "local setmetatable,stanza_mt=...;return function(data)";
91 local clone = { name = stanza.name, attr = attr, tags = tags, last_add = {} }; 108 for i=1,#lookup do
92 for k,v in pairs(stanza_attr) do attr[k] = v; end 109 f = f.."local _"..i.."="..lookup[i]..";";
93 lookup[stanza_attr] = attr;
94 for i=1,#stanza_tags do
95 local child = stanza_tags[i];
96 local new = fast_st_clone(child, lookup);
97 tags[i] = new;
98 lookup[child] = new;
99 end 110 end
100 for i=1,#stanza do 111 f = f.."return "..name..";end";
101 local child = stanza[i]; 112 local f,err = loadstring(f, chunkname);
102 clone[i] = lookup[child] or child; 113 if not f then error(err); end
103 end 114 return f(setmetatable, stanza_mt);
104 return setmetatable(clone, stanza_mt);
105 end 115 end
106 116
107 local function create_template(text) 117 local template_mt = { __tostring = function(t) return t.name end };
118 local function create_template(templates, text)
108 local stanza, err = parse_xml(text); 119 local stanza, err = parse_xml(text);
109 if not stanza then error(err); end 120 if not stanza then error(err); end
110 local ops = {}; 121
111 process_stanza(stanza, ops); 122 local info = debug.getinfo(3, "Sl");
112 123 info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
113 local template = {}; 124
114 local lookup = {}; 125 local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt);
115 function template.apply(data) 126 templates[text] = template;
116 local newstanza = fast_st_clone(stanza, lookup);
117 for i=1,#ops do
118 local op = ops[i];
119 local t, k, v, g = op[1], op[2], op[3], op[4];
120 if g then
121 lookup[t][k] = data[v];
122 else
123 lookup[t][k] = s_gsub(v, "{([^}]*)}", data);
124 end
125 end
126 return newstanza;
127 end
128 return template; 127 return template;
129 end 128 end
130 129
131 local templates = setmetatable({}, { __mode = 'k' }); 130 local templates = setmetatable({}, { __mode = 'k', __index = create_template });
132 return function(text) 131 return function(text)
133 local template = templates[text]; 132 return templates[text];
134 if not template then
135 template = create_template(text);
136 templates[text] = template;
137 end
138 return template;
139 end; 133 end;