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
|
@@ -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.
|
|
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-
|
|
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.
|
|
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.
|
|
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/
|
|
231
|
-
- lib/canon/comparison/dimensions/
|
|
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
|