canon 0.1.3 → 0.1.5
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.yml +9 -1
- data/.rubocop_todo.yml +276 -7
- data/README.adoc +203 -138
- data/_config.yml +116 -0
- data/docs/ADVANCED_TOPICS.adoc +20 -0
- data/docs/BASIC_USAGE.adoc +16 -0
- data/docs/CHARACTER_VISUALIZATION.adoc +567 -0
- data/docs/CLI.adoc +493 -0
- data/docs/CUSTOMIZING_BEHAVIOR.adoc +19 -0
- data/docs/DIFF_ARCHITECTURE.adoc +435 -0
- data/docs/DIFF_FORMATTING.adoc +540 -0
- data/docs/FORMATS.adoc +447 -0
- data/docs/INDEX.adoc +222 -0
- data/docs/INPUT_VALIDATION.adoc +477 -0
- data/docs/MATCH_ARCHITECTURE.adoc +463 -0
- data/docs/MATCH_OPTIONS.adoc +719 -0
- data/docs/MODES.adoc +432 -0
- data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +219 -0
- data/docs/OPTIONS.adoc +1387 -0
- data/docs/PREPROCESSING.adoc +491 -0
- data/docs/RSPEC.adoc +605 -0
- data/docs/RUBY_API.adoc +478 -0
- data/docs/SEMANTIC_DIFF_REPORT.adoc +528 -0
- data/docs/UNDERSTANDING_CANON.adoc +17 -0
- data/docs/VERBOSE.adoc +482 -0
- data/exe/canon +7 -0
- data/lib/canon/cli.rb +179 -0
- data/lib/canon/commands/diff_command.rb +195 -0
- data/lib/canon/commands/format_command.rb +113 -0
- data/lib/canon/comparison/base_comparator.rb +39 -0
- data/lib/canon/comparison/comparison_result.rb +79 -0
- data/lib/canon/comparison/html_comparator.rb +410 -0
- data/lib/canon/comparison/json_comparator.rb +212 -0
- data/lib/canon/comparison/match_options.rb +616 -0
- data/lib/canon/comparison/xml_comparator.rb +566 -0
- data/lib/canon/comparison/yaml_comparator.rb +93 -0
- data/lib/canon/comparison.rb +239 -0
- data/lib/canon/config.rb +172 -0
- data/lib/canon/diff/diff_block.rb +71 -0
- data/lib/canon/diff/diff_block_builder.rb +105 -0
- data/lib/canon/diff/diff_classifier.rb +46 -0
- data/lib/canon/diff/diff_context.rb +85 -0
- data/lib/canon/diff/diff_context_builder.rb +107 -0
- data/lib/canon/diff/diff_line.rb +77 -0
- data/lib/canon/diff/diff_node.rb +56 -0
- data/lib/canon/diff/diff_node_mapper.rb +148 -0
- data/lib/canon/diff/diff_report.rb +133 -0
- data/lib/canon/diff/diff_report_builder.rb +62 -0
- data/lib/canon/diff_formatter/by_line/base_formatter.rb +407 -0
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +672 -0
- data/lib/canon/diff_formatter/by_line/json_formatter.rb +284 -0
- data/lib/canon/diff_formatter/by_line/simple_formatter.rb +190 -0
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +860 -0
- data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +292 -0
- data/lib/canon/diff_formatter/by_object/base_formatter.rb +199 -0
- data/lib/canon/diff_formatter/by_object/json_formatter.rb +305 -0
- data/lib/canon/diff_formatter/by_object/xml_formatter.rb +248 -0
- data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +17 -0
- data/lib/canon/diff_formatter/character_map.yml +197 -0
- data/lib/canon/diff_formatter/debug_output.rb +431 -0
- data/lib/canon/diff_formatter/diff_detail_formatter.rb +551 -0
- data/lib/canon/diff_formatter/legend.rb +141 -0
- data/lib/canon/diff_formatter.rb +520 -0
- data/lib/canon/errors.rb +56 -0
- data/lib/canon/formatters/html4_formatter.rb +17 -0
- data/lib/canon/formatters/html5_formatter.rb +17 -0
- data/lib/canon/formatters/html_formatter.rb +37 -0
- data/lib/canon/formatters/html_formatter_base.rb +163 -0
- data/lib/canon/formatters/json_formatter.rb +3 -0
- data/lib/canon/formatters/xml_formatter.rb +20 -55
- data/lib/canon/formatters/yaml_formatter.rb +4 -1
- data/lib/canon/pretty_printer/html.rb +57 -0
- data/lib/canon/pretty_printer/json.rb +25 -0
- data/lib/canon/pretty_printer/xml.rb +29 -0
- data/lib/canon/rspec_matchers.rb +222 -80
- data/lib/canon/validators/base_validator.rb +49 -0
- data/lib/canon/validators/html_validator.rb +138 -0
- data/lib/canon/validators/json_validator.rb +89 -0
- data/lib/canon/validators/xml_validator.rb +53 -0
- data/lib/canon/validators/yaml_validator.rb +73 -0
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/attribute_handler.rb +80 -0
- data/lib/canon/xml/c14n.rb +36 -0
- data/lib/canon/xml/character_encoder.rb +38 -0
- data/lib/canon/xml/data_model.rb +225 -0
- data/lib/canon/xml/element_matcher.rb +196 -0
- data/lib/canon/xml/line_range_mapper.rb +158 -0
- data/lib/canon/xml/namespace_handler.rb +86 -0
- data/lib/canon/xml/node.rb +32 -0
- data/lib/canon/xml/nodes/attribute_node.rb +54 -0
- data/lib/canon/xml/nodes/comment_node.rb +23 -0
- data/lib/canon/xml/nodes/element_node.rb +56 -0
- data/lib/canon/xml/nodes/namespace_node.rb +38 -0
- data/lib/canon/xml/nodes/processing_instruction_node.rb +24 -0
- data/lib/canon/xml/nodes/root_node.rb +16 -0
- data/lib/canon/xml/nodes/text_node.rb +23 -0
- data/lib/canon/xml/processor.rb +151 -0
- data/lib/canon/xml/whitespace_normalizer.rb +72 -0
- data/lib/canon/xml/xml_base_handler.rb +188 -0
- data/lib/canon.rb +14 -3
- metadata +116 -21
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Semantic Diff Report
|
|
4
|
+
nav_order: 41
|
|
5
|
+
parent: Advanced Topics
|
|
6
|
+
---
|
|
7
|
+
= Canon semantic diff report
|
|
8
|
+
:toc:
|
|
9
|
+
:toclevels: 3
|
|
10
|
+
|
|
11
|
+
== General
|
|
12
|
+
|
|
13
|
+
The Semantic Diff Report provides dimension-specific, actionable details for
|
|
14
|
+
each difference found during comparison. Unlike the detailed line-by-line or
|
|
15
|
+
object tree diffs, which show every changed line, the Semantic Diff Report
|
|
16
|
+
focuses on WHAT changed and WHY it matters.
|
|
17
|
+
|
|
18
|
+
The report is always shown in verbose mode when differences exist, providing
|
|
19
|
+
a high-level summary before the detailed diff output.
|
|
20
|
+
|
|
21
|
+
Key features:
|
|
22
|
+
|
|
23
|
+
* XPath locations for XML/HTML elements
|
|
24
|
+
* JSON path locations for JSON/YAML data
|
|
25
|
+
* Dimension-specific formatting optimized for each type of difference
|
|
26
|
+
* Colorized output for easy visual scanning
|
|
27
|
+
* Whitespace preservation detection for `<pre>`, `<code>`, etc.
|
|
28
|
+
* Actionable change summaries (e.g., "Added: +xmlns:v, +xmlns:o")
|
|
29
|
+
|
|
30
|
+
== Architecture
|
|
31
|
+
|
|
32
|
+
The Semantic Diff Report is generated by the `DiffDetailFormatter` module
|
|
33
|
+
and integrated into the main diff output flow:
|
|
34
|
+
|
|
35
|
+
[source]
|
|
36
|
+
----
|
|
37
|
+
╔═════════════════════════════════════════════════════════════════════╗
|
|
38
|
+
║ SEMANTIC DIFF REPORT ARCHITECTURE ║
|
|
39
|
+
╚═════════════════════════════════════════════════════════════════════╝
|
|
40
|
+
|
|
41
|
+
Input: ComparisonResult with differences array
|
|
42
|
+
|
|
43
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
44
|
+
│ DiffDetailFormatter.format_report(differences) │
|
|
45
|
+
│ │
|
|
46
|
+
│ For each difference: │
|
|
47
|
+
│ 1. Detect type (DiffNode for XML/HTML, Hash for JSON/YAML) │
|
|
48
|
+
│ 2. Extract location (XPath or JSON path) │
|
|
49
|
+
│ 3. Dispatch to dimension-specific formatter │
|
|
50
|
+
│ 4. Format as vertical section with colors │
|
|
51
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
52
|
+
│
|
|
53
|
+
▼
|
|
54
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ Dimension-Specific Formatters │
|
|
56
|
+
│ │
|
|
57
|
+
│ XML/HTML: JSON/YAML: │
|
|
58
|
+
│ • attribute_presence • hash_diff │
|
|
59
|
+
│ • attribute_values (unified handling) │
|
|
60
|
+
│ • text_content │
|
|
61
|
+
│ • structural_whitespace │
|
|
62
|
+
│ • comments │
|
|
63
|
+
│ │
|
|
64
|
+
│ Each formatter returns: │
|
|
65
|
+
│ [detail1, detail2, changes_summary] │
|
|
66
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
67
|
+
│
|
|
68
|
+
▼
|
|
69
|
+
Output: Formatted semantic diff report with:
|
|
70
|
+
• Header with difference count
|
|
71
|
+
• Individual difference sections
|
|
72
|
+
• Color-coded for easy scanning
|
|
73
|
+
----
|
|
74
|
+
|
|
75
|
+
=== Integration with DiffFormatter
|
|
76
|
+
|
|
77
|
+
The Semantic Diff Report is integrated at the `format_comparison_result()`
|
|
78
|
+
level:
|
|
79
|
+
|
|
80
|
+
[source,ruby]
|
|
81
|
+
----
|
|
82
|
+
# In Canon::DiffFormatter
|
|
83
|
+
def format_comparison_result(comparison_result, expected, actual)
|
|
84
|
+
output = []
|
|
85
|
+
|
|
86
|
+
# 1. CANON VERBOSE tables (optional)
|
|
87
|
+
output << DebugOutput.verbose_tables_only(...)
|
|
88
|
+
|
|
89
|
+
# 2. Semantic Diff Report (always if diffs exist)
|
|
90
|
+
if comparison_result.differences.any?
|
|
91
|
+
output << DiffDetailFormatter.format_report(differences)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 3. Detailed diff (always)
|
|
95
|
+
output << format(...)
|
|
96
|
+
|
|
97
|
+
output.compact.join("\n")
|
|
98
|
+
end
|
|
99
|
+
----
|
|
100
|
+
|
|
101
|
+
This ensures the Semantic Diff Report is part of the main output, not debug
|
|
102
|
+
information.
|
|
103
|
+
|
|
104
|
+
== Output format
|
|
105
|
+
|
|
106
|
+
=== General structure
|
|
107
|
+
|
|
108
|
+
Each difference is displayed as a vertical section with these components:
|
|
109
|
+
|
|
110
|
+
[example]
|
|
111
|
+
====
|
|
112
|
+
[source]
|
|
113
|
+
----
|
|
114
|
+
🔍 DIFFERENCE #1/3 [STATUS]
|
|
115
|
+
──────────────────────────────────────────────────────────────────────
|
|
116
|
+
Dimension: dimension_name
|
|
117
|
+
Location: /xpath/or/json.path
|
|
118
|
+
|
|
119
|
+
⊖ Expected (File 1):
|
|
120
|
+
Details...
|
|
121
|
+
|
|
122
|
+
⊕ Actual (File 2):
|
|
123
|
+
Details...
|
|
124
|
+
|
|
125
|
+
✨ Changes:
|
|
126
|
+
Summary of what changed
|
|
127
|
+
----
|
|
128
|
+
====
|
|
129
|
+
|
|
130
|
+
Where:
|
|
131
|
+
|
|
132
|
+
`STATUS`:: Either `[NORMATIVE]` (green) or `[INFORMATIVE]` (yellow)
|
|
133
|
+
`Dimension`:: The match dimension that detected this difference (magenta)
|
|
134
|
+
`Location`:: XPath for XML/HTML, JSON path for JSON/YAML (blue)
|
|
135
|
+
`Expected`:: What was in File 1 (red heading)
|
|
136
|
+
`Actual`:: What was in File 2 (green heading)
|
|
137
|
+
`Changes`:: Actionable summary (yellow)
|
|
138
|
+
|
|
139
|
+
=== Color scheme
|
|
140
|
+
|
|
141
|
+
The report uses colors to make scanning easy:
|
|
142
|
+
|
|
143
|
+
* **Dimension name**: Magenta
|
|
144
|
+
* **XPath/JSON path**: Blue
|
|
145
|
+
* **Expected heading**: Red (bold)
|
|
146
|
+
* **Actual heading**: Green (bold)
|
|
147
|
+
* **Changes heading**: Yellow (bold)
|
|
148
|
+
* **Status [NORMATIVE]**: Green (bold)
|
|
149
|
+
* **Status [INFORMATIVE]**: Yellow (bold)
|
|
150
|
+
* **Added items**: Green (with `+` prefix)
|
|
151
|
+
* **Removed items**: Red (with `-` prefix)
|
|
152
|
+
* **Element names**: Magenta
|
|
153
|
+
* **Attribute names**: Cyan
|
|
154
|
+
|
|
155
|
+
=== Vertical layout
|
|
156
|
+
|
|
157
|
+
The vertical layout ensures no width constraints, making it easy to read
|
|
158
|
+
even with long attribute lists or deeply nested paths.
|
|
159
|
+
|
|
160
|
+
== XML/HTML dimensions
|
|
161
|
+
|
|
162
|
+
=== General
|
|
163
|
+
|
|
164
|
+
XML and HTML comparisons use the same set of dimensions, classified based
|
|
165
|
+
on what aspect of the document differs.
|
|
166
|
+
|
|
167
|
+
=== Attribute presence differences
|
|
168
|
+
|
|
169
|
+
Reports when attributes are missing or extra.
|
|
170
|
+
|
|
171
|
+
[example]
|
|
172
|
+
====
|
|
173
|
+
[source]
|
|
174
|
+
----
|
|
175
|
+
🔍 DIFFERENCE #1/1 [NORMATIVE]
|
|
176
|
+
──────────────────────────────────────────────────────────────────────
|
|
177
|
+
Dimension: attribute_presence
|
|
178
|
+
Location: /html/body/p
|
|
179
|
+
|
|
180
|
+
⊖ Expected (File 1):
|
|
181
|
+
<p> with 2 attributes: id, lang
|
|
182
|
+
|
|
183
|
+
⊕ Actual (File 2):
|
|
184
|
+
<p> with 5 attributes: id, lang, xmlns:o, xmlns:v, xmlns:w
|
|
185
|
+
|
|
186
|
+
✨ Changes:
|
|
187
|
+
Added: +xmlns:o, +xmlns:v, +xmlns:w
|
|
188
|
+
----
|
|
189
|
+
====
|
|
190
|
+
|
|
191
|
+
The report shows:
|
|
192
|
+
|
|
193
|
+
* Element name
|
|
194
|
+
* Total attribute count in each document
|
|
195
|
+
* Complete list of attributes
|
|
196
|
+
* Which were added (green `+` prefix) or removed (red `-` prefix)
|
|
197
|
+
|
|
198
|
+
This makes it immediately clear what needs to be added or removed to fix
|
|
199
|
+
the test.
|
|
200
|
+
|
|
201
|
+
=== Attribute value differences
|
|
202
|
+
|
|
203
|
+
Reports when an attribute value differs.
|
|
204
|
+
|
|
205
|
+
[example]
|
|
206
|
+
====
|
|
207
|
+
[source]
|
|
208
|
+
----
|
|
209
|
+
🔍 DIFFERENCE #1/1 [NORMATIVE]
|
|
210
|
+
──────────────────────────────────────────────────────────────────────
|
|
211
|
+
Dimension: attribute_values
|
|
212
|
+
Location: /html/body/div[@id="main"]
|
|
213
|
+
|
|
214
|
+
⊖ Expected (File 1):
|
|
215
|
+
<div> class=" container fluid "
|
|
216
|
+
|
|
217
|
+
⊕ Actual (File 2):
|
|
218
|
+
<div> class="container fluid"
|
|
219
|
+
|
|
220
|
+
✨ Changes:
|
|
221
|
+
Whitespace normalization difference
|
|
222
|
+
----
|
|
223
|
+
====
|
|
224
|
+
|
|
225
|
+
The report shows:
|
|
226
|
+
|
|
227
|
+
* Which specific attribute has different value (cyan highlighting)
|
|
228
|
+
* Exact values on both sides (with quotes)
|
|
229
|
+
* Analysis of the difference type:
|
|
230
|
+
** "Whitespace difference only" - Only leading/trailing whitespace differs
|
|
231
|
+
** "Whitespace normalization difference" - Whitespace runs differ
|
|
232
|
+
** "Value changed" - Actual content differs
|
|
233
|
+
|
|
234
|
+
=== Text content differences
|
|
235
|
+
|
|
236
|
+
Reports when element text content differs.
|
|
237
|
+
|
|
238
|
+
[example]
|
|
239
|
+
====
|
|
240
|
+
[source]
|
|
241
|
+
----
|
|
242
|
+
🔍 DIFFERENCE #1/1 [NORMATIVE]
|
|
243
|
+
──────────────────────────────────────────────────────────────────────
|
|
244
|
+
Dimension: text_content
|
|
245
|
+
Location: /html/body/div/table/tbody/tr/td/pre/text
|
|
246
|
+
|
|
247
|
+
⊖ Expected (File 1):
|
|
248
|
+
<text> "
|
|
249
|
+
puts \"Hello, world.\"
|
|
250
|
+
"
|
|
251
|
+
|
|
252
|
+
⊕ Actual (File 2):
|
|
253
|
+
<text> "puts \"Hello, world.\" "
|
|
254
|
+
|
|
255
|
+
✨ Changes:
|
|
256
|
+
⚠️ Whitespace preserved (inside <pre>, <code>, etc. - whitespace
|
|
257
|
+
is significant)
|
|
258
|
+
----
|
|
259
|
+
====
|
|
260
|
+
|
|
261
|
+
The report shows:
|
|
262
|
+
|
|
263
|
+
* Text preview (truncated at 100 characters if long)
|
|
264
|
+
* Element containing the text
|
|
265
|
+
* Special warning if the text is inside whitespace-preserving elements
|
|
266
|
+
(`<pre>`, `<code>`, `<textarea>`, `<script>`, `<style>`)
|
|
267
|
+
|
|
268
|
+
The whitespace warning is important because Canon automatically switches
|
|
269
|
+
from `text_content: normalize` to `:strict` mode inside these elements.
|
|
270
|
+
|
|
271
|
+
=== Structural whitespace differences
|
|
272
|
+
|
|
273
|
+
Reports whitespace-only text differences (usually informative).
|
|
274
|
+
|
|
275
|
+
[example]
|
|
276
|
+
====
|
|
277
|
+
[source]
|
|
278
|
+
----
|
|
279
|
+
🔍 DIFFERENCE #1/1 [INFORMATIVE]
|
|
280
|
+
──────────────────────────────────────────────────────────────────────
|
|
281
|
+
Dimension: structural_whitespace
|
|
282
|
+
Location: /root/section/p
|
|
283
|
+
|
|
284
|
+
⊖ Expected (File 1):
|
|
285
|
+
<p> "hello␣␣world"
|
|
286
|
+
|
|
287
|
+
⊕ Actual (File 2):
|
|
288
|
+
<p> "hello␣world"
|
|
289
|
+
|
|
290
|
+
✨ Changes:
|
|
291
|
+
Whitespace-only difference (informative)
|
|
292
|
+
----
|
|
293
|
+
====
|
|
294
|
+
|
|
295
|
+
The report shows:
|
|
296
|
+
|
|
297
|
+
* Whitespace visualized using Unicode symbols:
|
|
298
|
+
** `␣` - Space (U+0020)
|
|
299
|
+
** `→` - Tab
|
|
300
|
+
** `↵` - Newline
|
|
301
|
+
* Marked as `[INFORMATIVE]` (yellow) when `structural_whitespace: ignore`
|
|
302
|
+
|
|
303
|
+
=== Comment differences
|
|
304
|
+
|
|
305
|
+
Reports when HTML/XML comment content differs.
|
|
306
|
+
|
|
307
|
+
[example]
|
|
308
|
+
====
|
|
309
|
+
[source]
|
|
310
|
+
----
|
|
311
|
+
🔍 DIFFERENCE #1/1 [INFORMATIVE]
|
|
312
|
+
──────────────────────────────────────────────────────────────────────
|
|
313
|
+
Dimension: comments
|
|
314
|
+
Location: /html/head
|
|
315
|
+
|
|
316
|
+
⊖ Expected (File 1):
|
|
317
|
+
<!-- Original comment text -->
|
|
318
|
+
|
|
319
|
+
⊕ Actual (File 2):
|
|
320
|
+
<!-- Modified comment text -->
|
|
321
|
+
|
|
322
|
+
✨ Changes:
|
|
323
|
+
Comment content differs
|
|
324
|
+
----
|
|
325
|
+
====
|
|
326
|
+
|
|
327
|
+
== JSON/YAML dimensions
|
|
328
|
+
|
|
329
|
+
=== General
|
|
330
|
+
|
|
331
|
+
JSON and YAML comparisons use path-based difference reporting with Hash
|
|
332
|
+
objects containing:
|
|
333
|
+
|
|
334
|
+
* `:path` - The JSON path to the difference (e.g., `user.profile.email`)
|
|
335
|
+
* `:value1` - Expected value
|
|
336
|
+
* `:value2` - Actual value
|
|
337
|
+
* `:diff_code` - Type of difference (MISSING_HASH_KEY,
|
|
338
|
+
UNEQUAL_PRIMITIVES, etc.)
|
|
339
|
+
|
|
340
|
+
=== Hash key differences
|
|
341
|
+
|
|
342
|
+
Reports when a key is missing or has different value.
|
|
343
|
+
|
|
344
|
+
[example]
|
|
345
|
+
====
|
|
346
|
+
[source]
|
|
347
|
+
----
|
|
348
|
+
🔍 DIFFERENCE #1/1 [NORMATIVE]
|
|
349
|
+
──────────────────────────────────────────────────────────────────────
|
|
350
|
+
Dimension: 2
|
|
351
|
+
Location: user.email
|
|
352
|
+
|
|
353
|
+
⊖ Expected (File 1):
|
|
354
|
+
user.email = "alice@example.com"
|
|
355
|
+
|
|
356
|
+
⊕ Actual (File 2):
|
|
357
|
+
user.email = nil
|
|
358
|
+
|
|
359
|
+
✨ Changes:
|
|
360
|
+
Key missing
|
|
361
|
+
----
|
|
362
|
+
====
|
|
363
|
+
|
|
364
|
+
=== Primitive value differences
|
|
365
|
+
|
|
366
|
+
Reports when primitive values (strings, numbers, booleans) differ.
|
|
367
|
+
|
|
368
|
+
[example]
|
|
369
|
+
====
|
|
370
|
+
[source]
|
|
371
|
+
----
|
|
372
|
+
🔍 DIFFERENCE #1/1 [NORMATIVE]
|
|
373
|
+
──────────────────────────────────────────────────────────────────────
|
|
374
|
+
Dimension: 15
|
|
375
|
+
Location: users[0].age
|
|
376
|
+
|
|
377
|
+
⊖ Expected (File 1):
|
|
378
|
+
users[0].age = 25
|
|
379
|
+
|
|
380
|
+
⊕ Actual (File 2):
|
|
381
|
+
users[0].age = 30
|
|
382
|
+
|
|
383
|
+
✨ Changes:
|
|
384
|
+
Value changed
|
|
385
|
+
----
|
|
386
|
+
====
|
|
387
|
+
|
|
388
|
+
=== Array differences
|
|
389
|
+
|
|
390
|
+
Reports when arrays have different lengths or elements.
|
|
391
|
+
|
|
392
|
+
[example]
|
|
393
|
+
====
|
|
394
|
+
[source]
|
|
395
|
+
----
|
|
396
|
+
🔍 DIFFERENCE #1/1 [NORMATIVE]
|
|
397
|
+
──────────────────────────────────────────────────────────────────────
|
|
398
|
+
Dimension: 12
|
|
399
|
+
Location: items
|
|
400
|
+
|
|
401
|
+
⊖ Expected (File 1):
|
|
402
|
+
items = [...] (5 items)
|
|
403
|
+
|
|
404
|
+
⊕ Actual (File 2):
|
|
405
|
+
items = [...] (3 items)
|
|
406
|
+
|
|
407
|
+
✨ Changes:
|
|
408
|
+
Array length differs
|
|
409
|
+
----
|
|
410
|
+
====
|
|
411
|
+
|
|
412
|
+
Complex values (hashes, arrays) are shown as `{...} (N keys)` or
|
|
413
|
+
`[...] (N items)` to keep output concise.
|
|
414
|
+
|
|
415
|
+
== Special features
|
|
416
|
+
|
|
417
|
+
=== XPath location extraction
|
|
418
|
+
|
|
419
|
+
For XML/HTML differences, the report extracts XPath with:
|
|
420
|
+
|
|
421
|
+
* Full path from root: `/html/body/div/section/p`
|
|
422
|
+
* Position predicates when multiple siblings: `/p[2]`, `/div[3]`
|
|
423
|
+
* Safe traversal with depth limits to prevent infinite loops
|
|
424
|
+
* Graceful error handling for circular references
|
|
425
|
+
* Document node detection to stop at appropriate boundaries
|
|
426
|
+
|
|
427
|
+
=== Whitespace preservation detection
|
|
428
|
+
|
|
429
|
+
The report detects when text is inside whitespace-preserving HTML elements
|
|
430
|
+
and shows a special warning:
|
|
431
|
+
|
|
432
|
+
[source]
|
|
433
|
+
----
|
|
434
|
+
✨ Changes: ⚠️ Whitespace preserved (inside <pre>, <code>, etc. -
|
|
435
|
+
whitespace is significant)
|
|
436
|
+
----
|
|
437
|
+
|
|
438
|
+
This is important because Canon automatically switches to `:strict` mode
|
|
439
|
+
for text content inside these elements:
|
|
440
|
+
|
|
441
|
+
* `<pre>` - Preformatted text
|
|
442
|
+
* `<code>` - Code blocks
|
|
443
|
+
* `<textarea>` - Text input areas
|
|
444
|
+
* `<script>` - JavaScript code
|
|
445
|
+
* `<style>` - CSS style sheets
|
|
446
|
+
|
|
447
|
+
The warning helps developers understand why a seemingly minor whitespace
|
|
448
|
+
difference is causing a test failure.
|
|
449
|
+
|
|
450
|
+
=== Comprehensive error handling
|
|
451
|
+
|
|
452
|
+
The formatter includes multiple layers of error handling:
|
|
453
|
+
|
|
454
|
+
* Top-level rescue in `format_single_diff()` - Catches any formatting
|
|
455
|
+
errors
|
|
456
|
+
* Safe XPath extraction with depth limits and circular reference detection
|
|
457
|
+
* Safe parent traversal with document node checks
|
|
458
|
+
* Graceful fallbacks when node types are unexpected
|
|
459
|
+
|
|
460
|
+
This ensures the Semantic Diff Report never crashes, even with unusual DOM
|
|
461
|
+
structures.
|
|
462
|
+
|
|
463
|
+
== Implementation
|
|
464
|
+
|
|
465
|
+
=== DiffDetailFormatter class
|
|
466
|
+
|
|
467
|
+
Module: `Canon::DiffFormatter::DiffDetailFormatter`
|
|
468
|
+
|
|
469
|
+
Location: `lib/canon/diff_formatter/diff_detail_formatter.rb`
|
|
470
|
+
|
|
471
|
+
Main entry point:
|
|
472
|
+
|
|
473
|
+
[source,ruby]
|
|
474
|
+
----
|
|
475
|
+
# Format all differences as semantic diff report
|
|
476
|
+
def self.format_report(differences, use_color: true)
|
|
477
|
+
# Returns formatted string with all difference sections
|
|
478
|
+
end
|
|
479
|
+
----
|
|
480
|
+
|
|
481
|
+
=== Dimension dispatch mechanism
|
|
482
|
+
|
|
483
|
+
The formatter uses dimension-based dispatch:
|
|
484
|
+
|
|
485
|
+
[source,ruby]
|
|
486
|
+
----
|
|
487
|
+
def format_dimension_details(diff, use_color)
|
|
488
|
+
# Handle Hash diffs (JSON/YAML)
|
|
489
|
+
return format_hash_diff_details(diff) if diff.is_a?(Hash)
|
|
490
|
+
|
|
491
|
+
# Handle DiffNode (XML/HTML) based on dimension
|
|
492
|
+
case diff.dimension
|
|
493
|
+
when :attribute_presence
|
|
494
|
+
format_attribute_presence_details(diff)
|
|
495
|
+
when :attribute_values
|
|
496
|
+
format_attribute_values_details(diff)
|
|
497
|
+
when :text_content
|
|
498
|
+
format_text_content_details(diff)
|
|
499
|
+
# ... other dimensions
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
----
|
|
503
|
+
|
|
504
|
+
This ensures each difference type is optimally formatted.
|
|
505
|
+
|
|
506
|
+
=== Helper methods
|
|
507
|
+
|
|
508
|
+
Key helper methods:
|
|
509
|
+
|
|
510
|
+
`extract_xpath(node)`:: Extracts XPath from XML/HTML nodes with safety
|
|
511
|
+
limits and error handling
|
|
512
|
+
|
|
513
|
+
`extract_location(diff)`:: Dispatches to XPath extraction for XML/HTML or
|
|
514
|
+
returns JSON path for JSON/YAML
|
|
515
|
+
|
|
516
|
+
`inside_preserve_element?(node)`:: Detects if node is inside `<pre>`,
|
|
517
|
+
`<code>`, etc. with safe parent traversal
|
|
518
|
+
|
|
519
|
+
`get_attribute_names(node)`:: Extracts sorted attribute names from elements
|
|
520
|
+
|
|
521
|
+
`find_differing_attribute(node1, node2)`:: Finds which attribute has
|
|
522
|
+
different value
|
|
523
|
+
|
|
524
|
+
`format_json_value(value)`:: Formats JSON values concisely ({...},
|
|
525
|
+
[...], primitives)
|
|
526
|
+
|
|
527
|
+
All helpers include comprehensive error handling to ensure the report never
|
|
528
|
+
crashes.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Understanding Canon
|
|
4
|
+
nav_order: 3
|
|
5
|
+
has_children: true
|
|
6
|
+
---
|
|
7
|
+
= Understanding Canon
|
|
8
|
+
|
|
9
|
+
Learn how Canon works internally:
|
|
10
|
+
|
|
11
|
+
* **link:FORMATS[Format support]** - XML, HTML, JSON, YAML
|
|
12
|
+
canonicalization
|
|
13
|
+
* **link:MODES[Diff modes]** - By-line vs by-object comparison modes
|
|
14
|
+
* **link:MATCH_ARCHITECTURE[Match architecture]** - Three-phase
|
|
15
|
+
comparison flow
|
|
16
|
+
|
|
17
|
+
These documents explain Canon's core concepts and architecture.
|