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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +12 -22
- data/Rakefile +5 -2
- data/lib/canon/cache.rb +3 -1
- data/lib/canon/cli.rb +0 -3
- data/lib/canon/commands/diff_command.rb +0 -6
- data/lib/canon/commands/format_command.rb +0 -4
- data/lib/canon/commands.rb +9 -0
- data/lib/canon/comparison/child_realignment.rb +0 -2
- data/lib/canon/comparison/compare_profile.rb +30 -36
- data/lib/canon/comparison/comparison_result.rb +0 -2
- data/lib/canon/comparison/diff_node_builder.rb +353 -0
- data/lib/canon/comparison/dimensions/dimension.rb +51 -0
- data/lib/canon/comparison/dimensions/dimension_set.rb +49 -0
- data/lib/canon/comparison/dimensions/registry.rb +101 -60
- data/lib/canon/comparison/dimensions.rb +15 -46
- data/lib/canon/comparison/html_comparator.rb +18 -141
- data/lib/canon/comparison/html_compare_profile.rb +15 -18
- data/lib/canon/comparison/json_comparator.rb +4 -165
- data/lib/canon/comparison/json_parser.rb +0 -2
- data/lib/canon/comparison/markup_comparator.rb +14 -210
- data/lib/canon/comparison/match_options/base_resolver.rb +18 -29
- data/lib/canon/comparison/match_options/json_resolver.rb +4 -28
- data/lib/canon/comparison/match_options/xml_resolver.rb +4 -45
- data/lib/canon/comparison/match_options/yaml_resolver.rb +4 -30
- data/lib/canon/comparison/match_options.rb +13 -88
- data/lib/canon/comparison/pipeline.rb +269 -0
- data/lib/canon/comparison/profile_definition.rb +0 -2
- data/lib/canon/comparison/ruby_object_comparator.rb +1 -1
- data/lib/canon/comparison/strategies/match_strategy_factory.rb +9 -58
- data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +4 -11
- data/lib/canon/comparison/strategies.rb +16 -0
- data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +0 -3
- data/lib/canon/comparison/xml_comparator/attribute_filter.rb +0 -3
- data/lib/canon/comparison/xml_comparator/child_comparison.rb +0 -6
- data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +1 -6
- data/lib/canon/comparison/xml_comparator/node_parser.rb +0 -4
- data/lib/canon/comparison/xml_comparator.rb +4 -492
- data/lib/canon/comparison/xml_comparator_helpers.rb +21 -0
- data/lib/canon/comparison/xml_node_comparison.rb +4 -119
- data/lib/canon/comparison/yaml_comparator.rb +0 -3
- data/lib/canon/comparison.rb +143 -266
- data/lib/canon/config/config_dsl.rb +159 -0
- data/lib/canon/config/env_provider.rb +0 -3
- data/lib/canon/config/env_schema.rb +48 -58
- data/lib/canon/config/profile_loader.rb +0 -1
- data/lib/canon/config.rb +116 -468
- data/lib/canon/diff/diff_block_builder.rb +0 -2
- data/lib/canon/diff/diff_classifier.rb +0 -5
- data/lib/canon/diff/diff_context.rb +0 -2
- data/lib/canon/diff/diff_context_builder.rb +0 -2
- data/lib/canon/diff/diff_line_builder.rb +0 -3
- data/lib/canon/diff/diff_node_enricher.rb +0 -4
- data/lib/canon/diff/diff_node_mapper.rb +0 -4
- data/lib/canon/diff/diff_report_builder.rb +0 -4
- data/lib/canon/diff/formatting_detector.rb +0 -1
- data/lib/canon/diff/node_serializer.rb +0 -7
- data/lib/canon/diff.rb +39 -0
- data/lib/canon/diff_formatter/by_line/base_formatter.rb +4 -17
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +7 -19
- data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -3
- data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -3
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +7 -26
- data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -3
- data/lib/canon/diff_formatter/by_object/base_formatter.rb +8 -15
- data/lib/canon/diff_formatter/by_object/json_formatter.rb +0 -2
- data/lib/canon/diff_formatter/by_object/xml_formatter.rb +0 -2
- data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +0 -2
- data/lib/canon/diff_formatter/debug_output.rb +0 -2
- data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +24 -58
- data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +0 -2
- data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +1 -2
- data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +1 -7
- data/lib/canon/diff_formatter/diff_detail_formatter.rb +0 -7
- data/lib/canon/diff_formatter/diff_detail_formatter_helpers.rb +23 -0
- data/lib/canon/diff_formatter.rb +11 -9
- data/lib/canon/formatters/html4_formatter.rb +0 -2
- data/lib/canon/formatters/html5_formatter.rb +0 -2
- data/lib/canon/formatters/html_formatter.rb +0 -3
- data/lib/canon/formatters/json_formatter.rb +0 -1
- data/lib/canon/formatters/xml_formatter.rb +0 -4
- data/lib/canon/formatters/yaml_formatter.rb +0 -1
- data/lib/canon/formatters.rb +16 -0
- data/lib/canon/html/data_model.rb +0 -10
- data/lib/canon/html.rb +4 -3
- data/lib/canon/options/cli_generator.rb +0 -2
- data/lib/canon/options/registry.rb +0 -2
- data/lib/canon/options.rb +9 -0
- data/lib/canon/pretty_printer/html.rb +0 -1
- data/lib/canon/pretty_printer/xml_normalized.rb +0 -2
- data/lib/canon/pretty_printer.rb +12 -0
- data/lib/canon/tree_diff/adapters/html_adapter.rb +1 -1
- data/lib/canon/tree_diff/adapters.rb +14 -0
- data/lib/canon/tree_diff/core/attribute_comparator.rb +0 -6
- data/lib/canon/tree_diff/core/node_signature.rb +1 -1
- data/lib/canon/tree_diff/core/tree_node.rb +12 -5
- data/lib/canon/tree_diff/core.rb +17 -0
- data/lib/canon/tree_diff/matchers/hash_matcher.rb +0 -7
- data/lib/canon/tree_diff/matchers/similarity_matcher.rb +1 -5
- data/lib/canon/tree_diff/matchers/structural_propagator.rb +1 -5
- data/lib/canon/tree_diff/matchers.rb +15 -0
- data/lib/canon/tree_diff/operation_converter.rb +0 -8
- data/lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb +2 -12
- data/lib/canon/tree_diff/operation_converter_helpers/post_processor.rb +13 -7
- data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +2 -2
- data/lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb +4 -6
- data/lib/canon/tree_diff/operation_converter_helpers.rb +18 -0
- data/lib/canon/tree_diff/operations/operation_detector.rb +2 -5
- data/lib/canon/tree_diff/operations.rb +13 -0
- data/lib/canon/tree_diff.rb +26 -27
- data/lib/canon/validators/base_validator.rb +0 -2
- data/lib/canon/validators/html_validator.rb +0 -1
- data/lib/canon/validators/json_validator.rb +0 -1
- data/lib/canon/validators/xml_validator.rb +0 -1
- data/lib/canon/validators/yaml_validator.rb +0 -1
- data/lib/canon/validators.rb +12 -0
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/c14n.rb +0 -4
- data/lib/canon/xml/data_model.rb +0 -10
- data/lib/canon/xml/line_range_mapper.rb +0 -2
- data/lib/canon/xml/nodes/attribute_node.rb +0 -2
- data/lib/canon/xml/nodes/comment_node.rb +0 -2
- data/lib/canon/xml/nodes/element_node.rb +0 -2
- data/lib/canon/xml/nodes/namespace_node.rb +0 -2
- data/lib/canon/xml/nodes/processing_instruction_node.rb +0 -2
- data/lib/canon/xml/nodes/root_node.rb +0 -2
- data/lib/canon/xml/nodes/text_node.rb +0 -2
- data/lib/canon/xml/nodes.rb +19 -0
- data/lib/canon/xml/processor.rb +0 -5
- data/lib/canon/xml/sax_builder.rb +0 -7
- data/lib/canon/xml.rb +33 -0
- data/lib/canon/xml_backend.rb +50 -14
- data/lib/canon/xml_parsing.rb +4 -2
- data/lib/canon.rb +25 -15
- data/lib/tasks/performance.rake +0 -58
- data/lib/tasks/performance_comparator.rb +132 -65
- data/lib/tasks/performance_helpers.rb +4 -249
- data/lib/tasks/performance_report.rb +309 -0
- metadata +24 -11
- data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +0 -64
- data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +0 -64
- data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +0 -167
- data/lib/canon/comparison/dimensions/base_dimension.rb +0 -107
- data/lib/canon/comparison/dimensions/comments_dimension.rb +0 -117
- data/lib/canon/comparison/dimensions/element_position_dimension.rb +0 -86
- data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +0 -115
- data/lib/canon/comparison/dimensions/text_content_dimension.rb +0 -102
- 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
|
-
#
|
|
26
|
-
#
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
93
|
-
"#{self.class} must implement #match_dimensions"
|
|
92
|
+
dimension_set.names
|
|
94
93
|
end
|
|
95
94
|
|
|
96
95
|
protected
|
|
97
96
|
|
|
98
|
-
#
|
|
99
|
-
#
|
|
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 [
|
|
103
|
-
def
|
|
104
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
146
|
+
dim = set[dimension]
|
|
147
|
+
unless dim
|
|
157
148
|
raise Canon::Error,
|
|
158
149
|
"Unknown match dimension: #{dimension}. " \
|
|
159
|
-
"Valid dimensions: #{
|
|
150
|
+
"Valid dimensions: #{set.names.join(', ')}"
|
|
160
151
|
end
|
|
161
152
|
|
|
162
|
-
|
|
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
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|