canon 0.2.9 → 0.2.11
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 +19 -10
- data/Rakefile +22 -2
- data/lib/canon/cache.rb +16 -27
- data/lib/canon/comparison/html_comparator.rb +2 -0
- data/lib/canon/comparison/node_inspector.rb +13 -48
- data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +19 -2
- data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +30 -0
- data/lib/canon/comparison/xml_comparator/node_parser.rb +2 -2
- data/lib/canon/comparison/xml_comparator.rb +2 -2
- data/lib/canon/comparison.rb +1 -1
- data/lib/canon/diff/diff_line_builder.rb +2 -0
- data/lib/canon/diff/diff_node_mapper.rb +10 -8
- data/lib/canon/diff/formatting_detector.rb +3 -2
- data/lib/canon/diff/xml_serialization_formatter.rb +0 -3
- data/lib/canon/diff_formatter/by_object/base_formatter.rb +12 -2
- data/lib/canon/diff_formatter/by_object/xml_formatter.rb +119 -1
- data/lib/canon/diff_formatter/by_object_formatter.rb +1 -5
- data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +3 -3
- data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +26 -27
- data/lib/canon/diff_formatter.rb +15 -11
- data/lib/canon/html/data_model.rb +1 -1
- data/lib/canon/tree_diff/operation_converter.rb +7 -7
- data/lib/canon/tree_diff/operations/operation_detector.rb +4 -0
- data/lib/canon/validators/base_validator.rb +5 -8
- data/lib/canon/validators/html_validator.rb +2 -7
- data/lib/canon/validators/xml_validator.rb +2 -7
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/data_model.rb +5 -5
- data/lib/canon/xml/sax_builder.rb +1 -1
- data/lib/canon/xml/whitespace_normalizer.rb +2 -2
- data/lib/canon/xml_parsing.rb +28 -16
- metadata +6 -6
|
@@ -136,7 +136,16 @@ module Canon
|
|
|
136
136
|
theme_color(:changed, :marker) || :yellow,
|
|
137
137
|
)}"
|
|
138
138
|
end
|
|
139
|
-
when :
|
|
139
|
+
when :attribute_values
|
|
140
|
+
render_attribute_values_diffnode(diff_node, node1, node2, prefix,
|
|
141
|
+
output)
|
|
142
|
+
when :attribute_presence
|
|
143
|
+
render_attribute_presence_diffnode(diff_node, node1, node2, prefix,
|
|
144
|
+
output)
|
|
145
|
+
when :attribute_order
|
|
146
|
+
render_attribute_order_diffnode(diff_node, node1, node2, prefix,
|
|
147
|
+
output)
|
|
148
|
+
when :structural_whitespace, :attribute_whitespace
|
|
140
149
|
output << "#{prefix}└── #{colorize(
|
|
141
150
|
"[#{diff_node.dimension}: #{diff_node.reason}]",
|
|
142
151
|
theme_color(:changed, :marker) || :yellow,
|
|
@@ -150,6 +159,115 @@ module Canon
|
|
|
150
159
|
end
|
|
151
160
|
end
|
|
152
161
|
|
|
162
|
+
# Render attribute value DiffNode with per-attribute before/after
|
|
163
|
+
def render_attribute_values_diffnode(diff_node, node1, node2, prefix,
|
|
164
|
+
output)
|
|
165
|
+
attrs1 = diff_node.attributes_before ||
|
|
166
|
+
extract_attributes_hash(node1)
|
|
167
|
+
attrs2 = diff_node.attributes_after ||
|
|
168
|
+
extract_attributes_hash(node2)
|
|
169
|
+
element_name = if node1
|
|
170
|
+
node1.name
|
|
171
|
+
else
|
|
172
|
+
(node2 ? node2.name : "?")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
output << "#{prefix}└── #{colorize(
|
|
176
|
+
"Element: <#{element_name}>",
|
|
177
|
+
theme_color(:changed, :marker) || :yellow,
|
|
178
|
+
)}"
|
|
179
|
+
|
|
180
|
+
differing = find_differing_attributes(attrs1, attrs2)
|
|
181
|
+
differing.each_with_index do |name, i|
|
|
182
|
+
connector = i < differing.size - 1 ? "├──" : "└──"
|
|
183
|
+
val1 = attrs1[name].to_s
|
|
184
|
+
val2 = attrs2[name].to_s
|
|
185
|
+
output << "#{prefix} #{connector} " \
|
|
186
|
+
"#{colorize("#{name}:", :cyan)} " \
|
|
187
|
+
"#{colorize(val1,
|
|
188
|
+
theme_color(:removed, :content) || :red)} " \
|
|
189
|
+
"→ " \
|
|
190
|
+
"#{colorize(val2,
|
|
191
|
+
theme_color(:added, :content) || :green)}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Render attribute presence DiffNode with added/removed attributes
|
|
196
|
+
def render_attribute_presence_diffnode(diff_node, node1, node2, prefix,
|
|
197
|
+
output)
|
|
198
|
+
attrs1 = diff_node.attributes_before ||
|
|
199
|
+
extract_attributes_hash(node1)
|
|
200
|
+
attrs2 = diff_node.attributes_after ||
|
|
201
|
+
extract_attributes_hash(node2)
|
|
202
|
+
element_name = if node1
|
|
203
|
+
node1.name
|
|
204
|
+
else
|
|
205
|
+
(node2 ? node2.name : "?")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
output << "#{prefix}└── #{colorize(
|
|
209
|
+
"Element: <#{element_name}>",
|
|
210
|
+
theme_color(:changed, :marker) || :yellow,
|
|
211
|
+
)}"
|
|
212
|
+
|
|
213
|
+
keys1 = attrs1.keys.to_set
|
|
214
|
+
keys2 = attrs2.keys.to_set
|
|
215
|
+
removed = (keys1 - keys2).sort
|
|
216
|
+
added = (keys2 - keys1).sort
|
|
217
|
+
items = removed.map { |k| [:removed, k, attrs1[k]] }
|
|
218
|
+
added.each { |k| items << [:added, k, attrs2[k]] }
|
|
219
|
+
|
|
220
|
+
items.each_with_index do |(type, name, val), i|
|
|
221
|
+
connector = i < items.size - 1 ? "├──" : "└──"
|
|
222
|
+
color = if type == :removed
|
|
223
|
+
theme_color(:removed,
|
|
224
|
+
:content) || :red
|
|
225
|
+
else
|
|
226
|
+
theme_color(
|
|
227
|
+
:added, :content
|
|
228
|
+
) || :green
|
|
229
|
+
end
|
|
230
|
+
sign = type == :removed ? "-" : "+"
|
|
231
|
+
output << "#{prefix} #{connector} " \
|
|
232
|
+
"#{colorize("#{sign} #{name}=\"#{val}\"", color)}"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Render attribute order DiffNode with before/after order
|
|
237
|
+
def render_attribute_order_diffnode(diff_node, node1, node2, prefix,
|
|
238
|
+
output)
|
|
239
|
+
attrs1 = diff_node.attributes_before ||
|
|
240
|
+
extract_attributes_hash(node1)
|
|
241
|
+
attrs2 = diff_node.attributes_after ||
|
|
242
|
+
extract_attributes_hash(node2)
|
|
243
|
+
|
|
244
|
+
order1 = attrs1.keys.join(", ")
|
|
245
|
+
order2 = attrs2.keys.join(", ")
|
|
246
|
+
|
|
247
|
+
output << "#{prefix}├── #{colorize(
|
|
248
|
+
"- [#{order1}]",
|
|
249
|
+
theme_color(:removed, :content) || :red,
|
|
250
|
+
)}"
|
|
251
|
+
output << "#{prefix}└── #{colorize(
|
|
252
|
+
"+ [#{order2}]",
|
|
253
|
+
theme_color(:added, :content) || :green,
|
|
254
|
+
)}"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Extract attributes hash from a node
|
|
258
|
+
def extract_attributes_hash(node)
|
|
259
|
+
return {} unless node
|
|
260
|
+
|
|
261
|
+
Canon::Diff::NodeSerializer.extract_attributes(node) || {}
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Find attributes that differ between two attribute hashes
|
|
265
|
+
def find_differing_attributes(attrs1, attrs2)
|
|
266
|
+
(attrs1.keys | attrs2.keys).reject do |k|
|
|
267
|
+
attrs1[k.to_s] == attrs2[k.to_s]
|
|
268
|
+
end.sort
|
|
269
|
+
end
|
|
270
|
+
|
|
153
271
|
# Render unequal elements
|
|
154
272
|
def render_unequal_elements(diff, prefix, output)
|
|
155
273
|
node1 = diff[:node1]
|
|
@@ -21,9 +21,6 @@ module Canon
|
|
|
21
21
|
# @param format [Symbol] Document format (:xml, :json, :yaml)
|
|
22
22
|
# @return [String] Formatted diff output
|
|
23
23
|
def format(differences, format)
|
|
24
|
-
output = []
|
|
25
|
-
output << colorize("Visual Diff:", :cyan, :bold)
|
|
26
|
-
|
|
27
24
|
diffs_array = if differences.is_a?(Canon::Comparison::ComparisonResult)
|
|
28
25
|
differences.differences
|
|
29
26
|
else
|
|
@@ -37,8 +34,7 @@ module Canon
|
|
|
37
34
|
show_diffs: @show_diffs,
|
|
38
35
|
)
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
output.join("\n")
|
|
37
|
+
formatter.format(diffs_array, format)
|
|
42
38
|
end
|
|
43
39
|
|
|
44
40
|
private
|
|
@@ -707,7 +707,7 @@ expand_difference: false)
|
|
|
707
707
|
# @param diff [DiffNode, Hash] Difference node
|
|
708
708
|
# @return [Symbol] Dimension
|
|
709
709
|
def self.extract_dimension(diff)
|
|
710
|
-
if diff.
|
|
710
|
+
if diff.is_a?(Canon::Diff::DiffNode)
|
|
711
711
|
diff.dimension
|
|
712
712
|
elsif diff.is_a?(Hash)
|
|
713
713
|
diff[:dimension] || diff[:diff_code] || :unknown
|
|
@@ -721,7 +721,7 @@ expand_difference: false)
|
|
|
721
721
|
# @param diff [DiffNode, Hash] Difference node
|
|
722
722
|
# @return [Object] Node1
|
|
723
723
|
def self.extract_node1(diff)
|
|
724
|
-
if diff.
|
|
724
|
+
if diff.is_a?(Canon::Diff::DiffNode)
|
|
725
725
|
diff.node1
|
|
726
726
|
elsif diff.is_a?(Hash)
|
|
727
727
|
diff[:node1]
|
|
@@ -733,7 +733,7 @@ expand_difference: false)
|
|
|
733
733
|
# @param diff [DiffNode, Hash] Difference node
|
|
734
734
|
# @return [Object] Node2
|
|
735
735
|
def self.extract_node2(diff)
|
|
736
|
-
if diff.
|
|
736
|
+
if diff.is_a?(Canon::Diff::DiffNode)
|
|
737
737
|
diff.node2
|
|
738
738
|
elsif diff.is_a?(Hash)
|
|
739
739
|
diff[:node2]
|
|
@@ -17,12 +17,12 @@ module Canon
|
|
|
17
17
|
return "" unless diff
|
|
18
18
|
|
|
19
19
|
# Prefer pre-computed path if available (populated by MetadataEnricher)
|
|
20
|
-
if diff.
|
|
21
|
-
return
|
|
20
|
+
if diff.is_a?(Canon::Diff::DiffNode) && diff.path && !diff.path.empty?
|
|
21
|
+
return diff.path
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Fall back to extracting from nodes
|
|
25
|
-
node = if diff.
|
|
25
|
+
node = if diff.is_a?(Canon::Diff::DiffNode)
|
|
26
26
|
diff.node1 || diff.node2
|
|
27
27
|
elsif diff.is_a?(Hash)
|
|
28
28
|
diff[:node1] || diff[:node2]
|
|
@@ -30,8 +30,7 @@ module Canon
|
|
|
30
30
|
|
|
31
31
|
return "" unless node
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
xpath.empty? ? "" : "Location: #{xpath}"
|
|
33
|
+
extract_xpath(node)
|
|
35
34
|
end
|
|
36
35
|
|
|
37
36
|
# Extract XPath from a node
|
|
@@ -66,24 +65,26 @@ module Canon
|
|
|
66
65
|
current = node
|
|
67
66
|
|
|
68
67
|
while current
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
name = case current
|
|
69
|
+
when Canon::Xml::Node, Nokogiri::XML::Node
|
|
70
|
+
current.name
|
|
71
|
+
else
|
|
72
|
+
break
|
|
73
|
+
end
|
|
72
74
|
break if name.nil? || name.empty?
|
|
73
75
|
|
|
74
|
-
# Calculate position among siblings
|
|
75
76
|
index = calculate_sibling_index(current, name)
|
|
76
77
|
parts.unshift("#{name}[#{index}]")
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
current = case current
|
|
80
|
+
when Canon::Xml::Node, Nokogiri::XML::Node
|
|
80
81
|
current.parent
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
else
|
|
83
|
+
break
|
|
83
84
|
end
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
break if current.is_a?(Nokogiri::XML::Document) ||
|
|
87
|
+
current.is_a?(Canon::Xml::Nodes::RootNode)
|
|
87
88
|
end
|
|
88
89
|
|
|
89
90
|
parts.empty? ? "" : "/#{parts.join('/')}"
|
|
@@ -95,24 +96,22 @@ module Canon
|
|
|
95
96
|
# @param name [String] Node name
|
|
96
97
|
# @return [Integer] 1-based index
|
|
97
98
|
def self.calculate_sibling_index(node, name)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
parent = if node.respond_to?(:parent)
|
|
99
|
+
parent = case node
|
|
100
|
+
when Canon::Xml::Node, Nokogiri::XML::Node
|
|
101
101
|
node.parent
|
|
102
|
-
elsif node.respond_to?(:parent_node)
|
|
103
|
-
node.parent_node
|
|
104
102
|
end
|
|
105
103
|
|
|
106
104
|
return 1 unless parent
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
siblings = case parent
|
|
107
|
+
when Canon::Xml::Node, Nokogiri::XML::Node
|
|
110
108
|
parent.children.select do |n|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
case n
|
|
110
|
+
when Canon::Xml::Node, Nokogiri::XML::Node
|
|
111
|
+
n.name == name
|
|
112
|
+
else
|
|
113
|
+
false
|
|
114
|
+
end
|
|
116
115
|
end
|
|
117
116
|
else
|
|
118
117
|
[node]
|
data/lib/canon/diff_formatter.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "paint" unless RUBY_ENGINE == "opal"
|
|
4
|
-
require "yaml"
|
|
4
|
+
require "yaml" unless RUBY_ENGINE == "opal"
|
|
5
5
|
require_relative "comparison"
|
|
6
6
|
require_relative "diff/diff_block"
|
|
7
7
|
require_relative "diff/diff_context"
|
|
@@ -135,16 +135,17 @@ module Canon
|
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
# Default character visualization map (loaded from YAML)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
138
|
+
if RUBY_ENGINE == "opal"
|
|
139
|
+
DEFAULT_VISUALIZATION_MAP = {}.freeze
|
|
140
|
+
CHARACTER_CATEGORY_MAP = {}.freeze
|
|
141
|
+
CHARACTER_CATEGORY_NAMES = {}.freeze
|
|
142
|
+
CHARACTER_METADATA = {}.freeze
|
|
143
|
+
else
|
|
144
|
+
DEFAULT_VISUALIZATION_MAP = character_map_data[:visualization_map].freeze
|
|
145
|
+
CHARACTER_CATEGORY_MAP = character_map_data[:category_map].freeze
|
|
146
|
+
CHARACTER_CATEGORY_NAMES = character_map_data[:category_names].freeze
|
|
147
|
+
CHARACTER_METADATA = character_map_data[:character_metadata].freeze
|
|
148
|
+
end
|
|
148
149
|
|
|
149
150
|
# Map difference codes to human-readable descriptions
|
|
150
151
|
DIFF_DESCRIPTIONS = {
|
|
@@ -757,6 +758,9 @@ module Canon
|
|
|
757
758
|
output.join("\n")
|
|
758
759
|
end
|
|
759
760
|
|
|
761
|
+
public :format_line_numbered_inputs, :format_raw_inputs,
|
|
762
|
+
:format_preprocessed_inputs
|
|
763
|
+
|
|
760
764
|
# Build the final visualization map from various customization options
|
|
761
765
|
#
|
|
762
766
|
# @param visualization_map [Hash, nil] Complete custom visualization map
|
|
@@ -89,7 +89,7 @@ module Canon
|
|
|
89
89
|
def self.build_from_nokogiri(nokogiri_doc)
|
|
90
90
|
root = Canon::Xml::Nodes::RootNode.new
|
|
91
91
|
|
|
92
|
-
if nokogiri_doc.
|
|
92
|
+
if nokogiri_doc.is_a?(Nokogiri::XML::Document) && nokogiri_doc.root
|
|
93
93
|
# For Documents (HTML4, HTML5): process the root element
|
|
94
94
|
root.add_child(build_element_node(nokogiri_doc.root))
|
|
95
95
|
|
|
@@ -124,6 +124,8 @@ module Canon
|
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
+
public :convert_operation
|
|
128
|
+
|
|
127
129
|
# Convert INSERT operation to DiffNode
|
|
128
130
|
#
|
|
129
131
|
# @param operation [Operation] Insert operation
|
|
@@ -153,7 +155,7 @@ module Canon
|
|
|
153
155
|
|
|
154
156
|
# Determine dimension for INSERT/DELETE operations based on node type
|
|
155
157
|
def dimension_for_insert_delete(tree_node)
|
|
156
|
-
label = tree_node.
|
|
158
|
+
label = tree_node.is_a?(Canon::TreeDiff::Core::TreeNode) ? tree_node.label : nil
|
|
157
159
|
return :comments if label == "comment"
|
|
158
160
|
|
|
159
161
|
:element_structure
|
|
@@ -359,7 +361,7 @@ module Canon
|
|
|
359
361
|
def extract_source_node(tree_node)
|
|
360
362
|
return nil if tree_node.nil?
|
|
361
363
|
|
|
362
|
-
tree_node.
|
|
364
|
+
tree_node.is_a?(Canon::TreeDiff::Core::TreeNode) ? tree_node.source_node : tree_node
|
|
363
365
|
end
|
|
364
366
|
|
|
365
367
|
# Determine if a diff is normative based on match options
|
|
@@ -383,12 +385,10 @@ module Canon
|
|
|
383
385
|
return false if node.nil?
|
|
384
386
|
|
|
385
387
|
# Get element name from node
|
|
386
|
-
element_name = if node.
|
|
387
|
-
node.label
|
|
388
|
-
elsif node.respond_to?(:name)
|
|
389
|
-
node.name # Nokogiri node
|
|
388
|
+
element_name = if node.is_a?(Canon::TreeDiff::Core::TreeNode)
|
|
389
|
+
node.label
|
|
390
390
|
else
|
|
391
|
-
|
|
391
|
+
Canon::Comparison::NodeInspector.name(node)
|
|
392
392
|
end
|
|
393
393
|
|
|
394
394
|
# Check if it's in our metadata elements list
|
|
@@ -600,6 +600,10 @@ module Canon
|
|
|
600
600
|
depth
|
|
601
601
|
end
|
|
602
602
|
|
|
603
|
+
public :normalize_text, :calculate_depth, :text_similarity,
|
|
604
|
+
:extract_text_content, :collect_all_nodes, :nodes_identical?,
|
|
605
|
+
:detect_changes
|
|
606
|
+
|
|
603
607
|
# Check if a node is in a whitespace-sensitive context
|
|
604
608
|
#
|
|
605
609
|
# HTML elements where whitespace is significant: <pre>, <code>, <textarea>, <script>, <style>
|
|
@@ -29,17 +29,14 @@ module Canon
|
|
|
29
29
|
line = nil
|
|
30
30
|
column = nil
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
if error.respond_to?(:line)
|
|
32
|
+
if error.is_a?(Nokogiri::XML::SyntaxError)
|
|
34
33
|
line = error.line
|
|
34
|
+
column = error.column
|
|
35
35
|
elsif error.message =~ /line[:\s]+(\d+)/i
|
|
36
36
|
line = ::Regexp.last_match(1).to_i
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
column = error.column
|
|
41
|
-
elsif error.message =~ /column[:\s]+(\d+)/i
|
|
42
|
-
column = ::Regexp.last_match(1).to_i
|
|
37
|
+
if error.message =~ /column[:\s]+(\d+)/i
|
|
38
|
+
column = ::Regexp.last_match(1).to_i
|
|
39
|
+
end
|
|
43
40
|
end
|
|
44
41
|
|
|
45
42
|
{ line: line, column: column }
|
|
@@ -94,13 +94,8 @@ module Canon
|
|
|
94
94
|
#
|
|
95
95
|
# @param error [Nokogiri::XML::SyntaxError] The syntax error
|
|
96
96
|
# @return [String, nil] Additional details about the error
|
|
97
|
-
def self.extract_details(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
details = error.errors.map(&:message).reject do |msg|
|
|
101
|
-
msg == error.message
|
|
102
|
-
end
|
|
103
|
-
details.join("; ") unless details.empty?
|
|
97
|
+
def self.extract_details(_error)
|
|
98
|
+
nil
|
|
104
99
|
end
|
|
105
100
|
|
|
106
101
|
# Build error details from multiple errors
|
|
@@ -38,13 +38,8 @@ module Canon
|
|
|
38
38
|
#
|
|
39
39
|
# @param error [Nokogiri::XML::SyntaxError] The syntax error
|
|
40
40
|
# @return [String, nil] Additional details about the error
|
|
41
|
-
def self.extract_details(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
details = error.errors.map(&:message).reject do |msg|
|
|
45
|
-
msg == error.message
|
|
46
|
-
end
|
|
47
|
-
details.join("; ") unless details.empty?
|
|
41
|
+
def self.extract_details(_error)
|
|
42
|
+
nil
|
|
48
43
|
end
|
|
49
44
|
|
|
50
45
|
private_class_method :extract_details
|
data/lib/canon/version.rb
CHANGED
data/lib/canon/xml/data_model.rb
CHANGED
|
@@ -139,7 +139,7 @@ module Canon
|
|
|
139
139
|
def self.build_from_nokogiri(nokogiri_doc, preserve_whitespace: false)
|
|
140
140
|
root = Nodes::RootNode.new
|
|
141
141
|
|
|
142
|
-
if nokogiri_doc.
|
|
142
|
+
if nokogiri_doc.is_a?(Nokogiri::XML::Document) && nokogiri_doc.root
|
|
143
143
|
root.add_child(build_element_node(nokogiri_doc.root,
|
|
144
144
|
preserve_whitespace: preserve_whitespace))
|
|
145
145
|
nokogiri_doc.children.each do |child|
|
|
@@ -275,7 +275,7 @@ preserve_whitespace: false)
|
|
|
275
275
|
def self.build_from_moxml(moxml_doc, preserve_whitespace: false)
|
|
276
276
|
root = Nodes::RootNode.new
|
|
277
277
|
|
|
278
|
-
if moxml_doc.
|
|
278
|
+
if moxml_doc.is_a?(Moxml::Document) && moxml_doc.root
|
|
279
279
|
root.add_child(build_moxml_element_node(moxml_doc.root,
|
|
280
280
|
preserve_whitespace: preserve_whitespace))
|
|
281
281
|
end
|
|
@@ -327,7 +327,7 @@ preserve_whitespace: false)
|
|
|
327
327
|
element.add_namespace(ns_node)
|
|
328
328
|
end
|
|
329
329
|
|
|
330
|
-
unless element.
|
|
330
|
+
unless element.namespace_nodes.any? do |n|
|
|
331
331
|
n.prefix == "xml"
|
|
332
332
|
end
|
|
333
333
|
element.add_namespace(Nodes::NamespaceNode.new(
|
|
@@ -348,7 +348,7 @@ preserve_whitespace: false)
|
|
|
348
348
|
end
|
|
349
349
|
|
|
350
350
|
def self.build_moxml_text_node(moxml_text, preserve_whitespace: false)
|
|
351
|
-
content = moxml_text.
|
|
351
|
+
content = moxml_text.content
|
|
352
352
|
|
|
353
353
|
if !preserve_whitespace && content.strip.empty? && moxml_text.parent.is_a?(Moxml::Element)
|
|
354
354
|
return nil
|
|
@@ -358,7 +358,7 @@ preserve_whitespace: false)
|
|
|
358
358
|
end
|
|
359
359
|
|
|
360
360
|
def self.build_moxml_comment_node(moxml_comment)
|
|
361
|
-
Nodes::CommentNode.new(value: moxml_comment.
|
|
361
|
+
Nodes::CommentNode.new(value: moxml_comment.content)
|
|
362
362
|
end
|
|
363
363
|
|
|
364
364
|
def self.build_moxml_pi_node(moxml_pi)
|
|
@@ -28,7 +28,7 @@ module Canon
|
|
|
28
28
|
# For C14N, use strip_doctype: true to avoid DTD default attribute expansion:
|
|
29
29
|
# root = SaxBuilder.parse(xml_string, strip_doctype: true)
|
|
30
30
|
#
|
|
31
|
-
class SaxBuilder < Nokogiri::XML::SAX::Document
|
|
31
|
+
class SaxBuilder < (RUBY_ENGINE == "opal" ? Object : Nokogiri::XML::SAX::Document)
|
|
32
32
|
# Parse XML string and return Canon::Xml::Node tree
|
|
33
33
|
#
|
|
34
34
|
# @param xml_string [String] XML content to parse
|
|
@@ -43,9 +43,9 @@ module Canon
|
|
|
43
43
|
# @param node [Moxml::Node] Node to check
|
|
44
44
|
# @return [Boolean] true if node is whitespace-only and should be ignored
|
|
45
45
|
def inter_element_whitespace?(node)
|
|
46
|
-
return false unless node.
|
|
46
|
+
return false unless node.is_a?(Nokogiri::XML::Text) || node.is_a?(Moxml::Text)
|
|
47
47
|
|
|
48
|
-
text = node.
|
|
48
|
+
text = node.is_a?(Moxml::Text) ? node.content.to_s : node.content.to_s
|
|
49
49
|
text.strip.empty?
|
|
50
50
|
end
|
|
51
51
|
|
data/lib/canon/xml_parsing.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Canon
|
|
|
14
14
|
module XmlParsing
|
|
15
15
|
class << self
|
|
16
16
|
def moxml_context
|
|
17
|
-
@moxml_context ||= Moxml.new(:oga)
|
|
17
|
+
@moxml_context ||= Moxml.new(RUBY_ENGINE == "opal" ? :rexml : :oga)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# --- Parsing ---
|
|
@@ -47,10 +47,15 @@ module Canon
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# --- Type checks (backend-safe) ---
|
|
50
|
+
#
|
|
51
|
+
# Both Nokogiri and Moxml are loaded as dependencies. XmlBackend
|
|
52
|
+
# determines which is used for *parsing*, but nodes from either
|
|
53
|
+
# library may flow through comparison code (e.g. tests, format
|
|
54
|
+
# detection). Under Nokogiri backend, both types are checked.
|
|
50
55
|
|
|
51
56
|
def document?(obj)
|
|
52
57
|
if XmlBackend.nokogiri?
|
|
53
|
-
obj.is_a?(Nokogiri::XML::Document)
|
|
58
|
+
obj.is_a?(Nokogiri::XML::Document) || obj.is_a?(Moxml::Document)
|
|
54
59
|
else
|
|
55
60
|
obj.is_a?(Moxml::Document)
|
|
56
61
|
end
|
|
@@ -58,7 +63,7 @@ module Canon
|
|
|
58
63
|
|
|
59
64
|
def xml_node?(obj)
|
|
60
65
|
if XmlBackend.nokogiri?
|
|
61
|
-
obj.is_a?(Nokogiri::XML::Node)
|
|
66
|
+
obj.is_a?(Nokogiri::XML::Node) || obj.is_a?(Moxml::Node)
|
|
62
67
|
else
|
|
63
68
|
obj.is_a?(Moxml::Node)
|
|
64
69
|
end
|
|
@@ -66,7 +71,7 @@ module Canon
|
|
|
66
71
|
|
|
67
72
|
def element?(node)
|
|
68
73
|
if XmlBackend.nokogiri?
|
|
69
|
-
node.is_a?(Nokogiri::XML::Element)
|
|
74
|
+
node.is_a?(Nokogiri::XML::Element) || node.is_a?(Moxml::Element)
|
|
70
75
|
else
|
|
71
76
|
node.is_a?(Moxml::Element)
|
|
72
77
|
end
|
|
@@ -74,7 +79,7 @@ module Canon
|
|
|
74
79
|
|
|
75
80
|
def text_node?(node)
|
|
76
81
|
if XmlBackend.nokogiri?
|
|
77
|
-
node.is_a?(Nokogiri::XML::Text)
|
|
82
|
+
node.is_a?(Nokogiri::XML::Text) || node.is_a?(Moxml::Text)
|
|
78
83
|
else
|
|
79
84
|
node.is_a?(Moxml::Text)
|
|
80
85
|
end
|
|
@@ -82,7 +87,7 @@ module Canon
|
|
|
82
87
|
|
|
83
88
|
def comment?(node)
|
|
84
89
|
if XmlBackend.nokogiri?
|
|
85
|
-
node.is_a?(Nokogiri::XML::Comment)
|
|
90
|
+
node.is_a?(Nokogiri::XML::Comment) || node.is_a?(Moxml::Comment)
|
|
86
91
|
else
|
|
87
92
|
node.is_a?(Moxml::Comment)
|
|
88
93
|
end
|
|
@@ -90,7 +95,7 @@ module Canon
|
|
|
90
95
|
|
|
91
96
|
def cdata?(node)
|
|
92
97
|
if XmlBackend.nokogiri?
|
|
93
|
-
node.is_a?(Nokogiri::XML::CDATA)
|
|
98
|
+
node.is_a?(Nokogiri::XML::CDATA) || node.is_a?(Moxml::Cdata)
|
|
94
99
|
else
|
|
95
100
|
node.is_a?(Moxml::Cdata)
|
|
96
101
|
end
|
|
@@ -98,7 +103,7 @@ module Canon
|
|
|
98
103
|
|
|
99
104
|
def processing_instruction?(node)
|
|
100
105
|
if XmlBackend.nokogiri?
|
|
101
|
-
node.is_a?(Nokogiri::XML::ProcessingInstruction)
|
|
106
|
+
node.is_a?(Nokogiri::XML::ProcessingInstruction) || node.is_a?(Moxml::ProcessingInstruction)
|
|
102
107
|
else
|
|
103
108
|
node.is_a?(Moxml::ProcessingInstruction)
|
|
104
109
|
end
|
|
@@ -108,7 +113,7 @@ module Canon
|
|
|
108
113
|
if XmlBackend.nokogiri?
|
|
109
114
|
obj.is_a?(Nokogiri::XML::DocumentFragment)
|
|
110
115
|
else
|
|
111
|
-
|
|
116
|
+
false
|
|
112
117
|
end
|
|
113
118
|
end
|
|
114
119
|
|
|
@@ -142,7 +147,14 @@ module Canon
|
|
|
142
147
|
if XmlBackend.nokogiri?
|
|
143
148
|
node.is_a?(Nokogiri::XML::Node) ? node.content : node.to_s
|
|
144
149
|
else
|
|
145
|
-
|
|
150
|
+
case node
|
|
151
|
+
when Moxml::Text, Moxml::Cdata, Moxml::Comment
|
|
152
|
+
node.content.to_s
|
|
153
|
+
when Moxml::Node
|
|
154
|
+
node.text.to_s
|
|
155
|
+
else
|
|
156
|
+
node.to_s
|
|
157
|
+
end
|
|
146
158
|
end
|
|
147
159
|
end
|
|
148
160
|
|
|
@@ -257,12 +269,12 @@ module Canon
|
|
|
257
269
|
end
|
|
258
270
|
|
|
259
271
|
def moxml_node_type(node)
|
|
260
|
-
return :element if node.
|
|
261
|
-
return :text if node.
|
|
262
|
-
return :comment if node.
|
|
263
|
-
return :cdata if node.
|
|
264
|
-
return :document if node.
|
|
265
|
-
return :processing_instruction if node.
|
|
272
|
+
return :element if node.element?
|
|
273
|
+
return :text if node.text?
|
|
274
|
+
return :comment if node.comment?
|
|
275
|
+
return :cdata if node.cdata?
|
|
276
|
+
return :document if node.document?
|
|
277
|
+
return :processing_instruction if node.processing_instruction?
|
|
266
278
|
|
|
267
279
|
nil
|
|
268
280
|
end
|