canon 0.3.66 → 0.3.68
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/README.adoc +3 -4
- data/docs/features/match-options/index.adoc +33 -0
- data/docs/guides/choosing-configuration.adoc +23 -0
- data/lib/canon/comparison/comparison_result.rb +17 -0
- data/lib/canon/comparison/dimensions/registry.rb +4 -0
- data/lib/canon/comparison/match_options/xml_resolver.rb +7 -1
- data/lib/canon/comparison/xml_comparator/child_comparison.rb +19 -1
- data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +37 -0
- data/lib/canon/version.rb +1 -1
- 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: 590320aa865898c6342d5c96566e6313e678a6d6f31a4d1e9463a50c9f434052
|
|
4
|
+
data.tar.gz: 6daacd998c4d52ae54096bc91329cd48686d33b7942970313891f8d69edf2141
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5d9fe69844ee374debb42557852bdcfd3d8cd3cadb30d15f01c2e2b932ad7412339107996bb2c88f04669951b88752856c56b812d2fd4ecbd3f993390b682efe
|
|
7
|
+
data.tar.gz: bfee82458261d1b20f78d7745da3cf620ce9beff2a993c01edcf6ff1d22f2538e8ddf7c45f21237e64f41a0218d0a25432dffcf0ea6cbcff3443cd416d4a677b
|
data/README.adoc
CHANGED
|
@@ -21,10 +21,9 @@ Key features:
|
|
|
21
21
|
installed — Nokogiri otherwise and for HTML; both byte-compatible, engine
|
|
22
22
|
parity is spec-gated
|
|
23
23
|
** JSON on https://github.com/leptris/yeptris[libyeptris]' strict JSON surface
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Psych otherwise
|
|
24
|
+
(yeptris is a required gem — platform gems, zero compilation)
|
|
25
|
+
** YAML on yeptris (default — byte-parity with Psych, gated by the dump
|
|
26
|
+
corpus in the suite); Psych via `CANON_YAML_BACKEND=psych`
|
|
28
27
|
|
|
29
28
|
== When to use formatting vs comparison
|
|
30
29
|
|
|
@@ -486,6 +486,33 @@ Canon::Comparison.equivalent?(html1, html2,
|
|
|
486
486
|
|
|
487
487
|
`:strict`:: Namespace URIs must match (default and only supported behavior)
|
|
488
488
|
|
|
489
|
+
=== namespace_prefix
|
|
490
|
+
|
|
491
|
+
**Applies to**: XML only
|
|
492
|
+
|
|
493
|
+
**Purpose**: Controls whether namespace *prefix spelling* is significant.
|
|
494
|
+
Per XML Namespaces, the prefix is a binding convenience — `xmlns:ns="http://x.example"`
|
|
495
|
+
and `xmlns:m="http://x.example"` bind to the same URI, so `<ns:e>` and `<m:e>` are the
|
|
496
|
+
same `{uri}local` name. Documents that round-trip through XML tooling (Office,
|
|
497
|
+
serializers) often churn prefixes; with `:ignore`, such documents compare
|
|
498
|
+
equivalent as long as the *set of declared URIs* matches. Namespace
|
|
499
|
+
declarations that resolve differently still report differences.
|
|
500
|
+
|
|
501
|
+
**Behaviors**:
|
|
502
|
+
|
|
503
|
+
`:significant`:: Prefix spelling matters — `xmlns:ns` and `xmlns:m` are
|
|
504
|
+
different declarations (default; `strict` and `rendered` profiles)
|
|
505
|
+
|
|
506
|
+
`:ignore`:: Only the set of declared namespace URIs matters
|
|
507
|
+
(`spec_friendly` and `content_only` profiles)
|
|
508
|
+
|
|
509
|
+
[source,ruby]
|
|
510
|
+
----
|
|
511
|
+
Canon::Comparison.equivalent?(xml1, xml2,
|
|
512
|
+
format: :xml,
|
|
513
|
+
match: { namespace_prefix: :ignore })
|
|
514
|
+
----
|
|
515
|
+
|
|
489
516
|
**Note**: This dimension is always `:strict` for XML. Namespace prefixes are not significant - only the namespace URI matters. Elements with different prefixes but the same namespace URI are considered equivalent.
|
|
490
517
|
|
|
491
518
|
.Namespace URI comparison
|
|
@@ -601,6 +628,12 @@ Each format has sensible defaults based on typical usage:
|
|
|
601
628
|
|—
|
|
602
629
|
|`:strict`
|
|
603
630
|
|
|
631
|
+
|`namespace_prefix`
|
|
632
|
+
|`:significant`
|
|
633
|
+
|`:significant`
|
|
634
|
+
|—
|
|
635
|
+
|—
|
|
636
|
+
|
|
604
637
|
|`namespace_uri`
|
|
605
638
|
|`:strict`
|
|
606
639
|
|—
|
|
@@ -9,6 +9,29 @@ nav_order: 1
|
|
|
9
9
|
|
|
10
10
|
Canon's 4-layer architecture provides powerful flexibility, but this can be overwhelming. This guide helps you choose the right configuration for your use case through decision trees, use case scenarios, and practical recommendations.
|
|
11
11
|
|
|
12
|
+
== XML namespace prefixes
|
|
13
|
+
|
|
14
|
+
If your documents round-trip through XML tooling (Office files, serializers,
|
|
15
|
+
XSLT processors), namespace *prefixes* often churn while the resolved names
|
|
16
|
+
stay identical (`xmlns:ns="http://x.example"` vs `xmlns:m="http://x.example"`). Canon's
|
|
17
|
+
default treats prefix spelling as significant; choose `:ignore` when the
|
|
18
|
+
binding, not the spelling, is what matters:
|
|
19
|
+
|
|
20
|
+
[source,ruby]
|
|
21
|
+
----
|
|
22
|
+
# Per comparison
|
|
23
|
+
Canon::Comparison.equivalent?(a, b,
|
|
24
|
+
format: :xml, match: { namespace_prefix: :ignore })
|
|
25
|
+
|
|
26
|
+
# Or per suite via config
|
|
27
|
+
Canon::Config.instance.xml.match.options[:namespace_prefix] = :ignore
|
|
28
|
+
----
|
|
29
|
+
|
|
30
|
+
The `spec_friendly` and `content_only` profiles already set
|
|
31
|
+
`namespace_prefix: :ignore`. Canonicalization output (`Canon.format`,
|
|
32
|
+
C14N) never rewrites prefixes regardless of this option — canonical bytes
|
|
33
|
+
stay spec-conformant.
|
|
34
|
+
|
|
12
35
|
== Quick Decision Tree
|
|
13
36
|
|
|
14
37
|
[mermaid]
|
|
@@ -56,6 +56,23 @@ parse_errors_expected: nil, parse_errors_received: nil)
|
|
|
56
56
|
@original_strings || preprocessed_strings
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# The lazy display-string builder is a Proc, which Marshal (and
|
|
60
|
+
# every serializing test-distribution tool) cannot dump.
|
|
61
|
+
# Materialize first — the restored copy carries plain strings
|
|
62
|
+
# and round-trips exactly like the pre-lazy results.
|
|
63
|
+
def marshal_dump
|
|
64
|
+
preprocessed_strings
|
|
65
|
+
[@differences, @preprocessed_strings, @original_strings,
|
|
66
|
+
@format, @html_version, @match_options, @algorithm,
|
|
67
|
+
@parse_errors_expected, @parse_errors_received]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def marshal_load(data)
|
|
71
|
+
@differences, @preprocessed_strings, @original_strings,
|
|
72
|
+
@format, @html_version, @match_options, @algorithm,
|
|
73
|
+
@parse_errors_expected, @parse_errors_received = data
|
|
74
|
+
end
|
|
75
|
+
|
|
59
76
|
# Whether either side reported parse errors. Used by the diff
|
|
60
77
|
# formatter to decide whether to render the parse-error banner.
|
|
61
78
|
#
|
|
@@ -23,6 +23,7 @@ module Canon
|
|
|
23
23
|
element_position: :ignore,
|
|
24
24
|
comments: :ignore,
|
|
25
25
|
whitespace_type: :strict,
|
|
26
|
+
namespace_prefix: :significant,
|
|
26
27
|
},
|
|
27
28
|
xml: {
|
|
28
29
|
preprocessing: :none,
|
|
@@ -34,6 +35,7 @@ module Canon
|
|
|
34
35
|
element_position: :strict,
|
|
35
36
|
comments: :strict,
|
|
36
37
|
whitespace_type: :strict,
|
|
38
|
+
namespace_prefix: :significant,
|
|
37
39
|
},
|
|
38
40
|
}.freeze
|
|
39
41
|
|
|
@@ -49,6 +51,7 @@ module Canon
|
|
|
49
51
|
element_position: :strict,
|
|
50
52
|
comments: :strict,
|
|
51
53
|
whitespace_type: :strict,
|
|
54
|
+
namespace_prefix: :significant,
|
|
52
55
|
},
|
|
53
56
|
|
|
54
57
|
rendered: {
|
|
@@ -56,11 +59,12 @@ module Canon
|
|
|
56
59
|
text_content: :normalize,
|
|
57
60
|
structural_whitespace: :normalize,
|
|
58
61
|
attribute_presence: :strict,
|
|
59
|
-
attribute_order: :
|
|
62
|
+
attribute_order: :ignore,
|
|
60
63
|
attribute_values: :strict,
|
|
61
64
|
element_position: :strict,
|
|
62
65
|
comments: :ignore,
|
|
63
66
|
whitespace_type: :strict,
|
|
67
|
+
namespace_prefix: :significant,
|
|
64
68
|
},
|
|
65
69
|
|
|
66
70
|
html4: {
|
|
@@ -97,6 +101,7 @@ module Canon
|
|
|
97
101
|
element_position: :ignore,
|
|
98
102
|
comments: :ignore,
|
|
99
103
|
whitespace_type: :strict,
|
|
104
|
+
namespace_prefix: :ignore,
|
|
100
105
|
},
|
|
101
106
|
|
|
102
107
|
content_only: {
|
|
@@ -109,6 +114,7 @@ module Canon
|
|
|
109
114
|
element_position: :ignore,
|
|
110
115
|
comments: :ignore,
|
|
111
116
|
whitespace_type: :strict,
|
|
117
|
+
namespace_prefix: :ignore,
|
|
112
118
|
},
|
|
113
119
|
}.freeze
|
|
114
120
|
|
|
@@ -122,6 +122,23 @@ module Canon
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
# Process ElementMatcher results
|
|
125
|
+
# Position differences carry element identity — a bare
|
|
126
|
+
# "position 0 vs position 1" next to identical-looking
|
|
127
|
+
# Expected/Actual payloads reads as a canon false positive
|
|
128
|
+
# when the real change is sibling order (e.g. id="1" and
|
|
129
|
+
# id="2" swapped).
|
|
130
|
+
def position_reason(elem, pos)
|
|
131
|
+
return "position #{pos}" if elem.nil?
|
|
132
|
+
|
|
133
|
+
label = elem.name.to_s
|
|
134
|
+
attrs = Canon::Diff::NodeSerializer.extract_attributes(elem)
|
|
135
|
+
return "position #{pos}" if attrs.nil? || attrs.empty?
|
|
136
|
+
|
|
137
|
+
id_like = %w[id href name].filter_map { |k| attrs[k] && "#{k}=#{attrs[k]}" }
|
|
138
|
+
id_like << attrs.first.then { |k, v| "#{k}=#{v}" } if id_like.empty?
|
|
139
|
+
"#{label}[#{id_like.first}] at position #{pos}"
|
|
140
|
+
end
|
|
141
|
+
|
|
125
142
|
def process_matches(matches, _children1, _children2, _parent_node, comparator,
|
|
126
143
|
opts, child_opts, diff_children, differences)
|
|
127
144
|
all_equivalent = true
|
|
@@ -137,7 +154,8 @@ module Canon
|
|
|
137
154
|
# Only create DiffNode if element_position is not :ignore
|
|
138
155
|
if position_behavior != :ignore
|
|
139
156
|
comparator.add_difference(match.elem1, match.elem2,
|
|
140
|
-
|
|
157
|
+
position_reason(match.elem1, match.pos1),
|
|
158
|
+
position_reason(match.elem2, match.pos2),
|
|
141
159
|
:element_position, opts, differences)
|
|
142
160
|
all_equivalent = false if position_behavior == :strict
|
|
143
161
|
end
|
|
@@ -20,6 +20,22 @@ module Canon
|
|
|
20
20
|
# Most element pairs declare nothing — skip the set algebra.
|
|
21
21
|
return Comparison::EQUIVALENT if ns_decls1.empty? && ns_decls2.empty?
|
|
22
22
|
|
|
23
|
+
# namespace_prefix: :ignore — prefix spelling is cosmetic;
|
|
24
|
+
# what resolves differently is the set of declared URIs
|
|
25
|
+
# (XML Namespaces: the prefix is a binding convenience, the
|
|
26
|
+
# {uri}local name is the name).
|
|
27
|
+
if opts[:match_opts][:namespace_prefix] == :ignore
|
|
28
|
+
uris1 = ns_decls1.values.uniq.sort
|
|
29
|
+
uris2 = ns_decls2.values.uniq.sort
|
|
30
|
+
return Comparison::EQUIVALENT if uris1 == uris2
|
|
31
|
+
|
|
32
|
+
missing = (ns_decls1.values.uniq - ns_decls2.values).sort
|
|
33
|
+
extra = (ns_decls2.values.uniq - ns_decls1.values).sort
|
|
34
|
+
add_namespace_uri_difference(node1, node2, missing, extra,
|
|
35
|
+
opts, differences)
|
|
36
|
+
return Comparison::UNEQUAL_ATTRIBUTES
|
|
37
|
+
end
|
|
38
|
+
|
|
23
39
|
# Find missing, extra, and changed namespace declarations
|
|
24
40
|
missing = ns_decls1.keys - ns_decls2.keys # In node1 but not node2
|
|
25
41
|
extra = ns_decls2.keys - ns_decls1.keys # In node2 but not node1
|
|
@@ -125,6 +141,27 @@ module Canon
|
|
|
125
141
|
Canon::Xml::NamespaceHelper.namespace_declaration?(attr_name)
|
|
126
142
|
end
|
|
127
143
|
|
|
144
|
+
# URI-flavored difference for namespace_prefix: :ignore —
|
|
145
|
+
# reports the URIs that resolve differently, not the prefix
|
|
146
|
+
# spellings.
|
|
147
|
+
def self.add_namespace_uri_difference(node1, node2, missing, extra,
|
|
148
|
+
opts, differences)
|
|
149
|
+
reasons = []
|
|
150
|
+
reasons << "removed URIs: #{missing.join(', ')}" if missing.any?
|
|
151
|
+
reasons << "added URIs: #{extra.join(', ')}" if extra.any?
|
|
152
|
+
|
|
153
|
+
diff_node = Canon::Comparison::DiffNodeBuilder.build(
|
|
154
|
+
node1: node1,
|
|
155
|
+
node2: node2,
|
|
156
|
+
diff1: Comparison::UNEQUAL_ATTRIBUTES,
|
|
157
|
+
diff2: Comparison::UNEQUAL_ATTRIBUTES,
|
|
158
|
+
dimension: :namespace_declarations,
|
|
159
|
+
**opts,
|
|
160
|
+
)
|
|
161
|
+
diff_node.reason = reasons.join("; ") if diff_node && reasons.any?
|
|
162
|
+
differences << diff_node if diff_node
|
|
163
|
+
end
|
|
164
|
+
|
|
128
165
|
# Add a namespace declaration difference
|
|
129
166
|
#
|
|
130
167
|
# @param node1 [Object] First node
|
data/lib/canon/version.rb
CHANGED