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