util.stanza: Remove silly dependency on util.logger
[prosody.git] / util / stanza.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2009 Matthew Wild
3 -- Copyright (C) 2008-2009 Waqas Hussain
4 -- 
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9 local t_insert      =  table.insert;
10 local t_concat      =  table.concat;
11 local t_remove      =  table.remove;
12 local t_concat      =  table.concat;
13 local s_format      = string.format;
14 local s_match       =  string.match;
15 local tostring      =      tostring;
16 local setmetatable  =  setmetatable;
17 local getmetatable  =  getmetatable;
18 local pairs         =         pairs;
19 local ipairs        =        ipairs;
20 local type          =          type;
21 local next          =          next;
22 local print         =         print;
23 local unpack        =        unpack;
24 local s_gsub        =   string.gsub;
25 local s_char        =   string.char;
26 local s_find        =   string.find;
27 local os            =            os;
28
29 local do_pretty_printing = not os.getenv("WINDIR");
30 local getstyle, getstring;
31 if do_pretty_printing then
32         local ok, termcolours = pcall(require, "util.termcolours");
33         if ok then
34                 getstyle, getstring = termcolours.getstyle, termcolours.getstring;
35         else
36                 do_pretty_printing = nil;
37         end
38 end
39
40 module "stanza"
41
42 stanza_mt = { __type = "stanza" };
43 stanza_mt.__index = stanza_mt;
44
45 function stanza(name, attr)
46         local stanza = { name = name, attr = attr or {}, tags = {}, last_add = {}};
47         return setmetatable(stanza, stanza_mt);
48 end
49
50 function stanza_mt:query(xmlns)
51         return self:tag("query", { xmlns = xmlns });
52 end
53
54 function stanza_mt:body(text, attr)
55         return self:tag("body", attr):text(text);
56 end
57
58 function stanza_mt:tag(name, attrs)
59         local s = stanza(name, attrs);
60         (self.last_add[#self.last_add] or self):add_direct_child(s);
61         t_insert(self.last_add, s);
62         return self;
63 end
64
65 function stanza_mt:text(text)
66         (self.last_add[#self.last_add] or self):add_direct_child(text);
67         return self; 
68 end
69
70 function stanza_mt:up()
71         t_remove(self.last_add);
72         return self;
73 end
74
75 function stanza_mt:reset()
76         local last_add = self.last_add;
77         for i = 1,#last_add do
78                 last_add[i] = nil;
79         end
80         return self;
81 end
82
83 function stanza_mt:add_direct_child(child)
84         if type(child) == "table" then
85                 t_insert(self.tags, child);
86         end
87         t_insert(self, child);
88 end
89
90 function stanza_mt:add_child(child)
91         (self.last_add[#self.last_add] or self):add_direct_child(child);
92         return self;
93 end
94
95 function stanza_mt:child_with_name(name)
96         for _, child in ipairs(self.tags) do    
97                 if child.name == name then return child; end
98         end
99 end
100
101 function stanza_mt:child_with_ns(ns)
102         for _, child in ipairs(self.tags) do    
103                 if child.attr.xmlns == ns then return child; end
104         end
105 end
106
107 function stanza_mt:children()
108         local i = 0;
109         return function (a)
110                         i = i + 1
111                         local v = a[i]
112                         if v then return v; end
113                 end, self, i;
114                                             
115 end
116 function stanza_mt:childtags()
117         local i = 0;
118         return function (a)
119                         i = i + 1
120                         local v = self.tags[i]
121                         if v then return v; end
122                 end, self.tags[1], i;
123                                             
124 end
125
126 local xml_escape
127 do
128         local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
129         function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
130         _M.xml_escape = xml_escape;
131 end
132
133 local function _dostring(t, buf, self, xml_escape)
134         local nsid = 0;
135         local name = t.name
136         t_insert(buf, "<"..name);
137         for k, v in pairs(t.attr) do
138                 if s_find(k, "|", 1, true) then
139                         local ns, attrk = s_match(k, "^([^|]+)|(.+)$");
140                         nsid = nsid + 1;
141                         t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'");
142                 else
143                         t_insert(buf, " "..k.."='"..xml_escape(v).."'");
144                 end
145         end
146         local len = #t;
147         if len == 0 then
148                 t_insert(buf, "/>");
149         else
150                 t_insert(buf, ">");
151                 for n=1,len do
152                         local child = t[n];
153                         if child.name then
154                                 self(child, buf, self, xml_escape);
155                         else
156                                 t_insert(buf, xml_escape(child));
157                         end
158                 end
159                 t_insert(buf, "</"..name..">");
160         end
161 end
162 function stanza_mt.__tostring(t)
163         local buf = {};
164         _dostring(t, buf, _dostring, xml_escape);
165         return t_concat(buf);
166 end
167
168 function stanza_mt.top_tag(t)
169         local attr_string = "";
170         if t.attr then
171                 for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, xml_escape(tostring(v))); end end
172         end
173         return s_format("<%s%s>", t.name, attr_string);
174 end
175
176 function stanza_mt.get_text(t)
177         if #t.tags == 0 then
178                 return t_concat(t);
179         end
180 end
181
182 function stanza_mt.__add(s1, s2)
183         return s1:add_direct_child(s2);
184 end
185
186
187 do
188         local id = 0;
189         function new_id()
190                 id = id + 1;
191                 return "lx"..id;
192         end
193 end
194
195 function preserialize(stanza)
196         local s = { name = stanza.name, attr = stanza.attr };
197         for _, child in ipairs(stanza) do
198                 if type(child) == "table" then
199                         t_insert(s, preserialize(child));
200                 else
201                         t_insert(s, child);
202                 end
203         end
204         return s;
205 end
206
207 function deserialize(stanza)
208         -- Set metatable
209         if stanza then
210                 local attr = stanza.attr;
211                 for i=1,#attr do attr[i] = nil; end
212                 setmetatable(stanza, stanza_mt);
213                 for _, child in ipairs(stanza) do
214                         if type(child) == "table" then
215                                 deserialize(child);
216                         end
217                 end
218                 if not stanza.tags then
219                         -- Rebuild tags
220                         local tags = {};
221                         for _, child in ipairs(stanza) do
222                                 if type(child) == "table" then
223                                         t_insert(tags, child);
224                                 end
225                         end
226                         stanza.tags = tags;
227                         if not stanza.last_add then
228                                 stanza.last_add = {};
229                         end
230                 end
231         end
232         
233         return stanza;
234 end
235
236 function clone(stanza)
237         local lookup_table = {};
238         local function _copy(object)
239                 if type(object) ~= "table" then
240                         return object;
241                 elseif lookup_table[object] then
242                         return lookup_table[object];
243                 end
244                 local new_table = {};
245                 lookup_table[object] = new_table;
246                 for index, value in pairs(object) do
247                         new_table[_copy(index)] = _copy(value);
248                 end
249                 return setmetatable(new_table, getmetatable(object));
250         end
251         
252         return _copy(stanza)
253 end
254
255 function message(attr, body)
256         if not body then
257                 return stanza("message", attr);
258         else
259                 return stanza("message", attr):tag("body"):text(body);
260         end
261 end
262 function iq(attr)
263         if attr and not attr.id then attr.id = new_id(); end
264         return stanza("iq", attr or { id = new_id() });
265 end
266
267 function reply(orig)
268         return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) });
269 end
270
271 function error_reply(orig, type, condition, message)
272         local t = reply(orig);
273         t.attr.type = "error";
274         t:tag("error", {type = type})
275                 :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
276         if (message) then t:tag("text"):text(message):up(); end
277         return t; -- stanza ready for adding app-specific errors
278 end
279
280 function presence(attr)
281         return stanza("presence", attr);
282 end
283
284 if do_pretty_printing then
285         local style_attrk = getstyle("yellow");
286         local style_attrv = getstyle("red");
287         local style_tagname = getstyle("red");
288         local style_punc = getstyle("magenta");
289         
290         local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'");
291         local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">");
292         --local tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">").."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
293         local tag_format = top_tag_format.."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
294         function stanza_mt.pretty_print(t)
295                 local children_text = "";
296                 for n, child in ipairs(t) do
297                         if type(child) == "string" then 
298                                 children_text = children_text .. xml_escape(child);
299                         else
300                                 children_text = children_text .. child:pretty_print();
301                         end
302                 end
303
304                 local attr_string = "";
305                 if t.attr then
306                         for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end
307                 end
308                 return s_format(tag_format, t.name, attr_string, children_text, t.name);
309         end
310         
311         function stanza_mt.pretty_top_tag(t)
312                 local attr_string = "";
313                 if t.attr then
314                         for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end
315                 end
316                 return s_format(top_tag_format, t.name, attr_string);
317         end
318 else
319         -- Sorry, fresh out of colours for you guys ;)
320         stanza_mt.pretty_print = stanza_mt.__tostring;
321         stanza_mt.pretty_top_tag = stanza_mt.top_tag;
322 end
323
324 return _M;