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,719 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Match Options
|
|
4
|
+
nav_order: 30
|
|
5
|
+
parent: Customizing Behavior
|
|
6
|
+
---
|
|
7
|
+
= Match options
|
|
8
|
+
:toc:
|
|
9
|
+
:toclevels: 3
|
|
10
|
+
|
|
11
|
+
== Scope
|
|
12
|
+
|
|
13
|
+
This document provides a complete reference for Canon's match options,
|
|
14
|
+
including match dimensions, behaviors, and predefined profiles.
|
|
15
|
+
|
|
16
|
+
Match options control Phase 2 (semantic matching) of Canon's comparison
|
|
17
|
+
architecture. See link:MATCH_ARCHITECTURE[Match architecture] for the
|
|
18
|
+
complete flow.
|
|
19
|
+
|
|
20
|
+
== General
|
|
21
|
+
|
|
22
|
+
Match options control which aspects of documents are compared and how
|
|
23
|
+
strictly they are compared. Canon provides:
|
|
24
|
+
|
|
25
|
+
* **Match dimensions**: Independent aspects of documents (text, whitespace,
|
|
26
|
+
attributes, etc.)
|
|
27
|
+
* **Dimension behaviors**: How each dimension is compared (`:strict`,
|
|
28
|
+
`:normalize`, `:ignore`)
|
|
29
|
+
* **Match profiles**: Predefined combinations for common scenarios
|
|
30
|
+
|
|
31
|
+
== Match dimensions
|
|
32
|
+
|
|
33
|
+
Match dimensions are orthogonal aspects that can be configured independently.
|
|
34
|
+
|
|
35
|
+
=== text_content
|
|
36
|
+
|
|
37
|
+
**Applies to**: All formats
|
|
38
|
+
|
|
39
|
+
**Purpose**: Controls how text content within elements/values is compared.
|
|
40
|
+
|
|
41
|
+
**Behaviors**:
|
|
42
|
+
|
|
43
|
+
`:strict`:: Text must match exactly, character-for-character including all
|
|
44
|
+
whitespace
|
|
45
|
+
|
|
46
|
+
`:normalize`:: Whitespace is normalized (collapsed/trimmed) before comparison
|
|
47
|
+
|
|
48
|
+
`:ignore`:: Text content is completely ignored in comparison
|
|
49
|
+
|
|
50
|
+
.text_content examples
|
|
51
|
+
[example]
|
|
52
|
+
====
|
|
53
|
+
**Input**:
|
|
54
|
+
|
|
55
|
+
[source,xml]
|
|
56
|
+
----
|
|
57
|
+
<!-- File 1 -->
|
|
58
|
+
<message>Hello World</message>
|
|
59
|
+
|
|
60
|
+
<!-- File 2 -->
|
|
61
|
+
<message>Hello World</message>
|
|
62
|
+
----
|
|
63
|
+
|
|
64
|
+
**Results**:
|
|
65
|
+
|
|
66
|
+
* `:strict` → Different (whitespace differs: 3 spaces vs 1 space)
|
|
67
|
+
* `:normalize` → Equivalent (both normalize to "Hello World")
|
|
68
|
+
* `:ignore` → Equivalent (text content ignored, structure matches)
|
|
69
|
+
====
|
|
70
|
+
|
|
71
|
+
=== structural_whitespace
|
|
72
|
+
|
|
73
|
+
**Applies to**: All formats
|
|
74
|
+
|
|
75
|
+
**Purpose**: Controls how whitespace between elements (indentation, newlines)
|
|
76
|
+
is handled.
|
|
77
|
+
|
|
78
|
+
**Behaviors**:
|
|
79
|
+
|
|
80
|
+
`:strict`:: All structural whitespace must match exactly
|
|
81
|
+
|
|
82
|
+
`:normalize`:: Structural whitespace is normalized
|
|
83
|
+
|
|
84
|
+
`:ignore`:: Structural whitespace is completely ignored
|
|
85
|
+
|
|
86
|
+
.structural_whitespace examples
|
|
87
|
+
[example]
|
|
88
|
+
====
|
|
89
|
+
**Input**:
|
|
90
|
+
|
|
91
|
+
[source,xml]
|
|
92
|
+
----
|
|
93
|
+
<!-- File 1 -->
|
|
94
|
+
<root>
|
|
95
|
+
<item>A</item>
|
|
96
|
+
<item>B</item>
|
|
97
|
+
</root>
|
|
98
|
+
|
|
99
|
+
<!-- File 2 -->
|
|
100
|
+
<root><item>A</item><item>B</item></root>
|
|
101
|
+
----
|
|
102
|
+
|
|
103
|
+
**Results**:
|
|
104
|
+
|
|
105
|
+
* `:strict` → Different (indentation and newlines differ)
|
|
106
|
+
* `:normalize` → Equivalent (whitespace between elements normalized)
|
|
107
|
+
* `:ignore` → Equivalent (only element structure compared)
|
|
108
|
+
====
|
|
109
|
+
|
|
110
|
+
=== attribute_whitespace
|
|
111
|
+
|
|
112
|
+
**Applies to**: XML, HTML only
|
|
113
|
+
|
|
114
|
+
**Purpose**: Controls how whitespace in attribute values is handled.
|
|
115
|
+
|
|
116
|
+
**Behaviors**:
|
|
117
|
+
|
|
118
|
+
`:strict`:: Attribute value whitespace must match exactly
|
|
119
|
+
|
|
120
|
+
`:normalize`:: Whitespace in attribute values is normalized
|
|
121
|
+
|
|
122
|
+
`:ignore`:: Whitespace in attribute values is ignored
|
|
123
|
+
|
|
124
|
+
.attribute_whitespace examples
|
|
125
|
+
[example]
|
|
126
|
+
====
|
|
127
|
+
**Input**:
|
|
128
|
+
|
|
129
|
+
[source,xml]
|
|
130
|
+
----
|
|
131
|
+
<!-- File 1 -->
|
|
132
|
+
<div class="item active">Content</div>
|
|
133
|
+
|
|
134
|
+
<!-- File 2 -->
|
|
135
|
+
<div class="item active">Content</div>
|
|
136
|
+
----
|
|
137
|
+
|
|
138
|
+
**Results**:
|
|
139
|
+
|
|
140
|
+
* `:strict` → Different (2 spaces vs 1 space)
|
|
141
|
+
* `:normalize` → Equivalent ("item active" normalizes to "item active")
|
|
142
|
+
* `:ignore` → Equivalent (only attribute presence compared)
|
|
143
|
+
|
|
144
|
+
**HTML `class` attribute special handling**:
|
|
145
|
+
|
|
146
|
+
HTML's `class` attribute is space-separated, so normalization is particularly
|
|
147
|
+
useful:
|
|
148
|
+
|
|
149
|
+
[source,html]
|
|
150
|
+
----
|
|
151
|
+
<!-- These are equivalent with :normalize -->
|
|
152
|
+
<div class="btn primary active">Click</div>
|
|
153
|
+
<div class="btn primary active">Click</div>
|
|
154
|
+
----
|
|
155
|
+
====
|
|
156
|
+
|
|
157
|
+
=== attribute_order
|
|
158
|
+
|
|
159
|
+
**Applies to**: XML, HTML only
|
|
160
|
+
|
|
161
|
+
**Purpose**: Controls whether attribute order matters.
|
|
162
|
+
|
|
163
|
+
**Behaviors**:
|
|
164
|
+
|
|
165
|
+
`:strict`:: Attributes must appear in the same order
|
|
166
|
+
|
|
167
|
+
`:ignore`:: Attribute order doesn't matter (set-based comparison)
|
|
168
|
+
|
|
169
|
+
.attribute_order examples
|
|
170
|
+
[example]
|
|
171
|
+
====
|
|
172
|
+
**Input**:
|
|
173
|
+
|
|
174
|
+
[source,xml]
|
|
175
|
+
----
|
|
176
|
+
<!-- File 1 -->
|
|
177
|
+
<element id="123" class="active" data-value="test"/>
|
|
178
|
+
|
|
179
|
+
<!-- File 2 -->
|
|
180
|
+
<element class="active" data-value="test" id="123"/>
|
|
181
|
+
----
|
|
182
|
+
|
|
183
|
+
**Results**:
|
|
184
|
+
|
|
185
|
+
* `:strict` → Different (attribute order differs)
|
|
186
|
+
* `:ignore` → Equivalent (same attributes present, unordered comparison)
|
|
187
|
+
|
|
188
|
+
**HTML default**:
|
|
189
|
+
|
|
190
|
+
HTML attributes are inherently unordered per the HTML specification, so the
|
|
191
|
+
default for HTML is `:ignore`:
|
|
192
|
+
|
|
193
|
+
[source,html]
|
|
194
|
+
----
|
|
195
|
+
<!-- These are always equivalent for HTML -->
|
|
196
|
+
<input type="text" id="name" class="form-control">
|
|
197
|
+
<input class="form-control" id="name" type="text">
|
|
198
|
+
----
|
|
199
|
+
====
|
|
200
|
+
|
|
201
|
+
=== attribute_values
|
|
202
|
+
|
|
203
|
+
**Applies to**: XML, HTML only
|
|
204
|
+
|
|
205
|
+
**Purpose**: Controls how attribute values are compared.
|
|
206
|
+
|
|
207
|
+
**Behaviors**:
|
|
208
|
+
|
|
209
|
+
`:strict`:: Attribute values must match exactly
|
|
210
|
+
|
|
211
|
+
`:normalize`:: Whitespace in values is normalized
|
|
212
|
+
|
|
213
|
+
`:ignore`:: Only attribute presence is checked, values ignored
|
|
214
|
+
|
|
215
|
+
.attribute_values examples
|
|
216
|
+
[example]
|
|
217
|
+
====
|
|
218
|
+
**Input**:
|
|
219
|
+
|
|
220
|
+
[source,xml]
|
|
221
|
+
----
|
|
222
|
+
<!-- File 1 -->
|
|
223
|
+
<element id="123" class="normative"/>
|
|
224
|
+
|
|
225
|
+
<!-- File 2 -->
|
|
226
|
+
<element id="456" class="informative"/>
|
|
227
|
+
----
|
|
228
|
+
|
|
229
|
+
**Results**:
|
|
230
|
+
|
|
231
|
+
* `:strict` → Different (attribute values differ)
|
|
232
|
+
* `:normalize` → Different (values still differ after normalization)
|
|
233
|
+
* `:ignore` → Equivalent (both have `id` and `class` attributes, values
|
|
234
|
+
ignored)
|
|
235
|
+
|
|
236
|
+
**Use case**: Useful when you want to verify that certain attributes exist
|
|
237
|
+
but don't care about their specific values (e.g., testing that generated IDs
|
|
238
|
+
are present).
|
|
239
|
+
====
|
|
240
|
+
|
|
241
|
+
=== key_order
|
|
242
|
+
|
|
243
|
+
**Applies to**: JSON, YAML only
|
|
244
|
+
|
|
245
|
+
**Purpose**: Controls whether object key order matters.
|
|
246
|
+
|
|
247
|
+
**Behaviors**:
|
|
248
|
+
|
|
249
|
+
`:strict`:: Keys must appear in the same order
|
|
250
|
+
|
|
251
|
+
`:ignore`:: Key order doesn't matter (unordered comparison)
|
|
252
|
+
|
|
253
|
+
.key_order examples
|
|
254
|
+
[example]
|
|
255
|
+
====
|
|
256
|
+
**JSON input**:
|
|
257
|
+
|
|
258
|
+
[source,json]
|
|
259
|
+
----
|
|
260
|
+
// File 1
|
|
261
|
+
{
|
|
262
|
+
"name": "John",
|
|
263
|
+
"age": 30,
|
|
264
|
+
"city": "NYC"
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// File 2
|
|
268
|
+
{
|
|
269
|
+
"city": "NYC",
|
|
270
|
+
"name": "John",
|
|
271
|
+
"age": 30
|
|
272
|
+
}
|
|
273
|
+
----
|
|
274
|
+
|
|
275
|
+
**Results**:
|
|
276
|
+
|
|
277
|
+
* `:strict` → Different (key order differs)
|
|
278
|
+
* `:ignore` → Equivalent (same keys and values, unordered)
|
|
279
|
+
|
|
280
|
+
**YAML input**:
|
|
281
|
+
|
|
282
|
+
[source,yaml]
|
|
283
|
+
----
|
|
284
|
+
# File 1
|
|
285
|
+
name: John
|
|
286
|
+
age: 30
|
|
287
|
+
city: NYC
|
|
288
|
+
|
|
289
|
+
# File 2
|
|
290
|
+
city: NYC
|
|
291
|
+
name: John
|
|
292
|
+
age: 30
|
|
293
|
+
----
|
|
294
|
+
|
|
295
|
+
**Results**:
|
|
296
|
+
|
|
297
|
+
* `:strict` → Different (key order differs)
|
|
298
|
+
* `:ignore` → Equivalent (same structure and values)
|
|
299
|
+
====
|
|
300
|
+
|
|
301
|
+
=== comments
|
|
302
|
+
|
|
303
|
+
**Applies to**: XML, HTML, YAML (JSON doesn't support comments in standard
|
|
304
|
+
spec)
|
|
305
|
+
|
|
306
|
+
**Purpose**: Controls how comments are compared.
|
|
307
|
+
|
|
308
|
+
**Behaviors**:
|
|
309
|
+
|
|
310
|
+
`:strict`:: Comments must match exactly (including whitespace)
|
|
311
|
+
|
|
312
|
+
`:normalize`:: Whitespace in comments is normalized
|
|
313
|
+
|
|
314
|
+
`:ignore`:: Comments are completely ignored
|
|
315
|
+
|
|
316
|
+
.comments examples
|
|
317
|
+
[example]
|
|
318
|
+
====
|
|
319
|
+
**XML input**:
|
|
320
|
+
|
|
321
|
+
[source,xml]
|
|
322
|
+
----
|
|
323
|
+
<!-- File 1 -->
|
|
324
|
+
<root>
|
|
325
|
+
<!-- This is a comment -->
|
|
326
|
+
<element>Value</element>
|
|
327
|
+
</root>
|
|
328
|
+
|
|
329
|
+
<!-- File 2 -->
|
|
330
|
+
<root>
|
|
331
|
+
<element>Value</element>
|
|
332
|
+
</root>
|
|
333
|
+
----
|
|
334
|
+
|
|
335
|
+
**Results**:
|
|
336
|
+
|
|
337
|
+
* `:strict` → Different (File 1 has a comment, File 2 doesn't)
|
|
338
|
+
* `:normalize` → Different (still different, comment present vs absent)
|
|
339
|
+
* `:ignore` → Equivalent (comments ignored, structure matches)
|
|
340
|
+
|
|
341
|
+
**YAML input**:
|
|
342
|
+
|
|
343
|
+
[source,yaml]
|
|
344
|
+
----
|
|
345
|
+
# File 1
|
|
346
|
+
# Configuration file
|
|
347
|
+
name: test
|
|
348
|
+
# Database settings
|
|
349
|
+
database: prod
|
|
350
|
+
|
|
351
|
+
# File 2
|
|
352
|
+
name: test
|
|
353
|
+
database: prod
|
|
354
|
+
----
|
|
355
|
+
|
|
356
|
+
**Results**:
|
|
357
|
+
|
|
358
|
+
* `:strict` → Different (comments differ)
|
|
359
|
+
* `:normalize` → Different (comments still differ)
|
|
360
|
+
* `:ignore` → Equivalent (comments ignored)
|
|
361
|
+
====
|
|
362
|
+
|
|
363
|
+
== Match profiles
|
|
364
|
+
|
|
365
|
+
Profiles are predefined combinations of dimension settings for common
|
|
366
|
+
scenarios.
|
|
367
|
+
|
|
368
|
+
=== strict
|
|
369
|
+
|
|
370
|
+
**Purpose**: Exact matching - all dimensions use `:strict` behavior.
|
|
371
|
+
|
|
372
|
+
**When to use**:
|
|
373
|
+
|
|
374
|
+
* Character-perfect matching required
|
|
375
|
+
* Testing exact serializer output
|
|
376
|
+
* Verifying formatting compliance
|
|
377
|
+
* Maximum strictness needed
|
|
378
|
+
|
|
379
|
+
**Settings**:
|
|
380
|
+
|
|
381
|
+
[source,ruby]
|
|
382
|
+
----
|
|
383
|
+
{
|
|
384
|
+
preprocessing: :none,
|
|
385
|
+
text_content: :strict,
|
|
386
|
+
structural_whitespace: :strict,
|
|
387
|
+
attribute_whitespace: :strict,
|
|
388
|
+
attribute_order: :strict,
|
|
389
|
+
attribute_values: :strict,
|
|
390
|
+
key_order: :strict,
|
|
391
|
+
comments: :strict
|
|
392
|
+
}
|
|
393
|
+
----
|
|
394
|
+
|
|
395
|
+
.strict profile usage
|
|
396
|
+
[example]
|
|
397
|
+
====
|
|
398
|
+
[source,ruby]
|
|
399
|
+
----
|
|
400
|
+
Canon::Comparison.equivalent?(doc1, doc2,
|
|
401
|
+
match_profile: :strict
|
|
402
|
+
)
|
|
403
|
+
----
|
|
404
|
+
|
|
405
|
+
Every aspect must match exactly.
|
|
406
|
+
====
|
|
407
|
+
|
|
408
|
+
=== rendered
|
|
409
|
+
|
|
410
|
+
**Purpose**: Mimics how browsers/CSS engines render content.
|
|
411
|
+
|
|
412
|
+
**When to use**:
|
|
413
|
+
|
|
414
|
+
* Comparing HTML rendered output
|
|
415
|
+
* Formatting doesn't affect display
|
|
416
|
+
* Testing web page generation
|
|
417
|
+
* Browser-equivalent comparison
|
|
418
|
+
|
|
419
|
+
**Settings**:
|
|
420
|
+
|
|
421
|
+
[source,ruby]
|
|
422
|
+
----
|
|
423
|
+
{
|
|
424
|
+
preprocessing: :none,
|
|
425
|
+
text_content: :normalize,
|
|
426
|
+
structural_whitespace: :normalize,
|
|
427
|
+
attribute_whitespace: :normalize,
|
|
428
|
+
attribute_order: :ignore,
|
|
429
|
+
attribute_values: :strict,
|
|
430
|
+
key_order: :ignore,
|
|
431
|
+
comments: :ignore
|
|
432
|
+
}
|
|
433
|
+
----
|
|
434
|
+
|
|
435
|
+
.rendered profile usage
|
|
436
|
+
[example]
|
|
437
|
+
====
|
|
438
|
+
[source,ruby]
|
|
439
|
+
----
|
|
440
|
+
Canon::Comparison.equivalent?(html1, html2,
|
|
441
|
+
match_profile: :rendered
|
|
442
|
+
)
|
|
443
|
+
----
|
|
444
|
+
|
|
445
|
+
Focuses on how content would appear in a browser.
|
|
446
|
+
====
|
|
447
|
+
|
|
448
|
+
=== spec_friendly
|
|
449
|
+
|
|
450
|
+
**Purpose**: Test-friendly comparison that ignores most formatting
|
|
451
|
+
differences.
|
|
452
|
+
|
|
453
|
+
**When to use**:
|
|
454
|
+
|
|
455
|
+
* Writing RSpec tests
|
|
456
|
+
* Testing semantic correctness
|
|
457
|
+
* Ignoring pretty-printing differences
|
|
458
|
+
* Most common test scenario
|
|
459
|
+
|
|
460
|
+
**Settings**:
|
|
461
|
+
|
|
462
|
+
[source,ruby]
|
|
463
|
+
----
|
|
464
|
+
{
|
|
465
|
+
preprocessing: :normalize,
|
|
466
|
+
text_content: :normalize,
|
|
467
|
+
structural_whitespace: :ignore,
|
|
468
|
+
attribute_whitespace: :normalize,
|
|
469
|
+
attribute_order: :ignore,
|
|
470
|
+
attribute_values: :strict,
|
|
471
|
+
key_order: :ignore,
|
|
472
|
+
comments: :ignore
|
|
473
|
+
}
|
|
474
|
+
----
|
|
475
|
+
|
|
476
|
+
.spec_friendly profile usage
|
|
477
|
+
[example]
|
|
478
|
+
====
|
|
479
|
+
[source,ruby]
|
|
480
|
+
----
|
|
481
|
+
Canon::Comparison.equivalent?(doc1, doc2,
|
|
482
|
+
match_profile: :spec_friendly
|
|
483
|
+
)
|
|
484
|
+
----
|
|
485
|
+
|
|
486
|
+
Focuses on content, not formatting.
|
|
487
|
+
====
|
|
488
|
+
|
|
489
|
+
=== content_only
|
|
490
|
+
|
|
491
|
+
**Purpose**: Only semantic content matters - maximum tolerance for formatting.
|
|
492
|
+
|
|
493
|
+
**When to use**:
|
|
494
|
+
|
|
495
|
+
* Only care about data, not presentation
|
|
496
|
+
* Maximum flexibility needed
|
|
497
|
+
* Comparing across different formats
|
|
498
|
+
* Structural equivalence only
|
|
499
|
+
|
|
500
|
+
**Settings**:
|
|
501
|
+
|
|
502
|
+
[source,ruby]
|
|
503
|
+
----
|
|
504
|
+
{
|
|
505
|
+
preprocessing: :normalize,
|
|
506
|
+
text_content: :normalize,
|
|
507
|
+
structural_whitespace: :ignore,
|
|
508
|
+
attribute_whitespace: :ignore,
|
|
509
|
+
attribute_order: :ignore,
|
|
510
|
+
attribute_values: :ignore,
|
|
511
|
+
key_order: :ignore,
|
|
512
|
+
comments: :ignore
|
|
513
|
+
}
|
|
514
|
+
----
|
|
515
|
+
|
|
516
|
+
.content_only profile usage
|
|
517
|
+
[example]
|
|
518
|
+
====
|
|
519
|
+
[source,ruby]
|
|
520
|
+
----
|
|
521
|
+
Canon::Comparison.equivalent?(doc1, doc2,
|
|
522
|
+
match_profile: :content_only
|
|
523
|
+
)
|
|
524
|
+
----
|
|
525
|
+
|
|
526
|
+
Maximum tolerance, content focus only.
|
|
527
|
+
====
|
|
528
|
+
|
|
529
|
+
== Format defaults
|
|
530
|
+
|
|
531
|
+
Each format has sensible defaults based on typical usage:
|
|
532
|
+
|
|
533
|
+
[cols="1,1,1,1,1"]
|
|
534
|
+
|===
|
|
535
|
+
|Dimension |XML |HTML |JSON |YAML
|
|
536
|
+
|
|
537
|
+
|`text_content`
|
|
538
|
+
|`:strict`
|
|
539
|
+
|`:normalize`
|
|
540
|
+
|`:strict`
|
|
541
|
+
|`:strict`
|
|
542
|
+
|
|
543
|
+
|`structural_whitespace`
|
|
544
|
+
|`:strict`
|
|
545
|
+
|`:normalize`
|
|
546
|
+
|`:strict`
|
|
547
|
+
|`:strict`
|
|
548
|
+
|
|
549
|
+
|`attribute_whitespace`
|
|
550
|
+
|`:strict`
|
|
551
|
+
|`:normalize`
|
|
552
|
+
|—
|
|
553
|
+
|—
|
|
554
|
+
|
|
555
|
+
|`attribute_order`
|
|
556
|
+
|`:strict`
|
|
557
|
+
|`:ignore`
|
|
558
|
+
|—
|
|
559
|
+
|—
|
|
560
|
+
|
|
561
|
+
|`attribute_values`
|
|
562
|
+
|`:strict`
|
|
563
|
+
|`:strict`
|
|
564
|
+
|—
|
|
565
|
+
|—
|
|
566
|
+
|
|
567
|
+
|`key_order`
|
|
568
|
+
|—
|
|
569
|
+
|—
|
|
570
|
+
|`:strict`
|
|
571
|
+
|`:strict`
|
|
572
|
+
|
|
573
|
+
|`comments`
|
|
574
|
+
|`:strict`
|
|
575
|
+
|`:ignore`
|
|
576
|
+
|—
|
|
577
|
+
|`:strict`
|
|
578
|
+
|===
|
|
579
|
+
|
|
580
|
+
== Configuration precedence
|
|
581
|
+
|
|
582
|
+
When options are specified in multiple places, Canon resolves them using this
|
|
583
|
+
hierarchy (highest to lowest priority):
|
|
584
|
+
|
|
585
|
+
[source]
|
|
586
|
+
----
|
|
587
|
+
1. Per-comparison explicit options (highest)
|
|
588
|
+
↓
|
|
589
|
+
2. Per-comparison profile
|
|
590
|
+
↓
|
|
591
|
+
3. Global configuration explicit options
|
|
592
|
+
↓
|
|
593
|
+
4. Global configuration profile
|
|
594
|
+
↓
|
|
595
|
+
5. Format defaults (lowest)
|
|
596
|
+
----
|
|
597
|
+
|
|
598
|
+
.Precedence example
|
|
599
|
+
[example]
|
|
600
|
+
====
|
|
601
|
+
**Global configuration**:
|
|
602
|
+
|
|
603
|
+
[source,ruby]
|
|
604
|
+
----
|
|
605
|
+
Canon::RSpecMatchers.configure do |config|
|
|
606
|
+
config.xml.match.profile = :spec_friendly
|
|
607
|
+
config.xml.match.options = { comments: :strict }
|
|
608
|
+
end
|
|
609
|
+
----
|
|
610
|
+
|
|
611
|
+
The `:spec_friendly` profile sets:
|
|
612
|
+
|
|
613
|
+
* `text_content: :normalize`
|
|
614
|
+
* `structural_whitespace: :ignore`
|
|
615
|
+
* `comments: :ignore`
|
|
616
|
+
|
|
617
|
+
But the explicit `comments: :strict` overrides the profile setting.
|
|
618
|
+
|
|
619
|
+
**Per-test usage**:
|
|
620
|
+
|
|
621
|
+
[source,ruby]
|
|
622
|
+
----
|
|
623
|
+
expect(actual).to be_xml_equivalent_to(expected)
|
|
624
|
+
.with_profile(:rendered)
|
|
625
|
+
.with_options(structural_whitespace: :ignore)
|
|
626
|
+
----
|
|
627
|
+
|
|
628
|
+
**Final resolved options**:
|
|
629
|
+
|
|
630
|
+
* `text_content: :normalize` (from `:rendered` per-test profile)
|
|
631
|
+
* `structural_whitespace: :ignore` (from per-test explicit option)
|
|
632
|
+
* `comments: :strict` (from global explicit option)
|
|
633
|
+
* Other dimensions use `:rendered` profile or format defaults
|
|
634
|
+
====
|
|
635
|
+
|
|
636
|
+
== Usage
|
|
637
|
+
|
|
638
|
+
=== Ruby API
|
|
639
|
+
|
|
640
|
+
[source,ruby]
|
|
641
|
+
----
|
|
642
|
+
# Use specific dimensions
|
|
643
|
+
Canon::Comparison.equivalent?(doc1, doc2,
|
|
644
|
+
match: {
|
|
645
|
+
text_content: :normalize,
|
|
646
|
+
structural_whitespace: :ignore,
|
|
647
|
+
comments: :ignore
|
|
648
|
+
}
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
# Use a profile
|
|
652
|
+
Canon::Comparison.equivalent?(doc1, doc2,
|
|
653
|
+
match_profile: :spec_friendly
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
# Profile with dimension overrides
|
|
657
|
+
Canon::Comparison.equivalent?(doc1, doc2,
|
|
658
|
+
match_profile: :spec_friendly,
|
|
659
|
+
match: {
|
|
660
|
+
comments: :strict # Override profile
|
|
661
|
+
}
|
|
662
|
+
)
|
|
663
|
+
----
|
|
664
|
+
|
|
665
|
+
=== CLI
|
|
666
|
+
|
|
667
|
+
[source,bash]
|
|
668
|
+
----
|
|
669
|
+
# Use profile
|
|
670
|
+
$ canon diff file1.xml file2.xml \
|
|
671
|
+
--match-profile spec_friendly \
|
|
672
|
+
--verbose
|
|
673
|
+
|
|
674
|
+
# Override specific dimensions
|
|
675
|
+
$ canon diff file1.xml file2.xml \
|
|
676
|
+
--text-content normalize \
|
|
677
|
+
--structural-whitespace ignore \
|
|
678
|
+
--verbose
|
|
679
|
+
|
|
680
|
+
# Combine profile with overrides
|
|
681
|
+
$ canon diff file1.xml file2.xml \
|
|
682
|
+
--match-profile spec_friendly \
|
|
683
|
+
--comments strict \
|
|
684
|
+
--verbose
|
|
685
|
+
----
|
|
686
|
+
|
|
687
|
+
=== RSpec
|
|
688
|
+
|
|
689
|
+
[source,ruby]
|
|
690
|
+
----
|
|
691
|
+
# Global configuration
|
|
692
|
+
Canon::RSpecMatchers.configure do |config|
|
|
693
|
+
config.xml.match.profile = :spec_friendly
|
|
694
|
+
config.xml.match.options = {
|
|
695
|
+
text_content: :normalize,
|
|
696
|
+
comments: :ignore
|
|
697
|
+
}
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# Per-test override
|
|
701
|
+
expect(actual).to be_xml_equivalent_to(expected)
|
|
702
|
+
.with_profile(:strict)
|
|
703
|
+
|
|
704
|
+
# Per-test dimension override
|
|
705
|
+
expect(actual).to be_xml_equivalent_to(expected)
|
|
706
|
+
.with_options(
|
|
707
|
+
structural_whitespace: :strict,
|
|
708
|
+
text_content: :strict
|
|
709
|
+
)
|
|
710
|
+
----
|
|
711
|
+
|
|
712
|
+
== See also
|
|
713
|
+
|
|
714
|
+
* link:MATCH_ARCHITECTURE[Match architecture]
|
|
715
|
+
* link:PREPROCESSING[Preprocessing options]
|
|
716
|
+
* link:FORMATS[Format support]
|
|
717
|
+
* link:RUBY_API[Ruby API documentation]
|
|
718
|
+
* link:CLI[Command-line interface]
|
|
719
|
+
* link:RSPEC[RSpec matchers]
|