canon 0.1.5 → 0.1.7

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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +163 -67
  3. data/README.adoc +400 -7
  4. data/docs/Gemfile +9 -0
  5. data/docs/INDEX.adoc +99 -182
  6. data/docs/_config.yml +100 -0
  7. data/docs/advanced/diff-classification.adoc +547 -0
  8. data/docs/advanced/diff-pipeline.adoc +358 -0
  9. data/docs/advanced/index.adoc +214 -0
  10. data/docs/advanced/semantic-diff-report.adoc +390 -0
  11. data/docs/{VERBOSE.adoc → advanced/verbose-mode-architecture.adoc} +51 -53
  12. data/docs/features/diff-formatting/algorithm-specific-output.adoc +533 -0
  13. data/docs/{CHARACTER_VISUALIZATION.adoc → features/diff-formatting/character-visualization.adoc} +23 -62
  14. data/docs/features/diff-formatting/colors-and-symbols.adoc +606 -0
  15. data/docs/features/diff-formatting/context-and-grouping.adoc +490 -0
  16. data/docs/features/diff-formatting/display-filtering.adoc +472 -0
  17. data/docs/features/diff-formatting/index.adoc +140 -0
  18. data/docs/features/environment-configuration/index.adoc +327 -0
  19. data/docs/features/environment-configuration/override-system.adoc +436 -0
  20. data/docs/features/environment-configuration/size-limits.adoc +273 -0
  21. data/docs/features/index.adoc +173 -0
  22. data/docs/features/input-validation/index.adoc +521 -0
  23. data/docs/features/match-options/algorithm-specific-behavior.adoc +365 -0
  24. data/docs/features/match-options/html-policies.adoc +312 -0
  25. data/docs/features/match-options/index.adoc +621 -0
  26. data/docs/getting-started/index.adoc +83 -0
  27. data/docs/getting-started/quick-start.adoc +76 -0
  28. data/docs/guides/choosing-configuration.adoc +689 -0
  29. data/docs/guides/index.adoc +181 -0
  30. data/docs/{CLI.adoc → interfaces/cli/index.adoc} +18 -13
  31. data/docs/interfaces/index.adoc +101 -0
  32. data/docs/{RSPEC.adoc → interfaces/rspec/index.adoc} +242 -31
  33. data/docs/{RUBY_API.adoc → interfaces/ruby-api/index.adoc} +118 -16
  34. data/docs/lychee.toml +65 -0
  35. data/docs/reference/cli-options.adoc +418 -0
  36. data/docs/reference/environment-variables.adoc +375 -0
  37. data/docs/reference/index.adoc +204 -0
  38. data/docs/reference/options-across-interfaces.adoc +417 -0
  39. data/docs/understanding/algorithms/dom-diff.adoc +389 -0
  40. data/docs/understanding/algorithms/index.adoc +314 -0
  41. data/docs/understanding/algorithms/semantic-tree-diff.adoc +533 -0
  42. data/docs/understanding/architecture.adoc +447 -0
  43. data/docs/understanding/comparison-pipeline.adoc +317 -0
  44. data/docs/understanding/formats/html.adoc +380 -0
  45. data/docs/understanding/formats/index.adoc +261 -0
  46. data/docs/understanding/formats/json.adoc +390 -0
  47. data/docs/understanding/formats/xml.adoc +366 -0
  48. data/docs/understanding/formats/yaml.adoc +504 -0
  49. data/docs/understanding/index.adoc +130 -0
  50. data/lib/canon/cli.rb +42 -1
  51. data/lib/canon/commands/diff_command.rb +108 -23
  52. data/lib/canon/comparison/compare_profile.rb +101 -0
  53. data/lib/canon/comparison/comparison_result.rb +41 -2
  54. data/lib/canon/comparison/html_comparator.rb +292 -71
  55. data/lib/canon/comparison/html_compare_profile.rb +117 -0
  56. data/lib/canon/comparison/match_options.rb +42 -4
  57. data/lib/canon/comparison/strategies/base_match_strategy.rb +99 -0
  58. data/lib/canon/comparison/strategies/match_strategy_factory.rb +74 -0
  59. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +220 -0
  60. data/lib/canon/comparison/xml_comparator.rb +695 -91
  61. data/lib/canon/comparison.rb +207 -2
  62. data/lib/canon/config/env_provider.rb +71 -0
  63. data/lib/canon/config/env_schema.rb +58 -0
  64. data/lib/canon/config/override_resolver.rb +55 -0
  65. data/lib/canon/config/type_converter.rb +59 -0
  66. data/lib/canon/config.rb +158 -29
  67. data/lib/canon/data_model.rb +29 -0
  68. data/lib/canon/diff/diff_classifier.rb +74 -14
  69. data/lib/canon/diff/diff_context_builder.rb +41 -0
  70. data/lib/canon/diff/diff_line.rb +18 -2
  71. data/lib/canon/diff/diff_node.rb +18 -3
  72. data/lib/canon/diff/diff_node_mapper.rb +71 -12
  73. data/lib/canon/diff/formatting_detector.rb +53 -0
  74. data/lib/canon/diff_formatter/by_line/base_formatter.rb +60 -5
  75. data/lib/canon/diff_formatter/by_line/html_formatter.rb +68 -16
  76. data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -37
  77. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -42
  78. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +116 -31
  79. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -37
  80. data/lib/canon/diff_formatter/by_object/base_formatter.rb +126 -19
  81. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +30 -1
  82. data/lib/canon/diff_formatter/debug_output.rb +7 -1
  83. data/lib/canon/diff_formatter/diff_detail_formatter.rb +674 -57
  84. data/lib/canon/diff_formatter/legend.rb +42 -0
  85. data/lib/canon/diff_formatter.rb +78 -9
  86. data/lib/canon/errors.rb +56 -0
  87. data/lib/canon/formatters/html_formatter_base.rb +35 -1
  88. data/lib/canon/formatters/json_formatter.rb +3 -0
  89. data/lib/canon/formatters/yaml_formatter.rb +3 -0
  90. data/lib/canon/html/data_model.rb +229 -0
  91. data/lib/canon/html.rb +9 -0
  92. data/lib/canon/options/cli_generator.rb +70 -0
  93. data/lib/canon/options/registry.rb +234 -0
  94. data/lib/canon/rspec_matchers.rb +34 -13
  95. data/lib/canon/tree_diff/adapters/html_adapter.rb +316 -0
  96. data/lib/canon/tree_diff/adapters/json_adapter.rb +204 -0
  97. data/lib/canon/tree_diff/adapters/xml_adapter.rb +285 -0
  98. data/lib/canon/tree_diff/adapters/yaml_adapter.rb +213 -0
  99. data/lib/canon/tree_diff/core/attribute_comparator.rb +84 -0
  100. data/lib/canon/tree_diff/core/matching.rb +241 -0
  101. data/lib/canon/tree_diff/core/node_signature.rb +164 -0
  102. data/lib/canon/tree_diff/core/node_weight.rb +135 -0
  103. data/lib/canon/tree_diff/core/tree_node.rb +450 -0
  104. data/lib/canon/tree_diff/matchers/hash_matcher.rb +258 -0
  105. data/lib/canon/tree_diff/matchers/similarity_matcher.rb +168 -0
  106. data/lib/canon/tree_diff/matchers/structural_propagator.rb +242 -0
  107. data/lib/canon/tree_diff/matchers/universal_matcher.rb +220 -0
  108. data/lib/canon/tree_diff/operation_converter.rb +631 -0
  109. data/lib/canon/tree_diff/operations/operation.rb +92 -0
  110. data/lib/canon/tree_diff/operations/operation_detector.rb +626 -0
  111. data/lib/canon/tree_diff/tree_diff_integrator.rb +140 -0
  112. data/lib/canon/tree_diff.rb +33 -0
  113. data/lib/canon/validators/json_validator.rb +3 -1
  114. data/lib/canon/validators/yaml_validator.rb +3 -1
  115. data/lib/canon/version.rb +1 -1
  116. data/lib/canon/xml/data_model.rb +22 -23
  117. data/lib/canon/xml/element_matcher.rb +128 -20
  118. data/lib/canon/xml/namespace_helper.rb +110 -0
  119. data/lib/canon.rb +3 -0
  120. metadata +81 -23
  121. data/_config.yml +0 -116
  122. data/docs/ADVANCED_TOPICS.adoc +0 -20
  123. data/docs/BASIC_USAGE.adoc +0 -16
  124. data/docs/CUSTOMIZING_BEHAVIOR.adoc +0 -19
  125. data/docs/DIFF_ARCHITECTURE.adoc +0 -435
  126. data/docs/DIFF_FORMATTING.adoc +0 -540
  127. data/docs/FORMATS.adoc +0 -447
  128. data/docs/INPUT_VALIDATION.adoc +0 -477
  129. data/docs/MATCH_ARCHITECTURE.adoc +0 -463
  130. data/docs/MATCH_OPTIONS.adoc +0 -719
  131. data/docs/MODES.adoc +0 -432
  132. data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +0 -219
  133. data/docs/OPTIONS.adoc +0 -1387
  134. data/docs/PREPROCESSING.adoc +0 -491
  135. data/docs/SEMANTIC_DIFF_REPORT.adoc +0 -528
  136. data/docs/UNDERSTANDING_CANON.adoc +0 -17
@@ -0,0 +1,547 @@
1
+ ---
2
+ layout: default
3
+ title: Diff Classification
4
+ parent: Advanced
5
+ nav_order: 1
6
+ ---
7
+
8
+ :toc:
9
+ :toclevels: 3
10
+
11
+ == Purpose
12
+
13
+ Canon distinguishes between two types of differences when comparing documents:
14
+
15
+ * **Normative diffs**: Differences that affect equivalence based on your match options
16
+ * **Informative diffs**: Differences that don't affect equivalence (semantically equivalent per match options)
17
+
18
+ This allows you to focus on differences that matter while optionally viewing formatting-only changes.
19
+
20
+ == Classification System
21
+
22
+ === How Classification Works
23
+
24
+ Each difference is classified based on the match dimension's behavior:
25
+
26
+ [cols="2,2,3"]
27
+ |===
28
+ |Match Behavior |Classification |Meaning
29
+
30
+ |`:strict`
31
+ |Normative
32
+ |Must match exactly - any difference fails equivalence
33
+
34
+ |`:normalize`
35
+ |Normative
36
+ |Must match after normalization - remaining differences fail equivalence
37
+
38
+ |`:ignore`
39
+ |Informative
40
+ |Difference exists but doesn't affect equivalence
41
+ |===
42
+
43
+ .Example: Attribute order classification
44
+ [example]
45
+ ====
46
+ Given:
47
+ [source,xml]
48
+ ----
49
+ <div class="TOC" id="_"> <!-- Document 1 -->
50
+ <div id="_" class="TOC"> <!-- Document 2 -->
51
+ ----
52
+
53
+ Classification depends on `attribute_order` setting:
54
+
55
+ * `attribute_order: :strict` → **NORMATIVE** (order matters)
56
+ ** Shown in red/green
57
+ ** Causes equivalence to fail
58
+
59
+ * `attribute_order: :ignore` → **INFORMATIVE** (order doesn't matter)
60
+ ** Shown in cyan with `~~` markers
61
+ ** Documents are still equivalent
62
+ ====
63
+
64
+ == Architecture
65
+
66
+ [source]
67
+ ----
68
+ ┌──────────────────────────────────────────────────────────────────┐
69
+ │ COMPARISON LAYER │
70
+ │ │
71
+ │ Tree Comparator creates DiffNodes for each difference │
72
+ │ ├─ References to nodes in both trees │
73
+ │ ├─ Dimension (:text_content, :attribute_order, etc.) │
74
+ │ ├─ Reason code (UNEQUAL_TEXT_CONTENTS, etc.) │
75
+ │ └─ Normative flag (nil, to be classified) │
76
+ └───────────────────────────────────┬───────────────────────────────┘
77
+
78
+ ┌──────────────────────────────────────────────────────────────────┐
79
+ │ CLASSIFICATION LAYER │
80
+ │ │
81
+ │ DiffClassifier examines each DiffNode: │
82
+ │ │
83
+ │ For each dimension: │
84
+ │ behavior = match_options.behavior_for(dimension) │
85
+ │ │
86
+ │ if behavior == :ignore │
87
+ │ → INFORMATIVE (difference doesn't matter) │
88
+ │ else # :strict or :normalize │
89
+ │ → NORMATIVE (difference matters) │
90
+ │ │
91
+ │ Sets diff_node.normative = true/false │
92
+ └───────────────────────────────────┬───────────────────────────────┘
93
+
94
+ ┌──────────────────────────────────────────────────────────────────┐
95
+ │ RENDERING LAYER │
96
+ │ │
97
+ │ Normative diffs: Informative diffs: │
98
+ │ 18| - | <div ...> 18| ~~ | <div ...> │
99
+ │ 18| + | <div ...> 18| ~~ | <div ...> │
100
+ │ ▲ ▲ │
101
+ │ Red/Green markers Cyan markers │
102
+ └──────────────────────────────────────────────────────────────────┘
103
+ ----
104
+
105
+ == CompareProfile-Based Classification
106
+
107
+ === Overview
108
+
109
+ Canon's classification system uses `CompareProfile` to determine which differences
110
+ are normative (affect equivalence) versus informative (do not affect equivalence).
111
+
112
+ The classification flow:
113
+
114
+ [source,text]
115
+ ----
116
+ DiffNode → DiffClassifier → CompareProfile → normative?
117
+
118
+ dimension + behavior → decision
119
+ ----
120
+
121
+ === Classification Hierarchy
122
+
123
+ Canon uses a three-level hierarchy for classifying differences:
124
+
125
+ 1. **Formatting-only** (lowest priority)
126
+ - Pure whitespace/formatting differences
127
+ - Normalized content is identical
128
+ - Markers: `[` and `]` in diff output
129
+
130
+ 2. **Informative** (medium priority)
131
+ - Tracked but doesn't affect equivalence
132
+ - Based on behavior `:ignore`
133
+ - Markers: `<` and `>` in diff output
134
+
135
+ 3. **Normative** (highest priority)
136
+ - Affects equivalence
137
+ - Based on behavior `:strict`
138
+ - Markers: `-` and `+` in diff output
139
+
140
+ === Format-Specific Policies
141
+
142
+ Each format can define its own policies through CompareProfile subclasses:
143
+
144
+ ==== XML (Base CompareProfile)
145
+
146
+ Default policies:
147
+ - Comments: normative (`:strict`)
148
+ - Whitespace: normative (`:strict`)
149
+ - All dimensions: strict by default
150
+
151
+ ==== HTML (HtmlCompareProfile)
152
+
153
+ Default policies:
154
+ - Comments: informative (`:ignore`) - presentational content
155
+ - Whitespace: preserved in `<pre>`, `<code>`, `<textarea>`, `<script>`, `<style>`
156
+ - Case sensitivity: HTML5 respects case, HTML4 is case-insensitive
157
+
158
+ ==== Future formats
159
+
160
+ The architecture supports format-specific profiles for:
161
+ - JSON: key order policies, type handling
162
+ - YAML: comment handling, anchor/alias policies
163
+
164
+ === Dimension Policy Examples
165
+
166
+ ==== Structural Whitespace
167
+
168
+ * **`:strict` behavior** → Normative
169
+ - Whitespace must match exactly
170
+ - Any whitespace difference causes non-equivalence
171
+
172
+ * **`:normalize` behavior** → Informative (if formatting-only)
173
+ - Whitespace differences detected as formatting-only when normalized content matches
174
+ - Uses FormattingDetector to determine if difference is purely whitespace
175
+
176
+ * **`:ignore` behavior** → Informative
177
+ - Whitespace differences tracked but don't affect equivalence
178
+
179
+ .Example: Structural whitespace with normalize
180
+ ====
181
+ [source,ruby]
182
+ ----
183
+ xml1 = '<root><p>Hello world</p></root>'
184
+ xml2 = '<root><p>Hello\nworld</p></root>'
185
+
186
+ # Normalize mode: formatting-only
187
+ result = Canon::Comparison.equivalent?(
188
+ xml1, xml2,
189
+ match: { text_content: :normalize, structural_whitespace: :normalize }
190
+ )
191
+ # => true (line break is formatting-only)
192
+
193
+ # Strict mode: normative
194
+ result = Canon::Comparison.equivalent?(xml1, xml2)
195
+ # => false (line break is normative in strict mode)
196
+ ----
197
+ ====
198
+
199
+ ==== Comments
200
+
201
+ * **`:strict` behavior** → Normative
202
+ - Comments must match exactly
203
+ - Comment differences cause non-equivalence
204
+
205
+ * **`:ignore` behavior** → Informative
206
+ - Comment differences tracked but don't affect equivalence
207
+ - Default for HTML (comments are presentational)
208
+
209
+ .Example: Comment handling
210
+ ====
211
+ [source,ruby]
212
+ ----
213
+ xml1 = '<root><!-- v1 --><data>text</data></root>'
214
+ xml2 = '<root><!-- v2 --><data>text</data></root>'
215
+
216
+ # Ignore mode: informative
217
+ result = Canon::Comparison.equivalent?(
218
+ xml1, xml2,
219
+ match: { comments: :ignore }
220
+ )
221
+ # => true (comment difference is informative)
222
+
223
+ # Strict mode: normative
224
+ result = Canon::Comparison.equivalent?(
225
+ xml1, xml2,
226
+ match: { comments: :strict }
227
+ )
228
+ # => false (comment difference is normative)
229
+ ----
230
+ ====
231
+
232
+ === FormattingDetector Integration
233
+
234
+ For dimensions that support it (`:text_content`, `:structural_whitespace`),
235
+ `FormattingDetector` is consulted to determine if a difference is formatting-only:
236
+
237
+ 1. **Check normative status** based on CompareProfile policy
238
+ 2. **If non-normative**, check if formatting-only using FormattingDetector
239
+ 3. **If formatting-only**, mark with `formatting: true`
240
+
241
+ .Example: Formatting detection
242
+ ====
243
+ [source,xml]
244
+ ----
245
+ <!-- Extra spaces around tag delimiter -->
246
+ <div>content</div> <!-- xml1 -->
247
+ <div >content</div> <!-- xml2 - space before > -->
248
+ ----
249
+
250
+ With `:normalize` mode:
251
+ - Normalized: `<div>content</div>` (both)
252
+ - Classification: Formatting-only
253
+ - Markers: `[` and `]`
254
+ - Equivalent: true
255
+ ====
256
+
257
+ === Implementation Details
258
+
259
+ The [`CompareProfile`](../../lib/canon/comparison/compare_profile.rb) class provides:
260
+
261
+ * `normative_dimension?(dimension)` - Is this dimension normative?
262
+ * `affects_equivalence?(dimension)` - Does this dimension affect equivalence?
263
+ * `supports_formatting_detection?(dimension)` - Can this dimension have formatting-only diffs?
264
+
265
+ The [`DiffClassifier`](../../lib/canon/diff/diff_classifier.rb) uses CompareProfile to classify:
266
+
267
+ [source,ruby]
268
+ ----
269
+ def classify(diff_node)
270
+ # Check normative status based on policy
271
+ is_normative = profile.normative_dimension?(diff_node.dimension)
272
+
273
+ # Only check formatting for non-normative dimensions
274
+ if !is_normative && profile.supports_formatting_detection?(diff_node.dimension)
275
+ if formatting_only_diff?(diff_node)
276
+ diff_node.formatting = true
277
+ diff_node.normative = false
278
+ return diff_node
279
+ end
280
+ end
281
+
282
+ diff_node.normative = is_normative
283
+ diff_node
284
+ end
285
+ ----
286
+
287
+ == Visual Indicators
288
+
289
+ === Normative Diffs
290
+
291
+ **Colors**: Red (deletions) and Green (additions)
292
+
293
+ **Symbols**:
294
+ * `-` for deletions
295
+ * `+` for additions
296
+ * `~` for changes
297
+
298
+ .Normative diff example
299
+ [example]
300
+ ====
301
+ [source]
302
+ ----
303
+ 10| - | <title>Old Title</title>
304
+ | 11+ | <title>New Title</title>
305
+ ----
306
+
307
+ Red and green indicate this difference causes files to be non-equivalent.
308
+ ====
309
+
310
+ === Informative Diffs
311
+
312
+ **Color**: Cyan
313
+
314
+ **Symbol**: `~~` (double tilde)
315
+
316
+ .Informative diff example
317
+ [example]
318
+ ====
319
+ [source]
320
+ ----
321
+ 10| ~~ | <div class="TOC" id="_">
322
+ | ~~ | <div id="_" class="TOC">
323
+ ----
324
+
325
+ Cyan color indicates this difference is informational only - files are still equivalent.
326
+ ====
327
+
328
+ == Controlling Diff Display
329
+
330
+ Use the `show_diffs` option to filter which types of diffs are shown:
331
+
332
+ [cols="2,3"]
333
+ |===
334
+ |Option |What's Shown
335
+
336
+ |`:all` (default)
337
+ |Both normative and informative diffs
338
+
339
+ |`:normative`
340
+ |Only normative diffs (differences that matter)
341
+
342
+ |`:informative`
343
+ |Only informative diffs (for reference)
344
+ |===
345
+
346
+ === CLI
347
+
348
+ [source,bash]
349
+ ----
350
+ # Show only normative diffs
351
+ canon diff file1.xml file2.xml --show-diffs normative
352
+
353
+ # Show only informative diffs
354
+ canon diff file1.xml file2.xml --show-diffs informative
355
+
356
+ # Show all diffs (default)
357
+ canon diff file1.xml file2.xml --show-diffs all
358
+ ----
359
+
360
+ === Ruby API
361
+
362
+ [source,ruby]
363
+ ----
364
+ # Show only normative diffs
365
+ Canon.compare(file1, file2,
366
+ format: :xml,
367
+ show_diffs: :normative
368
+ )
369
+
370
+ # Show all diffs
371
+ Canon.compare(file1, file2,
372
+ format: :xml,
373
+ show_diffs: :all
374
+ )
375
+ ----
376
+
377
+ === RSpec
378
+
379
+ [source,ruby]
380
+ ----
381
+ # Configure globally
382
+ RSpec.configure do |config|
383
+ config.canon.xml.diff.show_diffs = :normative
384
+ end
385
+
386
+ # Or per-test
387
+ expect(actual).to match_xml(expected).with_options(
388
+ show_diffs: :normative
389
+ )
390
+ ----
391
+
392
+ == Common Scenarios
393
+
394
+ === Whitespace Differences
395
+
396
+ .With `structural_whitespace: :ignore`
397
+ [example]
398
+ ====
399
+ [source]
400
+ ----
401
+ 5| ~~ | <p>Hello world</p> # Extra space (informative)
402
+ | ~~ | <p>Hello world</p>
403
+ ----
404
+
405
+ The difference is shown in cyan because extra whitespace is ignored.
406
+ ====
407
+
408
+ .With `structural_whitespace: :strict`
409
+ [example]
410
+ ====
411
+ [source]
412
+ ----
413
+ 5| - | <p>Hello world</p> # Extra space (normative)
414
+ | 6+ | <p>Hello world</p>
415
+ ----
416
+
417
+ The difference is shown in red/green because whitespace must match exactly.
418
+ ====
419
+
420
+ === Attribute Order
421
+
422
+ .With `attribute_order: :ignore`
423
+ [example]
424
+ ====
425
+ [source]
426
+ ----
427
+ 10| ~~ | <div class="x" id="y"> # Informative
428
+ | ~~ | <div id="y" class="x">
429
+ ----
430
+ ====
431
+
432
+ .With `attribute_order: :strict`
433
+ [example]
434
+ ====
435
+ [source]
436
+ ----
437
+ 10| - | <div class="x" id="y"> # Normative
438
+ | 11+ | <div id="y" class="x">
439
+ ----
440
+ ====
441
+
442
+ === Comments
443
+
444
+ .With `comments: :ignore`
445
+ [example]
446
+ ====
447
+ [source]
448
+ ----
449
+ 3| ~~ | <!-- Old comment --> # Informative
450
+ | ~~ | <!-- New comment -->
451
+ ----
452
+ ====
453
+
454
+ .With `comments: :strict`
455
+ [example]
456
+ ====
457
+ [source]
458
+ ----
459
+ 3| - | <!-- Old comment --> # Normative
460
+ | 4+ | <!-- New comment -->
461
+ ----
462
+ ====
463
+
464
+ == Implementation
465
+
466
+ === DiffClassifier
467
+
468
+ The [`DiffClassifier`](../../lib/canon/diff/diff_classifier.rb) class handles classification:
469
+
470
+ [source,ruby]
471
+ ----
472
+ class DiffClassifier
473
+ def initialize(match_options)
474
+ @match_options = match_options
475
+ end
476
+
477
+ def classify(diff_node)
478
+ behavior = @match_options.behavior_for(diff_node.dimension)
479
+ diff_node.normative = (behavior != :ignore)
480
+ diff_node
481
+ end
482
+ end
483
+ ----
484
+
485
+ === Match Options Integration
486
+
487
+ Classification uses the resolved match options:
488
+
489
+ [source,ruby]
490
+ ----
491
+ # Match options define behavior for each dimension
492
+ match_options = ResolvedMatchOptions.new(
493
+ text_content: :normalize, # Normative after normalization
494
+ structural_whitespace: :ignore, # Informative
495
+ attribute_order: :ignore, # Informative
496
+ comments: :strict # Normative
497
+ )
498
+
499
+ # Classifier uses these to determine normative/informative
500
+ classifier = DiffClassifier.new(match_options)
501
+ ----
502
+
503
+ == Use Cases
504
+
505
+ === Focus on Real Differences
506
+
507
+ Use `show_diffs: :normative` to hide formatting changes:
508
+
509
+ [source,bash]
510
+ ----
511
+ canon diff spec1.xml spec2.xml \
512
+ --match-profile spec_friendly \
513
+ --show-diffs normative
514
+ ----
515
+
516
+ This shows only semantic differences, hiding attribute order, whitespace, and comments.
517
+
518
+ === Review All Changes
519
+
520
+ Use `show_diffs: :all` during code review to see everything:
521
+
522
+ [source,bash]
523
+ ----
524
+ canon diff old.xml new.xml --show-diffs all
525
+ ----
526
+
527
+ This shows both semantic and formatting changes for complete visibility.
528
+
529
+ === Understand Match Configuration
530
+
531
+ Use `show_diffs: :informative` to see what's being ignored:
532
+
533
+ [source,bash]
534
+ ----
535
+ canon diff file1.xml file2.xml \
536
+ --match-profile strict \
537
+ --show-diffs informative
538
+ ----
539
+
540
+ This helps verify your match configuration is working as expected.
541
+
542
+ == See Also
543
+
544
+ * link:../features/match-options/index.html[Match Options] - Configure match behavior
545
+ * link:../features/diff-formatting/colors-and-symbols.html[Colors and Symbols] - Visual indicators
546
+ * link:semantic-diff-report.html[Semantic Diff Report] - Detailed difference analysis
547
+ * link:../guides/choosing-configuration.html[Choosing Configuration] - Match profile selection