canon 0.3.112 → 0.3.113
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/docs/_references/comparison-theory.adoc +289 -0
- data/lib/canon/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c91f395c09165032ecc68e5a79f01c0620a66571411add6b4e083d68b9367d10
|
|
4
|
+
data.tar.gz: 1bbabc0e6112d88e3743fb20f0cb22f765b1a6d2fa8fd655f31b1bb2b368ce71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a94fd8fc2dc5493976494b2a0a3ab9031537d93557005c1a7c51405d4b3a64ab5a07806ac43831a41bddc194db61b231576481e151d994001287ef215c9165d8
|
|
7
|
+
data.tar.gz: f83ba6f4dc985daa93f062f4bffecd2adaf5a47c77473106263f810112e54e65a6da39163d47c06eb057832abebb7a53f6c0e31ec98ed9b445d1de31574e3ddc
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
= Canon comparison theory: dimensions, profiles, and the two algorithms
|
|
2
|
+
:toc:
|
|
3
|
+
|
|
4
|
+
This document is the specification of canon's comparison semantics.
|
|
5
|
+
It is REVIEWED AGAINST THE IMPLEMENTATION by
|
|
6
|
+
`spec/canon/docs/comparison_theory_spec.rb`: every dimension,
|
|
7
|
+
behavior, and profile row cited here must exist in the dimension
|
|
8
|
+
registry and the profile resolvers, and everything the registry
|
|
9
|
+
defines must appear here (docs match shipped behavior — the W6.3
|
|
10
|
+
standing rule).
|
|
11
|
+
|
|
12
|
+
== 1. The four-layer pipeline
|
|
13
|
+
|
|
14
|
+
Comparison flows through four independent layers. Each layer's
|
|
15
|
+
output is the next layer's input; no layer sees another layer's
|
|
16
|
+
options.
|
|
17
|
+
|
|
18
|
+
[cols="1,3,2"]
|
|
19
|
+
|===
|
|
20
|
+
|Layer |Decides |Resolved from
|
|
21
|
+
|
|
22
|
+
|1. Preprocessing
|
|
23
|
+
|What byte form enters the parse (`:none`, `:c14n`, `:normalize`,
|
|
24
|
+
`:format`, `:rendered`)
|
|
25
|
+
|The profile's `preprocessing` key
|
|
26
|
+
|
|
27
|
+
|2. Algorithm
|
|
28
|
+
|The comparison engine: `:dom` (position-based recursive descent) or
|
|
29
|
+
`:semantic` (signature matching producing insert/update/delete/move)
|
|
30
|
+
|`diff_algorithm:` (default `:dom`); the two are distinct by design
|
|
31
|
+
and are never merged
|
|
32
|
+
|
|
33
|
+
|3. Matching
|
|
34
|
+
|How nodes are deemed equal: one behavior per dimension
|
|
35
|
+
|The profile (or explicit per-dimension options) resolved through
|
|
36
|
+
the format's resolver
|
|
37
|
+
|
|
38
|
+
|4. Presentation
|
|
39
|
+
|How results are spelled (`by_line`, `by_object`, normative-only
|
|
40
|
+
filtering)
|
|
41
|
+
|`show_diffs:` and the formatter choice
|
|
42
|
+
|===
|
|
43
|
+
|
|
44
|
+
== 2. Dimensions
|
|
45
|
+
|
|
46
|
+
A *dimension* is one axis along which two nodes may differ. The
|
|
47
|
+
dimension registry (`Canon::Comparison::Dimensions::Registry`)
|
|
48
|
+
defines, per format, each dimension's name and its *valid
|
|
49
|
+
behaviors* — closed sets; an option outside the set raises at
|
|
50
|
+
resolution. HTML aliases (`html`, `html4`, `html5`) resolve to the
|
|
51
|
+
XML dimension set.
|
|
52
|
+
|
|
53
|
+
=== 2.1 XML and HTML (the `:xml` set)
|
|
54
|
+
|
|
55
|
+
[cols="1,2,3"]
|
|
56
|
+
|===
|
|
57
|
+
|Dimension |Behaviors |Semantics
|
|
58
|
+
|
|
59
|
+
|`text_content`
|
|
60
|
+
|`strict`, `normalize`, `ignore`
|
|
61
|
+
|`strict`: character-exact text comparison. `normalize`:
|
|
62
|
+
whitespace runs collapse to a single space and leading/trailing
|
|
63
|
+
whitespace strips before comparison. `ignore`: text content never
|
|
64
|
+
differs.
|
|
65
|
+
|
|
66
|
+
|`structural_whitespace`
|
|
67
|
+
|`strict`, `normalize`, `ignore`
|
|
68
|
+
|Whitespace-only text nodes between elements. `strict`: they are
|
|
69
|
+
content (normative: a `strict`-only dimension — a non-strict
|
|
70
|
+
behavior can never make documents inequivalent on its own).
|
|
71
|
+
`normalize`: whitespace-only nodes are equivalent regardless of
|
|
72
|
+
shape. `ignore`: whitespace-only nodes are dropped before the walk.
|
|
73
|
+
|
|
74
|
+
|`attribute_presence`
|
|
75
|
+
|`strict`, `ignore`
|
|
76
|
+
|Whether an attribute exists. `ignore` drops all attributes before
|
|
77
|
+
comparison.
|
|
78
|
+
|
|
79
|
+
|`attribute_order`
|
|
80
|
+
|`strict`, `ignore`
|
|
81
|
+
|`strict`: attribute order is significant. `ignore`: attributes
|
|
82
|
+
compare as a set.
|
|
83
|
+
|
|
84
|
+
|`attribute_values`
|
|
85
|
+
|`strict`, `strip`, `compact`, `normalize`, `ignore`
|
|
86
|
+
|`strict`: character-exact values. `strip`: leading/trailing
|
|
87
|
+
whitespace strips. `compact`: internal whitespace runs collapse
|
|
88
|
+
(verify: the value is compared after whitespace compaction).
|
|
89
|
+
`normalize`: both strip and collapse. `ignore`: values compare
|
|
90
|
+
equal regardless.
|
|
91
|
+
|
|
92
|
+
|`element_position`
|
|
93
|
+
|`strict`, `ignore`
|
|
94
|
+
|`strict`: child order is significant. `ignore`: children match by
|
|
95
|
+
identity (the ElementMatcher's id-attribute matching), not position.
|
|
96
|
+
|
|
97
|
+
|`comments`
|
|
98
|
+
|`strict`, `ignore`
|
|
99
|
+
|Comment nodes: content-compared or dropped.
|
|
100
|
+
|
|
101
|
+
|`namespace_prefix`
|
|
102
|
+
|`significant`, `ignore`
|
|
103
|
+
|`significant`: the written prefix is part of the node's identity.
|
|
104
|
+
`ignore`: only the namespace URI matters; the prefix is spelling.
|
|
105
|
+
|===
|
|
106
|
+
|
|
107
|
+
=== 2.2 JSON (the `:json` set)
|
|
108
|
+
|
|
109
|
+
[cols="1,2,3"]
|
|
110
|
+
|===
|
|
111
|
+
|Dimension |Behaviors |Semantics
|
|
112
|
+
|
|
113
|
+
|`text_content`
|
|
114
|
+
|`strict`, `normalize`, `ignore`
|
|
115
|
+
|String values, `strict` character-exact.
|
|
116
|
+
|
|
117
|
+
|`structural_whitespace`
|
|
118
|
+
|`strict`, `normalize`, `ignore`
|
|
119
|
+
|Whitespace between tokens outside strings. `strict`-only
|
|
120
|
+
normative.
|
|
121
|
+
|
|
122
|
+
|`key_order`
|
|
123
|
+
|`strict`, `ignore`
|
|
124
|
+
|`ignore`: objects compare as unordered maps (the canonical lane
|
|
125
|
+
sorts keys).
|
|
126
|
+
|===
|
|
127
|
+
|
|
128
|
+
=== 2.3 YAML (the `:yaml` set)
|
|
129
|
+
|
|
130
|
+
The `:json` set plus:
|
|
131
|
+
|
|
132
|
+
[cols="1,2,3"]
|
|
133
|
+
|===
|
|
134
|
+
|Dimension |Behaviors |Semantics
|
|
135
|
+
|
|
136
|
+
|`comments`
|
|
137
|
+
|`strict`, `ignore`
|
|
138
|
+
|YAML comments: compared or dropped before parse.
|
|
139
|
+
|===
|
|
140
|
+
|
|
141
|
+
== 3. Profiles and the profile algebra
|
|
142
|
+
|
|
143
|
+
A *profile* is a named total assignment: one behavior per dimension
|
|
144
|
+
of the format, plus a preprocessing layer. Profiles resolve through
|
|
145
|
+
the format's resolver; explicit per-dimension options override a
|
|
146
|
+
profile's entry for that dimension alone. XML/HTML ship FOUR
|
|
147
|
+
profiles; JSON and YAML ship THREE (`:rendered` does not exist for
|
|
148
|
+
them — the resolvers reject it). The resolved assignments below are
|
|
149
|
+
asserted by the review spec:
|
|
150
|
+
|
|
151
|
+
[cols="1,1,1"]
|
|
152
|
+
|===
|
|
153
|
+
|Profile |Intent |Resolved assignment
|
|
154
|
+
|
|
155
|
+
|`:strict`
|
|
156
|
+
|Default. Everything significant.
|
|
157
|
+
|`text_content=strict`, `structural_whitespace=strict`,
|
|
158
|
+
`attribute_presence=strict`, `attribute_order=strict`,
|
|
159
|
+
`attribute_values=strict`, `element_position=strict`,
|
|
160
|
+
`comments=strict`, `namespace_prefix=significant`,
|
|
161
|
+
`whitespace_type=strict`, `preprocessing=none`.
|
|
162
|
+
|
|
163
|
+
|`:spec_friendly`
|
|
164
|
+
|Normative-document comparison (the spec-familiar shape).
|
|
165
|
+
|`text_content=normalize`, `structural_whitespace=ignore`,
|
|
166
|
+
`attribute_presence=strict`, `attribute_values=normalize`,
|
|
167
|
+
`attribute_order=ignore`, `element_position=ignore`,
|
|
168
|
+
`comments=ignore`, `namespace_prefix=ignore`,
|
|
169
|
+
`whitespace_type=strict`, `preprocessing=rendered`.
|
|
170
|
+
|
|
171
|
+
|`:rendered` (XML/HTML only)
|
|
172
|
+
|The same document as rendered sources.
|
|
173
|
+
|`text_content=normalize`, `structural_whitespace=normalize`,
|
|
174
|
+
`attribute_presence=strict`, `attribute_order=ignore`,
|
|
175
|
+
`comments=ignore`, `attribute_values=strict`,
|
|
176
|
+
`element_position=strict`, `namespace_prefix=significant`,
|
|
177
|
+
`whitespace_type=strict`, `preprocessing=none`.
|
|
178
|
+
|
|
179
|
+
|`:content_only`
|
|
180
|
+
|Ignore all spelling; compare information content.
|
|
181
|
+
|`text_content=normalize`, `structural_whitespace=ignore`,
|
|
182
|
+
`attribute_presence=strict`, `attribute_order=ignore`,
|
|
183
|
+
`attribute_values=normalize`, `element_position=ignore`,
|
|
184
|
+
`comments=ignore`, `namespace_prefix=ignore`,
|
|
185
|
+
`whitespace_type=strict`, `preprocessing=c14n`.
|
|
186
|
+
|===
|
|
187
|
+
|
|
188
|
+
=== 3.1 JSON and YAML profile assignments
|
|
189
|
+
|
|
190
|
+
JSON ships `:strict`, `:spec_friendly`, and `:content_only` (no
|
|
191
|
+
`:rendered`); YAML the same plus its `comments` dimension.
|
|
192
|
+
|
|
193
|
+
[cols="1,1,1"]
|
|
194
|
+
|===
|
|
195
|
+
|Profile |JSON |YAML
|
|
196
|
+
|
|
197
|
+
|`:strict`
|
|
198
|
+
|`key_order=strict`, `text_content=strict`,
|
|
199
|
+
`structural_whitespace=strict`, `preprocessing=none`
|
|
200
|
+
|`key_order=strict`, `text_content=strict`,
|
|
201
|
+
`structural_whitespace=strict`, `comments=strict`,
|
|
202
|
+
`preprocessing=none`
|
|
203
|
+
|
|
204
|
+
|`:spec_friendly`
|
|
205
|
+
|`key_order=ignore`, `text_content=strict`,
|
|
206
|
+
`structural_whitespace=ignore`, `preprocessing=normalize`
|
|
207
|
+
|`key_order=ignore`, `text_content=strict`,
|
|
208
|
+
`structural_whitespace=ignore`, `comments=ignore`,
|
|
209
|
+
`preprocessing=normalize`
|
|
210
|
+
|
|
211
|
+
|`:content_only`
|
|
212
|
+
|`key_order=ignore`, `text_content=normalize`,
|
|
213
|
+
`structural_whitespace=ignore`, `preprocessing=normalize`
|
|
214
|
+
|`key_order=ignore`, `text_content=normalize`,
|
|
215
|
+
`structural_whitespace=ignore`, `comments=ignore`,
|
|
216
|
+
`preprocessing=normalize`
|
|
217
|
+
|===
|
|
218
|
+
|
|
219
|
+
*Registry note (the W6.2 audit finding)*: the resolvers resolve
|
|
220
|
+
one key beyond the registry's dimension sets — `whitespace_type`
|
|
221
|
+
(`strict` in every shipped profile) — governing whether whitespace
|
|
222
|
+
TYPE differences (space vs tab) are significant. It awaits registry
|
|
223
|
+
promotion; until then it is documented here as a resolver-level
|
|
224
|
+
option.
|
|
225
|
+
|
|
226
|
+
Profile algebra properties:
|
|
227
|
+
|
|
228
|
+
* *Totality*: a resolved profile assigns every format dimension
|
|
229
|
+
exactly one behavior (the resolver raises on a gap).
|
|
230
|
+
* *Monotone relaxation*: `:strict` is the identity of the algebra —
|
|
231
|
+
every other profile differs from `:strict` only by relaxing
|
|
232
|
+
dimensions toward `ignore`/`normalize`, never by strengthening.
|
|
233
|
+
* *Non-normativity of relaxed dimensions*: with the exception of
|
|
234
|
+
`text_content`, a dimension at `ignore` contributes no
|
|
235
|
+
equivalence-affecting differences (the normative/informative split
|
|
236
|
+
below).
|
|
237
|
+
|
|
238
|
+
== 4. Normative, informative, and formatting-only differences
|
|
239
|
+
|
|
240
|
+
A difference is classified when emitted:
|
|
241
|
+
|
|
242
|
+
* *Normative* — affects equivalence: the documents are not
|
|
243
|
+
equivalent. Produced by a dimension at a strict/significant
|
|
244
|
+
behavior.
|
|
245
|
+
* *Informative* — recorded but not equivalence-affecting: produced
|
|
246
|
+
by a dimension at a relaxed behavior where the difference is still
|
|
247
|
+
observable.
|
|
248
|
+
* *Formatting-only* — the canonical forms match; the difference is
|
|
249
|
+
pure spelling.
|
|
250
|
+
|
|
251
|
+
`show_diffs: :normative` filters the presentation to normative
|
|
252
|
+
findings. The classification is a property of the dimension's
|
|
253
|
+
resolved behavior, not of the documents.
|
|
254
|
+
|
|
255
|
+
== 5. The two algorithms
|
|
256
|
+
|
|
257
|
+
=== 5.1 DOM (`diff_algorithm: :dom`, default)
|
|
258
|
+
|
|
259
|
+
Position-based recursive descent. For each element pair: compare
|
|
260
|
+
names, dimensions resolve per-dimension (attributes as sets or
|
|
261
|
+
sequences per `attribute_order`, children positionally per
|
|
262
|
+
`element_position` with the ElementMatcher's identity matching when
|
|
263
|
+
relaxed). Complexity is linear in the sum of tree sizes. The
|
|
264
|
+
intermediate representation is comparison-result codes
|
|
265
|
+
(`EQUIVALENT`, `MISSING_NODE`, `UNEQUAL_TEXT_CONTENTS`, ...) which
|
|
266
|
+
convert to DiffNodes.
|
|
267
|
+
|
|
268
|
+
=== 5.2 Semantic (`diff_algorithm: :semantic`)
|
|
269
|
+
|
|
270
|
+
Signature-based tree matching in three phases: (1) hash matching —
|
|
271
|
+
identical subtrees match exactly; (2) similarity matching —
|
|
272
|
+
remaining nodes pair by content similarity above
|
|
273
|
+
`similarity_threshold`; (3) structural propagation — parent/child
|
|
274
|
+
correspondences derive from matched ancestors. The intermediate
|
|
275
|
+
representation is an operation stream (INSERT, DELETE, UPDATE,
|
|
276
|
+
MOVE) converted to DiffNodes. The algorithms share preprocessing,
|
|
277
|
+
configuration, match options, and output formatting; the comparison
|
|
278
|
+
cores never share code (distinct by design — see CLAUDE.md, "Two
|
|
279
|
+
Comparison Algorithms — Distinct by Design").
|
|
280
|
+
|
|
281
|
+
== 6. Registry citation
|
|
282
|
+
|
|
283
|
+
The review spec (`spec/canon/docs/comparison_theory_spec.rb`)
|
|
284
|
+
asserts, at every run: for each format, every registry dimension
|
|
285
|
+
name and every valid behavior token appears in this document; every
|
|
286
|
+
profile row's resolved assignment equals the resolver's output for
|
|
287
|
+
that profile. A dimension added to the registry without a §2 entry,
|
|
288
|
+
a behavior added without its semantics row, or a profile change
|
|
289
|
+
without a §3 row update, fails the battery.
|
data/lib/canon/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.113
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -173,6 +173,7 @@ files:
|
|
|
173
173
|
- docs/Gemfile
|
|
174
174
|
- docs/INDEX.adoc
|
|
175
175
|
- docs/_config.yml
|
|
176
|
+
- docs/_references/comparison-theory.adoc
|
|
176
177
|
- docs/advanced/diff-classification.adoc
|
|
177
178
|
- docs/advanced/diff-pipeline.adoc
|
|
178
179
|
- docs/advanced/extending-canon.adoc
|