aboutsummaryrefslogtreecommitdiffstats
path: root/util/stanza.lua
diff options
context:
space:
mode:
Diffstat (limited to 'util/stanza.lua')
-rw-r--r--util/stanza.lua249
1 files changed, 163 insertions, 86 deletions
diff --git a/util/stanza.lua b/util/stanza.lua
index a8c619d0..a6bc51c4 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -11,7 +11,6 @@ local error = error;
local t_insert = table.insert;
local t_remove = table.remove;
local t_concat = table.concat;
-local s_format = string.format;
local s_match = string.match;
local tostring = tostring;
local setmetatable = setmetatable;
@@ -22,20 +21,10 @@ local type = type;
local s_gsub = string.gsub;
local s_sub = string.sub;
local s_find = string.find;
-local os = os;
local valid_utf8 = require "util.encodings".utf8.valid;
-local do_pretty_printing = not os.getenv("WINDIR");
-local getstyle, getstring;
-if do_pretty_printing then
- local ok, termcolours = pcall(require, "util.termcolours");
- if ok then
- getstyle, getstring = termcolours.getstyle, termcolours.getstring;
- else
- do_pretty_printing = nil;
- end
-end
+local do_pretty_printing, termcolours = pcall(require, "util.termcolours");
local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
@@ -106,7 +95,7 @@ function stanza_mt:query(xmlns)
end
function stanza_mt:body(text, attr)
- return self:tag("body", attr):text(text);
+ return self:text_tag("body", text, attr);
end
function stanza_mt:text_tag(name, text, attr, namespaces)
@@ -136,6 +125,10 @@ function stanza_mt:up()
return self;
end
+function stanza_mt:at_top()
+ return self.last_add == nil or #self.last_add == 0
+end
+
function stanza_mt:reset()
self.last_add = nil;
return self;
@@ -198,6 +191,14 @@ function stanza_mt:child_with_ns(ns)
end
end
+function stanza_mt:get_child_with_attr(name, xmlns, attr_name, attr_value, normalize)
+ for tag in self:childtags(name, xmlns) do
+ if (normalize and normalize(tag.attr[attr_name]) or tag.attr[attr_name]) == attr_value then
+ return tag;
+ end
+ end
+end
+
function stanza_mt:children()
local i = 0;
return function (a)
@@ -278,6 +279,34 @@ function stanza_mt:find(path)
until not self
end
+local function _clone(stanza, only_top)
+ local attr, tags = {}, {};
+ for k,v in pairs(stanza.attr) do attr[k] = v; end
+ local old_namespaces, namespaces = stanza.namespaces;
+ if old_namespaces then
+ namespaces = {};
+ for k,v in pairs(old_namespaces) do namespaces[k] = v; end
+ end
+ local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags };
+ if not only_top then
+ for i=1,#stanza do
+ local child = stanza[i];
+ if child.name then
+ child = _clone(child);
+ t_insert(tags, child);
+ end
+ t_insert(new, child);
+ end
+ end
+ return setmetatable(new, stanza_mt);
+end
+
+local function clone(stanza, only_top)
+ if not is_stanza(stanza) then
+ error("bad argument to clone: expected stanza, got "..type(stanza));
+ end
+ return _clone(stanza, only_top);
+end
local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
local function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
@@ -318,11 +347,8 @@ function stanza_mt.__tostring(t)
end
function stanza_mt.top_tag(t)
- local attr_string = "";
- if t.attr then
- 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
- end
- return s_format("<%s%s>", t.name, attr_string);
+ local top_tag_clone = clone(t, true);
+ return tostring(top_tag_clone):sub(1,-3)..">";
end
function stanza_mt.get_text(t)
@@ -332,11 +358,11 @@ function stanza_mt.get_text(t)
end
function stanza_mt.get_error(stanza)
- local error_type, condition, text;
+ local error_type, condition, text, extra_tag;
local error_tag = stanza:get_child("error");
if not error_tag then
- return nil, nil, nil;
+ return nil, nil, nil, nil;
end
error_type = error_tag.attr.type;
@@ -347,12 +373,14 @@ function stanza_mt.get_error(stanza)
elseif not condition then
condition = child.name;
end
- if condition and text then
- break;
- end
+ else
+ extra_tag = child;
+ end
+ if condition and text and extra_tag then
+ break;
end
end
- return error_type, condition or "undefined-condition", text;
+ return error_type, condition or "undefined-condition", text, extra_tag;
end
local function preserialize(stanza)
@@ -396,50 +424,32 @@ local function deserialize(serialized)
end
end
-local function _clone(stanza)
- local attr, tags = {}, {};
- for k,v in pairs(stanza.attr) do attr[k] = v; end
- local old_namespaces, namespaces = stanza.namespaces;
- if old_namespaces then
- namespaces = {};
- for k,v in pairs(old_namespaces) do namespaces[k] = v; end
- end
- local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags };
- for i=1,#stanza do
- local child = stanza[i];
- if child.name then
- child = _clone(child);
- t_insert(tags, child);
- end
- t_insert(new, child);
- end
- return setmetatable(new, stanza_mt);
-end
-
-local function clone(stanza)
- if not is_stanza(stanza) then
- error("bad argument to clone: expected stanza, got "..type(stanza));
- end
- return _clone(stanza);
-end
-
local function message(attr, body)
if not body then
return new_stanza("message", attr);
else
- return new_stanza("message", attr):tag("body"):text(body):up();
+ return new_stanza("message", attr):text_tag("body", body);
end
end
local function iq(attr)
- if not (attr and attr.id) then
+ if not attr then
+ error("iq stanzas require id and type attributes");
+ end
+ if not attr.id then
error("iq stanzas require an id attribute");
end
+ if not attr.type then
+ error("iq stanzas require a type attribute");
+ end
return new_stanza("iq", attr);
end
local function reply(orig)
+ if not is_stanza(orig) then
+ error("bad argument to reply: expected stanza, got "..type(orig));
+ end
return new_stanza(orig.name,
- orig.attr and {
+ {
to = orig.attr.from,
from = orig.attr.to,
id = orig.attr.id,
@@ -448,12 +458,37 @@ local function reply(orig)
end
local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
-local function error_reply(orig, error_type, condition, error_message)
+local function error_reply(orig, error_type, condition, error_message, error_by)
+ if not is_stanza(orig) then
+ error("bad argument to error_reply: expected stanza, got "..type(orig));
+ elseif orig.attr.type == "error" then
+ error("bad argument to error_reply: got stanza of type error which must not be replied to");
+ end
local t = reply(orig);
t.attr.type = "error";
- t:tag("error", {type = error_type}) --COMPAT: Some day xmlns:stanzas goes here
- :tag(condition, xmpp_stanzas_attr):up();
- if error_message then t:tag("text", xmpp_stanzas_attr):text(error_message):up(); end
+ local extra;
+ if type(error_type) == "table" then -- an util.error or similar object
+ if type(error_type.extra) == "table" then
+ extra = error_type.extra;
+ end
+ if type(error_type.context) == "table" and type(error_type.context.by) == "string" then error_by = error_type.context.by; end
+ error_type, condition, error_message = error_type.type, error_type.condition, error_type.text;
+ end
+ if t.attr.from == error_by then
+ error_by = nil;
+ end
+ t:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here
+ :tag(condition, xmpp_stanzas_attr);
+ if extra and condition == "gone" and type(extra.uri) == "string" then
+ t:text(extra.uri);
+ end
+ t:up();
+ if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end
+ if extra and is_stanza(extra.tag) then
+ t:add_child(extra.tag);
+ elseif extra and extra.namespace and extra.condition then
+ t:tag(extra.condition, { xmlns = extra.namespace }):up();
+ end
return t; -- stanza ready for adding app-specific errors
end
@@ -461,39 +496,50 @@ local function presence(attr)
return new_stanza("presence", attr);
end
+local pretty;
if do_pretty_printing then
- local style_attrk = getstyle("yellow");
- local style_attrv = getstyle("red");
- local style_tagname = getstyle("red");
- local style_punc = getstyle("magenta");
-
- local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'");
- local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">");
- --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, ">");
- local tag_format = top_tag_format.."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
- function stanza_mt.pretty_print(t)
- local children_text = "";
- for _, child in ipairs(t) do
- if type(child) == "string" then
- children_text = children_text .. xml_escape(child);
- else
- children_text = children_text .. child:pretty_print();
- end
- end
+ local getstyle, getstring = termcolours.getstyle, termcolours.getstring;
+
+ local blue1 = getstyle("1b3967");
+ local blue2 = getstyle("13b5ea");
+ local green1 = getstyle("439639");
+ local green2 = getstyle("a0ce67");
+ local orange1 = getstyle("d9541e");
+ local orange2 = getstyle("e96d1f");
+
+ local attr_replace = (
+ getstring(green2, "%1") .. -- attr name
+ getstring(green1, "%2") .. -- equal
+ getstring(orange1, "%3") .. -- quote
+ getstring(orange2, "%4") .. -- attr value
+ getstring(orange1, "%5") -- quote
+ );
+
+ local text_replace = (
+ getstring(green1, "%1") .. -- &
+ getstring(green2, "%2") .. -- amp
+ getstring(green1, "%3") -- ;
+ );
+
+ function pretty(s)
+ -- Tag soup color
+ -- Outer gsub call takes each <tag>, applies colour to the brackets, the
+ -- tag name, then applies one inner gsub call to colour the attributes and
+ -- another for any text content.
+ return (s:gsub("(<[?/]?)([^ >/?]*)(.-)([?/]?>)([^<]*)", function(opening_bracket, tag_name, attrs, closing_bracket, content)
+ return getstring(blue1, opening_bracket)..getstring(blue2, tag_name)..
+ attrs:gsub("([^=]+)(=)([\"'])(.-)([\"'])", attr_replace) ..
+ getstring(blue1, closing_bracket) ..
+ content:gsub("(&#?)(%w+)(;)", text_replace);
+ end, 100));
+ end
- local attr_string = "";
- if t.attr then
- 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
- end
- return s_format(tag_format, t.name, attr_string, children_text, t.name);
+ function stanza_mt.pretty_print(t)
+ return pretty(tostring(t));
end
function stanza_mt.pretty_top_tag(t)
- local attr_string = "";
- if t.attr then
- 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
- end
- return s_format(top_tag_format, t.name, attr_string);
+ return pretty(t:top_tag());
end
else
-- Sorry, fresh out of colours for you guys ;)
@@ -501,6 +547,36 @@ else
stanza_mt.pretty_top_tag = stanza_mt.top_tag;
end
+function stanza_mt.indent(t, level, indent)
+ if #t == 0 or (#t == 1 and type(t[1]) == "string") then
+ -- Empty nodes wouldn't have any indentation
+ -- Text-only nodes are preserved as to not alter the text content
+ -- Optimization: Skip clone of these since we don't alter them
+ return t;
+ end
+
+ indent = indent or "\t";
+ level = level or 1;
+ local tag = clone(t, true);
+
+ for child in t:children() do
+ if type(child) == "string" then
+ -- Already indented text would look weird but let's ignore that for now.
+ if child:find("%S") then
+ tag:text("\n" .. indent:rep(level));
+ tag:text(child);
+ end
+ elseif is_stanza(child) then
+ tag:text("\n" .. indent:rep(level));
+ tag:add_direct_child(child:indent(level+1, indent));
+ end
+ end
+ -- before the closing tag
+ tag:text("\n" .. indent:rep((level-1)));
+
+ return tag;
+end
+
return {
stanza_mt = stanza_mt;
stanza = new_stanza;
@@ -514,4 +590,5 @@ return {
error_reply = error_reply;
presence = presence;
xml_escape = xml_escape;
+ pretty_print = pretty;
};