canon 0.1.6 → 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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +163 -67
- data/README.adoc +400 -7
- data/docs/Gemfile +9 -0
- data/docs/INDEX.adoc +99 -182
- data/docs/_config.yml +100 -0
- data/docs/advanced/diff-classification.adoc +547 -0
- data/docs/advanced/diff-pipeline.adoc +358 -0
- data/docs/advanced/index.adoc +214 -0
- data/docs/advanced/semantic-diff-report.adoc +390 -0
- data/docs/{VERBOSE.adoc → advanced/verbose-mode-architecture.adoc} +51 -53
- data/docs/features/diff-formatting/algorithm-specific-output.adoc +533 -0
- data/docs/{CHARACTER_VISUALIZATION.adoc → features/diff-formatting/character-visualization.adoc} +23 -62
- data/docs/features/diff-formatting/colors-and-symbols.adoc +606 -0
- data/docs/features/diff-formatting/context-and-grouping.adoc +490 -0
- data/docs/features/diff-formatting/display-filtering.adoc +472 -0
- data/docs/features/diff-formatting/index.adoc +140 -0
- data/docs/features/environment-configuration/index.adoc +327 -0
- data/docs/features/environment-configuration/override-system.adoc +436 -0
- data/docs/features/environment-configuration/size-limits.adoc +273 -0
- data/docs/features/index.adoc +173 -0
- data/docs/features/input-validation/index.adoc +521 -0
- data/docs/features/match-options/algorithm-specific-behavior.adoc +365 -0
- data/docs/features/match-options/html-policies.adoc +312 -0
- data/docs/features/match-options/index.adoc +621 -0
- data/docs/getting-started/index.adoc +83 -0
- data/docs/getting-started/quick-start.adoc +76 -0
- data/docs/guides/choosing-configuration.adoc +689 -0
- data/docs/guides/index.adoc +181 -0
- data/docs/{CLI.adoc → interfaces/cli/index.adoc} +18 -13
- data/docs/interfaces/index.adoc +101 -0
- data/docs/{RSPEC.adoc → interfaces/rspec/index.adoc} +242 -31
- data/docs/{RUBY_API.adoc → interfaces/ruby-api/index.adoc} +118 -16
- data/docs/lychee.toml +65 -0
- data/docs/reference/cli-options.adoc +418 -0
- data/docs/reference/environment-variables.adoc +375 -0
- data/docs/reference/index.adoc +204 -0
- data/docs/reference/options-across-interfaces.adoc +417 -0
- data/docs/understanding/algorithms/dom-diff.adoc +389 -0
- data/docs/understanding/algorithms/index.adoc +314 -0
- data/docs/understanding/algorithms/semantic-tree-diff.adoc +533 -0
- data/docs/understanding/architecture.adoc +447 -0
- data/docs/understanding/comparison-pipeline.adoc +317 -0
- data/docs/understanding/formats/html.adoc +380 -0
- data/docs/understanding/formats/index.adoc +261 -0
- data/docs/understanding/formats/json.adoc +390 -0
- data/docs/understanding/formats/xml.adoc +366 -0
- data/docs/understanding/formats/yaml.adoc +504 -0
- data/docs/understanding/index.adoc +130 -0
- data/lib/canon/cli.rb +42 -1
- data/lib/canon/commands/diff_command.rb +108 -23
- data/lib/canon/comparison/compare_profile.rb +101 -0
- data/lib/canon/comparison/comparison_result.rb +41 -2
- data/lib/canon/comparison/html_comparator.rb +292 -71
- data/lib/canon/comparison/html_compare_profile.rb +117 -0
- data/lib/canon/comparison/match_options.rb +42 -4
- data/lib/canon/comparison/strategies/base_match_strategy.rb +99 -0
- data/lib/canon/comparison/strategies/match_strategy_factory.rb +74 -0
- data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +220 -0
- data/lib/canon/comparison/xml_comparator.rb +695 -91
- data/lib/canon/comparison.rb +207 -2
- data/lib/canon/config/env_provider.rb +71 -0
- data/lib/canon/config/env_schema.rb +58 -0
- data/lib/canon/config/override_resolver.rb +55 -0
- data/lib/canon/config/type_converter.rb +59 -0
- data/lib/canon/config.rb +158 -29
- data/lib/canon/data_model.rb +29 -0
- data/lib/canon/diff/diff_classifier.rb +74 -14
- data/lib/canon/diff/diff_context_builder.rb +41 -0
- data/lib/canon/diff/diff_line.rb +18 -2
- data/lib/canon/diff/diff_node.rb +18 -3
- data/lib/canon/diff/diff_node_mapper.rb +71 -12
- data/lib/canon/diff/formatting_detector.rb +53 -0
- data/lib/canon/diff_formatter/by_line/base_formatter.rb +60 -5
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +68 -16
- data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -37
- data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -42
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +116 -31
- data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -37
- data/lib/canon/diff_formatter/by_object/base_formatter.rb +126 -19
- data/lib/canon/diff_formatter/by_object/xml_formatter.rb +30 -1
- data/lib/canon/diff_formatter/debug_output.rb +7 -1
- data/lib/canon/diff_formatter/diff_detail_formatter.rb +674 -57
- data/lib/canon/diff_formatter/legend.rb +42 -0
- data/lib/canon/diff_formatter.rb +78 -9
- data/lib/canon/errors.rb +56 -0
- data/lib/canon/formatters/html_formatter_base.rb +35 -1
- data/lib/canon/formatters/json_formatter.rb +3 -0
- data/lib/canon/formatters/yaml_formatter.rb +3 -0
- data/lib/canon/html/data_model.rb +229 -0
- data/lib/canon/html.rb +9 -0
- data/lib/canon/options/cli_generator.rb +70 -0
- data/lib/canon/options/registry.rb +234 -0
- data/lib/canon/rspec_matchers.rb +34 -13
- data/lib/canon/tree_diff/adapters/html_adapter.rb +316 -0
- data/lib/canon/tree_diff/adapters/json_adapter.rb +204 -0
- data/lib/canon/tree_diff/adapters/xml_adapter.rb +285 -0
- data/lib/canon/tree_diff/adapters/yaml_adapter.rb +213 -0
- data/lib/canon/tree_diff/core/attribute_comparator.rb +84 -0
- data/lib/canon/tree_diff/core/matching.rb +241 -0
- data/lib/canon/tree_diff/core/node_signature.rb +164 -0
- data/lib/canon/tree_diff/core/node_weight.rb +135 -0
- data/lib/canon/tree_diff/core/tree_node.rb +450 -0
- data/lib/canon/tree_diff/matchers/hash_matcher.rb +258 -0
- data/lib/canon/tree_diff/matchers/similarity_matcher.rb +168 -0
- data/lib/canon/tree_diff/matchers/structural_propagator.rb +242 -0
- data/lib/canon/tree_diff/matchers/universal_matcher.rb +220 -0
- data/lib/canon/tree_diff/operation_converter.rb +631 -0
- data/lib/canon/tree_diff/operations/operation.rb +92 -0
- data/lib/canon/tree_diff/operations/operation_detector.rb +626 -0
- data/lib/canon/tree_diff/tree_diff_integrator.rb +140 -0
- data/lib/canon/tree_diff.rb +33 -0
- data/lib/canon/validators/json_validator.rb +3 -1
- data/lib/canon/validators/yaml_validator.rb +3 -1
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/data_model.rb +22 -23
- data/lib/canon/xml/element_matcher.rb +128 -20
- data/lib/canon/xml/namespace_helper.rb +110 -0
- data/lib/canon.rb +3 -0
- metadata +81 -23
- data/_config.yml +0 -116
- data/docs/ADVANCED_TOPICS.adoc +0 -20
- data/docs/BASIC_USAGE.adoc +0 -16
- data/docs/CUSTOMIZING_BEHAVIOR.adoc +0 -19
- data/docs/DIFF_ARCHITECTURE.adoc +0 -435
- data/docs/DIFF_FORMATTING.adoc +0 -540
- data/docs/FORMATS.adoc +0 -447
- data/docs/INPUT_VALIDATION.adoc +0 -477
- data/docs/MATCH_ARCHITECTURE.adoc +0 -463
- data/docs/MATCH_OPTIONS.adoc +0 -719
- data/docs/MODES.adoc +0 -432
- data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +0 -219
- data/docs/OPTIONS.adoc +0 -1387
- data/docs/PREPROCESSING.adoc +0 -491
- data/docs/SEMANTIC_DIFF_REPORT.adoc +0 -528
- 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
|