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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -1
  3. data/.rubocop_todo.yml +276 -7
  4. data/README.adoc +203 -138
  5. data/_config.yml +116 -0
  6. data/docs/ADVANCED_TOPICS.adoc +20 -0
  7. data/docs/BASIC_USAGE.adoc +16 -0
  8. data/docs/CHARACTER_VISUALIZATION.adoc +567 -0
  9. data/docs/CLI.adoc +493 -0
  10. data/docs/CUSTOMIZING_BEHAVIOR.adoc +19 -0
  11. data/docs/DIFF_ARCHITECTURE.adoc +435 -0
  12. data/docs/DIFF_FORMATTING.adoc +540 -0
  13. data/docs/FORMATS.adoc +447 -0
  14. data/docs/INDEX.adoc +222 -0
  15. data/docs/INPUT_VALIDATION.adoc +477 -0
  16. data/docs/MATCH_ARCHITECTURE.adoc +463 -0
  17. data/docs/MATCH_OPTIONS.adoc +719 -0
  18. data/docs/MODES.adoc +432 -0
  19. data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +219 -0
  20. data/docs/OPTIONS.adoc +1387 -0
  21. data/docs/PREPROCESSING.adoc +491 -0
  22. data/docs/RSPEC.adoc +605 -0
  23. data/docs/RUBY_API.adoc +478 -0
  24. data/docs/SEMANTIC_DIFF_REPORT.adoc +528 -0
  25. data/docs/UNDERSTANDING_CANON.adoc +17 -0
  26. data/docs/VERBOSE.adoc +482 -0
  27. data/exe/canon +7 -0
  28. data/lib/canon/cli.rb +179 -0
  29. data/lib/canon/commands/diff_command.rb +195 -0
  30. data/lib/canon/commands/format_command.rb +113 -0
  31. data/lib/canon/comparison/base_comparator.rb +39 -0
  32. data/lib/canon/comparison/comparison_result.rb +79 -0
  33. data/lib/canon/comparison/html_comparator.rb +410 -0
  34. data/lib/canon/comparison/json_comparator.rb +212 -0
  35. data/lib/canon/comparison/match_options.rb +616 -0
  36. data/lib/canon/comparison/xml_comparator.rb +566 -0
  37. data/lib/canon/comparison/yaml_comparator.rb +93 -0
  38. data/lib/canon/comparison.rb +239 -0
  39. data/lib/canon/config.rb +172 -0
  40. data/lib/canon/diff/diff_block.rb +71 -0
  41. data/lib/canon/diff/diff_block_builder.rb +105 -0
  42. data/lib/canon/diff/diff_classifier.rb +46 -0
  43. data/lib/canon/diff/diff_context.rb +85 -0
  44. data/lib/canon/diff/diff_context_builder.rb +107 -0
  45. data/lib/canon/diff/diff_line.rb +77 -0
  46. data/lib/canon/diff/diff_node.rb +56 -0
  47. data/lib/canon/diff/diff_node_mapper.rb +148 -0
  48. data/lib/canon/diff/diff_report.rb +133 -0
  49. data/lib/canon/diff/diff_report_builder.rb +62 -0
  50. data/lib/canon/diff_formatter/by_line/base_formatter.rb +407 -0
  51. data/lib/canon/diff_formatter/by_line/html_formatter.rb +672 -0
  52. data/lib/canon/diff_formatter/by_line/json_formatter.rb +284 -0
  53. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +190 -0
  54. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +860 -0
  55. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +292 -0
  56. data/lib/canon/diff_formatter/by_object/base_formatter.rb +199 -0
  57. data/lib/canon/diff_formatter/by_object/json_formatter.rb +305 -0
  58. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +248 -0
  59. data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +17 -0
  60. data/lib/canon/diff_formatter/character_map.yml +197 -0
  61. data/lib/canon/diff_formatter/debug_output.rb +431 -0
  62. data/lib/canon/diff_formatter/diff_detail_formatter.rb +551 -0
  63. data/lib/canon/diff_formatter/legend.rb +141 -0
  64. data/lib/canon/diff_formatter.rb +520 -0
  65. data/lib/canon/errors.rb +56 -0
  66. data/lib/canon/formatters/html4_formatter.rb +17 -0
  67. data/lib/canon/formatters/html5_formatter.rb +17 -0
  68. data/lib/canon/formatters/html_formatter.rb +37 -0
  69. data/lib/canon/formatters/html_formatter_base.rb +163 -0
  70. data/lib/canon/formatters/json_formatter.rb +3 -0
  71. data/lib/canon/formatters/xml_formatter.rb +20 -55
  72. data/lib/canon/formatters/yaml_formatter.rb +4 -1
  73. data/lib/canon/pretty_printer/html.rb +57 -0
  74. data/lib/canon/pretty_printer/json.rb +25 -0
  75. data/lib/canon/pretty_printer/xml.rb +29 -0
  76. data/lib/canon/rspec_matchers.rb +222 -80
  77. data/lib/canon/validators/base_validator.rb +49 -0
  78. data/lib/canon/validators/html_validator.rb +138 -0
  79. data/lib/canon/validators/json_validator.rb +89 -0
  80. data/lib/canon/validators/xml_validator.rb +53 -0
  81. data/lib/canon/validators/yaml_validator.rb +73 -0
  82. data/lib/canon/version.rb +1 -1
  83. data/lib/canon/xml/attribute_handler.rb +80 -0
  84. data/lib/canon/xml/c14n.rb +36 -0
  85. data/lib/canon/xml/character_encoder.rb +38 -0
  86. data/lib/canon/xml/data_model.rb +225 -0
  87. data/lib/canon/xml/element_matcher.rb +196 -0
  88. data/lib/canon/xml/line_range_mapper.rb +158 -0
  89. data/lib/canon/xml/namespace_handler.rb +86 -0
  90. data/lib/canon/xml/node.rb +32 -0
  91. data/lib/canon/xml/nodes/attribute_node.rb +54 -0
  92. data/lib/canon/xml/nodes/comment_node.rb +23 -0
  93. data/lib/canon/xml/nodes/element_node.rb +56 -0
  94. data/lib/canon/xml/nodes/namespace_node.rb +38 -0
  95. data/lib/canon/xml/nodes/processing_instruction_node.rb +24 -0
  96. data/lib/canon/xml/nodes/root_node.rb +16 -0
  97. data/lib/canon/xml/nodes/text_node.rb +23 -0
  98. data/lib/canon/xml/processor.rb +151 -0
  99. data/lib/canon/xml/whitespace_normalizer.rb +72 -0
  100. data/lib/canon/xml/xml_base_handler.rb +188 -0
  101. data/lib/canon.rb +14 -3
  102. 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]