Merge from waqas
[prosody.git] / util / stanza.lua
1 -- Prosody IM v0.1
2 -- Copyright (C) 2008 Matthew Wild
3 -- Copyright (C) 2008 Waqas Hussain
4 -- 
5 -- This program is free software; you can redistribute it and/or
6 -- modify it under the terms of the GNU General Public License
7 -- as published by the Free Software Foundation; either version 2
8 -- of the License, or (at your option) any later version.
9 -- 
10 -- This program is distributed in the hope that it will be useful,
11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 -- GNU General Public License for more details.
14 -- 
15 -- You should have received a copy of the GNU General Public License
16 -- along with this program; if not, write to the Free Software
17 -- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 --
19
20
21 local t_insert      =  table.insert;
22 local t_remove      =  table.remove;
23 local s_format      = string.format;
24 local tostring      =      tostring;
25 local setmetatable  =  setmetatable;
26 local pairs         =         pairs;
27 local ipairs        =        ipairs;
28 local type          =          type;
29 local next          =          next;
30 local print          =          print;
31 local unpack        =        unpack;
32 local s_gsub        =   string.gsub;
33 local os = os;
34
35 local do_pretty_printing = not os.getenv("WINDIR");
36 local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
37
38 local log = require "util.logger".init("stanza");
39
40 module "stanza"
41
42 stanza_mt = {};
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:add_direct_child(child)
76         if type(child) == "table" then
77                 t_insert(self.tags, child);
78         end
79         t_insert(self, child);
80 end
81
82 function stanza_mt:add_child(child)
83         (self.last_add[#self.last_add] or self):add_direct_child(child);
84         return self;
85 end
86
87 function stanza_mt:child_with_name(name)
88         for _, child in ipairs(self) do 
89                 if child.name == name then return child; end
90         end
91 end
92
93 function stanza_mt:children()
94         local i = 0;
95         return function (a)
96                         i = i + 1
97                         local v = a[i]
98                         if v then return v; end
99                 end, self, i;
100                                             
101 end
102 function stanza_mt:childtags()
103         local i = 0;
104         return function (a)
105                         i = i + 1
106                         local v = self.tags[i]
107                         if v then return v; end
108                 end, self.tags[1], i;
109                                             
110 end
111
112 do
113         local xml_entities = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
114         function xml_escape(s) return s_gsub(s, "['&<>\"]", xml_entities); end
115 end
116
117 local xml_escape = xml_escape;
118
119 function stanza_mt.__tostring(t)
120         local children_text = "";
121         for n, child in ipairs(t) do
122                 if type(child) == "string" then 
123                         children_text = children_text .. xml_escape(child);
124                 else
125                         children_text = children_text .. tostring(child);
126                 end
127         end
128
129         local attr_string = "";
130         if t.attr then
131                 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
132         end
133         return s_format("<%s%s>%s</%s>", t.name, attr_string, children_text, t.name);
134 end
135
136 function stanza_mt.top_tag(t)
137         local attr_string = "";
138         if t.attr then
139                 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
140         end
141         return s_format("<%s%s>", t.name, attr_string);
142 end
143
144 function stanza_mt.__add(s1, s2)
145         return s1:add_direct_child(s2);
146 end
147
148
149 do
150         local id = 0;
151         function new_id()
152                 id = id + 1;
153                 return "lx"..id;
154         end
155 end
156
157 function preserialize(stanza)
158         local s = { name = stanza.name, attr = stanza.attr };
159         for _, child in ipairs(stanza) do
160                 if type(child) == "table" then
161                         t_insert(s, preserialize(child));
162                 else
163                         t_insert(s, child);
164                 end
165         end
166         return s;
167 end
168
169 function deserialize(stanza)
170         -- Set metatable
171         if stanza then
172                 setmetatable(stanza, stanza_mt);
173                 for _, child in ipairs(stanza) do
174                         if type(child) == "table" then
175                                 deserialize(child);
176                         end
177                 end
178                 if not stanza.tags then
179                         -- Rebuild tags
180                         local tags = {};
181                         for _, child in ipairs(stanza) do
182                                 if type(child) == "table" then
183                                         t_insert(tags, child);
184                                 end
185                         end
186                         stanza.tags = tags;
187                 end
188         end
189         
190         return stanza;
191 end
192
193 function message(attr, body)
194         if not body then
195                 return stanza("message", attr);
196         else
197                 return stanza("message", attr):tag("body"):text(body);
198         end
199 end
200 function iq(attr)
201         if attr and not attr.id then attr.id = new_id(); end
202         return stanza("iq", attr or { id = new_id() });
203 end
204
205 function reply(orig)
206         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) });
207 end
208
209 function error_reply(orig, type, condition, message, clone)
210         local t = reply(orig);
211         t.attr.type = "error";
212         -- TODO use clone
213         t:tag("error", {type = type})
214                 :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
215         if (message) then t:tag("text"):text(message):up(); end
216         return t; -- stanza ready for adding app-specific errors
217 end
218
219 function presence(attr)
220         return stanza("presence", attr);
221 end
222
223 if do_pretty_printing then
224         local style_attrk = getstyle("yellow");
225         local style_attrv = getstyle("red");
226         local style_tagname = getstyle("red");
227         local style_punc = getstyle("magenta");
228         
229         local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'");
230         local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">");
231         --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, ">");
232         local tag_format = top_tag_format.."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
233         function stanza_mt.pretty_print(t)
234                 local children_text = "";
235                 for n, child in ipairs(t) do
236                         if type(child) == "string" then 
237                                 children_text = children_text .. xml_escape(child);
238                         else
239                                 children_text = children_text .. child:pretty_print();
240                         end
241                 end
242
243                 local attr_string = "";
244                 if t.attr then
245                         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
246                 end
247                 return s_format(tag_format, t.name, attr_string, children_text, t.name);
248         end
249         
250         function stanza_mt.pretty_top_tag(t)
251                 local attr_string = "";
252                 if t.attr then
253                         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
254                 end
255                 return s_format(top_tag_format, t.name, attr_string);
256         end
257 else
258         -- Sorry, fresh out of colours for you guys ;)
259         stanza_mt.pretty_print = stanza_mt.__tostring;
260         stanza_mt.pretty_top_tag = stanza_mt.top_tag;
261 end
262
263 return _M;