scansion/ordered_serializer.lua

Thu, 23 Mar 2023 15:12:30 +0000

author
Matthew Wild <mwild1@gmail.com>
date
Thu, 23 Mar 2023 15:12:30 +0000
changeset 174
662bd8c5ae28
child 178
e547ddf8b64d
permissions
-rw-r--r--

Serialize XML in a consistent order by default

This overrides all XML serialization to emit attributes in an ordered form, so
the XML will match across multiple runs. This can be useful for comparing
different runs, or even two stanzas printed in the same run (e.g. if there is
a mismatch).

local s_find, s_gsub, s_match = string.find, string.gsub, string.match;
local t_concat, t_insert = table.concat, table.insert;

local pairs = require "util.iterators".sorted_pairs;

local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
local function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end

local function _dostring(t, buf, self, _xml_escape, parentns)
	local nsid = 0;
	local name = t.name
	t_insert(buf, "<"..name);
	for k, v in pairs(t.attr) do
		if s_find(k, "\1", 1, true) then
			local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$");
			nsid = nsid + 1;
			t_insert(buf, " xmlns:ns"..nsid.."='".._xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='".._xml_escape(v).."'");
		elseif not(k == "xmlns" and v == parentns) then
			t_insert(buf, " "..k.."='".._xml_escape(v).."'");
		end
	end
	local len = #t;
	if len == 0 then
		t_insert(buf, "/>");
	else
		t_insert(buf, ">");
		for n=1,len do
			local child = t[n];
			if child.name then
				self(child, buf, self, _xml_escape, t.attr.xmlns);
			else
				t_insert(buf, _xml_escape(child));
			end
		end
		t_insert(buf, "</"..name..">");
	end
end

return {
	enable = function ()
		local stanza_mt = require "util.stanza".stanza_mt;
		stanza_mt._unsorted_tostring = stanza_mt.__tostring;
		function stanza_mt.__tostring(t)
			local buf = {};
			_dostring(t, buf, _dostring, xml_escape, nil);
			return t_concat(buf);
		end;
	end;
	disable = function ()
		local stanza_mt = require "util.stanza".stanza_mt;
		if stanza_mt._unsorted_tostring then
			stanza_mt.__tostring = stanza_mt._unsorted_tostring;
		end
	end;
};

mercurial