Fri, 01 Feb 2019 11:22:20 +0000
docs: Add initial documentation
-- 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 wants_strict(tag, default) local opt = tag.attr["scansion:strict"] or default or "yes"; if opt == "no" or opt == "false" or opt == "0" then return false; elseif opt == "yes" or opt == "true" or opt == "1" then return true; end error("Unexpected scansion:strict value: "..opt); 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 not k:match("^scansion:") and v ~= "{scansion:any}" and stanza2.attr[k] ~= v then return false; end end for k, v in pairs(stanza2.attr) do if stanza1.attr[k] ~= "{scansion:any}" and 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 child2: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 stanza2_pos = stanza2_pos + 1; end end return true; end -- Everything in stanza1 should be present in stanza2 local function stanzas_match(stanza1, stanza2) if wants_strict(stanza1, stanza1.attr.xmlns == nil and "no" or "yes") then return stanzas_strict_match(stanza1, stanza2); end 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 not k:match("^scansion:") and v ~= "{scansion:any}" and stanza2.attr[k] ~= v then return false; end end local matched_children = {}; 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 local stanza2_pos = 1; while stanza2_pos <= #stanza2 do if not matched_children[stanza2_pos] then 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 match = stanzas_match(child, child2); elseif type(child) == "string" then -- Text nodes, must be equal, ignoring leading/trailing whitespace match = trim(child) == trim(child2); end if match then break; end end end end if not match then --print(_, "No match for ", child:pretty_print()) return false; end end end return true; end return { stanzas_match = stanzas_match; stanzas_strict_match = stanzas_strict_match; };