scansion/stanzacmp.lua

Sat, 05 Sep 2015 23:24:15 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Sat, 05 Sep 2015 23:24:15 +0100
changeset 6
0c94ea0cabec
child 32
0eabed6c91e7
permissions
-rw-r--r--

client: Implement send/receive, including new stanzacmp library

-- This is my attempt at a utility library to compare two XMPP stanzas
-- It is not testing for exact equivalency, but through some vague rules that felt right.
-- For example, the second stanza passed to stanzas_match() is allowed to have unexpected
-- elements and attributes at the top level. Beyond this, they must match exactly, except
-- for whitespace differences only.
--
-- There are probably bugs, and it can probably be smarter, but I don't want to spend too
-- much time on it right now.

local function trim(s)
	return (s:gsub("^%s+", ""):gsub("%s+$", ""));
end

local function stanzas_strict_match(stanza1, stanza2)
	if stanza1.name ~= stanza2.name or stanza1.attr.xmlns ~= stanza2.attr.xmlns then
		return false;
	end
	
	for k, v in pairs(stanza1.attr) do
		if stanza2.attr[k] ~= v then
			return false;
		end
	end
	
	for k, v in pairs(stanza2.attr) do
		if stanza1.attr[k] ~= v then
			return false;
		end
	end
	
	if #stanza1.tags ~= #stanza2.tags then
		return false;
	end
	
	local stanza2_pos = 1;
	for _, child in ipairs(stanza1) do
		if type(child) == "table" or child:match("%S") then
			local match;
			local child2 = stanza2[stanza2_pos];
			while child2 and not(type(child2) == "table" or child:match("%S")) do
				stanza2_pos = stanza2_pos + 1;
				child2 = stanza2[stanza2_pos];
			end
			if type(child) ~= type(child2) then
				return false;
			end
			if type(child) == "table" and child2.name == child.name and child2.attr.xmlns == child.attr.xmlns then
				-- Strict deep match
				match = stanzas_strict_match(child, child2);
			elseif type(child) == "string" then -- Text nodes, must be equal, ignoring leading/trailing whitespace
				match = trim(child) == trim(child2);
			end
			if not match then
				return false;
			end
		end
	end
	return true;
end

-- Everything in stanza1 should be present in stanza2

local function stanzas_match(stanza1, stanza2)
	if stanza1.name ~= stanza2.name or stanza1.attr.xmlns ~= stanza2.attr.xmlns then
		return false;
	end
	
	for k, v in pairs(stanza1.attr) do
		if stanza2.attr[k] ~= v then
			return false;
		end
	end
	
	local stanza2_pos = 1;
	for _, child in ipairs(stanza1) do
		if type(child) == "table" or child:match("%S") then
			local match;
			-- Iterate through remaining nodes in stanza2, looking for a match
			while stanza2_pos <= #stanza2 do
				local child2 = stanza2[stanza2_pos];
				stanza2_pos = stanza2_pos + 1;

				if type(child2) == type(child) then
					if type(child) == "table" and child2.name == child.name and child2.attr.xmlns == child.attr.xmlns then
						-- Strict deep match 
						match = stanzas_strict_match(child, child2);
					elseif type(child) == "string" then -- Text nodes, must be equal, ignoring leading/trailing whitespace
						match = trim(child) == trim(child2);
					end
					break;
				end
			end
			if not match then
				return false;
			end
		end
	end
	return true;
end


return {
	stanzas_match = stanzas_match;
	stanzas_strict_match = stanzas_strict_match;
};

mercurial