canon 0.2.11 → 0.3.0
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 +62 -25
- data/CLAUDE.md +197 -0
- 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 +1 -8
- 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 +1 -4
- 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/format_detector.rb +27 -29
- data/lib/canon/comparison/html_comparator.rb +20 -143
- 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/node_inspector.rb +5 -9
- 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 +1 -4
- 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 +3 -7
- 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 +6 -121
- data/lib/canon/comparison/yaml_comparator.rb +0 -3
- data/lib/canon/comparison.rb +144 -267
- 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 +5 -19
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +8 -21
- 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_line_formatter.rb +1 -2
- 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 +3 -4
- 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/theme.rb +2 -2
- data/lib/canon/diff_formatter.rb +13 -13
- 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/nokogiri_support.rb +60 -0
- data/lib/canon/html.rb +5 -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.rb +6 -1
- data/lib/canon/pretty_printer/xml_normalized.rb +5 -7
- 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 +13 -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 +206 -43
- 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 +10 -7
- data/lib/canon/xml/sax/moxml_driver.rb +58 -0
- data/lib/canon/xml/sax/nokogiri_driver.rb +54 -0
- data/lib/canon/xml/sax.rb +37 -0
- data/lib/canon/xml/sax_builder.rb +31 -27
- data/lib/canon/xml.rb +34 -0
- data/lib/canon/xml_backend.rb +40 -19
- data/lib/canon/xml_parsing.rb +89 -116
- data/lib/canon.rb +26 -17
- data/lib/tasks/benchmark_runner.rb +4 -0
- data/lib/tasks/performance.rake +2 -58
- data/lib/tasks/performance_comparator.rb +132 -65
- data/lib/tasks/performance_helpers.rb +4 -249
- data/lib/tasks/performance_report.rb +313 -0
- metadata +31 -13
- 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
|
@@ -53,9 +53,6 @@ expand_difference: false)
|
|
|
53
53
|
# @param use_color [Boolean] Whether to use colors
|
|
54
54
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
55
55
|
def self.format_namespace_uri_details(diff, use_color)
|
|
56
|
-
require_relative "color_helper"
|
|
57
|
-
require_relative "node_utils"
|
|
58
|
-
|
|
59
56
|
node1 = extract_node1(diff)
|
|
60
57
|
node2 = extract_node2(diff)
|
|
61
58
|
|
|
@@ -87,10 +84,6 @@ expand_difference: false)
|
|
|
87
84
|
# @param use_color [Boolean] Whether to use colors
|
|
88
85
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
89
86
|
def self.format_namespace_declarations_details(diff, use_color)
|
|
90
|
-
require_relative "color_helper"
|
|
91
|
-
require_relative "node_utils"
|
|
92
|
-
require_relative "text_utils"
|
|
93
|
-
|
|
94
87
|
node1 = extract_node1(diff)
|
|
95
88
|
node2 = extract_node2(diff)
|
|
96
89
|
|
|
@@ -174,9 +167,6 @@ expand_difference: false)
|
|
|
174
167
|
# @param use_color [Boolean] Whether to use colors
|
|
175
168
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
176
169
|
def self.format_element_structure_details(diff, use_color)
|
|
177
|
-
require_relative "color_helper"
|
|
178
|
-
require_relative "node_utils"
|
|
179
|
-
|
|
180
170
|
node1 = extract_node1(diff)
|
|
181
171
|
node2 = extract_node2(diff)
|
|
182
172
|
|
|
@@ -236,9 +226,6 @@ expand_difference: false)
|
|
|
236
226
|
# @param use_color [Boolean] Whether to use colors
|
|
237
227
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
238
228
|
def self.format_attribute_presence_details(diff, use_color)
|
|
239
|
-
require_relative "color_helper"
|
|
240
|
-
require_relative "node_utils"
|
|
241
|
-
|
|
242
229
|
node1 = extract_node1(diff)
|
|
243
230
|
node2 = extract_node2(diff)
|
|
244
231
|
|
|
@@ -291,9 +278,6 @@ expand_difference: false)
|
|
|
291
278
|
# @param use_color [Boolean] Whether to use colors
|
|
292
279
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
293
280
|
def self.format_attribute_values_details(diff, use_color)
|
|
294
|
-
require_relative "color_helper"
|
|
295
|
-
require_relative "node_utils"
|
|
296
|
-
|
|
297
281
|
node1 = extract_node1(diff)
|
|
298
282
|
node2 = extract_node2(diff)
|
|
299
283
|
|
|
@@ -330,9 +314,6 @@ expand_difference: false)
|
|
|
330
314
|
# @param use_color [Boolean] Whether to use colors
|
|
331
315
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
332
316
|
def self.format_attribute_order_details(diff, use_color)
|
|
333
|
-
require_relative "color_helper"
|
|
334
|
-
require_relative "node_utils"
|
|
335
|
-
|
|
336
317
|
node1 = extract_node1(diff)
|
|
337
318
|
node2 = extract_node2(diff)
|
|
338
319
|
|
|
@@ -361,10 +342,6 @@ expand_difference: false)
|
|
|
361
342
|
# @param compact [Boolean] Whether to serialize element nodes as compact XML
|
|
362
343
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
363
344
|
def self.format_text_content_details(diff, use_color, compact: false)
|
|
364
|
-
require_relative "color_helper"
|
|
365
|
-
require_relative "node_utils"
|
|
366
|
-
require_relative "text_utils"
|
|
367
|
-
|
|
368
345
|
node1 = extract_node1(diff)
|
|
369
346
|
node2 = extract_node2(diff)
|
|
370
347
|
|
|
@@ -465,10 +442,6 @@ expand_difference: false)
|
|
|
465
442
|
# @param use_color [Boolean] Whether to apply ANSI colours
|
|
466
443
|
# @return [Array<String>] Tuple of [detail1, detail2, changes]
|
|
467
444
|
def self.format_text_content_one_sided(node1, node2, use_color)
|
|
468
|
-
require_relative "color_helper"
|
|
469
|
-
require_relative "node_utils"
|
|
470
|
-
require_relative "text_utils"
|
|
471
|
-
|
|
472
445
|
present = node1 || node2
|
|
473
446
|
|
|
474
447
|
# Defensive: if a one-sided text-content diff carries an
|
|
@@ -515,10 +488,6 @@ expand_difference: false)
|
|
|
515
488
|
# @param use_color [Boolean] Whether to use colors
|
|
516
489
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
517
490
|
def self.format_whitespace_adjacency_details(diff, use_color)
|
|
518
|
-
require_relative "color_helper"
|
|
519
|
-
require_relative "node_utils"
|
|
520
|
-
require_relative "text_utils"
|
|
521
|
-
|
|
522
491
|
node1 = extract_node1(diff)
|
|
523
492
|
node2 = extract_node2(diff)
|
|
524
493
|
|
|
@@ -568,10 +537,6 @@ expand_difference: false)
|
|
|
568
537
|
# @param use_color [Boolean] Whether to use colors
|
|
569
538
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
570
539
|
def self.format_structural_whitespace_details(diff, use_color)
|
|
571
|
-
require_relative "color_helper"
|
|
572
|
-
require_relative "node_utils"
|
|
573
|
-
require_relative "text_utils"
|
|
574
|
-
|
|
575
540
|
node1 = extract_node1(diff)
|
|
576
541
|
node2 = extract_node2(diff)
|
|
577
542
|
|
|
@@ -594,9 +559,6 @@ expand_difference: false)
|
|
|
594
559
|
# @param use_color [Boolean] Whether to use colors
|
|
595
560
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
596
561
|
def self.format_comments_details(diff, use_color)
|
|
597
|
-
require_relative "color_helper"
|
|
598
|
-
require_relative "node_utils"
|
|
599
|
-
|
|
600
562
|
node1 = extract_node1(diff)
|
|
601
563
|
node2 = extract_node2(diff)
|
|
602
564
|
|
|
@@ -619,8 +581,6 @@ expand_difference: false)
|
|
|
619
581
|
# @param use_color [Boolean] Whether to use colors
|
|
620
582
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
621
583
|
def self.format_hash_diff_details(diff, use_color)
|
|
622
|
-
require_relative "color_helper"
|
|
623
|
-
|
|
624
584
|
detail1 = if diff.is_a?(Hash) && diff[:value1]
|
|
625
585
|
ColorHelper.colorize(format_json_value(diff[:value1]),
|
|
626
586
|
:red, use_color)
|
|
@@ -653,9 +613,6 @@ expand_difference: false)
|
|
|
653
613
|
# @param compact [Boolean] Whether to serialize element nodes as compact XML
|
|
654
614
|
# @return [Array] Tuple of [detail1, detail2, changes]
|
|
655
615
|
def self.format_fallback_details(diff, use_color, compact: false)
|
|
656
|
-
require_relative "color_helper"
|
|
657
|
-
require_relative "node_utils"
|
|
658
|
-
|
|
659
616
|
node1 = extract_node1(diff)
|
|
660
617
|
node2 = extract_node2(diff)
|
|
661
618
|
|
|
@@ -749,8 +706,7 @@ expand_difference: false)
|
|
|
749
706
|
|
|
750
707
|
declarations = {}
|
|
751
708
|
|
|
752
|
-
|
|
753
|
-
if node.respond_to?(:namespace_nodes)
|
|
709
|
+
if node.is_a?(Canon::Xml::Node)
|
|
754
710
|
node.namespace_nodes.each do |ns|
|
|
755
711
|
next if ns.prefix == "xml" && ns.uri == "http://www.w3.org/XML/1998/namespace"
|
|
756
712
|
|
|
@@ -760,30 +716,28 @@ expand_difference: false)
|
|
|
760
716
|
return declarations
|
|
761
717
|
end
|
|
762
718
|
|
|
763
|
-
# Handle Nokogiri/Moxml nodes (use attributes)
|
|
764
|
-
raw_attrs = node.
|
|
719
|
+
# Handle Nokogiri/Moxml nodes (use attribute_nodes or attributes).
|
|
720
|
+
raw_attrs = if node.is_a?(Nokogiri::XML::Node)
|
|
721
|
+
node.attribute_nodes
|
|
722
|
+
else
|
|
723
|
+
node.attributes
|
|
724
|
+
end
|
|
765
725
|
|
|
766
726
|
if raw_attrs.is_a?(Array)
|
|
767
727
|
raw_attrs.each do |attr|
|
|
768
|
-
name = attr.
|
|
769
|
-
value = attr.
|
|
728
|
+
name = attr.is_a?(Nokogiri::XML::Attr) ? attr.name : attr.to_s
|
|
729
|
+
value = attr.is_a?(Nokogiri::XML::Attr) ? attr.value : attr.to_s
|
|
770
730
|
|
|
771
731
|
if namespace_declaration?(name)
|
|
772
732
|
prefix = name == "xmlns" ? "" : name.split(":", 2)[1]
|
|
773
733
|
declarations[prefix] = value
|
|
774
734
|
end
|
|
775
735
|
end
|
|
776
|
-
elsif raw_attrs.
|
|
736
|
+
elsif raw_attrs.is_a?(Hash)
|
|
777
737
|
raw_attrs.each do |key, val|
|
|
778
|
-
name =
|
|
779
|
-
|
|
780
|
-
else
|
|
781
|
-
(key.respond_to?(:name) ? key.name : key.to_s)
|
|
782
|
-
end
|
|
783
|
-
value = if val.respond_to?(:value)
|
|
738
|
+
name = hash_key_to_name(key)
|
|
739
|
+
value = if val.is_a?(Nokogiri::XML::Attr)
|
|
784
740
|
val.value
|
|
785
|
-
elsif val.respond_to?(:content)
|
|
786
|
-
val.content
|
|
787
741
|
else
|
|
788
742
|
val.to_s
|
|
789
743
|
end
|
|
@@ -805,6 +759,18 @@ expand_difference: false)
|
|
|
805
759
|
def self.namespace_declaration?(name)
|
|
806
760
|
name == "xmlns" || name.to_s.start_with?("xmlns:")
|
|
807
761
|
end
|
|
762
|
+
|
|
763
|
+
# Coerce a hash key (from a backend-supplied attribute hash) into a
|
|
764
|
+
# plain String attribute name.
|
|
765
|
+
#
|
|
766
|
+
# @param key [String, Nokogiri::XML::Attr, Object]
|
|
767
|
+
# @return [String]
|
|
768
|
+
def self.hash_key_to_name(key)
|
|
769
|
+
return key if key.is_a?(String)
|
|
770
|
+
return key.name if key.is_a?(Nokogiri::XML::Attr)
|
|
771
|
+
|
|
772
|
+
key.to_s
|
|
773
|
+
end
|
|
808
774
|
end
|
|
809
775
|
end
|
|
810
776
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "cgi"
|
|
4
|
-
require_relative "../../xml/namespace_helper"
|
|
5
4
|
|
|
6
5
|
module Canon
|
|
7
6
|
class DiffFormatter
|
|
@@ -94,7 +93,7 @@ module Canon
|
|
|
94
93
|
def self.get_namespace_uri_for_display(node)
|
|
95
94
|
return "" unless node
|
|
96
95
|
|
|
97
|
-
Canon::Comparison::
|
|
96
|
+
Canon::Comparison::NodeInspector.namespace_uri(node).to_s
|
|
98
97
|
end
|
|
99
98
|
|
|
100
99
|
# --- Display helpers ---
|
|
@@ -227,7 +226,7 @@ module Canon
|
|
|
227
226
|
end
|
|
228
227
|
|
|
229
228
|
def serialize_backend_node_compact(node)
|
|
230
|
-
if
|
|
229
|
+
if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Node)
|
|
231
230
|
serialize_nokogiri_node_compact(node)
|
|
232
231
|
elsif node.is_a?(Canon::Xml::Node)
|
|
233
232
|
serialize_node_compact(node)
|
|
@@ -257,7 +256,7 @@ module Canon
|
|
|
257
256
|
end
|
|
258
257
|
|
|
259
258
|
def serialize_backend_open_tag(node)
|
|
260
|
-
if
|
|
259
|
+
if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Element)
|
|
261
260
|
tag = node.name.to_s
|
|
262
261
|
attrs = node.attribute_nodes.map do |a|
|
|
263
262
|
" #{a.name}=\"#{CGI.escapeHTML(a.value.to_s)}\""
|
|
@@ -45,13 +45,7 @@ module Canon
|
|
|
45
45
|
def self.extract_content_preview(node, max_length = 50)
|
|
46
46
|
return "" unless node
|
|
47
47
|
|
|
48
|
-
text =
|
|
49
|
-
node.text
|
|
50
|
-
elsif node.respond_to?(:content)
|
|
51
|
-
node.content
|
|
52
|
-
else
|
|
53
|
-
node.to_s
|
|
54
|
-
end
|
|
48
|
+
text = Canon::Comparison::NodeInspector.text_content(node).to_s
|
|
55
49
|
|
|
56
50
|
return "" if text.nil? || text.empty?
|
|
57
51
|
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rainbow" unless RUBY_ENGINE == "opal"
|
|
4
|
-
require_relative "../xml/namespace_helper"
|
|
5
|
-
# DiffDetailFormatter helper modules
|
|
6
|
-
require_relative "diff_detail_formatter/text_utils"
|
|
7
|
-
require_relative "diff_detail_formatter/color_helper"
|
|
8
|
-
require_relative "diff_detail_formatter/location_extractor"
|
|
9
|
-
require_relative "diff_detail_formatter/node_utils"
|
|
10
|
-
require_relative "diff_detail_formatter/dimension_formatter"
|
|
11
4
|
|
|
12
5
|
module Canon
|
|
13
6
|
class DiffFormatter
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Canon
|
|
4
|
+
class DiffFormatter
|
|
5
|
+
# Helper modules consumed by {DiffDetailFormatter} to format per-dimension
|
|
6
|
+
# difference details. Files live under `diff_detail_formatter/` but the
|
|
7
|
+
# constants live under this namespace.
|
|
8
|
+
#
|
|
9
|
+
# Children are autoloaded — never `require_relative` them.
|
|
10
|
+
module DiffDetailFormatterHelpers
|
|
11
|
+
autoload :ColorHelper,
|
|
12
|
+
"canon/diff_formatter/diff_detail_formatter/color_helper"
|
|
13
|
+
autoload :DimensionFormatter,
|
|
14
|
+
"canon/diff_formatter/diff_detail_formatter/dimension_formatter"
|
|
15
|
+
autoload :LocationExtractor,
|
|
16
|
+
"canon/diff_formatter/diff_detail_formatter/location_extractor"
|
|
17
|
+
autoload :NodeUtils,
|
|
18
|
+
"canon/diff_formatter/diff_detail_formatter/node_utils"
|
|
19
|
+
autoload :TextUtils,
|
|
20
|
+
"canon/diff_formatter/diff_detail_formatter/text_utils"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -125,11 +125,11 @@ module Canon
|
|
|
125
125
|
},
|
|
126
126
|
added: {
|
|
127
127
|
marker: { color: :green, bg: nil, bold: false },
|
|
128
|
-
content: { color: :green,
|
|
128
|
+
content: { color: :green, bg: nil, bold: false,
|
|
129
129
|
underline: false, strikethrough: false },
|
|
130
130
|
},
|
|
131
131
|
changed: {
|
|
132
|
-
marker: { color: :yellow,
|
|
132
|
+
marker: { color: :yellow, bg: nil, bold: true },
|
|
133
133
|
content_old: { color: :bright_red, bg: nil, bold: false,
|
|
134
134
|
underline: false, strikethrough: true },
|
|
135
135
|
content_new: { color: :bright_green, bg: nil, bold: false,
|
data/lib/canon/diff_formatter.rb
CHANGED
|
@@ -2,14 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "paint" unless RUBY_ENGINE == "opal"
|
|
4
4
|
require "yaml" unless RUBY_ENGINE == "opal"
|
|
5
|
-
require_relative "comparison"
|
|
6
|
-
require_relative "diff/diff_block"
|
|
7
|
-
require_relative "diff/diff_context"
|
|
8
|
-
require_relative "diff/diff_report"
|
|
9
|
-
require_relative "diff_formatter/debug_output"
|
|
10
|
-
require_relative "diff_formatter/by_line_formatter"
|
|
11
|
-
require_relative "diff_formatter/by_object_formatter"
|
|
12
|
-
require_relative "diff_formatter/pretty_diff_formatter"
|
|
13
5
|
|
|
14
6
|
module Canon
|
|
15
7
|
# Formatter for displaying semantic differences with color support
|
|
@@ -65,6 +57,16 @@ module Canon
|
|
|
65
57
|
# )
|
|
66
58
|
#
|
|
67
59
|
class DiffFormatter
|
|
60
|
+
autoload :ByLineFormatter, "canon/diff_formatter/by_line_formatter"
|
|
61
|
+
autoload :ByObjectFormatter, "canon/diff_formatter/by_object_formatter"
|
|
62
|
+
autoload :DebugOutput, "canon/diff_formatter/debug_output"
|
|
63
|
+
autoload :DiffDetailFormatter, "canon/diff_formatter/diff_detail_formatter"
|
|
64
|
+
autoload :DiffDetailFormatterHelpers,
|
|
65
|
+
"canon/diff_formatter/diff_detail_formatter_helpers"
|
|
66
|
+
autoload :Legend, "canon/diff_formatter/legend"
|
|
67
|
+
autoload :PrettyDiffFormatter, "canon/diff_formatter/pretty_diff_formatter"
|
|
68
|
+
autoload :Theme, "canon/diff_formatter/theme"
|
|
69
|
+
|
|
68
70
|
# Namespace for by-object mode formatters
|
|
69
71
|
module ByObject
|
|
70
72
|
autoload :BaseFormatter, "canon/diff_formatter/by_object/base_formatter"
|
|
@@ -76,6 +78,7 @@ module Canon
|
|
|
76
78
|
# Namespace for by-line mode formatters
|
|
77
79
|
module ByLine
|
|
78
80
|
autoload :BaseFormatter, "canon/diff_formatter/by_line/base_formatter"
|
|
81
|
+
autoload :HtmlFormatter, "canon/diff_formatter/by_line/html_formatter"
|
|
79
82
|
autoload :SimpleFormatter, "canon/diff_formatter/by_line/simple_formatter"
|
|
80
83
|
autoload :XmlFormatter, "canon/diff_formatter/by_line/xml_formatter"
|
|
81
84
|
autoload :JsonFormatter, "canon/diff_formatter/by_line/json_formatter"
|
|
@@ -308,11 +311,10 @@ module Canon
|
|
|
308
311
|
# In by-line mode with both docs present, always use by-line diff
|
|
309
312
|
if @mode == :by_line && doc1 && doc2
|
|
310
313
|
doc1, doc2 = apply_display_preprocessing(doc1, doc2, format)
|
|
311
|
-
# rubocop:disable Layout/HashAlignment
|
|
314
|
+
# rubocop:disable-next Layout/HashAlignment
|
|
312
315
|
return by_line_formatter.format(doc1, doc2, format: format,
|
|
313
316
|
html_version: html_version,
|
|
314
317
|
differences: differences)
|
|
315
|
-
# rubocop:enable Layout/HashAlignment
|
|
316
318
|
end
|
|
317
319
|
|
|
318
320
|
# In pretty_diff mode, always use text-LCS diff (bypasses DiffNodeMapper).
|
|
@@ -340,11 +342,10 @@ module Canon
|
|
|
340
342
|
doc1, doc2 = apply_display_preprocessing(doc1, doc2,
|
|
341
343
|
format)
|
|
342
344
|
end
|
|
343
|
-
# rubocop:disable Layout/HashAlignment
|
|
345
|
+
# rubocop:disable-next Layout/HashAlignment
|
|
344
346
|
by_line_formatter.format(doc1, doc2, format: format,
|
|
345
347
|
html_version: html_version,
|
|
346
348
|
differences: differences)
|
|
347
|
-
# rubocop:enable Layout/HashAlignment
|
|
348
349
|
when :pretty_diff
|
|
349
350
|
d1, d2 = if doc1 && doc2
|
|
350
351
|
apply_display_preprocessing(doc1, doc2,
|
|
@@ -420,7 +421,6 @@ module Canon
|
|
|
420
421
|
# 2. Semantic Diff Report (ALWAYS if diffs exist)
|
|
421
422
|
if comparison_result.is_a?(Canon::Comparison::ComparisonResult) &&
|
|
422
423
|
comparison_result.differences.any?
|
|
423
|
-
require_relative "diff_formatter/diff_detail_formatter"
|
|
424
424
|
output << DiffDetailFormatter.format_report(
|
|
425
425
|
comparison_result.differences,
|
|
426
426
|
use_color: @use_color,
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "nokogiri" unless RUBY_ENGINE == "opal"
|
|
4
|
-
require_relative "../xml_backend"
|
|
5
|
-
require_relative "../xml/c14n"
|
|
6
|
-
require_relative "../pretty_printer/xml"
|
|
7
|
-
require_relative "../validators/xml_validator"
|
|
8
4
|
|
|
9
5
|
module Canon
|
|
10
6
|
module Formatters
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Canon
|
|
4
|
+
# Format-specific canonicalizers and parsers for the top-level
|
|
5
|
+
# `Canon.format` / `Canon.parse` API. Distinct from {DiffFormatter},
|
|
6
|
+
# which renders comparison output.
|
|
7
|
+
module Formatters
|
|
8
|
+
autoload :Html4Formatter, "canon/formatters/html4_formatter"
|
|
9
|
+
autoload :Html5Formatter, "canon/formatters/html5_formatter"
|
|
10
|
+
autoload :HtmlFormatter, "canon/formatters/html_formatter"
|
|
11
|
+
autoload :HtmlFormatterBase, "canon/formatters/html_formatter_base"
|
|
12
|
+
autoload :JsonFormatter, "canon/formatters/json_formatter"
|
|
13
|
+
autoload :XmlFormatter, "canon/formatters/xml_formatter"
|
|
14
|
+
autoload :YamlFormatter, "canon/formatters/yaml_formatter"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "nokogiri" unless RUBY_ENGINE == "opal"
|
|
4
|
-
require_relative "../data_model"
|
|
5
|
-
require_relative "../xml/nodes/root_node"
|
|
6
|
-
require_relative "../xml/nodes/element_node"
|
|
7
|
-
require_relative "../xml/nodes/namespace_node"
|
|
8
|
-
require_relative "../xml/nodes/attribute_node"
|
|
9
|
-
require_relative "../xml/nodes/text_node"
|
|
10
|
-
require_relative "../xml/nodes/comment_node"
|
|
11
|
-
require_relative "../xml/nodes/processing_instruction_node"
|
|
12
4
|
|
|
13
5
|
module Canon
|
|
14
6
|
module Html
|
|
@@ -81,7 +73,6 @@ module Canon
|
|
|
81
73
|
def self.serialize(node)
|
|
82
74
|
# HTML nodes use the same serialization as XML
|
|
83
75
|
# Delegate to XML serialization implementation
|
|
84
|
-
require_relative "../xml/data_model"
|
|
85
76
|
Canon::Xml::DataModel.serialize(node)
|
|
86
77
|
end
|
|
87
78
|
|
|
@@ -222,7 +213,6 @@ module Canon
|
|
|
222
213
|
whitespace_sensitive_tags = %w[pre code textarea script style]
|
|
223
214
|
|
|
224
215
|
# Check if whitespace is between inline siblings
|
|
225
|
-
require_relative "../comparison/whitespace_sensitivity"
|
|
226
216
|
unless whitespace_sensitive_tags.include?(parent_name) ||
|
|
227
217
|
Canon::Comparison::WhitespaceSensitivity.inline_whitespace_significant?(nokogiri_text)
|
|
228
218
|
return nil
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Canon
|
|
4
|
+
module Html
|
|
5
|
+
# Nokogiri node utilities for the HTML pipeline.
|
|
6
|
+
#
|
|
7
|
+
# HTML parsing is Nokogiri's job on every CRuby runtime regardless of
|
|
8
|
+
# the XML engine (XmlBackend): moxml has no HTML adapter and leptris no
|
|
9
|
+
# HTML parser. Under Opal Nokogiri is unavailable and these helpers
|
|
10
|
+
# degrade the same way the moxml backend always did (false / nil /
|
|
11
|
+
# raise), which keeps HTML comparison unsupported there.
|
|
12
|
+
module NokogiriSupport
|
|
13
|
+
class << self
|
|
14
|
+
# Whether the node is a document fragment (any variant).
|
|
15
|
+
def document_fragment?(node)
|
|
16
|
+
return false unless defined?(Nokogiri)
|
|
17
|
+
|
|
18
|
+
node.is_a?(Nokogiri::XML::DocumentFragment) ||
|
|
19
|
+
node.is_a?(Nokogiri::HTML4::DocumentFragment) ||
|
|
20
|
+
node.is_a?(Nokogiri::HTML5::DocumentFragment)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Whether the node is an HTML document (any variant).
|
|
24
|
+
def html_document?(node)
|
|
25
|
+
return false unless defined?(Nokogiri)
|
|
26
|
+
|
|
27
|
+
node.is_a?(Nokogiri::HTML::Document) ||
|
|
28
|
+
node.is_a?(Nokogiri::HTML4::Document) ||
|
|
29
|
+
node.is_a?(Nokogiri::HTML5::Document)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Detect HTML version from a Nokogiri node.
|
|
33
|
+
# Returns :html5 or :html4. Defaults to :html5 for non-Nokogiri nodes.
|
|
34
|
+
def html_version_from_node(node)
|
|
35
|
+
return :html5 unless defined?(Nokogiri)
|
|
36
|
+
|
|
37
|
+
if node.is_a?(Nokogiri::HTML5::Document) ||
|
|
38
|
+
node.is_a?(Nokogiri::HTML5::DocumentFragment)
|
|
39
|
+
:html5
|
|
40
|
+
elsif node.is_a?(Nokogiri::HTML4::Document) ||
|
|
41
|
+
node.is_a?(Nokogiri::HTML4::DocumentFragment)
|
|
42
|
+
:html4
|
|
43
|
+
else
|
|
44
|
+
:html5
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Parse an HTML string into an XML fragment for the XML comparator.
|
|
49
|
+
def xml_fragment(html_string)
|
|
50
|
+
unless defined?(Nokogiri)
|
|
51
|
+
raise Canon::Error,
|
|
52
|
+
"HTML fragment parsing requires Nokogiri"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
Nokogiri::XML.fragment(html_string)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/canon/html.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "html/data_model"
|
|
4
|
-
|
|
5
3
|
module Canon
|
|
6
|
-
# HTML-specific functionality for Canon
|
|
4
|
+
# HTML-specific functionality for Canon.
|
|
5
|
+
#
|
|
6
|
+
# Children are autoloaded — never `require_relative` them.
|
|
7
7
|
module Html
|
|
8
|
+
autoload :DataModel, "canon/html/data_model"
|
|
9
|
+
autoload :NokogiriSupport, "canon/html/nokogiri_support"
|
|
8
10
|
end
|
|
9
11
|
end
|
|
@@ -11,7 +11,12 @@ module Canon
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def format(xml_string)
|
|
14
|
-
|
|
14
|
+
# Output parity: pretty-printed bytes are canon's product. The
|
|
15
|
+
# moxml serializer cannot yet match Nokogiri's output (empty
|
|
16
|
+
# element expansion, declaration placement, tab indent), so the
|
|
17
|
+
# Nokogiri pipeline is used whenever available — the moxml branch
|
|
18
|
+
# is the Opal fallback.
|
|
19
|
+
if defined?(Nokogiri)
|
|
15
20
|
nokogiri_format(xml_string)
|
|
16
21
|
else
|
|
17
22
|
moxml_format(xml_string)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "nokogiri" unless RUBY_ENGINE == "opal"
|
|
4
|
-
require_relative "html_void_elements"
|
|
5
4
|
|
|
6
5
|
module Canon
|
|
7
6
|
module PrettyPrinter
|
|
@@ -154,12 +153,12 @@ module Canon
|
|
|
154
153
|
# @return [String] Serialized XML, one node per line, with content
|
|
155
154
|
# whitespace visualized at line boundaries
|
|
156
155
|
def format(xml_string)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
Nokogiri::HTML5(xml_string)
|
|
156
|
+
# Output parity: see PrettyPrinter::Xml — the Nokogiri pipeline
|
|
157
|
+
# produces canon's byte-stable output; moxml is the Opal fallback.
|
|
158
|
+
doc = if defined?(Nokogiri)
|
|
159
|
+
@html_mode ? Nokogiri::HTML5(xml_string) : Nokogiri::XML(xml_string)
|
|
161
160
|
else
|
|
162
|
-
|
|
161
|
+
Canon::XmlParsing.parse(xml_string)
|
|
163
162
|
end
|
|
164
163
|
lines = []
|
|
165
164
|
|
|
@@ -398,7 +397,6 @@ module Canon
|
|
|
398
397
|
|
|
399
398
|
# Load the default visualization map from DiffFormatter constants.
|
|
400
399
|
def default_vis_map
|
|
401
|
-
require_relative "../diff_formatter"
|
|
402
400
|
Canon::DiffFormatter::DEFAULT_VISUALIZATION_MAP
|
|
403
401
|
rescue LoadError, NameError
|
|
404
402
|
{ " " => "░", "\t" => "⇥", "\n" => "↵", "\r" => "⏎", "\u00A0" => "␣" }
|