compare-xml 1.0.0 → 1.0.2
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/README.md +41 -13
- data/lib/compare-xml/version.rb +1 -1
- data/lib/compare-xml.rb +49 -23
- metadata +5 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed20155c59d477002fe69c2883e0a8d3b3b9507aa042f95d41cb3693b044e56d
|
|
4
|
+
data.tar.gz: 99c9374d3ac9bd98fe3e4b1fbad74b7bb4e1bea0f55ee475b41f401d8a60123e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba79f1cd40d906b02752a50cdc37f7c82200077a7f1e7c9e5f530e046c67ad445a4830d87082715c9f96405363c116deb5cbc9d1df276906dc52dcd1098565d9
|
|
7
|
+
data.tar.gz: 9b25dfa936e79f2e15137ceece697f7f909344b628b79e51450598660989ca0b7604b6e44272fcbb4a19676ebd6eb719939e8b1c4b4a6d424edf57aac2786067
|
data/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# CompareXML
|
|
2
2
|
|
|
3
|
-
[](https://rubygems.org/gems/compare-xml)
|
|
4
|
-
[](https://rubygems.org/gems/compare-xml)
|
|
4
|
+
[](https://github.com/vkononov/compare-xml/blob/main/.github/workflows/test.yml)
|
|
5
|
+
[](https://github.com/vkononov/compare-xml/actions/workflows/test.yml)
|
|
6
|
+
[](https://github.com/vkononov/compare-xml/actions/workflows/lint.yml)
|
|
7
|
+
[](https://opensource.org/license/MIT)
|
|
7
8
|
|
|
8
9
|
CompareXML is a fast, lightweight and feature-rich tool that will solve your XML/HTML comparison or diffing needs. Its purpose is to compare two instances of `Nokogiri::XML::Node` or `Nokogiri::XML::NodeSet` for equality or equivalency.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
Supports **Ruby 2.4 → latest**, with the full Ruby matrix tested daily in CI.
|
|
11
12
|
|
|
12
13
|
**Features**
|
|
13
14
|
|
|
@@ -30,7 +31,9 @@ And then execute:
|
|
|
30
31
|
|
|
31
32
|
Or install it yourself as:
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
```sh
|
|
35
|
+
gem install compare-xml
|
|
36
|
+
```
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
|
|
@@ -49,8 +52,8 @@ where `doc1` and `doc2` are instances of `Nokogiri::XML::Node` or `Nokogiri::XML
|
|
|
49
52
|
Suppose you have two files `1.html` and `2.html` that you would like to compare. You could do it as follows:
|
|
50
53
|
|
|
51
54
|
```ruby
|
|
52
|
-
doc1 = Nokogiri::HTML(
|
|
53
|
-
doc2 = Nokogiri::HTML(
|
|
55
|
+
doc1 = Nokogiri::HTML(File.read('1.html'))
|
|
56
|
+
doc2 = Nokogiri::HTML(File.read('2.html'))
|
|
54
57
|
puts CompareXML.equivalent?(doc1, doc2)
|
|
55
58
|
```
|
|
56
59
|
|
|
@@ -81,7 +84,7 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true})
|
|
|
81
84
|
- when provided, ignores all attributes that contain substrings `string`, `string2`, etc.
|
|
82
85
|
|
|
83
86
|
- `ignore_attrs: [css_selector1, css_selector1, ...]` default: **`[]`** [show examples ⇨](#ignore_attrs)
|
|
84
|
-
- when provided, ignores specific *attributes* using [CSS selectors](
|
|
87
|
+
- when provided, ignores specific *attributes* using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.php)
|
|
85
88
|
|
|
86
89
|
- `ignore_attrs_by_name: [matcher1, matcher2, ...]` default: **`[]`** [show examples ⇨](#ignore_attrs_by_name)
|
|
87
90
|
- when provided, ignores *attributes* whose name matches a `String` (exact) or `Regexp` (pattern)
|
|
@@ -90,7 +93,7 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true})
|
|
|
90
93
|
- when `true`, ignores comments, such as `<!-- comment -->`
|
|
91
94
|
|
|
92
95
|
- `ignore_nodes: [css_selector1, css_selector1, ...]` default: **`[]`** [show examples ⇨](#ignore_nodes)
|
|
93
|
-
- when provided, ignores specific *nodes* using [CSS selectors](
|
|
96
|
+
- when provided, ignores specific *nodes* using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.php)
|
|
94
97
|
|
|
95
98
|
- `ignore_text_nodes: {true|false}` default: **`false`** [show examples ⇨](#ignore_text_nodes)
|
|
96
99
|
- when `true`, ignores all text content within a document
|
|
@@ -188,7 +191,7 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true})
|
|
|
188
191
|
|
|
189
192
|
- <a id="ignore_attrs"></a>`ignore_attrs: [css_selector1, css_selector1, ...]` default: **`[]`**
|
|
190
193
|
|
|
191
|
-
When provided, ignores all **attributes** that satisfy a particular rule using [CSS selectors](
|
|
194
|
+
When provided, ignores all **attributes** that satisfy a particular rule using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.php).
|
|
192
195
|
|
|
193
196
|
**Usage Example:** `CompareXML.equivalent?(doc1, doc2, {ignore_attrs: ['a[rel="nofollow"]', 'input[type="hidden"']})`
|
|
194
197
|
|
|
@@ -250,7 +253,7 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true})
|
|
|
250
253
|
|
|
251
254
|
- <a id="ignore_nodes"></a>`ignore_nodes: [css_selector1, css_selector1, ...]` default: **`[]`**
|
|
252
255
|
|
|
253
|
-
When provided, ignores all **nodes** that satisfy a particular rule using [CSS selectors](
|
|
256
|
+
When provided, ignores all **nodes** that satisfy a particular rule using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.php).
|
|
254
257
|
|
|
255
258
|
**Usage Example:** `CompareXML.equivalent?(doc1, doc2, {ignore_nodes: ['script', 'object']})`
|
|
256
259
|
|
|
@@ -382,6 +385,31 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true})
|
|
|
382
385
|
|
|
383
386
|
----------
|
|
384
387
|
|
|
388
|
+
## Separate Options for Child Nodes
|
|
389
|
+
|
|
390
|
+
`CompareXML.equivalent?` accepts two further optional arguments that let child nodes be compared under a different set of options than the root:
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
CompareXML.equivalent?(doc1, doc2, opts = {}, child_opts = {}, diff_children = false)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
- `child_opts` - a second options hash, merged over the defaults the same way as `opts`, applied to child nodes
|
|
397
|
+
- `diff_children` - when `true`, child nodes are compared using `child_opts` instead of `opts`
|
|
398
|
+
|
|
399
|
+
`child_opts` is ignored unless `diff_children` is `true`.
|
|
400
|
+
|
|
401
|
+
**Example:** compare the root strictly, but ignore text content within child nodes:
|
|
402
|
+
|
|
403
|
+
```ruby
|
|
404
|
+
doc1 = Nokogiri::HTML('<html><body><p>Hello</p></body></html>')
|
|
405
|
+
doc2 = Nokogiri::HTML('<html><body><p>Goodbye</p></body></html>')
|
|
406
|
+
|
|
407
|
+
CompareXML.equivalent?(doc1, doc2) # => false
|
|
408
|
+
CompareXML.equivalent?(doc1, doc2, {}, { ignore_text_nodes: true }, true) # => true
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
----------
|
|
412
|
+
|
|
385
413
|
## Contributing
|
|
386
414
|
|
|
387
415
|
1. Fork it
|
|
@@ -400,4 +428,4 @@ This gem was inspired by [Michael B. Klein](https://github.com/mbklein)'s gem [`
|
|
|
400
428
|
|
|
401
429
|
## License
|
|
402
430
|
|
|
403
|
-
The gem is available as open source under the terms of the [MIT License](
|
|
431
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/license/MIT).
|
data/lib/compare-xml/version.rb
CHANGED
data/lib/compare-xml.rb
CHANGED
|
@@ -107,6 +107,8 @@ module CompareXML
|
|
|
107
107
|
def compare_nodes(n1, n2, opts, differences, child_opts = {}, diff_children: false)
|
|
108
108
|
if n1.instance_of?(n2.class)
|
|
109
109
|
case n1
|
|
110
|
+
when Nokogiri::XML::ProcessingInstruction
|
|
111
|
+
status = compare_processing_instructions(n1, n2, opts, differences)
|
|
110
112
|
when Nokogiri::XML::Comment
|
|
111
113
|
status = compare_comment_nodes(n1, n2, opts, differences)
|
|
112
114
|
when Nokogiri::HTML::Document
|
|
@@ -115,6 +117,8 @@ module CompareXML
|
|
|
115
117
|
status = diff_children ? compare_element_nodes(n1, n2, opts, differences, child_opts, diff_children: diff_children) : compare_element_nodes(n1, n2, opts, differences)
|
|
116
118
|
when Nokogiri::XML::Text
|
|
117
119
|
status = compare_text_nodes(n1, n2, opts, differences)
|
|
120
|
+
when Nokogiri::XML::NodeSet
|
|
121
|
+
status = diff_children ? compare_children(n1, n2, child_opts, differences, diff_children: diff_children) : compare_children(n1, n2, opts, differences)
|
|
118
122
|
else
|
|
119
123
|
raise 'Comparison only allowed between objects of type Nokogiri::XML::Node and Nokogiri::XML::NodeSet.' unless n1.is_a?(Nokogiri::XML::Node) || n1.is_a?(Nokogiri::XML::NodeSet)
|
|
120
124
|
status = compare_children(n1.children, n2.children, opts, differences)
|
|
@@ -135,6 +139,13 @@ module CompareXML
|
|
|
135
139
|
status
|
|
136
140
|
end
|
|
137
141
|
|
|
142
|
+
def compare_processing_instructions(n1, n2, opts, differences)
|
|
143
|
+
return EQUIVALENT if n1.name == n2.name && n1.content == n2.content
|
|
144
|
+
|
|
145
|
+
add_difference(n1, n2, n1, n2, opts, differences)
|
|
146
|
+
UNEQUAL_ELEMENTS
|
|
147
|
+
end
|
|
148
|
+
|
|
138
149
|
##
|
|
139
150
|
# Compares two nodes of type Nokogiri::HTML::Comment.
|
|
140
151
|
#
|
|
@@ -273,7 +284,7 @@ module CompareXML
|
|
|
273
284
|
end
|
|
274
285
|
|
|
275
286
|
##
|
|
276
|
-
# Builds a
|
|
287
|
+
# Builds a structured, option-aware fingerprint for a node so that two nodes with equal
|
|
277
288
|
# fingerprints are guaranteed to be equivalent under the current options. Used to
|
|
278
289
|
# anchor identical subtrees during alignment. Results are memoized per comparison.
|
|
279
290
|
#
|
|
@@ -283,19 +294,16 @@ module CompareXML
|
|
|
283
294
|
|
|
284
295
|
def build_node_fingerprint(node, opts, cache)
|
|
285
296
|
case node
|
|
286
|
-
when Nokogiri::XML::Comment
|
|
287
|
-
|
|
288
|
-
when Nokogiri::XML::Text
|
|
289
|
-
"T:#{opts[:collapse_whitespace] ? collapse(node.content) : node.content}"
|
|
297
|
+
when Nokogiri::XML::Comment, Nokogiri::XML::Text
|
|
298
|
+
[node.class, opts[:collapse_whitespace] ? collapse(node.content) : node.content]
|
|
290
299
|
when Nokogiri::XML::Element
|
|
291
|
-
attrs = node.attribute_nodes
|
|
292
|
-
attrs = attrs.sort_by(
|
|
293
|
-
|
|
294
|
-
children = node.children.reject { |
|
|
295
|
-
|
|
296
|
-
"E:#{node.name}[#{attr_str}](#{child_str})"
|
|
300
|
+
attrs = node.attribute_nodes.reject { |attr| attribute_excluded?(attr, opts) }
|
|
301
|
+
attrs = attrs.sort_by { |attr| attribute_name(attr) } if opts[:ignore_attr_order]
|
|
302
|
+
attributes = attrs.map { |attr| [attribute_name(attr), attr.value] }
|
|
303
|
+
children = opts[:ignore_children] ? [] : node.children.reject { |child| node_excluded?(child, opts) }
|
|
304
|
+
[node.class, element_name(node), attributes, children.map { |child| node_fingerprint(child, opts, cache) }]
|
|
297
305
|
else
|
|
298
|
-
|
|
306
|
+
[node.class, node.to_s]
|
|
299
307
|
end
|
|
300
308
|
end
|
|
301
309
|
|
|
@@ -345,7 +353,7 @@ module CompareXML
|
|
|
345
353
|
# @return type of equivalence (from equivalence constants)
|
|
346
354
|
#
|
|
347
355
|
def compare_element_nodes(n1, n2, opts, differences, child_opts = {}, diff_children: false, status: EQUIVALENT)
|
|
348
|
-
if n1
|
|
356
|
+
if element_name(n1) == element_name(n2)
|
|
349
357
|
result = compare_attribute_sets(n1, n2, n1.attribute_nodes, n2.attribute_nodes, opts, differences)
|
|
350
358
|
return result unless result == EQUIVALENT || opts[:force_children] == true
|
|
351
359
|
status = result unless result == EQUIVALENT
|
|
@@ -353,11 +361,24 @@ module CompareXML
|
|
|
353
361
|
status = result unless result == EQUIVALENT
|
|
354
362
|
else
|
|
355
363
|
status = UNEQUAL_ELEMENTS
|
|
356
|
-
add_difference(n1, n2, n1
|
|
364
|
+
add_difference(n1, n2, element_name(n1), element_name(n2), opts, differences)
|
|
357
365
|
end
|
|
358
366
|
status
|
|
359
367
|
end
|
|
360
368
|
|
|
369
|
+
##
|
|
370
|
+
# Returns the element name qualified by its namespace URI, so that elements with
|
|
371
|
+
# the same local name in different namespaces are not treated as equal.
|
|
372
|
+
#
|
|
373
|
+
# @param [Nokogiri::XML::Element] node the element
|
|
374
|
+
#
|
|
375
|
+
# @return [String] the local name, prefixed with "{uri}" when namespaced
|
|
376
|
+
#
|
|
377
|
+
def element_name(node)
|
|
378
|
+
href = node.namespace&.href
|
|
379
|
+
href ? "{#{href}}#{node.name}" : node.name
|
|
380
|
+
end
|
|
381
|
+
|
|
361
382
|
##
|
|
362
383
|
# Compares two nodes of type Nokogiri::XML::Text.
|
|
363
384
|
#
|
|
@@ -423,8 +444,8 @@ module CompareXML
|
|
|
423
444
|
# @return type of equivalence (from equivalence constants)
|
|
424
445
|
#
|
|
425
446
|
def compare_sorted_attribute_sets(n1, n2, a1_set, a2_set, opts, differences, status = EQUIVALENT)
|
|
426
|
-
a1_set = a1_set.sort_by(
|
|
427
|
-
a2_set = a2_set.sort_by(
|
|
447
|
+
a1_set = a1_set.sort_by { |attr| attribute_name(attr) }
|
|
448
|
+
a2_set = a2_set.sort_by { |attr| attribute_name(attr) }
|
|
428
449
|
i = j = 0
|
|
429
450
|
|
|
430
451
|
while i < a1_set.length || j < a2_set.length
|
|
@@ -435,10 +456,10 @@ module CompareXML
|
|
|
435
456
|
elsif a2_set[j].nil?
|
|
436
457
|
result = compare_attributes(n1, n2, a1_set[i], nil, opts, differences)
|
|
437
458
|
i += 1
|
|
438
|
-
elsif a1_set[i]
|
|
459
|
+
elsif attribute_name(a1_set[i]) < attribute_name(a2_set[j])
|
|
439
460
|
result = compare_attributes(n1, n2, a1_set[i], nil, opts, differences)
|
|
440
461
|
i += 1
|
|
441
|
-
elsif a1_set[i]
|
|
462
|
+
elsif attribute_name(a1_set[i]) > attribute_name(a2_set[j])
|
|
442
463
|
result = compare_attributes(n1, n2, nil, a2_set[j], opts, differences)
|
|
443
464
|
j += 1
|
|
444
465
|
else
|
|
@@ -478,6 +499,11 @@ module CompareXML
|
|
|
478
499
|
status
|
|
479
500
|
end
|
|
480
501
|
|
|
502
|
+
def attribute_name(attribute)
|
|
503
|
+
href = attribute.namespace&.href
|
|
504
|
+
href ? "{#{href}}#{attribute.name}" : attribute.name
|
|
505
|
+
end
|
|
506
|
+
|
|
481
507
|
##
|
|
482
508
|
# Compares two attributes by name and value.
|
|
483
509
|
#
|
|
@@ -494,18 +520,18 @@ module CompareXML
|
|
|
494
520
|
def compare_attributes(n1, n2, a1, a2, opts, differences, status = EQUIVALENT)
|
|
495
521
|
if a1.nil?
|
|
496
522
|
status = MISSING_ATTRIBUTE
|
|
497
|
-
add_difference(n1, n2, nil, "#{a2
|
|
523
|
+
add_difference(n1, n2, nil, "#{attribute_name(a2)}=\"#{a2.value}\"", opts, differences)
|
|
498
524
|
elsif a2.nil?
|
|
499
525
|
status = MISSING_ATTRIBUTE
|
|
500
|
-
add_difference(n1, n2, "#{a1
|
|
501
|
-
elsif a1
|
|
526
|
+
add_difference(n1, n2, "#{attribute_name(a1)}=\"#{a1.value}\"", nil, opts, differences)
|
|
527
|
+
elsif attribute_name(a1) == attribute_name(a2)
|
|
502
528
|
if a1.value != a2.value
|
|
503
529
|
status = UNEQUAL_ATTRIBUTES
|
|
504
|
-
add_difference(n1, n2, "#{a1
|
|
530
|
+
add_difference(n1, n2, "#{attribute_name(a1)}=\"#{a1.value}\"", "#{attribute_name(a2)}=\"#{a2.value}\"", opts, differences)
|
|
505
531
|
end
|
|
506
532
|
else
|
|
507
533
|
status = UNEQUAL_ATTRIBUTES
|
|
508
|
-
add_difference(n1, n2, "#{a1
|
|
534
|
+
add_difference(n1, n2, "#{attribute_name(a1)}=\"#{a1.value}\"", "#{attribute_name(a2)}=\"#{a2.value}\"", opts, differences)
|
|
509
535
|
end
|
|
510
536
|
status
|
|
511
537
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: compare-xml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vadim Kononov
|
|
@@ -23,9 +23,8 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.6'
|
|
26
|
-
description:
|
|
27
|
-
|
|
28
|
-
of Nokogiri::XML::Node or Nokogiri::XML::NodeSet for equality or equivalency.
|
|
26
|
+
description: Compares Nokogiri XML and HTML documents, nodes and node sets for equality
|
|
27
|
+
or equivalency. Returns a list of differences and can exclude chosen nodes or attributes.
|
|
29
28
|
email:
|
|
30
29
|
- vadim@konoson.com
|
|
31
30
|
executables: []
|
|
@@ -58,8 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
58
57
|
- !ruby/object:Gem::Version
|
|
59
58
|
version: '0'
|
|
60
59
|
requirements: []
|
|
61
|
-
rubygems_version: 4.0.
|
|
60
|
+
rubygems_version: 4.0.20
|
|
62
61
|
specification_version: 4
|
|
63
|
-
summary:
|
|
64
|
-
equality or equivalency.
|
|
62
|
+
summary: Compare XML and HTML documents or Nokogiri nodes for equality with a diff
|
|
65
63
|
test_files: []
|