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;
11 local unpack = unpack;
12 local s_gsub = string.gsub;
15 local log = require "util.logger".init("stanza");
20 stanza_mt.__index = stanza_mt;
22 function stanza(name, attr)
23 local stanza = { name = name, attr = attr or {}, tags = {}, last_add = {}};
24 return setmetatable(stanza, stanza_mt);
27 function stanza_mt:query(xmlns)
28 return self:tag("query", { xmlns = xmlns });
30 function stanza_mt:tag(name, attrs)
31 local s = stanza(name, attrs);
32 (self.last_add[#self.last_add] or self):add_child(s);
33 t_insert(self.last_add, s);
37 function stanza_mt:text(text)
38 (self.last_add[#self.last_add] or self):add_child(text);
42 function stanza_mt:up()
43 t_remove(self.last_add);
47 function stanza_mt:add_child(child)
48 if type(child) == "table" then
49 t_insert(self.tags, child);
51 t_insert(self, child);
54 function stanza_mt:child_with_name(name)
55 for _, child in ipairs(self) do
56 if child.name == name then return child; end
60 function stanza_mt:children()
65 if v then return v; end
69 function stanza_mt:childtags()
73 local v = self.tags[i]
74 if v then return v; end
80 local xml_entities = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" };
81 function xml_escape(s) return s_gsub(s, "['&<>\"]", xml_entities); end
84 local xml_escape = xml_escape;
86 function stanza_mt.__tostring(t)
87 local children_text = "";
88 for n, child in ipairs(t) do
89 if type(child) == "string" then
90 children_text = children_text .. xml_escape(child);
92 children_text = children_text .. tostring(child);
96 local attr_string = "";
98 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
100 return s_format("<%s%s>%s</%s>", t.name, attr_string, children_text, t.name);
103 function stanza_mt.__add(s1, s2)
104 return s1:add_child(s2);
116 function preserialize(stanza)
117 local s = { name = stanza.name, attr = stanza.attr };
118 for _, child in ipairs(stanza) do
119 if type(child) == "table" then
120 t_insert(s, preserialize(child));
128 function deserialize(stanza)
131 setmetatable(stanza, stanza_mt);
132 for _, child in ipairs(stanza) do
133 if type(child) == "table" then
137 if not stanza.tags then
140 for _, child in ipairs(stanza) do
141 if type(child) == "table" then
142 t_insert(tags, child);
152 function message(attr, body)
154 return stanza("message", attr);
156 return stanza("message", attr):tag("body"):text(body);
160 if attr and not attr.id then attr.id = new_id(); end
161 return stanza("iq", attr or { id = new_id() });
165 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) });
168 function error_reply(orig, type, condition, message, clone)
169 local t = reply(orig);
170 t.attr.type = "error";
172 t:tag("error", {type = type})
173 :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
174 if (message) then t:tag("text"):text(message):up(); end
175 return t; -- stanza ready for adding app-specific errors
178 function presence(attr)
179 return stanza("presence", attr);