canon 0.3.36 → 0.3.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc55555287eb70fb4fc5201029aea89c2af55d8a9df26549d1d17b639a6de5de
|
|
4
|
+
data.tar.gz: 59ac3ba881495f29ee1355eda305da05d03d62a5611aa738404bf029f66d1721
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b2d535b82b40ece128bea902b603b1b43c06c7bf4f40535f651e3b43c24674345de4e0c0767380fd6fc50d2986dc80ecf0e2c1b2d5286a7dd02524d65641920
|
|
7
|
+
data.tar.gz: '09af782d9c914502a54ca9b961448c89502bb20c3cffa93ec9268b7afc72099bcea2dcc2f7f8e36ccbf7fd6a6f8e3320ced5b7edfd67f7d70d0f5f95e7cf8d85'
|
|
@@ -29,9 +29,7 @@ module Canon
|
|
|
29
29
|
# difference exists (strict key_order compares insertion
|
|
30
30
|
# order, which keys == keys preserves). Most object pairs in
|
|
31
31
|
# similar documents hit this and skip the path-building walk.
|
|
32
|
-
|
|
33
|
-
return Comparison::EQUIVALENT
|
|
34
|
-
end
|
|
32
|
+
return Comparison::EQUIVALENT if deep_equal?(obj1, obj2)
|
|
35
33
|
|
|
36
34
|
case obj1
|
|
37
35
|
when Hash
|
|
@@ -68,8 +66,16 @@ module Canon
|
|
|
68
66
|
# Sort keys if order should be ignored (based on match options)
|
|
69
67
|
match_opts = opts[:match_opts]
|
|
70
68
|
if match_opts && match_opts[:key_order] != :strict
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
# Identical key arrays: one sort replaces two (the sorted
|
|
70
|
+
# forms are trivially equal, and the set algebra below
|
|
71
|
+
# degenerates — missing/extra empty, common = the sorted
|
|
72
|
+
# keys themselves).
|
|
73
|
+
if keys1 == keys2
|
|
74
|
+
keys1 = keys2 = keys1.sort_by(&:to_s)
|
|
75
|
+
else
|
|
76
|
+
keys1 = keys1.sort_by(&:to_s)
|
|
77
|
+
keys2 = keys2.sort_by(&:to_s)
|
|
78
|
+
end
|
|
73
79
|
elsif match_opts && match_opts[:key_order] == :strict
|
|
74
80
|
# Strict mode: key order matters
|
|
75
81
|
# Check if keys are in same order
|
|
@@ -85,8 +91,8 @@ module Canon
|
|
|
85
91
|
end
|
|
86
92
|
|
|
87
93
|
# Check for missing keys
|
|
88
|
-
missing_in_second = keys1 - keys2
|
|
89
|
-
missing_in_first = keys2 - keys1
|
|
94
|
+
missing_in_second = keys1 == keys2 ? EMPTY_KEYS : keys1 - keys2
|
|
95
|
+
missing_in_first = keys1 == keys2 ? EMPTY_KEYS : keys2 - keys1
|
|
90
96
|
|
|
91
97
|
missing_in_second.each do |key|
|
|
92
98
|
key_path = path.empty? ? key.to_s : "#{path}.#{key}"
|
|
@@ -102,13 +108,19 @@ module Canon
|
|
|
102
108
|
|
|
103
109
|
has_missing_keys = !missing_in_first.empty? || !missing_in_second.empty?
|
|
104
110
|
|
|
105
|
-
# Compare common keys
|
|
106
|
-
|
|
111
|
+
# Compare common keys. The path string is built lazily: deep-equal
|
|
112
|
+
# pairs (the overwhelming majority in similar documents) answer
|
|
113
|
+
# equivalent without ever needing it.
|
|
114
|
+
common_keys = keys1 == keys2 ? keys1 : keys1 & keys2
|
|
107
115
|
all_equivalent = true
|
|
108
116
|
common_keys.each do |key|
|
|
117
|
+
value1 = hash1[key]
|
|
118
|
+
value2 = hash2[key]
|
|
119
|
+
next if deep_equal?(value1, value2)
|
|
120
|
+
|
|
109
121
|
key_path = path.empty? ? key.to_s : "#{path}.#{key}"
|
|
110
|
-
result = compare_objects(
|
|
111
|
-
|
|
122
|
+
result = compare_objects(value1, value2, opts, differences,
|
|
123
|
+
key_path)
|
|
112
124
|
all_equivalent = false unless result == Comparison::EQUIVALENT
|
|
113
125
|
end
|
|
114
126
|
|
|
@@ -138,6 +150,8 @@ module Canon
|
|
|
138
150
|
all_equivalent = true
|
|
139
151
|
arr1.each_with_index do |elem1, index|
|
|
140
152
|
elem2 = arr2[index]
|
|
153
|
+
next if deep_equal?(elem1, elem2)
|
|
154
|
+
|
|
141
155
|
elem_path = "#{path}[#{index}]"
|
|
142
156
|
result = compare_objects(elem1, elem2, opts, differences,
|
|
143
157
|
elem_path)
|
|
@@ -166,6 +180,16 @@ module Canon
|
|
|
166
180
|
end
|
|
167
181
|
end
|
|
168
182
|
|
|
183
|
+
# Deep-equal with identical key order — compare_objects returns
|
|
184
|
+
# EQUIVALENT without consulting the path for such pairs, which
|
|
185
|
+
# is what makes lazy path construction in the hash/array walks
|
|
186
|
+
# behavior-identical.
|
|
187
|
+
def self.deep_equal?(obj1, obj2)
|
|
188
|
+
obj1 == obj2 && (!obj1.is_a?(Hash) || obj1.keys == obj2.keys)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
EMPTY_KEYS = [].freeze
|
|
192
|
+
|
|
169
193
|
# Add a Ruby object difference
|
|
170
194
|
#
|
|
171
195
|
# @param path [String] Path to the difference
|
|
@@ -75,6 +75,17 @@ module Canon
|
|
|
75
75
|
# Use ElementMatcher for semantic comparison
|
|
76
76
|
def use_element_matcher_comparison(children1, children2, parent_node, comparator,
|
|
77
77
|
opts, child_opts, diff_children, differences)
|
|
78
|
+
# FAST PATH: positionally paired children — compare pairwise
|
|
79
|
+
# with zero MatchResult allocation. Same pairing the matcher
|
|
80
|
+
# would emit; pos1==pos2 so position_changed? never fires.
|
|
81
|
+
if Canon::Xml::ElementMatcher.positionally_paired?(children1,
|
|
82
|
+
children2)
|
|
83
|
+
return compare_positionally_paired(
|
|
84
|
+
children1, children2, comparator, opts, child_opts,
|
|
85
|
+
diff_children, differences
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
78
89
|
# Single-level matching: the comparator itself descends via
|
|
79
90
|
# compare_nodes below, so nested matches would be discarded.
|
|
80
91
|
matches = Canon::Xml::ElementMatcher.new
|
|
@@ -92,6 +103,24 @@ module Canon
|
|
|
92
103
|
opts, child_opts, diff_children, differences)
|
|
93
104
|
end
|
|
94
105
|
|
|
106
|
+
# Pairwise compare when ElementMatcher.positionally_paired?
|
|
107
|
+
# has already proved the pairing. No MatchResult objects.
|
|
108
|
+
def compare_positionally_paired(children1, children2, comparator,
|
|
109
|
+
_opts, child_opts, diff_children,
|
|
110
|
+
differences)
|
|
111
|
+
all_equivalent = true
|
|
112
|
+
i = 0
|
|
113
|
+
while i < children1.length
|
|
114
|
+
result = comparator.compare_nodes(
|
|
115
|
+
children1[i], children2[i],
|
|
116
|
+
child_opts, child_opts, diff_children, differences
|
|
117
|
+
)
|
|
118
|
+
all_equivalent = false unless result == Comparison::EQUIVALENT
|
|
119
|
+
i += 1
|
|
120
|
+
end
|
|
121
|
+
all_equivalent ? Comparison::EQUIVALENT : Comparison::UNEQUAL_ELEMENTS
|
|
122
|
+
end
|
|
123
|
+
|
|
95
124
|
# Process ElementMatcher results
|
|
96
125
|
def process_matches(matches, _children1, _children2, _parent_node, comparator,
|
|
97
126
|
opts, child_opts, diff_children, differences)
|
data/lib/canon/version.rb
CHANGED
|
@@ -26,12 +26,12 @@ module Canon
|
|
|
26
26
|
return false unless Canon::XmlBackend.moxml? &&
|
|
27
27
|
Canon::XmlParsing.moxml_adapter_name == :leptris
|
|
28
28
|
|
|
29
|
-
# Node#digest
|
|
30
|
-
#
|
|
29
|
+
# moxml Node#digest (moxml#173) wraps leptris Node#digest;
|
|
30
|
+
# feature-detect on a throwaway document.
|
|
31
31
|
doc = Canon::XmlParsing.moxml_context.parse("<r/>", readonly: true,
|
|
32
32
|
strict: false)
|
|
33
33
|
root = doc.root
|
|
34
|
-
digestable = !root.
|
|
34
|
+
digestable = !root.digest(drop_ws_text: true).nil?
|
|
35
35
|
doc.free
|
|
36
36
|
digestable
|
|
37
37
|
rescue StandardError
|
|
@@ -85,7 +85,13 @@ module Canon
|
|
|
85
85
|
when Moxml::Text then child.content.strip.empty? ? nil : child.to_s
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
|
-
|
|
88
|
+
digest = root.digest(drop_ws_text: true)
|
|
89
|
+
# nil digest means the adapter has no Merkle support — never
|
|
90
|
+
# treat two nils as equal (that would false-positive under
|
|
91
|
+
# Opal / non-leptris backends where every tree digests nil).
|
|
92
|
+
return nil if digest.nil?
|
|
93
|
+
|
|
94
|
+
[digest, skeleton]
|
|
89
95
|
ensure
|
|
90
96
|
doc&.free
|
|
91
97
|
end
|
|
@@ -54,6 +54,7 @@ module Canon
|
|
|
54
54
|
class ElementMatcher
|
|
55
55
|
# Default attributes used to identify elements
|
|
56
56
|
DEFAULT_IDENTITY_ATTRS = %w[id ref name key class].freeze
|
|
57
|
+
EMPTY_PATH = [].freeze
|
|
57
58
|
|
|
58
59
|
# Represents the result of matching an element across two DOM trees
|
|
59
60
|
#
|
|
@@ -145,6 +146,21 @@ module Canon
|
|
|
145
146
|
@matches
|
|
146
147
|
end
|
|
147
148
|
|
|
149
|
+
# Public: true when both lists pair positionally (equal length,
|
|
150
|
+
# equal name/ns/identity at every index). ChildComparison uses
|
|
151
|
+
# this to skip MatchResult allocation on the common same-structure
|
|
152
|
+
# path.
|
|
153
|
+
def self.positionally_paired?(elems1, elems2)
|
|
154
|
+
return false if elems1.length != elems2.length || elems1.empty?
|
|
155
|
+
|
|
156
|
+
new.paired_positionally?(elems1, elems2)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Instance form of .positionally_paired?
|
|
160
|
+
def paired_positionally?(elems1, elems2)
|
|
161
|
+
pairwise_corresponding?(elems1, elems2)
|
|
162
|
+
end
|
|
163
|
+
|
|
148
164
|
private
|
|
149
165
|
|
|
150
166
|
# Match children recursively
|
|
@@ -295,7 +311,11 @@ module Canon
|
|
|
295
311
|
def record_positional_matches(elems1, elems2, path, recursive:)
|
|
296
312
|
elems1.each_index do |i|
|
|
297
313
|
elem1 = elems1[i]
|
|
298
|
-
|
|
314
|
+
# match_children_only never reads path; keep a shared empty
|
|
315
|
+
# for the non-recursive entry so we do not allocate one
|
|
316
|
+
# [name] array per element. Recursive match_trees still
|
|
317
|
+
# builds real paths for its public API.
|
|
318
|
+
elem_path = recursive || !path.empty? ? element_path(path, elem1) : EMPTY_PATH
|
|
299
319
|
@matches << MatchResult.new(
|
|
300
320
|
status: :matched,
|
|
301
321
|
elem1: elem1,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: canon
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.38
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|