Make add_child() behave as expected. Old add_child() is now add_direct_child()
[prosody.git] / util / stanza.lua
1 local t_insert      =  table.insert;
2 local t_remove      =  table.remove;
3 local s_format      = string.format;
4 local tostring      =      tostring;
5 local setmetatable  =  setmetatable;
6 local pairs         =         pairs;
7 local ipairs        =        ipairs;
8 local type          =          type;
9 local next          =          next;
10 local print          =          print;
11 local unpack        =        unpack;
12 local s_gsub        =   string.gsub;
13
14 local debug = debug;
15 local log = require "util.logger".init("stanza");
16
17 module "stanza"
18
19 stanza_mt = {};
20 stanza_mt.__index = stanza_mt;
21
22 function stanza(name, attr)
23         local stanza = { name = name, attr = attr or {}, tags = {}, last_add = {}};
24         return setmetatable(stanza, stanza_mt);
25 end
26
27 function stanza_mt:query(xmlns)
28         return self:tag("query", { xmlns = xmlns });
29 end
30 function stanza_mt:tag(name, attrs)
31         local s = stanza(name, attrs);
32         (self.last_add[#self.last_add] or self):add_direct_child(s);
33         t_insert(self.last_add, s);
34         return self;
35 end
36
37 function stanza_mt:text(text)
38         (self.last_add[#self.last_add] or self):add_direct_child(text);
39         return self; 
40 end
41
42 function stanza_mt:up()
43         t_remove(self.last_add);
44         return self;
45 end
46
47 function stanza_mt:add_direct_child(child)
48         if type(child) == "table" then
49                 t_insert(self.tags, child);
50         end
51         t_insert(self, child);
52 end
53
54 function stanza_mt:add_child(child)
55         (self.last_add[#self.last_add] or self):add_direct_child(child);
56         t_insert(self.last_add, s);
57         return self;
58 end
59
60 function stanza_mt:child_with_name(name)
61         for _, child in ipairs(self) do 
62                 if child.name == name then return child; end
63         end
64 end
65
66 function stanza_mt:children()
67         local i = 0;
68         return function (a)
69                         i = i + 1
70                         local v = a[i]
71                         if v then return v; end
72                 end, self, i;
73                                             
74 end
75 function stanza_mt:childtags()
76         local i = 0;
77         return function (a)
78                         i = i + 1
79                         local v = self.tags[i]
80                         if v then return v; end
81                 end, self.tags[1], i;
82                                             
83 end
84
85 do
86         local xml_entities = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
87         function xml_escape(s) return s_gsub(s, "['&<>\"]", xml_entities); end
88 end
89
90 local xml_escape = xml_escape;
91
92 function stanza_mt.__tostring(t)
93         local children_text = "";
94         for n, child in ipairs(t) do
95                 if type(child) == "string" then 
96                         children_text = children_text .. xml_escape(child);
97                 else
98                         children_text = children_text .. tostring(child);
99                 end
100         end
101
102         local attr_string = "";
103         if t.attr then
104                 for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, tostring(v)); end end
105         end
106         return s_format("<%s%s>%s</%s>", t.name, attr_string, children_text, t.name);
107 end
108
109 function stanza_mt.__add(s1, s2)
110         return s1:add_direct_child(s2);
111 end
112
113
114 do
115         local id = 0;
116         function new_id()
117                 id = id + 1;
118                 return "lx"..id;
119         end
120 end
121
122 function preserialize(stanza)
123         local s = { name = stanza.name, attr = stanza.attr };
124         for _, child in ipairs(stanza) do
125                 if type(child) == "table" then
126                         t_insert(s, preserialize(child));
127                 else
128                         t_insert(s, child);
129                 end
130         end
131         return s;
132 end
133
134 function deserialize(stanza)
135         -- Set metatable
136         if stanza then
137                 setmetatable(stanza, stanza_mt);
138                 for _, child in ipairs(stanza) do
139                         if type(child) == "table" then
140                                 deserialize(child);
141                         end
142                 end
143                 if not stanza.tags then
144                         -- Rebuild tags
145                         local tags = {};
146                         for _, child in ipairs(stanza) do
147                                 if type(child) == "table" then
148                                         t_insert(tags, child);
149                                 end
150                         end
151                         stanza.tags = tags;
152                 end
153         end
154         
155         return stanza;
156 end
157
158 function message(attr, body)
159         if not body then
160                 return stanza("message", attr);
161         else
162                 return stanza("message", attr):tag("body"):text(body);
163         end
164 end
165 function iq(attr)
166         if attr and not attr.id then attr.id = new_id(); end
167         return stanza("iq", attr or { id = new_id() });
168 end
169
170 function reply(orig)
171         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 nil) });
172 end
173
174 function error_reply(orig, type, condition, message, clone)
175         local t = reply(orig);
176         t.attr.type = "error";
177         -- TODO use clone
178         t:tag("error", {type = type})
179                 :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
180         if (message) then t:tag("text"):text(message):up(); end
181         return t; -- stanza ready for adding app-specific errors
182 end
183
184 function presence(attr)
185         return stanza("presence", attr);
186 end
187
188 return _M;