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.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +62 -25
  3. data/CLAUDE.md +197 -0
  4. data/Rakefile +5 -2
  5. data/lib/canon/cache.rb +3 -1
  6. data/lib/canon/cli.rb +0 -3
  7. data/lib/canon/commands/diff_command.rb +1 -8
  8. data/lib/canon/commands/format_command.rb +0 -4
  9. data/lib/canon/commands.rb +9 -0
  10. data/lib/canon/comparison/child_realignment.rb +0 -2
  11. data/lib/canon/comparison/compare_profile.rb +30 -36
  12. data/lib/canon/comparison/comparison_result.rb +1 -4
  13. data/lib/canon/comparison/diff_node_builder.rb +353 -0
  14. data/lib/canon/comparison/dimensions/dimension.rb +51 -0
  15. data/lib/canon/comparison/dimensions/dimension_set.rb +49 -0
  16. data/lib/canon/comparison/dimensions/registry.rb +101 -60
  17. data/lib/canon/comparison/dimensions.rb +15 -46
  18. data/lib/canon/comparison/format_detector.rb +27 -29
  19. data/lib/canon/comparison/html_comparator.rb +20 -143
  20. data/lib/canon/comparison/html_compare_profile.rb +15 -18
  21. data/lib/canon/comparison/json_comparator.rb +4 -165
  22. data/lib/canon/comparison/json_parser.rb +0 -2
  23. data/lib/canon/comparison/markup_comparator.rb +14 -210
  24. data/lib/canon/comparison/match_options/base_resolver.rb +18 -29
  25. data/lib/canon/comparison/match_options/json_resolver.rb +4 -28
  26. data/lib/canon/comparison/match_options/xml_resolver.rb +4 -45
  27. data/lib/canon/comparison/match_options/yaml_resolver.rb +4 -30
  28. data/lib/canon/comparison/match_options.rb +13 -88
  29. data/lib/canon/comparison/node_inspector.rb +5 -9
  30. data/lib/canon/comparison/pipeline.rb +269 -0
  31. data/lib/canon/comparison/profile_definition.rb +0 -2
  32. data/lib/canon/comparison/ruby_object_comparator.rb +1 -1
  33. data/lib/canon/comparison/strategies/match_strategy_factory.rb +9 -58
  34. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +4 -11
  35. data/lib/canon/comparison/strategies.rb +16 -0
  36. data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +1 -4
  37. data/lib/canon/comparison/xml_comparator/attribute_filter.rb +0 -3
  38. data/lib/canon/comparison/xml_comparator/child_comparison.rb +0 -6
  39. data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +1 -6
  40. data/lib/canon/comparison/xml_comparator/node_parser.rb +3 -7
  41. data/lib/canon/comparison/xml_comparator.rb +4 -492
  42. data/lib/canon/comparison/xml_comparator_helpers.rb +21 -0
  43. data/lib/canon/comparison/xml_node_comparison.rb +6 -121
  44. data/lib/canon/comparison/yaml_comparator.rb +0 -3
  45. data/lib/canon/comparison.rb +144 -267
  46. data/lib/canon/config/config_dsl.rb +159 -0
  47. data/lib/canon/config/env_provider.rb +0 -3
  48. data/lib/canon/config/env_schema.rb +48 -58
  49. data/lib/canon/config/profile_loader.rb +0 -1
  50. data/lib/canon/config.rb +116 -468
  51. data/lib/canon/diff/diff_block_builder.rb +0 -2
  52. data/lib/canon/diff/diff_classifier.rb +0 -5
  53. data/lib/canon/diff/diff_context.rb +0 -2
  54. data/lib/canon/diff/diff_context_builder.rb +0 -2
  55. data/lib/canon/diff/diff_line_builder.rb +0 -3
  56. data/lib/canon/diff/diff_node_enricher.rb +0 -4
  57. data/lib/canon/diff/diff_node_mapper.rb +0 -4
  58. data/lib/canon/diff/diff_report_builder.rb +0 -4
  59. data/lib/canon/diff/formatting_detector.rb +0 -1
  60. data/lib/canon/diff/node_serializer.rb +0 -7
  61. data/lib/canon/diff.rb +39 -0
  62. data/lib/canon/diff_formatter/by_line/base_formatter.rb +5 -19
  63. data/lib/canon/diff_formatter/by_line/html_formatter.rb +8 -21
  64. data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -3
  65. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -3
  66. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +7 -26
  67. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -3
  68. data/lib/canon/diff_formatter/by_line_formatter.rb +1 -2
  69. data/lib/canon/diff_formatter/by_object/base_formatter.rb +8 -15
  70. data/lib/canon/diff_formatter/by_object/json_formatter.rb +0 -2
  71. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +0 -2
  72. data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +0 -2
  73. data/lib/canon/diff_formatter/debug_output.rb +0 -2
  74. data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +24 -58
  75. data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +0 -2
  76. data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +3 -4
  77. data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +1 -7
  78. data/lib/canon/diff_formatter/diff_detail_formatter.rb +0 -7
  79. data/lib/canon/diff_formatter/diff_detail_formatter_helpers.rb +23 -0
  80. data/lib/canon/diff_formatter/theme.rb +2 -2
  81. data/lib/canon/diff_formatter.rb +13 -13
  82. data/lib/canon/formatters/html4_formatter.rb +0 -2
  83. data/lib/canon/formatters/html5_formatter.rb +0 -2
  84. data/lib/canon/formatters/html_formatter.rb +0 -3
  85. data/lib/canon/formatters/json_formatter.rb +0 -1
  86. data/lib/canon/formatters/xml_formatter.rb +0 -4
  87. data/lib/canon/formatters/yaml_formatter.rb +0 -1
  88. data/lib/canon/formatters.rb +16 -0
  89. data/lib/canon/html/data_model.rb +0 -10
  90. data/lib/canon/html/nokogiri_support.rb +60 -0
  91. data/lib/canon/html.rb +5 -3
  92. data/lib/canon/options/cli_generator.rb +0 -2
  93. data/lib/canon/options/registry.rb +0 -2
  94. data/lib/canon/options.rb +9 -0
  95. data/lib/canon/pretty_printer/html.rb +0 -1
  96. data/lib/canon/pretty_printer/xml.rb +6 -1
  97. data/lib/canon/pretty_printer/xml_normalized.rb +5 -7
  98. data/lib/canon/pretty_printer.rb +12 -0
  99. data/lib/canon/tree_diff/adapters/html_adapter.rb +1 -1
  100. data/lib/canon/tree_diff/adapters.rb +14 -0
  101. data/lib/canon/tree_diff/core/attribute_comparator.rb +0 -6
  102. data/lib/canon/tree_diff/core/node_signature.rb +1 -1
  103. data/lib/canon/tree_diff/core/tree_node.rb +13 -5
  104. data/lib/canon/tree_diff/core.rb +17 -0
  105. data/lib/canon/tree_diff/matchers/hash_matcher.rb +0 -7
  106. data/lib/canon/tree_diff/matchers/similarity_matcher.rb +1 -5
  107. data/lib/canon/tree_diff/matchers/structural_propagator.rb +1 -5
  108. data/lib/canon/tree_diff/matchers.rb +15 -0
  109. data/lib/canon/tree_diff/operation_converter.rb +0 -8
  110. data/lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb +2 -12
  111. data/lib/canon/tree_diff/operation_converter_helpers/post_processor.rb +13 -7
  112. data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +2 -2
  113. data/lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb +4 -6
  114. data/lib/canon/tree_diff/operation_converter_helpers.rb +18 -0
  115. data/lib/canon/tree_diff/operations/operation_detector.rb +2 -5
  116. data/lib/canon/tree_diff/operations.rb +13 -0
  117. data/lib/canon/tree_diff.rb +26 -27
  118. data/lib/canon/validators/base_validator.rb +0 -2
  119. data/lib/canon/validators/html_validator.rb +0 -1
  120. data/lib/canon/validators/json_validator.rb +0 -1
  121. data/lib/canon/validators/xml_validator.rb +0 -1
  122. data/lib/canon/validators/yaml_validator.rb +0 -1
  123. data/lib/canon/validators.rb +12 -0
  124. data/lib/canon/version.rb +1 -1
  125. data/lib/canon/xml/c14n.rb +0 -4
  126. data/lib/canon/xml/data_model.rb +206 -43
  127. data/lib/canon/xml/line_range_mapper.rb +0 -2
  128. data/lib/canon/xml/nodes/attribute_node.rb +0 -2
  129. data/lib/canon/xml/nodes/comment_node.rb +0 -2
  130. data/lib/canon/xml/nodes/element_node.rb +0 -2
  131. data/lib/canon/xml/nodes/namespace_node.rb +0 -2
  132. data/lib/canon/xml/nodes/processing_instruction_node.rb +0 -2
  133. data/lib/canon/xml/nodes/root_node.rb +0 -2
  134. data/lib/canon/xml/nodes/text_node.rb +0 -2
  135. data/lib/canon/xml/nodes.rb +19 -0
  136. data/lib/canon/xml/processor.rb +10 -7
  137. data/lib/canon/xml/sax/moxml_driver.rb +58 -0
  138. data/lib/canon/xml/sax/nokogiri_driver.rb +54 -0
  139. data/lib/canon/xml/sax.rb +37 -0
  140. data/lib/canon/xml/sax_builder.rb +31 -27
  141. data/lib/canon/xml.rb +34 -0
  142. data/lib/canon/xml_backend.rb +40 -19
  143. data/lib/canon/xml_parsing.rb +89 -116
  144. data/lib/canon.rb +26 -17
  145. data/lib/tasks/benchmark_runner.rb +4 -0
  146. data/lib/tasks/performance.rake +2 -58
  147. data/lib/tasks/performance_comparator.rb +132 -65
  148. data/lib/tasks/performance_helpers.rb +4 -249
  149. data/lib/tasks/performance_report.rb +313 -0
  150. metadata +31 -13
  151. data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +0 -64
  152. data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +0 -64
  153. data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +0 -167
  154. data/lib/canon/comparison/dimensions/base_dimension.rb +0 -107
  155. data/lib/canon/comparison/dimensions/comments_dimension.rb +0 -117
  156. data/lib/canon/comparison/dimensions/element_position_dimension.rb +0 -86
  157. data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +0 -115
  158. data/lib/canon/comparison/dimensions/text_content_dimension.rb +0 -102
  159. data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +0 -300
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark/ips"
4
+ require "json"
5
+ require "securerandom"
6
+ require "stringio"
7
+ require "time"
8
+ require "yaml"
9
+
10
+ lib_path = File.expand_path(File.join(__dir__, "..", "..", "lib"))
11
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
12
+
13
+ require "canon"
14
+
15
+ begin
16
+ require "canon/xml/sax_builder"
17
+ SAX_AVAILABLE = true
18
+ rescue LoadError
19
+ SAX_AVAILABLE = false
20
+ end
21
+
22
+ # Standalone benchmark runner that emits JSON results on stdout.
23
+ #
24
+ # Used by performance_comparator.rb to run benchmarks in a clean Ruby
25
+ # process per branch — sharing no state with the comparator itself.
26
+ # This isolates each branch's Canon implementation from the other.
27
+ module Performance
28
+ REPORT_ID = SecureRandom.hex(4).freeze
29
+
30
+ DEFAULT_RUN_TIME = Integer(ENV.fetch("CANON_PERF_RUN_TIME", "5"))
31
+ DEFAULT_WARMUP = Integer(ENV.fetch("CANON_PERF_WARMUP", "2"))
32
+ DEFAULT_ITEMS = Integer(ENV.fetch("CANON_PERF_ITEMS", "50"))
33
+
34
+ BENCHMARKS = {
35
+ xml_parse_dom_simple: { category: "xml_parsing", name: "DOM (simple)" },
36
+ xml_parse_sax_simple: { category: "xml_parsing", name: "SAX (simple)" },
37
+ xml_parse_dom_large: { category: "xml_parsing", name: "DOM (large)" },
38
+ xml_parse_sax_large: { category: "xml_parsing", name: "SAX (large)" },
39
+ html_parse_simple: { category: "html_parsing", name: "Simple HTML" },
40
+ html_parse_complex: { category: "html_parsing", name: "Complex HTML" },
41
+ xml_compare_identical: { category: "xml_comparison",
42
+ name: "Identical XML" },
43
+ xml_compare_similar: { category: "xml_comparison", name: "Similar XML" },
44
+ xml_compare_different: { category: "xml_comparison",
45
+ name: "Different XML" },
46
+ html_compare_identical: { category: "html_comparison",
47
+ name: "Identical HTML" },
48
+ html_compare_similar: { category: "html_comparison", name: "Similar HTML" },
49
+ html_compare_different: { category: "html_comparison",
50
+ name: "Different HTML" },
51
+ xml_c14n_format: { category: "formatting", name: "XML C14N" },
52
+ json_format: { category: "formatting", name: "JSON" },
53
+ yaml_format: { category: "formatting", name: "YAML" },
54
+ }.freeze
55
+
56
+ module DataGenerator
57
+ DEFAULT_ITEMS = Performance::DEFAULT_ITEMS
58
+
59
+ class << self
60
+ def generate_xml(items: DEFAULT_ITEMS, depth: 1, with_namespaces: false,
61
+ with_attributes: true)
62
+ ns = with_namespaces ? 'xmlns:ns="http://example.org"' : ""
63
+ prefix = with_namespaces ? "ns:" : ""
64
+
65
+ build_xml_element(items, depth, prefix, with_attributes, ns)
66
+ end
67
+
68
+ def build_xml_element(items, depth, prefix, with_attrs, ns_decl)
69
+ attrs = with_attrs ? " id=\"#{rand(1000)}\" status=\"active\"" : ""
70
+ ns_attr = ns_decl.empty? ? "" : " #{ns_decl}"
71
+
72
+ if depth <= 1
73
+ children = Array.new(items) do |i|
74
+ inner_attrs = with_attrs ? " index=\"#{i}\"" : ""
75
+ "<#{prefix}item#{inner_attrs}>Item #{i} content with some text</#{prefix}item>"
76
+ end.join
77
+ "<#{prefix}root#{ns_attr}#{attrs}>#{children}</#{prefix}root>"
78
+ else
79
+ child = build_xml_element(items / 2, depth - 1, prefix, with_attrs,
80
+ "")
81
+ "<#{prefix}root#{ns_attr}#{attrs}>#{child}</#{prefix}root>"
82
+ end
83
+ end
84
+
85
+ def generate_html(items: DEFAULT_ITEMS, with_scripts: false,
86
+ with_tables: false)
87
+ scripts = if with_scripts
88
+ <<~HTML
89
+ <script type="text/javascript">
90
+ function test() { return true; }
91
+ </script>
92
+ <style>
93
+ body { margin: 0; }
94
+ </style>
95
+ HTML
96
+ else
97
+ ""
98
+ end
99
+
100
+ tables = if with_tables
101
+ rows = Array.new(items) do |i|
102
+ "<tr><td>Cell #{i}A</td><td>Cell #{i}B</td></tr>"
103
+ end.join
104
+ "<table>#{rows}</table>"
105
+ else
106
+ ""
107
+ end
108
+
109
+ list_items = Array.new(items) do |i|
110
+ "<li class=\"item-#{i}\">List item #{i} with bold text</li>"
111
+ end.join("\n ")
112
+
113
+ nav_count = [(items / 5), 2].max
114
+ nav_items = items.times.first(nav_count).map do |i|
115
+ "<li class=\"nav-#{i}\">Nav #{i}</li>"
116
+ end.join("\n ")
117
+
118
+ <<~HTML
119
+ <!DOCTYPE html>
120
+ <html lang="en">
121
+ <head>
122
+ <meta charset="UTF-8">
123
+ <title>Benchmark Document</title>
124
+ #{scripts}
125
+ </head>
126
+ <body>
127
+ <header>
128
+ <h1>Benchmark Test Document</h1>
129
+ <nav>
130
+ <ul>
131
+ #{nav_items}
132
+ </ul>
133
+ </nav>
134
+ </header>
135
+ <main>
136
+ <section id="content">
137
+ <p>This is a paragraph with emphasized and strong text.</p>
138
+ <ul>
139
+ #{list_items}
140
+ </ul>
141
+ #{tables}
142
+ </section>
143
+ </main>
144
+ <footer>
145
+ <p>Copyright 2024</p>
146
+ </footer>
147
+ </body>
148
+ </html>
149
+ HTML
150
+ end
151
+
152
+ def generate_json(items: DEFAULT_ITEMS)
153
+ data = {
154
+ metadata: { version: "1.0", generated: Time.now.iso8601 },
155
+ items: Array.new(items) do |i|
156
+ {
157
+ id: i,
158
+ name: "Item #{i}",
159
+ value: rand * 1000,
160
+ tags: ["tag1", "tag2", "tag#{i % 10}"],
161
+ nested: {
162
+ level1: { level2: { data: "deeply nested value #{i}" } },
163
+ },
164
+ }
165
+ end,
166
+ }
167
+ JSON.generate(data)
168
+ end
169
+
170
+ def generate_yaml(items: DEFAULT_ITEMS)
171
+ JSON.parse(generate_json(items: items)).to_yaml
172
+ end
173
+
174
+ def generate_large_xml(items: 500)
175
+ generate_xml(items: items, depth: 3, with_namespaces: true,
176
+ with_attributes: true)
177
+ end
178
+ end
179
+ end
180
+
181
+ class Report
182
+ def initialize(run_time: DEFAULT_RUN_TIME, warmup: DEFAULT_WARMUP,
183
+ items: DEFAULT_ITEMS)
184
+ @run_time = run_time
185
+ @warmup = warmup
186
+ @items = items
187
+ @results = {}
188
+ end
189
+
190
+ def run_all
191
+ BENCHMARKS.each do |method, meta|
192
+ next if method.to_s.start_with?("xml_parse_sax") && !SAX_AVAILABLE
193
+
194
+ label = "#{meta[:category]}: #{meta[:name]}"
195
+ @results[label] = run_benchmark(method)
196
+ end
197
+ @results
198
+ end
199
+
200
+ def as_json
201
+ {
202
+ report_id: REPORT_ID,
203
+ ruby_version: RUBY_VERSION,
204
+ platform: RUBY_PLATFORM,
205
+ run_time: @run_time,
206
+ warmup: @warmup,
207
+ items: @items,
208
+ sax_available: SAX_AVAILABLE,
209
+ benchmarks: @results,
210
+ }
211
+ end
212
+
213
+ private
214
+
215
+ def run_benchmark(method)
216
+ original_stdout = $stdout
217
+ $stdout = StringIO.new
218
+ begin
219
+ job = Benchmark::IPS::Job.new
220
+ job.config(time: @run_time, warmup: @warmup)
221
+ job.report("test") { dispatch(method) }
222
+ job.run
223
+
224
+ entry = job.full_report.entries.first
225
+ ensure
226
+ $stdout = original_stdout
227
+ end
228
+
229
+ samples = entry.stats.samples
230
+ return { lower: 0, upper: 0 } if samples.empty?
231
+
232
+ mean = samples.sum.to_f / samples.size
233
+ variance = samples.sum { |x| (x - mean)**2 } / (samples.size - 1)
234
+ std_dev = Math.sqrt(variance)
235
+ error_margin = std_dev / mean
236
+ error_pct = error_margin.round(4)
237
+
238
+ # Clamp lower bound to positive — for very fast operations the
239
+ # error margin can exceed the mean, producing nonsensical
240
+ # negative IPS values that break the regression comparison.
241
+ lower = (mean * (1 - error_pct)).round(4)
242
+ lower = (mean * 0.5).round(4) if lower <= 0
243
+ upper = (mean * (1 + error_pct)).round(4)
244
+
245
+ { lower: lower, upper: upper }
246
+ end
247
+
248
+ def dispatch(method)
249
+ case method
250
+ when :xml_parse_dom_simple
251
+ Canon::Xml::DataModel.from_xml(DataGenerator.generate_xml(items: @items))
252
+ when :xml_parse_sax_simple
253
+ Canon::Xml::SaxBuilder.parse(DataGenerator.generate_xml(items: @items))
254
+ when :xml_parse_dom_large
255
+ Canon::Xml::DataModel.from_xml(DataGenerator.generate_large_xml(items: @items * 5))
256
+ when :xml_parse_sax_large
257
+ Canon::Xml::SaxBuilder.parse(DataGenerator.generate_large_xml(items: @items * 5))
258
+ when :html_parse_simple
259
+ Canon.parse_html(DataGenerator.generate_html(items: @items))
260
+ when :html_parse_complex
261
+ Canon.parse_html(DataGenerator.generate_html(items: @items,
262
+ with_scripts: true,
263
+ with_tables: true))
264
+ when :xml_compare_identical
265
+ xml = DataGenerator.generate_xml(items: @items)
266
+ Canon::Comparison.equivalent?(xml, xml, format: :xml)
267
+ when :xml_compare_similar
268
+ Canon::Comparison.equivalent?(
269
+ DataGenerator.generate_xml(items: @items),
270
+ DataGenerator.generate_xml(items: @items),
271
+ format: :xml,
272
+ )
273
+ when :xml_compare_different
274
+ Canon::Comparison.equivalent?(
275
+ DataGenerator.generate_xml(items: @items),
276
+ DataGenerator.generate_xml(items: @items, with_namespaces: true),
277
+ format: :xml,
278
+ )
279
+ when :html_compare_identical
280
+ html = DataGenerator.generate_html(items: @items)
281
+ Canon::Comparison.equivalent?(html, html, format: :html)
282
+ when :html_compare_similar
283
+ Canon::Comparison.equivalent?(
284
+ DataGenerator.generate_html(items: @items),
285
+ DataGenerator.generate_html(items: @items),
286
+ format: :html,
287
+ )
288
+ when :html_compare_different
289
+ Canon::Comparison.equivalent?(
290
+ DataGenerator.generate_html(items: @items),
291
+ DataGenerator.generate_html(items: @items, with_tables: true),
292
+ format: :html,
293
+ )
294
+ when :xml_c14n_format
295
+ Canon.format_xml(DataGenerator.generate_xml(items: @items,
296
+ with_namespaces: true))
297
+ when :json_format
298
+ Canon.format_json(JSON.parse(DataGenerator.generate_json(items: @items)))
299
+ when :yaml_format
300
+ Canon.format_yaml(YAML.safe_load(DataGenerator.generate_yaml(items: @items),
301
+ permitted_classes: [Time]))
302
+ else
303
+ raise "Unknown benchmark: #{method}"
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ if $PROGRAM_NAME == __FILE__
310
+ report = Performance::Report.new
311
+ report.run_all
312
+ puts JSON.generate(report.as_json)
313
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.11
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-29 00:00:00.000000000 Z
11
+ date: 2026-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.1.22
47
+ version: 0.5.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.1.22
54
+ version: 0.5.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: nokogiri
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -151,6 +151,7 @@ files:
151
151
  - ".rspec-opal"
152
152
  - ".rubocop.yml"
153
153
  - ".rubocop_todo.yml"
154
+ - CLAUDE.md
154
155
  - CODE_OF_CONDUCT.md
155
156
  - README.adoc
156
157
  - Rakefile
@@ -219,6 +220,7 @@ files:
219
220
  - lib/canon/cache.rb
220
221
  - lib/canon/cli.rb
221
222
  - lib/canon/color_detector.rb
223
+ - lib/canon/commands.rb
222
224
  - lib/canon/commands/diff_command.rb
223
225
  - lib/canon/commands/format_command.rb
224
226
  - lib/canon/comparison.rb
@@ -226,16 +228,11 @@ files:
226
228
  - lib/canon/comparison/child_realignment.rb
227
229
  - lib/canon/comparison/compare_profile.rb
228
230
  - lib/canon/comparison/comparison_result.rb
231
+ - lib/canon/comparison/diff_node_builder.rb
229
232
  - lib/canon/comparison/dimensions.rb
230
- - lib/canon/comparison/dimensions/attribute_order_dimension.rb
231
- - lib/canon/comparison/dimensions/attribute_presence_dimension.rb
232
- - lib/canon/comparison/dimensions/attribute_values_dimension.rb
233
- - lib/canon/comparison/dimensions/base_dimension.rb
234
- - lib/canon/comparison/dimensions/comments_dimension.rb
235
- - lib/canon/comparison/dimensions/element_position_dimension.rb
233
+ - lib/canon/comparison/dimensions/dimension.rb
234
+ - lib/canon/comparison/dimensions/dimension_set.rb
236
235
  - lib/canon/comparison/dimensions/registry.rb
237
- - lib/canon/comparison/dimensions/structural_whitespace_dimension.rb
238
- - lib/canon/comparison/dimensions/text_content_dimension.rb
239
236
  - lib/canon/comparison/format_detector.rb
240
237
  - lib/canon/comparison/html_comparator.rb
241
238
  - lib/canon/comparison/html_compare_profile.rb
@@ -249,8 +246,10 @@ files:
249
246
  - lib/canon/comparison/match_options/xml_resolver.rb
250
247
  - lib/canon/comparison/match_options/yaml_resolver.rb
251
248
  - lib/canon/comparison/node_inspector.rb
249
+ - lib/canon/comparison/pipeline.rb
252
250
  - lib/canon/comparison/profile_definition.rb
253
251
  - lib/canon/comparison/ruby_object_comparator.rb
252
+ - lib/canon/comparison/strategies.rb
254
253
  - lib/canon/comparison/strategies/base_match_strategy.rb
255
254
  - lib/canon/comparison/strategies/match_strategy_factory.rb
256
255
  - lib/canon/comparison/strategies/semantic_tree_match_strategy.rb
@@ -259,14 +258,15 @@ files:
259
258
  - lib/canon/comparison/xml_comparator/attribute_comparator.rb
260
259
  - lib/canon/comparison/xml_comparator/attribute_filter.rb
261
260
  - lib/canon/comparison/xml_comparator/child_comparison.rb
262
- - lib/canon/comparison/xml_comparator/diff_node_builder.rb
263
261
  - lib/canon/comparison/xml_comparator/namespace_comparator.rb
264
262
  - lib/canon/comparison/xml_comparator/node_parser.rb
265
263
  - lib/canon/comparison/xml_comparator/node_type_comparator.rb
264
+ - lib/canon/comparison/xml_comparator_helpers.rb
266
265
  - lib/canon/comparison/xml_node_comparison.rb
267
266
  - lib/canon/comparison/xml_parser.rb
268
267
  - lib/canon/comparison/yaml_comparator.rb
269
268
  - lib/canon/config.rb
269
+ - lib/canon/config/config_dsl.rb
270
270
  - lib/canon/config/env_provider.rb
271
271
  - lib/canon/config/env_schema.rb
272
272
  - lib/canon/config/override_resolver.rb
@@ -275,6 +275,7 @@ files:
275
275
  - lib/canon/config/profiles/metanorma_debug.yml
276
276
  - lib/canon/config/type_converter.rb
277
277
  - lib/canon/data_model.rb
278
+ - lib/canon/diff.rb
278
279
  - lib/canon/diff/diff_block.rb
279
280
  - lib/canon/diff/diff_block_builder.rb
280
281
  - lib/canon/diff/diff_char_range.rb
@@ -315,10 +316,12 @@ files:
315
316
  - lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb
316
317
  - lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb
317
318
  - lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb
319
+ - lib/canon/diff_formatter/diff_detail_formatter_helpers.rb
318
320
  - lib/canon/diff_formatter/legend.rb
319
321
  - lib/canon/diff_formatter/pretty_diff_formatter.rb
320
322
  - lib/canon/diff_formatter/theme.rb
321
323
  - lib/canon/errors.rb
324
+ - lib/canon/formatters.rb
322
325
  - lib/canon/formatters/html4_formatter.rb
323
326
  - lib/canon/formatters/html5_formatter.rb
324
327
  - lib/canon/formatters/html_formatter.rb
@@ -328,8 +331,11 @@ files:
328
331
  - lib/canon/formatters/yaml_formatter.rb
329
332
  - lib/canon/html.rb
330
333
  - lib/canon/html/data_model.rb
334
+ - lib/canon/html/nokogiri_support.rb
335
+ - lib/canon/options.rb
331
336
  - lib/canon/options/cli_generator.rb
332
337
  - lib/canon/options/registry.rb
338
+ - lib/canon/pretty_printer.rb
333
339
  - lib/canon/pretty_printer/html.rb
334
340
  - lib/canon/pretty_printer/html_void_elements.rb
335
341
  - lib/canon/pretty_printer/json.rb
@@ -337,34 +343,41 @@ files:
337
343
  - lib/canon/pretty_printer/xml_normalized.rb
338
344
  - lib/canon/rspec_matchers.rb
339
345
  - lib/canon/tree_diff.rb
346
+ - lib/canon/tree_diff/adapters.rb
340
347
  - lib/canon/tree_diff/adapters/html_adapter.rb
341
348
  - lib/canon/tree_diff/adapters/json_adapter.rb
342
349
  - lib/canon/tree_diff/adapters/xml_adapter.rb
343
350
  - lib/canon/tree_diff/adapters/yaml_adapter.rb
351
+ - lib/canon/tree_diff/core.rb
344
352
  - lib/canon/tree_diff/core/attribute_comparator.rb
345
353
  - lib/canon/tree_diff/core/matching.rb
346
354
  - lib/canon/tree_diff/core/node_signature.rb
347
355
  - lib/canon/tree_diff/core/node_weight.rb
348
356
  - lib/canon/tree_diff/core/tree_node.rb
349
357
  - lib/canon/tree_diff/core/xml_entity_decoder.rb
358
+ - lib/canon/tree_diff/matchers.rb
350
359
  - lib/canon/tree_diff/matchers/hash_matcher.rb
351
360
  - lib/canon/tree_diff/matchers/similarity_matcher.rb
352
361
  - lib/canon/tree_diff/matchers/structural_propagator.rb
353
362
  - lib/canon/tree_diff/matchers/universal_matcher.rb
354
363
  - lib/canon/tree_diff/operation_converter.rb
364
+ - lib/canon/tree_diff/operation_converter_helpers.rb
355
365
  - lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb
356
366
  - lib/canon/tree_diff/operation_converter_helpers/post_processor.rb
357
367
  - lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb
358
368
  - lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb
369
+ - lib/canon/tree_diff/operations.rb
359
370
  - lib/canon/tree_diff/operations/operation.rb
360
371
  - lib/canon/tree_diff/operations/operation_detector.rb
361
372
  - lib/canon/tree_diff/tree_diff_integrator.rb
373
+ - lib/canon/validators.rb
362
374
  - lib/canon/validators/base_validator.rb
363
375
  - lib/canon/validators/html_validator.rb
364
376
  - lib/canon/validators/json_validator.rb
365
377
  - lib/canon/validators/xml_validator.rb
366
378
  - lib/canon/validators/yaml_validator.rb
367
379
  - lib/canon/version.rb
380
+ - lib/canon/xml.rb
368
381
  - lib/canon/xml/attribute_handler.rb
369
382
  - lib/canon/xml/c14n.rb
370
383
  - lib/canon/xml/character_encoder.rb
@@ -374,6 +387,7 @@ files:
374
387
  - lib/canon/xml/namespace_handler.rb
375
388
  - lib/canon/xml/namespace_helper.rb
376
389
  - lib/canon/xml/node.rb
390
+ - lib/canon/xml/nodes.rb
377
391
  - lib/canon/xml/nodes/attribute_node.rb
378
392
  - lib/canon/xml/nodes/comment_node.rb
379
393
  - lib/canon/xml/nodes/element_node.rb
@@ -382,6 +396,9 @@ files:
382
396
  - lib/canon/xml/nodes/root_node.rb
383
397
  - lib/canon/xml/nodes/text_node.rb
384
398
  - lib/canon/xml/processor.rb
399
+ - lib/canon/xml/sax.rb
400
+ - lib/canon/xml/sax/moxml_driver.rb
401
+ - lib/canon/xml/sax/nokogiri_driver.rb
385
402
  - lib/canon/xml/sax_builder.rb
386
403
  - lib/canon/xml/whitespace_normalizer.rb
387
404
  - lib/canon/xml/xml_base_handler.rb
@@ -392,6 +409,7 @@ files:
392
409
  - lib/tasks/performance.rake
393
410
  - lib/tasks/performance_comparator.rb
394
411
  - lib/tasks/performance_helpers.rb
412
+ - lib/tasks/performance_report.rb
395
413
  - lib/xml-c14n.rb
396
414
  - sig/xml/c14n.rbs
397
415
  homepage: https://github.com/lutaml/canon
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_dimension"
4
-
5
- module Canon
6
- module Comparison
7
- module Dimensions
8
- # Attribute order dimension
9
- #
10
- # Handles comparison of attribute ordering.
11
- # Supports :strict and :ignore behaviors.
12
- #
13
- # Behaviors:
14
- # - :strict - Attributes must appear in the same order
15
- # - :ignore - Attribute order doesn't matter
16
- class AttributeOrderDimension < BaseDimension
17
- # Extract attribute order from a node
18
- #
19
- # @param node [Moxml::Node, Nokogiri::XML::Node] Node to extract from
20
- # @return [Array<Symbol>] Array of attribute names in order
21
- def extract_data(node)
22
- return [] unless node
23
-
24
- if Canon::XmlBackend.nokogiri?
25
- extract_from_nokogiri(node)
26
- else
27
- extract_from_moxml(node)
28
- end
29
- end
30
-
31
- # Strict attribute order comparison
32
- #
33
- # @param order1 [Array<Symbol>] First attribute order
34
- # @param order2 [Array<Symbol>] Second attribute order
35
- # @return [Boolean] true if attribute order is exactly the same
36
- def compare_strict(order1, order2) # rubocop:disable Naming/PredicateMethod
37
- order1 == order2
38
- end
39
-
40
- private
41
-
42
- # Extract attribute order from Moxml node
43
- #
44
- # @param node [Moxml::Node] Moxml node
45
- # @return [Array<Symbol>] Array of attribute names in order
46
- def extract_from_moxml(node)
47
- return [] unless node.node_type == :element
48
-
49
- node.attributes.map { |attr| attr.name.to_sym }
50
- end
51
-
52
- # Extract attribute order from Nokogiri node
53
- #
54
- # @param node [Nokogiri::XML::Node] Nokogiri node
55
- # @return [Array<Symbol>] Array of attribute names in order
56
- def extract_from_nokogiri(node)
57
- return [] unless node.node_type == Nokogiri::XML::Node::ELEMENT_NODE
58
-
59
- node.attribute_nodes.map { |attr| attr.name.to_sym }
60
- end
61
- end
62
- end
63
- end
64
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_dimension"
4
-
5
- module Canon
6
- module Comparison
7
- module Dimensions
8
- # Attribute presence dimension
9
- #
10
- # Handles comparison of attribute presence (which attributes exist).
11
- # Supports :strict and :ignore behaviors.
12
- #
13
- # Behaviors:
14
- # - :strict - Attribute names must match exactly
15
- # - :ignore - Skip attribute presence comparison
16
- class AttributePresenceDimension < BaseDimension
17
- # Extract attribute names from a node
18
- #
19
- # @param node [Moxml::Node, Nokogiri::XML::Node] Node to extract from
20
- # @return [Array<Symbol>] Array of attribute names
21
- def extract_data(node)
22
- return [] unless node
23
-
24
- if Canon::XmlBackend.nokogiri?
25
- extract_from_nokogiri(node)
26
- else
27
- extract_from_moxml(node)
28
- end
29
- end
30
-
31
- # Strict attribute presence comparison
32
- #
33
- # @param names1 [Array<Symbol>] First attribute names
34
- # @param names2 [Array<Symbol>] Second attribute names
35
- # @return [Boolean] true if attribute names are exactly equal
36
- def compare_strict(names1, names2) # rubocop:disable Naming/PredicateMethod
37
- names1.sort == names2.sort
38
- end
39
-
40
- private
41
-
42
- # Extract attribute names from Moxml node
43
- #
44
- # @param node [Moxml::Node] Moxml node
45
- # @return [Array<Symbol>] Array of attribute names
46
- def extract_from_moxml(node)
47
- return [] unless node.node_type == :element
48
-
49
- node.attributes.map { |attr| attr.name.to_sym }
50
- end
51
-
52
- # Extract attribute names from Nokogiri node
53
- #
54
- # @param node [Nokogiri::XML::Node] Nokogiri node
55
- # @return [Array<Symbol>] Array of attribute names
56
- def extract_from_nokogiri(node)
57
- return [] unless node.node_type == Nokogiri::XML::Node::ELEMENT_NODE
58
-
59
- node.attribute_nodes.map { |attr| attr.name.to_sym }
60
- end
61
- end
62
- end
63
- end
64
- end