19 return true; |
19 return true; |
20 end |
20 end |
21 error("Unexpected scansion:strict value: "..opt); |
21 error("Unexpected scansion:strict value: "..opt); |
22 end |
22 end |
23 |
23 |
24 local function stanzas_strict_match(stanza1, stanza2) |
24 local function is_wildcard(k, v) |
|
25 if v == "{scansion:any}" then |
|
26 return "attr:"..k; |
|
27 end |
|
28 return (v:match("^{scansion:capture:([^}]+)}$")); |
|
29 end |
|
30 |
|
31 -- stanza1 == expected, stanza2 == variable |
|
32 -- captures is an optional table to store captures (captures["foo"] == {scansion:capture:foo}) |
|
33 local function stanzas_strict_match(stanza1, stanza2, captures) |
25 if stanza1.name ~= stanza2.name or stanza1.attr.xmlns ~= stanza2.attr.xmlns then |
34 if stanza1.name ~= stanza2.name or stanza1.attr.xmlns ~= stanza2.attr.xmlns then |
26 return false; |
35 return false; |
27 end |
36 end |
28 |
37 |
29 for k, v in pairs(stanza1.attr) do |
38 for k, v in pairs(stanza1.attr) do |
30 if not k:match("^scansion:") and v ~= "{scansion:any}" and stanza2.attr[k] ~= v then |
39 local wildcard = is_wildcard(k, v); |
|
40 if not k:match("^scansion:") and not wildcard and stanza2.attr[k] ~= v then |
31 return false; |
41 return false; |
|
42 end |
|
43 if wildcard and captures then |
|
44 captures[wildcard] = stanza2.attr[k]; |
32 end |
45 end |
33 end |
46 end |
34 |
47 |
35 for k, v in pairs(stanza2.attr) do |
48 for k, v in pairs(stanza2.attr) do |
36 if stanza1.attr[k] ~= "{scansion:any}" and stanza1.attr[k] ~= v then |
49 local wildcard = is_wildcard(k, stanza1.attr[k]); |
|
50 if not wildcard and stanza1.attr[k] ~= v then |
37 return false; |
51 return false; |
|
52 end |
|
53 if wildcard and captures then |
|
54 captures[wildcard] = v; |
38 end |
55 end |
39 end |
56 end |
40 |
57 |
41 if #stanza1.tags ~= #stanza2.tags then |
58 if #stanza1.tags ~= #stanza2.tags then |
42 return false; |
59 return false; |
54 if type(child) ~= type(child2) then |
71 if type(child) ~= type(child2) then |
55 return false; |
72 return false; |
56 end |
73 end |
57 if type(child) == "table" and child2.name == child.name and child2.attr.xmlns == child.attr.xmlns then |
74 if type(child) == "table" and child2.name == child.name and child2.attr.xmlns == child.attr.xmlns then |
58 -- Strict deep match |
75 -- Strict deep match |
59 match = stanzas_strict_match(child, child2); |
76 match = stanzas_strict_match(child, child2, captures); |
60 elseif type(child) == "string" then -- Text nodes, must be equal, ignoring leading/trailing whitespace |
77 elseif type(child) == "string" then -- Text nodes, must be equal, ignoring leading/trailing whitespace |
61 match = trim(child) == trim(child2); |
78 match = trim(child) == trim(child2); |
62 end |
79 end |
63 if not match then |
80 if not match then |
64 return false; |
81 return false; |
69 return true; |
86 return true; |
70 end |
87 end |
71 |
88 |
72 -- Everything in stanza1 should be present in stanza2 |
89 -- Everything in stanza1 should be present in stanza2 |
73 |
90 |
74 local function stanzas_match(stanza1, stanza2) |
91 local function stanzas_match(stanza1, stanza2, captures) |
75 if wants_strict(stanza1, stanza1.attr.xmlns == nil and "no" or "yes") then |
92 if wants_strict(stanza1, stanza1.attr.xmlns == nil and "no" or "yes") then |
76 return stanzas_strict_match(stanza1, stanza2); |
93 return stanzas_strict_match(stanza1, stanza2, captures); |
77 end |
94 end |
78 if stanza1.name ~= stanza2.name or stanza1.attr.xmlns ~= stanza2.attr.xmlns then |
95 if stanza1.name ~= stanza2.name or stanza1.attr.xmlns ~= stanza2.attr.xmlns then |
79 return false; |
96 return false; |
80 end |
97 end |
81 |
98 |
82 for k, v in pairs(stanza1.attr) do |
99 for k, v in pairs(stanza1.attr) do |
83 if not k:match("^scansion:") and v ~= "{scansion:any}" and stanza2.attr[k] ~= v then |
100 local wildcard = is_wildcard(k, v); |
|
101 if not k:match("^scansion:") and not wildcard and stanza2.attr[k] ~= v then |
84 return false; |
102 return false; |
|
103 end |
|
104 if wildcard and captures then |
|
105 captures[wildcard] = stanza2.attr[k]; |
85 end |
106 end |
86 end |
107 end |
87 |
108 |
88 local matched_children = {}; |
109 local matched_children = {}; |
89 for _, child in ipairs(stanza1) do |
110 for _, child in ipairs(stanza1) do |
96 local child2 = stanza2[stanza2_pos]; |
117 local child2 = stanza2[stanza2_pos]; |
97 stanza2_pos = stanza2_pos + 1; |
118 stanza2_pos = stanza2_pos + 1; |
98 |
119 |
99 if type(child2) == type(child) then |
120 if type(child2) == type(child) then |
100 if type(child) == "table" and child2.name == child.name and child2.attr.xmlns == child.attr.xmlns then |
121 if type(child) == "table" and child2.name == child.name and child2.attr.xmlns == child.attr.xmlns then |
101 match = stanzas_match(child, child2); |
122 match = stanzas_match(child, child2, captures); |
102 elseif type(child) == "string" then -- Text nodes, must be equal, ignoring leading/trailing whitespace |
123 elseif type(child) == "string" then -- Text nodes, must be equal, ignoring leading/trailing whitespace |
103 match = trim(child) == trim(child2); |
124 match = trim(child) == trim(child2); |
104 end |
125 end |
105 if match then |
126 if match then |
106 break; |
127 break; |