canon 0.3.14 → 0.3.15
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 +4 -4
- data/lib/canon/comparison/match_options.rb +22 -6
- data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +8 -0
- data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +3 -0
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/node.rb +9 -6
- data/lib/canon/xml/nodes/element_node.rb +12 -8
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d48fa5b4488a85c95b613bcab72881a5e286fdb9859da76aafc78553ff506419
|
|
4
|
+
data.tar.gz: f0bd3b76b81a0181dc4ae801bed2288828f8d8bc85a6414a98c993d10d770b7a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b32e0477c8932748179074f076478747d3eb1bce3892e6d7fd8ecae3e1dc7e686fa51907cdbd6444ad87267eb9521206b4caaa6f2d85a93b36f5cc2b3758e4a
|
|
7
|
+
data.tar.gz: 11c89d97b5144a557746e7d84e9128db443e8951fc40381486da826935f012ccc15249de122232a0ca95b32b5c32ebffe64a2415b6db8f8b9790af2e1eda1b10
|
|
@@ -70,6 +70,10 @@ module Canon
|
|
|
70
70
|
when :strict
|
|
71
71
|
text1 == text2
|
|
72
72
|
when :normalize
|
|
73
|
+
# Identical strings normalize identically — skip the two
|
|
74
|
+
# gsub+strip chains.
|
|
75
|
+
return true if text1 == text2
|
|
76
|
+
|
|
73
77
|
if whitespace_type == :normalize
|
|
74
78
|
normalize_text(text1) == normalize_text(text2)
|
|
75
79
|
else
|
|
@@ -92,22 +96,34 @@ module Canon
|
|
|
92
96
|
.strip # Remove leading/trailing whitespace
|
|
93
97
|
end
|
|
94
98
|
|
|
95
|
-
# Normalize text preserving Unicode whitespace type distinctions.
|
|
96
99
|
# Fast form of `normalize_text(text).empty?`, called per text
|
|
97
100
|
# node by node_excluded?. Pure-ASCII whitespace (the common
|
|
98
101
|
# case — pretty-print indentation) matches a plain class with
|
|
99
102
|
# no intermediate strings and no \p{} property (Opal's JS
|
|
100
|
-
# regexes do not honor \p{Space});
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
+
# regexes do not honor \p{Space}); a pure-ASCII miss is
|
|
104
|
+
# conclusively non-whitespace; only non-ASCII text falls back
|
|
105
|
+
# to normalize_text's exact Unicode semantics. NUL is included
|
|
106
|
+
# because String#strip strips nulls too.
|
|
103
107
|
ASCII_WHITESPACE_ONLY = /\A[ \t\r\n\v\f\x00]*\z/
|
|
108
|
+
ASCII_ONLY = /\A[\x00-\x7f]*\z/
|
|
104
109
|
|
|
105
110
|
def whitespace_only?(text)
|
|
106
111
|
text = text.to_s
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
return true if text.empty?
|
|
113
|
+
return true if text.match?(ASCII_WHITESPACE_ONLY)
|
|
114
|
+
# A pure-ASCII string that failed the class contains an ASCII
|
|
115
|
+
# non-whitespace character, which survives both the collapse
|
|
116
|
+
# and the strip — conclusively not whitespace-only, with no
|
|
117
|
+
# intermediate strings. Only non-ASCII text (NBSP, U+3000,
|
|
118
|
+
# ...) needs normalize_text's exact Unicode semantics — and
|
|
119
|
+
# no \p{} classes appear in the fast paths, which Opal's JS
|
|
120
|
+
# regexes would not honor anyway.
|
|
121
|
+
return false if text.match?(ASCII_ONLY)
|
|
122
|
+
|
|
123
|
+
normalize_text(text).empty?
|
|
109
124
|
end
|
|
110
125
|
|
|
126
|
+
# Normalize text preserving Unicode whitespace type distinctions.
|
|
111
127
|
def normalize_text_preserving_type(text)
|
|
112
128
|
return "" if text.nil?
|
|
113
129
|
|
|
@@ -26,6 +26,14 @@ module Canon
|
|
|
26
26
|
match_opts = opts[:match_opts]
|
|
27
27
|
attribute_order_behavior = match_opts[:attribute_order] || :strict
|
|
28
28
|
|
|
29
|
+
# FAST PATH: identical filtered attributes in identical key
|
|
30
|
+
# order — exact equality implies behavioral equality for every
|
|
31
|
+
# value behavior, and no order difference exists. Most element
|
|
32
|
+
# pairs hit this and skip the sort/hash/value work entirely.
|
|
33
|
+
if attrs1 == attrs2 && attrs1.keys == attrs2.keys
|
|
34
|
+
return Comparison::EQUIVALENT
|
|
35
|
+
end
|
|
36
|
+
|
|
29
37
|
# Check attribute order if not ignored
|
|
30
38
|
keys1 = attrs1.keys.map(&:to_s)
|
|
31
39
|
keys2 = attrs2.keys.map(&:to_s)
|
|
@@ -17,6 +17,9 @@ module Canon
|
|
|
17
17
|
ns_decls1 = extract_declarations(node1)
|
|
18
18
|
ns_decls2 = extract_declarations(node2)
|
|
19
19
|
|
|
20
|
+
# Most element pairs declare nothing — skip the set algebra.
|
|
21
|
+
return Comparison::EQUIVALENT if ns_decls1.empty? && ns_decls2.empty?
|
|
22
|
+
|
|
20
23
|
# Find missing, extra, and changed namespace declarations
|
|
21
24
|
missing = ns_decls1.keys - ns_decls2.keys # In node1 but not node2
|
|
22
25
|
extra = ns_decls2.keys - ns_decls1.keys # In node2 but not node1
|
data/lib/canon/version.rb
CHANGED
data/lib/canon/xml/node.rb
CHANGED
|
@@ -4,6 +4,13 @@ module Canon
|
|
|
4
4
|
module Xml
|
|
5
5
|
# Base class for all XPath data model nodes
|
|
6
6
|
class Node
|
|
7
|
+
# Shared by every childless node: leaves (text, attributes,
|
|
8
|
+
# namespaces, comments, PIs) and empty elements read `children`
|
|
9
|
+
# without materializing a private array. Mutating the returned
|
|
10
|
+
# array is a bug — writers go through add_child/children=, which
|
|
11
|
+
# install a private array first.
|
|
12
|
+
EMPTY_CHILDREN = [].freeze
|
|
13
|
+
|
|
7
14
|
attr_reader :parent
|
|
8
15
|
|
|
9
16
|
def initialize
|
|
@@ -12,12 +19,8 @@ module Canon
|
|
|
12
19
|
@in_node_set = true
|
|
13
20
|
end
|
|
14
21
|
|
|
15
|
-
# Leaf nodes (text, attributes, namespaces, comments, PIs) never
|
|
16
|
-
# gain children; allocating the array eagerly cost one Array per
|
|
17
|
-
# node — the single largest retained-allocation source in a built
|
|
18
|
-
# tree — so it materializes on first add or read.
|
|
19
22
|
def children
|
|
20
|
-
@children
|
|
23
|
+
@children || EMPTY_CHILDREN
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
def children=(new_children)
|
|
@@ -26,7 +29,7 @@ module Canon
|
|
|
26
29
|
|
|
27
30
|
def add_child(child)
|
|
28
31
|
child.parent = self
|
|
29
|
-
children << child
|
|
32
|
+
(@children ||= []) << child
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
def in_node_set?
|
|
@@ -15,12 +15,16 @@ module Canon
|
|
|
15
15
|
@attribute_nodes = nil
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
# Lazy:
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
18
|
+
# Lazy: attribute-free elements (common in real documents) share
|
|
19
|
+
# one frozen empty array on read instead of materializing one
|
|
20
|
+
# per element; writers install a private array first. Inherited
|
|
21
|
+
# namespace nodes arrive as one shared frozen array from
|
|
22
|
+
# TreeBuilder#attach_namespace_scope.
|
|
23
|
+
EMPTY_ATTRIBUTE_NODES = [].freeze
|
|
24
|
+
EMPTY_NAMESPACE_NODES = [].freeze
|
|
25
|
+
|
|
22
26
|
def namespace_nodes
|
|
23
|
-
@namespace_nodes
|
|
27
|
+
@namespace_nodes || EMPTY_NAMESPACE_NODES
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def namespace_nodes=(nodes)
|
|
@@ -28,7 +32,7 @@ module Canon
|
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def attribute_nodes
|
|
31
|
-
@attribute_nodes
|
|
35
|
+
@attribute_nodes || EMPTY_ATTRIBUTE_NODES
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
def node_type
|
|
@@ -41,12 +45,12 @@ module Canon
|
|
|
41
45
|
|
|
42
46
|
def add_namespace(namespace_node)
|
|
43
47
|
namespace_node.parent = self
|
|
44
|
-
namespace_nodes << namespace_node
|
|
48
|
+
(@namespace_nodes ||= []) << namespace_node
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
def add_attribute(attribute_node)
|
|
48
52
|
attribute_node.parent = self
|
|
49
|
-
attribute_nodes << attribute_node
|
|
53
|
+
(@attribute_nodes ||= []) << attribute_node
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
# Get namespace nodes in sorted order (lexicographically by local name)
|