Part one of internal component support
[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         return self;
57 end
58
59 function stanza_mt:child_with_name(name)
60         for _, child in ipairs(self) do 
61                 if child.name == name then return child; end
62         end
63 end
64
65 function stanza_mt:children()
66         local i = 0;
67         return function (a)
68                         i = i + 1
69                         local v = a[i]
70                         if v then return v; end
71                 end, self, i;
72                                             
73 end
74 function stanza_mt:childtags()
75         local i = 0;
76         return function (a)
77                         i = i + 1
78                         local v = self.tags[i]
79                         if v then return v; end
80                 end, self.tags[1], i;
81                                             
82 end
83
84 do
85         local xml_entities = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
86         function xml_escape(s) return s_gsub(s, "['&<>\"]", xml_entities); end
87 end
88
89 local xml_escape = xml_escape;
90
91 function stanza_mt.__tostring(t)
92         local children_text = "";
93         for n, child in ipairs(t) do
94                 if type(child) == "string" then 
95                         children_text = children_text .. xml_escape(child);
96                 else
97                         children_text = children_text .. tostring(child);
98                 end
99         end
100
101         local attr_string = "";
102         if t.attr then
103                 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
104         end
105         return s_format("<%s%s>%s</%s>", t.name, attr_string, children_text, t.name);
106 end
107
108 function stanza_mt.__add(s1, s2)
109         return s1:add_direct_child(s2);
110 end
111
112
113 do
114         local id = 0;
115         function new_id()
116                 id = id + 1;
117                 return "lx"..id;
118         end
119 end
120
121 function preserialize(stanza)
122         local s = { name = stanza.name, attr = stanza.attr };
123         for _, child in ipairs(stanza) do
124                 if type(child) == "table" then
125                         t_insert(s, preserialize(child));
126                 else
127                         t_insert(s, child);
128                 end
129         end
130         return s;
131 end
132
133 function deserialize(stanza)
134         -- Set metatable
135         if stanza then
136                 setmetatable(stanza, stanza_mt);
137                 for _, child in ipairs(stanza) do
138                         if type(child) == "table" then
139                                 deserialize(child);
140                         end
141                 end
142                 if not stanza.tags then
143                         -- Rebuild tags
144                         local tags = {};
145                         for _, child in ipairs(stanza) do
146                                 if type(child) == "table" then
147                                         t_insert(tags, child);
148                                 end
149                         end
150                         stanza.tags = tags;
151                 end
152         end
153         
154         return stanza;
155 end
156
157 function message(attr, body)
158         if not body then
159                 return stanza("message", attr);
160         else
161                 return stanza("message", attr):tag("body"):text(body);
162         end
163 end
164 function iq(attr)
165         if attr and not attr.id then attr.id = new_id(); end
166         return stanza("iq", attr or { id = new_id() });
167 end
168
169 function reply(orig)
170         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) });
171 end
172
173 function error_reply(orig, type, condition, message, clone)
174         local t = reply(orig);
175         t.attr.type = "error";
176         -- TODO use clone
177         t:tag("error", {type = type})
178                 :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
179         if (message) then t:tag("text"):text(message):up(); end
180         return t; -- stanza ready for adding app-specific errors
181 end
182
183 function presence(attr)
184         return stanza("presence", attr);
185 end
186
187 return _M;