util/stanza.lua
changeset 12806 4a8740e01813
parent 12729 12ced5db29b2
child 12979 d10957394a3c
--- a/util/stanza.lua	Mon Dec 12 20:40:23 2022 +0100
+++ b/util/stanza.lua	Mon Dec 12 07:10:54 2022 +0100
@@ -21,12 +21,15 @@
 local s_gsub        =   string.gsub;
 local s_sub         =    string.sub;
 local s_find        =   string.find;
+local t_move        =    table.move or require "util.table".move;
+local t_create = require"util.table".create;
 
 local valid_utf8 = require "util.encodings".utf8.valid;
 
 local do_pretty_printing, termcolours = pcall(require, "util.termcolours");
 
 local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
+local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
 
 local _ENV = nil;
 -- luacheck: std none
@@ -179,6 +182,14 @@
 	return nil;
 end
 
+function stanza_mt:get_child_attr(name, xmlns, attr)
+	local tag = self:get_child(name, xmlns);
+	if tag then
+		return tag.attr[attr];
+	end
+	return nil;
+end
+
 function stanza_mt:child_with_name(name)
 	for _, child in ipairs(self.tags) do
 		if child.name == name then return child; end
@@ -283,25 +294,33 @@
 end
 
 local function _clone(stanza, only_top)
-	local attr, tags = {}, {};
+	local attr = {};
 	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 };
+	local tags, new;
+	if only_top then
+		tags = {};
+		new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags };
+	else
+		tags = t_create(#stanza.tags, 0);
+		new = t_create(#stanza, 4);
+		new.name = stanza.name;
+		new.attr = attr;
+		new.namespaces = namespaces;
+		new.tags = tags;
+	end
+
+	setmetatable(new, stanza_mt);
 	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
+		t_move(stanza, 1, #stanza, 1, new);
+		t_move(stanza.tags, 1, #stanza.tags, 1, tags);
+		new:maptags(_clone);
 	end
-	return setmetatable(new, stanza_mt);
+	return new;
 end
 
 local function clone(stanza, only_top)
@@ -387,6 +406,33 @@
 	return error_type, condition or "undefined-condition", text, extra_tag;
 end
 
+function stanza_mt.add_error(stanza, error_type, condition, error_message, error_by)
+	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 stanza.attr.from == error_by then
+		error_by = nil;
+	end
+	stanza: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
+		stanza:text(extra.uri);
+	end
+	stanza:up();
+	if error_message then stanza:text_tag("text", error_message, xmpp_stanzas_attr); end
+	if extra and is_stanza(extra.tag) then
+		stanza:add_child(extra.tag);
+	elseif extra and extra.namespace and extra.condition then
+		stanza:tag(extra.condition, { xmlns = extra.namespace }):up();
+	end
+	return stanza:up();
+end
+
 local function preserialize(stanza)
 	local s = { name = stanza.name, attr = stanza.attr };
 	for _, child in ipairs(stanza) do
@@ -461,7 +507,6 @@
 		});
 end
 
-local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
 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));
@@ -470,30 +515,9 @@
 	end
 	local t = reply(orig);
 	t.attr.type = "error";
-	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
+	t:add_error(error_type, condition, error_message, error_by);
+	t.last_add = { t[1] }; -- ready to add application-specific errors
+	return t;
 end
 
 local function presence(attr)