canon 0.2.11 → 0.2.12

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +12 -22
  3. data/Rakefile +5 -2
  4. data/lib/canon/cache.rb +3 -1
  5. data/lib/canon/cli.rb +0 -3
  6. data/lib/canon/commands/diff_command.rb +0 -6
  7. data/lib/canon/commands/format_command.rb +0 -4
  8. data/lib/canon/commands.rb +9 -0
  9. data/lib/canon/comparison/child_realignment.rb +0 -2
  10. data/lib/canon/comparison/compare_profile.rb +30 -36
  11. data/lib/canon/comparison/comparison_result.rb +0 -2
  12. data/lib/canon/comparison/diff_node_builder.rb +353 -0
  13. data/lib/canon/comparison/dimensions/dimension.rb +51 -0
  14. data/lib/canon/comparison/dimensions/dimension_set.rb +49 -0
  15. data/lib/canon/comparison/dimensions/registry.rb +101 -60
  16. data/lib/canon/comparison/dimensions.rb +15 -46
  17. data/lib/canon/comparison/html_comparator.rb +18 -141
  18. data/lib/canon/comparison/html_compare_profile.rb +15 -18
  19. data/lib/canon/comparison/json_comparator.rb +4 -165
  20. data/lib/canon/comparison/json_parser.rb +0 -2
  21. data/lib/canon/comparison/markup_comparator.rb +14 -210
  22. data/lib/canon/comparison/match_options/base_resolver.rb +18 -29
  23. data/lib/canon/comparison/match_options/json_resolver.rb +4 -28
  24. data/lib/canon/comparison/match_options/xml_resolver.rb +4 -45
  25. data/lib/canon/comparison/match_options/yaml_resolver.rb +4 -30
  26. data/lib/canon/comparison/match_options.rb +13 -88
  27. data/lib/canon/comparison/pipeline.rb +269 -0
  28. data/lib/canon/comparison/profile_definition.rb +0 -2
  29. data/lib/canon/comparison/ruby_object_comparator.rb +1 -1
  30. data/lib/canon/comparison/strategies/match_strategy_factory.rb +9 -58
  31. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +4 -11
  32. data/lib/canon/comparison/strategies.rb +16 -0
  33. data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +0 -3
  34. data/lib/canon/comparison/xml_comparator/attribute_filter.rb +0 -3
  35. data/lib/canon/comparison/xml_comparator/child_comparison.rb +0 -6
  36. data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +1 -6
  37. data/lib/canon/comparison/xml_comparator/node_parser.rb +0 -4
  38. data/lib/canon/comparison/xml_comparator.rb +4 -492
  39. data/lib/canon/comparison/xml_comparator_helpers.rb +21 -0
  40. data/lib/canon/comparison/xml_node_comparison.rb +4 -119
  41. data/lib/canon/comparison/yaml_comparator.rb +0 -3
  42. data/lib/canon/comparison.rb +143 -266
  43. data/lib/canon/config/config_dsl.rb +159 -0
  44. data/lib/canon/config/env_provider.rb +0 -3
  45. data/lib/canon/config/env_schema.rb +48 -58
  46. data/lib/canon/config/profile_loader.rb +0 -1
  47. data/lib/canon/config.rb +116 -468
  48. data/lib/canon/diff/diff_block_builder.rb +0 -2
  49. data/lib/canon/diff/diff_classifier.rb +0 -5
  50. data/lib/canon/diff/diff_context.rb +0 -2
  51. data/lib/canon/diff/diff_context_builder.rb +0 -2
  52. data/lib/canon/diff/diff_line_builder.rb +0 -3
  53. data/lib/canon/diff/diff_node_enricher.rb +0 -4
  54. data/lib/canon/diff/diff_node_mapper.rb +0 -4
  55. data/lib/canon/diff/diff_report_builder.rb +0 -4
  56. data/lib/canon/diff/formatting_detector.rb +0 -1
  57. data/lib/canon/diff/node_serializer.rb +0 -7
  58. data/lib/canon/diff.rb +39 -0
  59. data/lib/canon/diff_formatter/by_line/base_formatter.rb +4 -17
  60. data/lib/canon/diff_formatter/by_line/html_formatter.rb +7 -19
  61. data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -3
  62. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -3
  63. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +7 -26
  64. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -3
  65. data/lib/canon/diff_formatter/by_object/base_formatter.rb +8 -15
  66. data/lib/canon/diff_formatter/by_object/json_formatter.rb +0 -2
  67. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +0 -2
  68. data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +0 -2
  69. data/lib/canon/diff_formatter/debug_output.rb +0 -2
  70. data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +24 -58
  71. data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +0 -2
  72. data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +1 -2
  73. data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +1 -7
  74. data/lib/canon/diff_formatter/diff_detail_formatter.rb +0 -7
  75. data/lib/canon/diff_formatter/diff_detail_formatter_helpers.rb +23 -0
  76. data/lib/canon/diff_formatter.rb +11 -9
  77. data/lib/canon/formatters/html4_formatter.rb +0 -2
  78. data/lib/canon/formatters/html5_formatter.rb +0 -2
  79. data/lib/canon/formatters/html_formatter.rb +0 -3
  80. data/lib/canon/formatters/json_formatter.rb +0 -1
  81. data/lib/canon/formatters/xml_formatter.rb +0 -4
  82. data/lib/canon/formatters/yaml_formatter.rb +0 -1
  83. data/lib/canon/formatters.rb +16 -0
  84. data/lib/canon/html/data_model.rb +0 -10
  85. data/lib/canon/html.rb +4 -3
  86. data/lib/canon/options/cli_generator.rb +0 -2
  87. data/lib/canon/options/registry.rb +0 -2
  88. data/lib/canon/options.rb +9 -0
  89. data/lib/canon/pretty_printer/html.rb +0 -1
  90. data/lib/canon/pretty_printer/xml_normalized.rb +0 -2
  91. data/lib/canon/pretty_printer.rb +12 -0
  92. data/lib/canon/tree_diff/adapters/html_adapter.rb +1 -1
  93. data/lib/canon/tree_diff/adapters.rb +14 -0
  94. data/lib/canon/tree_diff/core/attribute_comparator.rb +0 -6
  95. data/lib/canon/tree_diff/core/node_signature.rb +1 -1
  96. data/lib/canon/tree_diff/core/tree_node.rb +12 -5
  97. data/lib/canon/tree_diff/core.rb +17 -0
  98. data/lib/canon/tree_diff/matchers/hash_matcher.rb +0 -7
  99. data/lib/canon/tree_diff/matchers/similarity_matcher.rb +1 -5
  100. data/lib/canon/tree_diff/matchers/structural_propagator.rb +1 -5
  101. data/lib/canon/tree_diff/matchers.rb +15 -0
  102. data/lib/canon/tree_diff/operation_converter.rb +0 -8
  103. data/lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb +2 -12
  104. data/lib/canon/tree_diff/operation_converter_helpers/post_processor.rb +13 -7
  105. data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +2 -2
  106. data/lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb +4 -6
  107. data/lib/canon/tree_diff/operation_converter_helpers.rb +18 -0
  108. data/lib/canon/tree_diff/operations/operation_detector.rb +2 -5
  109. data/lib/canon/tree_diff/operations.rb +13 -0
  110. data/lib/canon/tree_diff.rb +26 -27
  111. data/lib/canon/validators/base_validator.rb +0 -2
  112. data/lib/canon/validators/html_validator.rb +0 -1
  113. data/lib/canon/validators/json_validator.rb +0 -1
  114. data/lib/canon/validators/xml_validator.rb +0 -1
  115. data/lib/canon/validators/yaml_validator.rb +0 -1
  116. data/lib/canon/validators.rb +12 -0
  117. data/lib/canon/version.rb +1 -1
  118. data/lib/canon/xml/c14n.rb +0 -4
  119. data/lib/canon/xml/data_model.rb +0 -10
  120. data/lib/canon/xml/line_range_mapper.rb +0 -2
  121. data/lib/canon/xml/nodes/attribute_node.rb +0 -2
  122. data/lib/canon/xml/nodes/comment_node.rb +0 -2
  123. data/lib/canon/xml/nodes/element_node.rb +0 -2
  124. data/lib/canon/xml/nodes/namespace_node.rb +0 -2
  125. data/lib/canon/xml/nodes/processing_instruction_node.rb +0 -2
  126. data/lib/canon/xml/nodes/root_node.rb +0 -2
  127. data/lib/canon/xml/nodes/text_node.rb +0 -2
  128. data/lib/canon/xml/nodes.rb +19 -0
  129. data/lib/canon/xml/processor.rb +0 -5
  130. data/lib/canon/xml/sax_builder.rb +0 -7
  131. data/lib/canon/xml.rb +33 -0
  132. data/lib/canon/xml_backend.rb +50 -14
  133. data/lib/canon/xml_parsing.rb +4 -2
  134. data/lib/canon.rb +25 -15
  135. data/lib/tasks/performance.rake +0 -58
  136. data/lib/tasks/performance_comparator.rb +132 -65
  137. data/lib/tasks/performance_helpers.rb +4 -249
  138. data/lib/tasks/performance_report.rb +309 -0
  139. metadata +24 -11
  140. data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +0 -64
  141. data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +0 -64
  142. data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +0 -167
  143. data/lib/canon/comparison/dimensions/base_dimension.rb +0 -107
  144. data/lib/canon/comparison/dimensions/comments_dimension.rb +0 -117
  145. data/lib/canon/comparison/dimensions/element_position_dimension.rb +0 -86
  146. data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +0 -115
  147. data/lib/canon/comparison/dimensions/text_content_dimension.rb +0 -102
  148. data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +0 -300
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../comparison" # Load base module with constants
4
- require_relative "node_inspector"
5
- require_relative "../diff/diff_node"
6
- require_relative "../diff/path_builder"
7
-
8
3
  module Canon
9
4
  module Comparison
10
5
  # Base class for markup document comparison (XML, HTML)
@@ -17,67 +12,16 @@ module Canon
17
12
  # inherit from this class and add format-specific behavior.
18
13
  class MarkupComparator
19
14
  class << self
20
- # Add a difference to the differences array
21
- #
22
- # Creates a DiffNode with enriched metadata including path,
23
- # serialized content, and attributes for Stage 4 rendering.
15
+ # Add a difference to the differences array.
24
16
  #
25
- # @param node1 [Object, nil] First node
26
- # @param node2 [Object, nil] Second node
27
- # @param diff1 [Symbol] Difference type for node1
28
- # @param diff2 [Symbol] Difference type for node2
29
- # @param dimension [Symbol] The match dimension causing this difference
30
- # @param _opts [Hash] Options (unused but kept for interface compatibility)
31
- # @param differences [Array] Array to append difference to
17
+ # Delegates to DiffNodeBuilder, the single DiffNode factory for
18
+ # the DOM comparison path.
32
19
  def add_difference(node1, node2, diff1, diff2, dimension, _opts,
33
20
  differences)
34
- # All differences must be DiffNode objects (OO architecture)
35
- if dimension.nil?
36
- raise ArgumentError,
37
- "dimension required for DiffNode"
38
- end
39
-
40
- # Build informative reason message
41
- reason = build_difference_reason(node1, node2, diff1, diff2,
42
- dimension)
43
-
44
- # Enrich with path, serialized content, and attributes for Stage 4 rendering
45
- metadata = enrich_diff_metadata(node1, node2)
46
-
47
- diff_node = Canon::Diff::DiffNode.new(
48
- node1: node1,
49
- node2: node2,
50
- dimension: dimension,
51
- reason: reason,
52
- **metadata,
21
+ differences << Canon::Comparison::DiffNodeBuilder.build(
22
+ node1: node1, node2: node2, diff1: diff1, diff2: diff2,
23
+ dimension: dimension
53
24
  )
54
- differences << diff_node
55
- end
56
-
57
- # Enrich DiffNode with canonical path, serialized content, and attributes
58
- # This extracts presentation-ready metadata from nodes for Stage 4 rendering
59
- #
60
- # @param node1 [Object, nil] First node
61
- # @param node2 [Object, nil] Second node
62
- # @return [Hash] Enriched metadata hash
63
- def enrich_diff_metadata(node1, node2)
64
- {
65
- path: build_path_for_node(node1 || node2),
66
- serialized_before: serialize_node(node1),
67
- serialized_after: serialize_node(node2),
68
- attributes_before: extract_attributes(node1),
69
- attributes_after: extract_attributes(node2),
70
- }
71
- end
72
-
73
- # Build canonical path for a node
74
- #
75
- # @param node [Object] Node to build path for
76
- # @return [String, nil] Canonical path with ordinal indices
77
- def build_path_for_node(node)
78
- return nil if node.nil?
79
-
80
- Canon::Diff::PathBuilder.build(node, format: :document)
81
25
  end
82
26
 
83
27
  # Serialize a node to string for display
@@ -87,24 +31,7 @@ module Canon
87
31
  def serialize_node(node)
88
32
  return nil if node.nil?
89
33
 
90
- # Canon::Xml::Node types
91
- case node
92
- when Canon::Xml::Nodes::RootNode
93
- # Serialize all children of root
94
- node.children.map { |child| serialize_node(child) }.join
95
- when Canon::Xml::Nodes::ElementNode
96
- serialize_element_node(node)
97
- when Canon::Xml::Nodes::TextNode
98
- # Use original text (with entity references) if available,
99
- # otherwise fall back to value (decoded text)
100
- node.original || node.value
101
- when Canon::Xml::Nodes::CommentNode
102
- "<!--#{node.value}-->"
103
- when Canon::Xml::Nodes::ProcessingInstructionNode
104
- "<?#{node.target} #{node.data}?>"
105
- else
106
- node.to_s
107
- end
34
+ Canon::Diff::NodeSerializer.serialize(node)
108
35
  end
109
36
 
110
37
  # Extract attributes from a node
@@ -114,19 +41,7 @@ module Canon
114
41
  def extract_attributes(node)
115
42
  return nil if node.nil?
116
43
 
117
- # Canon::Xml::Node ElementNode
118
- if node.is_a?(Canon::Xml::Nodes::ElementNode)
119
- node.attribute_nodes.to_h do |attr|
120
- [attr.name, attr.value]
121
- end
122
- # Nokogiri elements
123
- elsif node.is_a?(Nokogiri::XML::Element)
124
- node.attributes.to_h do |_, attr|
125
- [attr.name, attr.value]
126
- end
127
- else
128
- {}
129
- end
44
+ Canon::Diff::NodeSerializer.extract_attributes(node)
130
45
  end
131
46
 
132
47
  # Filter children based on options
@@ -278,65 +193,11 @@ module Canon
278
193
 
279
194
  # Build a human-readable reason for a difference
280
195
  #
281
- # @param node1 [Object, nil] First node
282
- # @param node2 [Object, nil] Second node
283
- # @param diff1 [Symbol] Difference type for node1
284
- # @param diff2 [Symbol] Difference type for node2
285
- # @param dimension [Symbol] The dimension of the difference
286
- # @return [String] Human-readable reason
196
+ # Delegates to DiffNodeBuilder for consistency.
287
197
  def build_difference_reason(node1, node2, diff1, diff2, dimension)
288
- # For attribute presence differences, show what attributes differ
289
- if dimension == :attribute_presence
290
- attrs1 = extract_attributes(node1)
291
- attrs2 = extract_attributes(node2)
292
- return build_attribute_difference_reason(attrs1, attrs2)
293
- end
294
-
295
- # For text content differences, show the actual text (truncated if needed)
296
- if dimension == :text_content
297
- text1 = extract_text_content_from_node(node1)
298
- text2 = extract_text_content_from_node(node2)
299
- return build_text_difference_reason(text1, text2)
300
- end
301
-
302
- # Default reason - can be overridden in subclasses
303
- if diff1 == Canon::Comparison::MISSING_NODE && diff2 == Canon::Comparison::MISSING_NODE
304
- "element structure mismatch (children differ)"
305
- else
306
- Canon::Comparison.code_pair_label(diff1, diff2)
307
- end
308
- end
309
-
310
- # Build a clear reason message for attribute presence differences
311
- # Shows which attributes are only in node1, only in node2, or different values
312
- #
313
- # @param attrs1 [Hash, nil] First node's attributes
314
- # @param attrs2 [Hash, nil] Second node's attributes
315
- # @return [String] Clear explanation of the attribute difference
316
- def build_attribute_difference_reason(attrs1, attrs2)
317
- return "#{attrs1&.keys&.size || 0} vs #{attrs2&.keys&.size || 0} attributes" unless attrs1 && attrs2
318
-
319
- require "set"
320
- keys1 = attrs1.keys.to_set
321
- keys2 = attrs2.keys.to_set
322
-
323
- only_in_1 = keys1 - keys2
324
- only_in_2 = keys2 - keys1
325
- common = keys1 & keys2
326
-
327
- # Check if values differ for common keys
328
- different_values = common.reject { |k| attrs1[k] == attrs2[k] }
329
-
330
- parts = []
331
- parts << "only in first: #{only_in_1.to_a.sort.join(', ')}" if only_in_1.any?
332
- parts << "only in second: #{only_in_2.to_a.sort.join(', ')}" if only_in_2.any?
333
- parts << "different values: #{different_values.sort.join(', ')}" if different_values.any?
334
-
335
- if parts.empty?
336
- "#{keys1.size} vs #{keys2.size} attributes (same names)"
337
- else
338
- parts.join("; ")
339
- end
198
+ Canon::Comparison::DiffNodeBuilder.build_reason(
199
+ node1, node2, diff1, diff2, dimension
200
+ )
340
201
  end
341
202
 
342
203
  # Extract text content from a node for diff reason
@@ -344,69 +205,12 @@ module Canon
344
205
  # @param node [Object, nil] Node to extract text from
345
206
  # @return [String, nil] Text content or nil
346
207
  def extract_text_content_from_node(node)
347
- return nil if node.nil?
348
-
349
- case node
350
- when Canon::Xml::Nodes::TextNode
351
- node.value
352
- when Canon::Xml::Node
353
- node.text_content
354
- when Nokogiri::XML::Node
355
- node.content.to_s
356
- when String
357
- node
358
- else
359
- node.to_s
360
- end
361
- rescue StandardError
362
- nil
363
- end
364
-
365
- # Build a clear reason message for text content differences
366
- # Shows the actual text content (truncated if too long)
367
- #
368
- # @param text1 [String, nil] First text content
369
- # @param text2 [String, nil] Second text content
370
- # @return [String] Clear explanation of the text difference
371
- def build_text_difference_reason(text1, text2)
372
- # Handle nil cases
373
- return "missing vs '#{truncate_text(text2)}'" if text1.nil? && text2
374
- return "'#{truncate_text(text1)}' vs missing" if text1 && text2.nil?
375
- return "both missing" if text1.nil? && text2.nil?
376
-
377
- # Both have content - show truncated versions
378
- "'#{truncate_text(text1)}' vs '#{truncate_text(text2)}'"
208
+ Canon::Comparison::DiffNodeBuilder.extract_text_content(node)
379
209
  end
380
210
 
381
211
  # Truncate text for display in reason messages
382
- #
383
- # @param text [String] Text to truncate
384
- # @param max_length [Integer] Maximum length
385
- # @return [String] Truncated text
386
212
  def truncate_text(text, max_length = 40)
387
- return "" if text.nil?
388
-
389
- text = text.to_s
390
- return text if text.length <= max_length
391
-
392
- "#{text[0...max_length]}..."
393
- end
394
-
395
- # Serialize an element node to string
396
- #
397
- # @param node [Canon::Xml::Nodes::ElementNode] Element node
398
- # @return [String] Serialized element
399
- def serialize_element_node(node)
400
- attrs = node.attribute_nodes.map do |a|
401
- " #{a.name}=\"#{a.value}\""
402
- end.join
403
- children_xml = node.children.map { |c| serialize_node(c) }.join
404
-
405
- if children_xml.empty?
406
- "<#{node.name}#{attrs}/>"
407
- else
408
- "<#{node.name}#{attrs}>#{children_xml}</#{node.name}>"
409
- end
213
+ Canon::Comparison::DiffNodeBuilder.truncate(text, max_length)
410
214
  end
411
215
 
412
216
  # Determine the appropriate dimension for a node type
@@ -84,34 +84,23 @@ module Canon
84
84
  "#{self.class} must implement #get_profile_options"
85
85
  end
86
86
 
87
- # Get valid match dimensions for this format
88
- # Subclasses should override this
87
+ # Get valid match dimensions for this format.
88
+ # Delegates to the DimensionSet from the Registry.
89
89
  #
90
90
  # @return [Array<Symbol>] Valid dimensions
91
91
  def match_dimensions
92
- raise NotImplementedError,
93
- "#{self.class} must implement #match_dimensions"
92
+ dimension_set.names
94
93
  end
95
94
 
96
95
  protected
97
96
 
98
- # Valid match behaviors per dimension for this format.
99
- # Override in subclasses to provide format-specific behaviors.
100
- # Used for per-dimension validation in validate_match_options!
97
+ # The DimensionSet for this resolver's format.
98
+ # Subclasses override to declare which format they handle.
101
99
  #
102
- # @return [Hash{Symbol => Array<Symbol>}] Dimension to valid behaviors mapping
103
- def dimension_behaviors
104
- # Default: XML/HTML behaviors (override in JSON/YAML resolvers)
105
- {
106
- text_content: %i[strict normalize ignore].freeze,
107
- structural_whitespace: %i[strict normalize ignore].freeze,
108
- attribute_presence: %i[strict ignore].freeze,
109
- attribute_order: %i[strict ignore].freeze,
110
- attribute_values: %i[strict strip compact normalize
111
- ignore].freeze,
112
- element_position: %i[strict ignore].freeze,
113
- comments: %i[strict ignore].freeze,
114
- }
100
+ # @return [Canon::Comparison::Dimensions::DimensionSet]
101
+ def dimension_set
102
+ raise NotImplementedError,
103
+ "#{self.class} must implement #dimension_set"
115
104
  end
116
105
 
117
106
  # Validate preprocessing option
@@ -126,12 +115,14 @@ module Canon
126
115
  end
127
116
  end
128
117
 
129
- # Validate match options using per-dimension behavior validation
118
+ # Validate match options using the DimensionSet for dimension
119
+ # and behavior validation.
130
120
  #
131
121
  # @param match_options [Hash] Options to validate
132
122
  # @raise [Canon::Error] If invalid dimension or behavior
133
123
  def validate_match_options!(match_options)
134
- # Special options that don't need validation as dimensions
124
+ set = dimension_set
125
+
135
126
  special_options = %i[
136
127
  format
137
128
  preprocessing
@@ -150,21 +141,19 @@ module Canon
150
141
  ]
151
142
 
152
143
  match_options.each do |dimension, behavior|
153
- # Skip special options (validated elsewhere or passed through)
154
144
  next if special_options.include?(dimension)
155
145
 
156
- unless match_dimensions.include?(dimension)
146
+ dim = set[dimension]
147
+ unless dim
157
148
  raise Canon::Error,
158
149
  "Unknown match dimension: #{dimension}. " \
159
- "Valid dimensions: #{match_dimensions.join(', ')}"
150
+ "Valid dimensions: #{set.names.join(', ')}"
160
151
  end
161
152
 
162
- # Per-dimension behavior validation using overridable method
163
- valid_behaviors = dimension_behaviors[dimension]
164
- unless valid_behaviors&.include?(behavior)
153
+ unless dim.valid_behavior?(behavior)
165
154
  raise Canon::Error,
166
155
  "Unknown match behavior: #{behavior} for #{dimension}. " \
167
- "Valid behaviors for #{dimension}: #{valid_behaviors&.join(', ')}"
156
+ "Valid behaviors for #{dimension}: #{dim.valid_behaviors.join(', ')}"
168
157
  end
169
158
  end
170
159
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_resolver"
4
-
5
3
  module Canon
6
4
  module Comparison
7
5
  module MatchOptions
@@ -19,7 +17,6 @@ module Canon
19
17
 
20
18
  # Predefined match profiles for JSON
21
19
  MATCH_PROFILES = {
22
- # Strict: Match exactly
23
20
  strict: {
24
21
  preprocessing: :none,
25
22
  text_content: :strict,
@@ -27,7 +24,6 @@ module Canon
27
24
  key_order: :strict,
28
25
  },
29
26
 
30
- # Spec-friendly: Formatting and order don't matter
31
27
  spec_friendly: {
32
28
  preprocessing: :normalize,
33
29
  text_content: :strict,
@@ -35,7 +31,6 @@ module Canon
35
31
  key_order: :ignore,
36
32
  },
37
33
 
38
- # Content-only: Only values matter
39
34
  content_only: {
40
35
  preprocessing: :normalize,
41
36
  text_content: :normalize,
@@ -45,28 +40,12 @@ module Canon
45
40
  }.freeze
46
41
 
47
42
  class << self
48
- # Matching dimensions for JSON (collectively exhaustive)
49
- def match_dimensions
50
- %i[
51
- text_content
52
- structural_whitespace
53
- key_order
54
- ].freeze
55
- end
56
-
57
43
  # Get format-specific default options
58
- #
59
- # @param format [Symbol] Format type (:json)
60
- # @return [Hash] Default options for the format
61
44
  def format_defaults(format)
62
45
  FORMAT_DEFAULTS[format]&.dup || FORMAT_DEFAULTS[:json].dup
63
46
  end
64
47
 
65
48
  # Get options for a named profile
66
- #
67
- # @param profile [Symbol] Profile name
68
- # @return [Hash] Profile options
69
- # @raise [Canon::Error] If profile is unknown
70
49
  def get_profile_options(profile)
71
50
  unless MATCH_PROFILES.key?(profile)
72
51
  raise Canon::Error,
@@ -76,13 +55,10 @@ module Canon
76
55
  MATCH_PROFILES[profile].dup
77
56
  end
78
57
 
79
- # JSON-specific dimension behaviors
80
- def dimension_behaviors
81
- {
82
- text_content: %i[strict normalize ignore].freeze,
83
- structural_whitespace: %i[strict normalize ignore].freeze,
84
- key_order: %i[strict ignore].freeze,
85
- }
58
+ protected
59
+
60
+ def dimension_set
61
+ Canon::Comparison::Dimensions::Registry.for(:json)
86
62
  end
87
63
  end
88
64
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_resolver"
4
-
5
3
  module Canon
6
4
  module Comparison
7
5
  module MatchOptions
@@ -41,9 +39,6 @@ module Canon
41
39
 
42
40
  # Predefined match profiles for XML/HTML
43
41
  MATCH_PROFILES = {
44
- # Strict: Match exactly as written in source (XML default).
45
- # Structural whitespace is stripped by default for XML.
46
- # Use preserve_whitespace_elements to preserve structural whitespace in specific elements.
47
42
  strict: {
48
43
  preprocessing: :none,
49
44
  text_content: :strict,
@@ -56,8 +51,6 @@ module Canon
56
51
  whitespace_type: :strict,
57
52
  },
58
53
 
59
- # Rendered: Match rendered output (HTML default)
60
- # Mimics CSS whitespace collapsing
61
54
  rendered: {
62
55
  preprocessing: :none,
63
56
  text_content: :normalize,
@@ -70,8 +63,6 @@ module Canon
70
63
  whitespace_type: :strict,
71
64
  },
72
65
 
73
- # HTML4: Match HTML4 rendered output
74
- # HTML4 rendering normalizes attribute whitespace
75
66
  html4: {
76
67
  preprocessing: :rendered,
77
68
  text_content: :normalize,
@@ -84,7 +75,6 @@ module Canon
84
75
  whitespace_type: :strict,
85
76
  },
86
77
 
87
- # HTML5: Match HTML5 rendered output (same as rendered)
88
78
  html5: {
89
79
  preprocessing: :rendered,
90
80
  text_content: :normalize,
@@ -97,8 +87,6 @@ module Canon
97
87
  whitespace_type: :strict,
98
88
  },
99
89
 
100
- # Spec-friendly: Formatting doesn't matter
101
- # Uses :rendered preprocessing for HTML which normalizes via to_html
102
90
  spec_friendly: {
103
91
  preprocessing: :rendered,
104
92
  text_content: :normalize,
@@ -111,7 +99,6 @@ module Canon
111
99
  whitespace_type: :strict,
112
100
  },
113
101
 
114
- # Content-only: Only content matters
115
102
  content_only: {
116
103
  preprocessing: :c14n,
117
104
  text_content: :normalize,
@@ -126,32 +113,12 @@ module Canon
126
113
  }.freeze
127
114
 
128
115
  class << self
129
- # Matching dimensions for XML/HTML (collectively exhaustive)
130
- def match_dimensions
131
- %i[
132
- text_content
133
- structural_whitespace
134
- attribute_presence
135
- attribute_order
136
- attribute_values
137
- element_position
138
- comments
139
- ].freeze
140
- end
141
-
142
116
  # Get format-specific default options
143
- #
144
- # @param format [Symbol] Format type (:xml or :html)
145
- # @return [Hash] Default options for the format
146
117
  def format_defaults(format)
147
118
  FORMAT_DEFAULTS[format]&.dup || FORMAT_DEFAULTS[:xml].dup
148
119
  end
149
120
 
150
121
  # Get options for a named profile
151
- #
152
- # @param profile [Symbol] Profile name
153
- # @return [Hash] Profile options
154
- # @raise [Canon::Error] If profile is unknown
155
122
  def get_profile_options(profile)
156
123
  unless MATCH_PROFILES.key?(profile)
157
124
  raise Canon::Error,
@@ -161,18 +128,10 @@ module Canon
161
128
  MATCH_PROFILES[profile].dup
162
129
  end
163
130
 
164
- # XML/HTML-specific dimension behaviors
165
- def dimension_behaviors
166
- {
167
- text_content: %i[strict normalize ignore].freeze,
168
- structural_whitespace: %i[strict normalize ignore].freeze,
169
- attribute_presence: %i[strict ignore].freeze,
170
- attribute_order: %i[strict ignore].freeze,
171
- attribute_values: %i[strict strip compact normalize
172
- ignore].freeze,
173
- element_position: %i[strict ignore].freeze,
174
- comments: %i[strict ignore].freeze,
175
- }
131
+ protected
132
+
133
+ def dimension_set
134
+ Canon::Comparison::Dimensions::Registry.for(:xml)
176
135
  end
177
136
  end
178
137
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_resolver"
4
-
5
3
  module Canon
6
4
  module Comparison
7
5
  module MatchOptions
@@ -20,7 +18,6 @@ module Canon
20
18
 
21
19
  # Predefined match profiles for YAML
22
20
  MATCH_PROFILES = {
23
- # Strict: Match exactly
24
21
  strict: {
25
22
  preprocessing: :none,
26
23
  text_content: :strict,
@@ -29,7 +26,6 @@ module Canon
29
26
  comments: :strict,
30
27
  },
31
28
 
32
- # Spec-friendly: Formatting and order don't matter
33
29
  spec_friendly: {
34
30
  preprocessing: :normalize,
35
31
  text_content: :strict,
@@ -38,7 +34,6 @@ module Canon
38
34
  comments: :ignore,
39
35
  },
40
36
 
41
- # Content-only: Only values matter
42
37
  content_only: {
43
38
  preprocessing: :normalize,
44
39
  text_content: :normalize,
@@ -49,29 +44,12 @@ module Canon
49
44
  }.freeze
50
45
 
51
46
  class << self
52
- # Matching dimensions for YAML (collectively exhaustive)
53
- def match_dimensions
54
- %i[
55
- text_content
56
- structural_whitespace
57
- key_order
58
- comments
59
- ].freeze
60
- end
61
-
62
47
  # Get format-specific default options
63
- #
64
- # @param format [Symbol] Format type (:yaml)
65
- # @return [Hash] Default options for the format
66
48
  def format_defaults(format)
67
49
  FORMAT_DEFAULTS[format]&.dup || FORMAT_DEFAULTS[:yaml].dup
68
50
  end
69
51
 
70
52
  # Get options for a named profile
71
- #
72
- # @param profile [Symbol] Profile name
73
- # @return [Hash] Profile options
74
- # @raise [Canon::Error] If profile is unknown
75
53
  def get_profile_options(profile)
76
54
  unless MATCH_PROFILES.key?(profile)
77
55
  raise Canon::Error,
@@ -81,14 +59,10 @@ module Canon
81
59
  MATCH_PROFILES[profile].dup
82
60
  end
83
61
 
84
- # YAML-specific dimension behaviors
85
- def dimension_behaviors
86
- {
87
- text_content: %i[strict normalize ignore].freeze,
88
- structural_whitespace: %i[strict normalize ignore].freeze,
89
- key_order: %i[strict ignore].freeze,
90
- comments: %i[strict ignore].freeze,
91
- }
62
+ protected
63
+
64
+ def dimension_set
65
+ Canon::Comparison::Dimensions::Registry.for(:yaml)
92
66
  end
93
67
  end
94
68
  end