compare-xml 0.66 → 1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb6c3fb3e704a49642e05b7c2ff26442ad63a77aa22c4a2dc461ffce22c5dc59
4
- data.tar.gz: 8fa4604c0f7216f077fa48807a68c4b222fefca2dd6beedfc0d745cb3f5074ee
3
+ metadata.gz: f7e6709bb5f5150832a22634027282fb7a2e80571f0a5c8210f7f8982f59bb67
4
+ data.tar.gz: ba0a234e1c095d4d0a97b72c22a8e6831d53b2203acf860965602dc81bdfbb85
5
5
  SHA512:
6
- metadata.gz: 7be5a2a949660aff92a2d4fea1fa6820a960ceb7b177479ee6a7b3464cc0f9b12ce7a81f08fe4567243a658987ab48c34362cf09b9a2f11767724d669cc0f142
7
- data.tar.gz: 7d17c1fd43511146021eadc83037cf27d7c259fad86658ecba24f6c021ec4095b984d1e5cb9becaa1cd2e78b4338db95f0b437e00ab60aed0f54f6733087dde9
6
+ metadata.gz: c2e9309f2980da1b6faa1e1ecea38293654492e04f216b1aa45e53c93817f2b394811511b4b233a8e74e3d498f6f71df0020b4d55635b5c95b6314aea7701f5b
7
+ data.tar.gz: ac6934cf5624163ea039cdd3985f07b28bd7f60e7f77e9329b70343d87b48e237b790e380316294293073a8d40de8f111d6f44addfb39a477d6b3f5389332f61
data/README.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # CompareXML
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/compare-xml.svg)](https://rubygems.org/gems/compare-xml)
3
+ [![Gem Version](https://img.shields.io/gem/v/compare-xml.svg?label=Gem&logo=rubygems&logoColor=white)](https://rubygems.org/gems/compare-xml)
4
+ [![Ruby](https://img.shields.io/badge/Ruby-2.4%20to%204.0-CC342D?logo=ruby&logoColor=white)](https://github.com/vkononov/compare-xml/blob/main/.github/workflows/test.yml)
5
+ [![Test Matrix](https://img.shields.io/github/actions/workflow/status/vkononov/compare-xml/test.yml?branch=main&label=Test%20Matrix&logo=github)](https://github.com/vkononov/compare-xml/actions/workflows/test.yml)
6
+ [![Lint](https://img.shields.io/github/actions/workflow/status/vkononov/compare-xml/lint.yml?branch=main&label=Lint&logo=github)](https://github.com/vkononov/compare-xml/actions/workflows/lint.yml)
7
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/license/MIT)
4
8
 
5
- 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
+ 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.
10
+
11
+ Supports **Ruby 2.4 → latest**, with the full Ruby matrix tested daily in CI.
12
+
13
+ [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png)](https://www.buymeacoffee.com/vkononov)
6
14
 
7
15
  **Features**
8
16
 
@@ -44,8 +52,8 @@ where `doc1` and `doc2` are instances of `Nokogiri::XML::Node` or `Nokogiri::XML
44
52
  Suppose you have two files `1.html` and `2.html` that you would like to compare. You could do it as follows:
45
53
 
46
54
  ```ruby
47
- doc1 = Nokogiri::HTML(open('1.html'))
48
- doc2 = Nokogiri::HTML(open('2.html'))
55
+ doc1 = Nokogiri::HTML(File.read('1.html'))
56
+ doc2 = Nokogiri::HTML(File.read('2.html'))
49
57
  puts CompareXML.equivalent?(doc1, doc2)
50
58
  ```
51
59
 
@@ -63,7 +71,7 @@ require 'compare-xml'
63
71
  CompareXML has a variety of options that can be invoked as an optional argument, e.g.:
64
72
 
65
73
  ```ruby
66
- CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true, ...})
74
+ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true})
67
75
  ```
68
76
 
69
77
  - `collapse_whitespace: {true|false}` default: **`true`**     [show examples ⇨](#collapse_whitespace)
@@ -76,16 +84,16 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true, .
76
84
  - when provided, ignores all attributes that contain substrings `string`, `string2`, etc.
77
85
 
78
86
  - `ignore_attrs: [css_selector1, css_selector1, ...]` default: **`[]`**     [show examples ⇨](#ignore_attrs)
79
- - when provided, ignores specific *attributes* using [CSS selectors](http://www.w3schools.com/cssref/css_selectors.asp)
87
+ - when provided, ignores specific *attributes* using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.php)
80
88
 
81
- - `ignore_attrs_by_name: [string1, string2, ...]` default: **`[]`**     [show examples ⇨](#ignore_attrs_by_name)
82
- - when provided, ignores specific *attributes* using [String]
89
+ - `ignore_attrs_by_name: [matcher1, matcher2, ...]` default: **`[]`**     [show examples ⇨](#ignore_attrs_by_name)
90
+ - when provided, ignores *attributes* whose name matches a `String` (exact) or `Regexp` (pattern)
83
91
 
84
92
  - `ignore_comments: {true|false}` default: **`true`**     [show examples ⇨](#ignore_comments)
85
93
  - when `true`, ignores comments, such as `<!-- comment -->`
86
94
 
87
95
  - `ignore_nodes: [css_selector1, css_selector1, ...]` default: **`[]`** &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[show examples ⇨](#ignore_nodes)
88
- - when provided, ignores specific *nodes* using [CSS selectors](http://www.w3schools.com/cssref/css_selectors.asp)
96
+ - when provided, ignores specific *nodes* using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.php)
89
97
 
90
98
  - `ignore_text_nodes: {true|false}` default: **`false`**&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[show examples ⇨](#ignore_text_nodes)
91
99
  - when `true`, ignores all text content within a document
@@ -99,6 +107,9 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true, .
99
107
  - `force_children {true|false}` default **`false`**&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[show examples ⇨](#force_children)
100
108
  - when `true`, the subnodes of a node are checked independently of the status of the parent node
101
109
 
110
+ - `align_children {true|false}` default **`false`**&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[show examples ⇨](#align_children)
111
+ - when `true` (in `verbose` mode), aligns child nodes so insertions and removals are reported as additions/removals instead of changes
112
+
102
113
 
103
114
  ## Options in Depth
104
115
 
@@ -180,7 +191,7 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true, .
180
191
 
181
192
  - <a id="ignore_attrs"></a>`ignore_attrs: [css_selector1, css_selector1, ...]` default: **`[]`**
182
193
 
183
- When provided, ignores all **attributes** that satisfy a particular rule using [CSS selectors](http://www.w3schools.com/cssref/css_selectors.asp).
194
+ When provided, ignores all **attributes** that satisfy a particular rule using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.php).
184
195
 
185
196
  **Usage Example:** `CompareXML.equivalent?(doc1, doc2, {ignore_attrs: ['a[rel="nofollow"]', 'input[type="hidden"']})`
186
197
 
@@ -196,17 +207,27 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true, .
196
207
 
197
208
  ----------
198
209
 
199
- - <a id="ignore_attrs_by_name"></a>`ignore_attrs_by_name: [string1, string2, ...]` default: **`false`**
210
+ - <a id="ignore_attrs_by_name"></a>`ignore_attrs_by_name: [matcher1, matcher2, ...]` default: **`[]`**
200
211
 
201
- When provided, ignores all **attributes** which name is specified in the string array.
212
+ When provided, ignores all **attributes** whose name matches one of the given matchers. A `String` matches the attribute name exactly, while a `Regexp` matches it as a pattern.
202
213
 
203
- **Usage Example:** `CompareXML.equivalent?(doc1, doc2, {ignore_attrs_by_name: ['target'])`
214
+ **Usage Example:** `CompareXML.equivalent?(doc1, doc2, {ignore_attrs_by_name: ['target', /^data-/]})`
204
215
 
205
216
  **Example:** With `ignore_attrs_by_name: ['target', 'rel']` the following HTML strings are considered equal:
206
217
 
207
218
  <a href="/admin" class="button" target="_blank">Link</a>
208
219
  <a href="/admin" class="button" target="_self" rel="nofollow">Link</a>
209
220
 
221
+ **Example:** With `ignore_attrs_by_name: [/^data-/]` the following HTML strings are considered equal:
222
+
223
+ <div data-id="1" data-role="row">Link</div>
224
+ <div data-id="2" data-role="cell">Link</div>
225
+
226
+ An ignored attribute does not need to be present on both elements. With `ignore_attrs_by_name: ['class']` the following HTML strings are considered equal:
227
+
228
+ <div class="foo"></div>
229
+ <div></div>
230
+
210
231
  ----------
211
232
 
212
233
 
@@ -232,7 +253,7 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true, .
232
253
 
233
254
  - <a id="ignore_nodes"></a>`ignore_nodes: [css_selector1, css_selector1, ...]` default: **`[]`**
234
255
 
235
- When provided, ignores all **nodes** that satisfy a particular rule using [CSS selectors](http://www.w3schools.com/cssref/css_selectors.asp).
256
+ When provided, ignores all **nodes** that satisfy a particular rule using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.php).
236
257
 
237
258
  **Usage Example:** `CompareXML.equivalent?(doc1, doc2, {ignore_nodes: ['script', 'object']})`
238
259
 
@@ -280,42 +301,42 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true, .
280
301
 
281
302
  **Example:** When `true` given the following HTML strings:
282
303
 
283
- ![diffing](https://github.com/vkononov/compare-xml/raw/master/img/diffing.png)
304
+ ![diffing](https://github.com/vkononov/compare-xml/raw/main/img/diffing.png)
284
305
 
285
306
  `CompareXML.equivalent?(doc1, doc2, {verbose: true})` will produce an array shown below.
286
307
 
287
308
  ```ruby
288
309
  [
289
- {
290
- node1: '<title>TITLE</title>',
291
- node2: '<title>ANOTHER TITLE</title>',
292
- diff1: 'TITLE',
293
- diff2: 'ANOTHER TITLE',
294
- },
295
- {
296
- node1: '<h1>SOME HEADING</h1>',
297
- node2: '<h1 id="main">SOME HEADING</h1>',
298
- diff1: nil,
299
- diff2: 'id="main"',
300
- },
301
- {
302
- node1: '<a href="/admin" rel="icon">Link</a>',
303
- node2: '<a rel="button" href="/admin">Link</a>',
304
- diff1: '"rel="icon"',
305
- diff2: '"rel="button"',
306
- },
307
- {
308
- node1: '<cite>Author Name</cite>',
309
- node2: nil,
310
- diff1: '<cite>Author Name</cite>',
311
- diff2: nil,
312
- },
313
- {
314
- node1: '<p class="footer">FOOTER</p>',
315
- node2: '<div class="footer">FOOTER</div>',
316
- diff1: 'p',
317
- diff2: 'div',
318
- }
310
+ {
311
+ node1: '<title>TITLE</title>',
312
+ node2: '<title>ANOTHER TITLE</title>',
313
+ diff1: 'TITLE',
314
+ diff2: 'ANOTHER TITLE'
315
+ },
316
+ {
317
+ node1: '<h1>SOME HEADING</h1>',
318
+ node2: '<h1 id="main">SOME HEADING</h1>',
319
+ diff1: nil,
320
+ diff2: 'id="main"'
321
+ },
322
+ {
323
+ node1: '<a href="/admin" rel="icon">Link</a>',
324
+ node2: '<a rel="button" href="/admin">Link</a>',
325
+ diff1: '"rel="icon"',
326
+ diff2: '"rel="button"'
327
+ },
328
+ {
329
+ node1: '<cite>Author Name</cite>',
330
+ node2: nil,
331
+ diff1: '<cite>Author Name</cite>',
332
+ diff2: nil
333
+ },
334
+ {
335
+ node1: '<p class="footer">FOOTER</p>',
336
+ node2: '<div class="footer">FOOTER</div>',
337
+ diff1: 'p',
338
+ diff2: 'div'
339
+ }
319
340
  ]
320
341
  ```
321
342
 
@@ -345,7 +366,47 @@ CompareXML.equivalent?(doc1, doc2, {collapse_whitespace: false, verbose: true, .
345
366
 
346
367
  When provided, compares all **subnodes** of any node.
347
368
 
348
- **Usage Example:** `CompareXML.equivalent?(doc1, doc2, {ignore_children: true})`
369
+ **Usage Example:** `CompareXML.equivalent?(doc1, doc2, {force_children: true})`
370
+
371
+ ----------
372
+
373
+ - <a id="align_children"></a>`align_children: {true|false}` default: **`false`**
374
+
375
+ Only takes effect in `verbose` mode. When `true`, sibling nodes are aligned before comparison so that an inserted or removed node is reported as an addition or removal (with one side `nil`) rather than causing the remaining siblings to be reported as a series of changes. Identical subtrees are matched as anchors, and the unmatched nodes between them are compared in order.
376
+
377
+ **Usage Example:** `CompareXML.equivalent?(doc1, doc2, {verbose: true, align_children: true})`
378
+
379
+ **Example:** Given a left document with three children and a right document with only the third:
380
+
381
+ <properties><a/><b/><c/></properties>
382
+ <properties><c/></properties>
383
+
384
+ With `align_children: true`, `<a>` and `<b>` are reported as removals (`node2` is `nil`); without it, they collapse into a single positional change.
385
+
386
+ ----------
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
+ ```
349
410
 
350
411
  ----------
351
412
 
@@ -367,4 +428,4 @@ This gem was inspired by [Michael B. Klein](https://github.com/mbklein)'s gem [`
367
428
 
368
429
  ## License
369
430
 
370
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
431
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/license/MIT).
@@ -1,3 +1,3 @@
1
1
  module CompareXML
2
- VERSION = '0.66'.freeze
2
+ VERSION = '1.0.1'.freeze
3
3
  end
data/lib/compare-xml.rb CHANGED
@@ -16,6 +16,10 @@ module CompareXML
16
16
  # when false, children of elements are not compared if the root is different
17
17
  force_children: false,
18
18
 
19
+ # when true (verbose mode only), child nodes are aligned before being compared so that
20
+ # inserted or removed nodes are reported as additions/removals instead of as changes
21
+ align_children: false,
22
+
19
23
  # when true, children of elements are never compared
20
24
  # when false, children of elements are compared if root is different or see force_children
21
25
  ignore_children: false,
@@ -72,16 +76,16 @@ module CompareXML
72
76
  # @param [Nokogiri::XML::Element] n1 left node element
73
77
  # @param [Nokogiri::XML::Element] n2 right node element
74
78
  # @param [Hash] opts user-overridden options
75
- # @param [Hash] childopts user-overridden options used for the child nodes
76
- # @param [Bool] diffchildren use different options for the child nodes
79
+ # @param [Hash] child_opts user-overridden options used for the child nodes
80
+ # @param [Bool] diff_children use different options for the child nodes
77
81
  #
78
82
  # @return true if equal, [Array] differences otherwise
79
83
  #
80
- def equivalent?(n1, n2, opts = {}, childopts = {}, diffchildren = false)
84
+ def equivalent?(n1, n2, opts = {}, child_opts = {}, diff_children = false)
81
85
  opts = DEFAULTS_OPTS.merge(opts)
82
- childopts = DEFAULTS_OPTS.merge(childopts)
86
+ child_opts = DEFAULTS_OPTS.merge(child_opts)
83
87
  differences = []
84
- result = diffchildren ? compareNodes(n1, n2, opts, differences, childopts, diffchildren) : compareNodes(n1, n2, opts, differences)
88
+ result = diff_children ? compare_nodes(n1, n2, opts, differences, child_opts, diff_children: diff_children) : compare_nodes(n1, n2, opts, differences)
85
89
  opts[:verbose] ? differences : result == EQUIVALENT
86
90
  end
87
91
 
@@ -91,42 +95,43 @@ module CompareXML
91
95
  # Compares two nodes for equivalence. The nodes could be any subclass
92
96
  # of Nokogiri::XML::Node including node sets and document fragments.
93
97
  #
94
- # @param [Nokogiri::XML::Node] n1 left node
95
- # @param [Nokogiri::XML::Node] n2 right node
98
+ # @param [Nokogiri::XML::Element] n1 left node
99
+ # @param [Nokogiri::XML::Element] n2 right node
96
100
  # @param [Hash] opts user-overridden options
97
101
  # @param [Array] differences inequivalence messages
98
- # @param [Hash] childopts user-overridden options used for the child nodes
99
- # @param [Bool] diffchildren use different options for the child nodes
100
- # @param [int] status comparison status code (EQUIVALENT by default)
102
+ # @param [Hash] child_opts user-overridden options used for the child nodes
103
+ # @param [Bool] diff_children use different options for the child nodes
101
104
  #
102
105
  # @return type of equivalence (from equivalence constants)
103
106
  #
104
- def compareNodes(n1, n2, opts, differences, childopts = {}, diffchildren = false, status = EQUIVALENT)
105
- if n1.class == n2.class
107
+ def compare_nodes(n1, n2, opts, differences, child_opts = {}, diff_children: false)
108
+ if n1.instance_of?(n2.class)
106
109
  case n1
107
110
  when Nokogiri::XML::Comment
108
- status = compareCommentNodes(n1, n2, opts, differences)
111
+ status = compare_comment_nodes(n1, n2, opts, differences)
109
112
  when Nokogiri::HTML::Document
110
- status = diffchildren ? compareDocumentNodes(n1, n2, opts, differences, childopts, diffchildren) : compareDocumentNodes(n1, n2, opts, differences)
113
+ status = diff_children ? compare_document_nodes(n1, n2, opts, differences, child_opts, diff_children: diff_children) : compare_document_nodes(n1, n2, opts, differences)
111
114
  when Nokogiri::XML::Element
112
- status = diffchildren ? compareElementNodes(n1, n2, opts, differences, childopts, diffchildren) : compareElementNodes(n1, n2, opts, differences)
115
+ status = diff_children ? compare_element_nodes(n1, n2, opts, differences, child_opts, diff_children: diff_children) : compare_element_nodes(n1, n2, opts, differences)
113
116
  when Nokogiri::XML::Text
114
- status = compareTextNodes(n1, n2, opts, differences)
117
+ status = compare_text_nodes(n1, n2, opts, differences)
118
+ when Nokogiri::XML::NodeSet
119
+ status = diff_children ? compare_children(n1, n2, child_opts, differences, diff_children: diff_children) : compare_children(n1, n2, opts, differences)
115
120
  else
116
121
  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)
117
- status = compareChildren(n1.children, n2.children, opts, differences)
122
+ status = compare_children(n1.children, n2.children, opts, differences)
118
123
  end
119
124
  elsif n1.nil? || n2.nil?
120
125
  status = MISSING_NODE
121
- addDifference(n1, n2, n1, n2, opts, differences)
126
+ add_difference(n1, n2, n1, n2, opts, differences)
122
127
  else
123
128
  status = UNEQUAL_NODES_TYPES
124
129
  if n1.is_a? Nokogiri::XML::Text
125
- addDifference(n1.parent, n2, n1, n2, opts, differences)
130
+ add_difference(n1.parent, n2, n1, n2, opts, differences)
126
131
  elsif n2.is_a? Nokogiri::XML::Text
127
- addDifference(n1, n2.parent, n1, n2, opts, differences)
132
+ add_difference(n1, n2.parent, n1, n2, opts, differences)
128
133
  else
129
- addDifference(n1, n2, n1, n2, opts, differences)
134
+ add_difference(n1, n2, n1, n2, opts, differences)
130
135
  end
131
136
  end
132
137
  status
@@ -135,16 +140,16 @@ module CompareXML
135
140
  ##
136
141
  # Compares two nodes of type Nokogiri::HTML::Comment.
137
142
  #
138
- # @param [Nokogiri::XML::Comment] n1 left comment
139
- # @param [Nokogiri::XML::Comment] n2 right comment
143
+ # @param [Nokogiri::XML::Element] n1 left comment
144
+ # @param [Nokogiri::XML::Element] n2 right comment
140
145
  # @param [Hash] opts user-overridden options
141
146
  # @param [Array] differences inequivalence messages
142
147
  # @param [int] status comparison status code (EQUIVALENT by default)
143
148
  #
144
149
  # @return type of equivalence (from equivalence constants)
145
150
  #
146
- def compareCommentNodes(n1, n2, opts, differences, status = EQUIVALENT)
147
- return true if opts[:ignore_comments]
151
+ def compare_comment_nodes(n1, n2, opts, differences, status: EQUIVALENT)
152
+ return status if opts[:ignore_comments]
148
153
  t1 = n1.content
149
154
  t2 = n2.content
150
155
  if opts[:collapse_whitespace]
@@ -153,7 +158,7 @@ module CompareXML
153
158
  end
154
159
  unless t1 == t2
155
160
  status = UNEQUAL_COMMENTS
156
- addDifference(n1.parent, n2.parent, t1, t2, opts, differences)
161
+ add_difference(n1.parent, n2.parent, t1, t2, opts, differences)
157
162
  end
158
163
  status
159
164
  end
@@ -161,22 +166,21 @@ module CompareXML
161
166
  ##
162
167
  # Compares two nodes of type Nokogiri::HTML::Document.
163
168
  #
164
- # @param [Nokogiri::XML::Document] n1 left document
165
- # @param [Nokogiri::XML::Document] n2 right document
169
+ # @param [Nokogiri::XML::Element] n1 left document
170
+ # @param [Nokogiri::XML::Element] n2 right document
166
171
  # @param [Hash] opts user-overridden options
167
172
  # @param [Array] differences inequivalence messages
168
- # @param [Hash] childopts user-overridden options used for the child nodes
169
- # @param [Bool] diffchildren use different options for the child nodes
170
- # @param [int] status comparison status code (EQUIVALENT by default)
173
+ # @param [Hash] child_opts user-overridden options used for the child nodes
174
+ # @param [Bool] diff_children use different options for the child nodes
171
175
  #
172
176
  # @return type of equivalence (from equivalence constants)
173
177
  #
174
- def compareDocumentNodes(n1, n2, opts, differences, childopts = {}, diffchildren = false, status = EQUIVALENT)
178
+ def compare_document_nodes(n1, n2, opts, differences, child_opts = {}, diff_children: false)
175
179
  if n1.name == n2.name
176
- status = diffchildren ? compareChildren(n1.children, n2.children, childopts, differences, diffchildren) : compareChildren(n1.children, n2.children, opts, differences)
180
+ status = diff_children ? compare_children(n1.children, n2.children, child_opts, differences, diff_children: diff_children) : compare_children(n1.children, n2.children, opts, differences)
177
181
  else
178
182
  status = UNEQUAL_DOCUMENTS
179
- addDifference(n1, n2, n1, n2, opts, differences)
183
+ add_difference(n1, n2, n1, n2, opts, differences)
180
184
  end
181
185
  status
182
186
  end
@@ -188,21 +192,23 @@ module CompareXML
188
192
  # @param [Nokogiri::XML::NodeSet] n2_set right set of Nokogiri::XML::Node elements
189
193
  # @param [Hash] opts user-overridden options
190
194
  # @param [Array] differences inequivalence messages
191
- # @param [Bool] diffchildren use different options for the child nodes
195
+ # @param [Bool] diff_children use different options for the child nodes
192
196
  # @param [int] status comparison status code (EQUIVALENT by default)
193
197
  #
194
198
  # @return type of equivalence (from equivalence constants)
195
199
  #
196
- def compareChildren(n1_set, n2_set, opts, differences, diffchildren = false, status = EQUIVALENT)
197
- i = 0; j = 0
198
- return if opts[:ignore_children]
200
+ def compare_children(n1_set, n2_set, opts, differences, diff_children: false, status: EQUIVALENT)
201
+ return status if opts[:ignore_children]
202
+ return compare_aligned_children(n1_set, n2_set, opts, differences, diff_children: diff_children, status: status) if opts[:verbose] && opts[:align_children]
203
+ i = 0
204
+ j = 0
199
205
  while i < n1_set.length || j < n2_set.length
200
- if !n1_set[i].nil? && nodeExcluded?(n1_set[i], opts)
206
+ if !n1_set[i].nil? && node_excluded?(n1_set[i], opts)
201
207
  i += 1 # increment counter if left node is excluded
202
- elsif !n2_set[j].nil? && nodeExcluded?(n2_set[j], opts)
208
+ elsif !n2_set[j].nil? && node_excluded?(n2_set[j], opts)
203
209
  j += 1 # increment counter if right node is excluded
204
210
  else
205
- result = diffchildren ? compareNodes(n1_set[i], n2_set[j], opts, differences, opts, diffchildren) : compareNodes(n1_set[i], n2_set[j], opts, differences)
211
+ result = diff_children ? compare_nodes(n1_set[i], n2_set[j], opts, differences, opts, diff_children: diff_children) : compare_nodes(n1_set[i], n2_set[j], opts, differences)
206
212
  status = result unless result == EQUIVALENT
207
213
 
208
214
  # return false so that this subtree could halt comparison on error
@@ -213,10 +219,116 @@ module CompareXML
213
219
  break unless status == EQUIVALENT || opts[:verbose]
214
220
 
215
221
  # increment both counters when both nodes have been compared
216
- i += 1; j += 1
222
+ i += 1
223
+ j += 1
217
224
  end
218
- status
219
225
  end
226
+ status
227
+ end
228
+
229
+ ##
230
+ # Compares two sets of child nodes by first aligning them, so that inserted or
231
+ # removed nodes are reported as additions/removals rather than as changes.
232
+ # Identical subtrees are matched as anchors (via a longest common subsequence
233
+ # of their fingerprints); the unmatched nodes between anchors are compared
234
+ # positionally, and any leftovers become additions or removals.
235
+ #
236
+ def compare_aligned_children(n1_set, n2_set, opts, differences, diff_children: false, status: EQUIVALENT)
237
+ left = n1_set.reject { |n| node_excluded?(n, opts) }
238
+ right = n2_set.reject { |n| node_excluded?(n, opts) }
239
+ cache = {}.compare_by_identity
240
+ fingerprints_left = left.map { |n| node_fingerprint(n, opts, cache) }
241
+ fingerprints_right = right.map { |n| node_fingerprint(n, opts, cache) }
242
+
243
+ prev_i = 0
244
+ prev_j = 0
245
+ anchors = longest_common_subsequence(fingerprints_left, fingerprints_right)
246
+ (anchors + [[left.length, right.length]]).each do |anchor_i, anchor_j|
247
+ result = compare_child_gap(left[prev_i...anchor_i], right[prev_j...anchor_j], opts, differences, diff_children)
248
+ status = result unless result == EQUIVALENT
249
+ prev_i = anchor_i + 1
250
+ prev_j = anchor_j + 1
251
+ end
252
+ status
253
+ end
254
+
255
+ ##
256
+ # Compares a run of unmatched left and right nodes that lies between two anchors.
257
+ # Overlapping nodes are compared positionally; remaining left nodes are reported
258
+ # as removals and remaining right nodes as additions.
259
+ #
260
+ def compare_child_gap(left, right, opts, differences, diff_children, status: EQUIVALENT)
261
+ common = [left.length, right.length].min
262
+ common.times do |k|
263
+ result = diff_children ? compare_nodes(left[k], right[k], opts, differences, opts, diff_children: diff_children) : compare_nodes(left[k], right[k], opts, differences)
264
+ status = result unless result == EQUIVALENT
265
+ end
266
+ left.drop(common).each do |n|
267
+ status = MISSING_NODE
268
+ add_difference(n, nil, n, nil, opts, differences)
269
+ end
270
+ right.drop(common).each do |n|
271
+ status = MISSING_NODE
272
+ add_difference(nil, n, nil, n, opts, differences)
273
+ end
274
+ status
275
+ end
276
+
277
+ ##
278
+ # Builds a canonical, option-aware string for a node so that two nodes with equal
279
+ # fingerprints are guaranteed to be equivalent under the current options. Used to
280
+ # anchor identical subtrees during alignment. Results are memoized per comparison.
281
+ #
282
+ def node_fingerprint(node, opts, cache)
283
+ cache[node] ||= build_node_fingerprint(node, opts, cache)
284
+ end
285
+
286
+ def build_node_fingerprint(node, opts, cache)
287
+ case node
288
+ when Nokogiri::XML::Comment
289
+ "C:#{opts[:collapse_whitespace] ? collapse(node.content) : node.content}"
290
+ when Nokogiri::XML::Text
291
+ "T:#{opts[:collapse_whitespace] ? collapse(node.content) : node.content}"
292
+ when Nokogiri::XML::Element
293
+ attrs = node.attribute_nodes
294
+ attrs = attrs.sort_by(&:name) if opts[:ignore_attr_order]
295
+ attr_str = attrs.map { |a| "#{a.name}=#{a.value}" }.join(' ')
296
+ children = node.children.reject { |c| node_excluded?(c, opts) }
297
+ child_str = children.map { |c| node_fingerprint(c, opts, cache) }.join
298
+ "E:#{element_name(node)}[#{attr_str}](#{child_str})"
299
+ else
300
+ "O:#{node.class}:#{node}"
301
+ end
302
+ end
303
+
304
+ ##
305
+ # Returns the index pairs of a longest common subsequence of the two arrays.
306
+ #
307
+ def longest_common_subsequence(a, b)
308
+ m = a.length
309
+ n = b.length
310
+ lengths = Array.new(m + 1) { Array.new(n + 1, 0) }
311
+ (m - 1).downto(0) do |i|
312
+ (n - 1).downto(0) do |j|
313
+ lengths[i][j] = a[i] == b[j] ? lengths[i + 1][j + 1] + 1 : [lengths[i + 1][j], lengths[i][j + 1]].max
314
+ end
315
+ end
316
+
317
+ pairs = []
318
+ i = 0
319
+ j = 0
320
+ while i < m && j < n
321
+ if a[i] == b[j]
322
+ pairs << [i, j]
323
+ i += 1
324
+ j += 1
325
+ elsif lengths[i + 1][j] >= lengths[i][j + 1]
326
+ i += 1
327
+ else
328
+ j += 1
329
+ end
330
+ end
331
+ pairs
220
332
  end
221
333
 
222
334
  ##
@@ -228,38 +340,52 @@ module CompareXML
228
340
  # @param [Nokogiri::XML::Element] n2 right node element
229
341
  # @param [Hash] opts user-overridden options
230
342
  # @param [Array] differences inequivalence messages
231
- # @param [Hash] childopts user-overridden options used for the child nodes
232
- # @param [Bool] diffchildren use different options for the child nodes
343
+ # @param [Hash] child_opts user-overridden options used for the child nodes
344
+ # @param [Bool] diff_children use different options for the child nodes
233
345
  # @param [int] status comparison status code (EQUIVALENT by default)
234
346
  #
235
347
  # @return type of equivalence (from equivalence constants)
236
348
  #
237
- def compareElementNodes(n1, n2, opts, differences, childopts = {}, diffchildren = false, status = EQUIVALENT)
238
- if n1.name == n2.name
239
- result = compareAttributeSets(n1, n2, n1.attribute_nodes, n2.attribute_nodes, opts, differences)
349
+ def compare_element_nodes(n1, n2, opts, differences, child_opts = {}, diff_children: false, status: EQUIVALENT)
350
+ if element_name(n1) == element_name(n2)
351
+ result = compare_attribute_sets(n1, n2, n1.attribute_nodes, n2.attribute_nodes, opts, differences)
240
352
  return result unless result == EQUIVALENT || opts[:force_children] == true
241
- result = diffchildren ? compareChildren(n1.children, n2.children, childopts, differences, diffchildren) : compareChildren(n1.children, n2.children, opts, differences)
353
+ status = result unless result == EQUIVALENT
354
+ result = diff_children ? compare_children(n1.children, n2.children, child_opts, differences, diff_children: diff_children) : compare_children(n1.children, n2.children, opts, differences)
242
355
  status = result unless result == EQUIVALENT
243
356
  else
244
357
  status = UNEQUAL_ELEMENTS
245
- addDifference(n1, n2, n1.name, n2.name, opts, differences)
358
+ add_difference(n1, n2, element_name(n1), element_name(n2), opts, differences)
246
359
  end
247
360
  status
248
361
  end
249
362
 
363
+ ##
364
+ # Returns the element name qualified by its namespace URI, so that elements with
365
+ # the same local name in different namespaces are not treated as equal.
366
+ #
367
+ # @param [Nokogiri::XML::Element] node the element
368
+ #
369
+ # @return [String] the local name, prefixed with "{uri}" when namespaced
370
+ #
371
+ def element_name(node)
372
+ href = node.namespace&.href
373
+ href ? "{#{href}}#{node.name}" : node.name
374
+ end
375
+
250
376
  ##
251
377
  # Compares two nodes of type Nokogiri::XML::Text.
252
378
  #
253
- # @param [Nokogiri::XML::Text] n1 left text node
254
- # @param [Nokogiri::XML::Text] n2 right text node
379
+ # @param [Nokogiri::XML::Element] n1 left text node
380
+ # @param [Nokogiri::XML::Element] n2 right text node
255
381
  # @param [Hash] opts user-overridden options
256
382
  # @param [Array] differences inequivalence messages
257
383
  # @param [int] status comparison status code (EQUIVALENT by default)
258
384
  #
259
385
  # @return type of equivalence (from equivalence constants)
260
386
  #
261
- def compareTextNodes(n1, n2, opts, differences, status = EQUIVALENT)
262
- return true if opts[:ignore_text_nodes]
387
+ def compare_text_nodes(n1, n2, opts, differences, status = EQUIVALENT)
388
+ return status if opts[:ignore_text_nodes]
263
389
  t1 = n1.content
264
390
  t2 = n2.content
265
391
  if opts[:collapse_whitespace]
@@ -268,7 +394,7 @@ module CompareXML
268
394
  end
269
395
  unless t1 == t2
270
396
  status = UNEQUAL_TEXT_CONTENTS
271
- addDifference(n1.parent, n2.parent, t1, t2, opts, differences)
397
+ add_difference(n1.parent, n2.parent, t1, t2, opts, differences)
272
398
  end
273
399
  status
274
400
  end
@@ -285,12 +411,14 @@ module CompareXML
285
411
  #
286
412
  # @return type of equivalence (from equivalence constants)
287
413
  #
288
- def compareAttributeSets(n1, n2, a1_set, a2_set, opts, differences)
414
+ def compare_attribute_sets(n1, n2, a1_set, a2_set, opts, differences)
415
+ a1_set = a1_set.reject { |a| attribute_excluded?(a, opts) }
416
+ a2_set = a2_set.reject { |a| attribute_excluded?(a, opts) }
289
417
  return false unless a1_set.length == a2_set.length || opts[:verbose]
290
418
  if opts[:ignore_attr_order]
291
- compareSortedAttributeSets(n1, n2, a1_set, a2_set, opts, differences)
419
+ compare_sorted_attribute_sets(n1, n2, a1_set, a2_set, opts, differences)
292
420
  else
293
- compareUnsortedAttributeSets(n1, n2, a1_set, a2_set, opts, differences)
421
+ compare_unsorted_attribute_sets(n1, n2, a1_set, a2_set, opts, differences)
294
422
  end
295
423
  end
296
424
 
@@ -309,7 +437,7 @@ module CompareXML
309
437
  #
310
438
  # @return type of equivalence (from equivalence constants)
311
439
  #
312
- def compareSortedAttributeSets(n1, n2, a1_set, a2_set, opts, differences, status = EQUIVALENT)
440
+ def compare_sorted_attribute_sets(n1, n2, a1_set, a2_set, opts, differences, status = EQUIVALENT)
313
441
  a1_set = a1_set.sort_by(&:name)
314
442
  a2_set = a2_set.sort_by(&:name)
315
443
  i = j = 0
@@ -317,15 +445,21 @@ module CompareXML
317
445
  while i < a1_set.length || j < a2_set.length
318
446
 
319
447
  if a1_set[i].nil?
320
- result = compareAttributes(n1, n2, nil, a2_set[j], opts, differences); j += 1
448
+ result = compare_attributes(n1, n2, nil, a2_set[j], opts, differences)
449
+ j += 1
321
450
  elsif a2_set[j].nil?
322
- result = compareAttributes(n1, n2, a1_set[i], nil, opts, differences); i += 1
451
+ result = compare_attributes(n1, n2, a1_set[i], nil, opts, differences)
452
+ i += 1
323
453
  elsif a1_set[i].name < a2_set[j].name
324
- result = compareAttributes(n1, n2, a1_set[i], nil, opts, differences); i += 1
454
+ result = compare_attributes(n1, n2, a1_set[i], nil, opts, differences)
455
+ i += 1
325
456
  elsif a1_set[i].name > a2_set[j].name
326
- result = compareAttributes(n1, n2, nil, a2_set[j], opts, differences); j += 1
457
+ result = compare_attributes(n1, n2, nil, a2_set[j], opts, differences)
458
+ j += 1
327
459
  else
328
- result = compareAttributes(n1, n2, a1_set[i], a2_set[j], opts, differences); i += 1; j += 1
460
+ result = compare_attributes(n1, n2, a1_set[i], a2_set[j], opts, differences)
461
+ i += 1
462
+ j += 1
329
463
  end
330
464
 
331
465
  status = result unless result == EQUIVALENT
@@ -350,11 +484,11 @@ module CompareXML
350
484
  #
351
485
  # @return type of equivalence (from equivalence constants)
352
486
  #
353
- def compareUnsortedAttributeSets(n1, n2, a1_set, a2_set, opts, differences, status = EQUIVALENT)
487
+ def compare_unsorted_attribute_sets(n1, n2, a1_set, a2_set, opts, differences, status = EQUIVALENT)
354
488
  [a1_set.length, a2_set.length].max.times do |i|
355
- result = compareAttributes(n1, n2, a1_set[i], a2_set[i], opts, differences)
489
+ result = compare_attributes(n1, n2, a1_set[i], a2_set[i], opts, differences)
356
490
  status = result unless result == EQUIVALENT
357
- break unless status == EQUIVALENT
491
+ break unless status == EQUIVALENT || opts[:verbose]
358
492
  end
359
493
  status
360
494
  end
@@ -362,34 +496,31 @@ module CompareXML
362
496
  ##
363
497
  # Compares two attributes by name and value.
364
498
  #
365
- # @param [Nokogiri::XML::Element] n1 left node element
366
- # @param [Nokogiri::XML::Element] n2 right node element
367
- # @param [Nokogiri::XML::Attr] a1 left attribute
368
- # @param [Nokogiri::XML::Attr] a2 right attribute
499
+ # @param [Nokogiri::XML::Element|NilClass] n1 left node element
500
+ # @param [Nokogiri::XML::Element|NilClass] n2 right node element
501
+ # @param [Nokogiri::XML::Attr|NilClass] a1 left attribute
502
+ # @param [Nokogiri::XML::Attr|NilClass] a2 right attribute
369
503
  # @param [Hash] opts user-overridden options
370
504
  # @param [Array] differences inequivalence messages
371
505
  # @param [int] status comparison status code (EQUIVALENT by default)
372
506
  #
373
507
  # @return type of equivalence (from equivalence constants)
374
508
  #
375
- def compareAttributes(n1, n2, a1, a2, opts, differences, status = EQUIVALENT)
509
+ def compare_attributes(n1, n2, a1, a2, opts, differences, status = EQUIVALENT)
376
510
  if a1.nil?
377
511
  status = MISSING_ATTRIBUTE
378
- addDifference(n1, n2, nil, "#{a2.name}=\"#{a2.value}\"", opts, differences)
512
+ add_difference(n1, n2, nil, "#{a2.name}=\"#{a2.value}\"", opts, differences)
379
513
  elsif a2.nil?
380
514
  status = MISSING_ATTRIBUTE
381
- addDifference(n1, n2, "#{a1.name}=\"#{a1.value}\"", nil, opts, differences)
515
+ add_difference(n1, n2, "#{a1.name}=\"#{a1.value}\"", nil, opts, differences)
382
516
  elsif a1.name == a2.name
383
- return status if attrNameExcluded?(a1.name, a2.name, opts)
384
- return status if attrsExcluded?(a1, a2, opts)
385
- return status if attrContentExcluded?(a1, a2, opts)
386
517
  if a1.value != a2.value
387
518
  status = UNEQUAL_ATTRIBUTES
388
- addDifference(n1, n2, "#{a1.name}=\"#{a1.value}\"", "#{a2.name}=\"#{a2.value}\"", opts, differences)
519
+ add_difference(n1, n2, "#{a1.name}=\"#{a1.value}\"", "#{a2.name}=\"#{a2.value}\"", opts, differences)
389
520
  end
390
521
  else
391
522
  status = UNEQUAL_ATTRIBUTES
392
- addDifference(n1, n2, "#{a1.name}=\"#{a1.value}\"", "#{a2.name}=\"#{a2.value}\"", opts, differences)
523
+ add_difference(n1, n2, "#{a1.name}=\"#{a1.value}\"", "#{a2.name}=\"#{a2.value}\"", opts, differences)
393
524
  end
394
525
  status
395
526
  end
@@ -408,7 +539,7 @@ module CompareXML
408
539
  #
409
540
  # @return true if excluded, false otherwise
410
541
  #
411
- def nodeExcluded?(n, opts)
542
+ def node_excluded?(n, opts)
412
543
  return true if n.is_a?(Nokogiri::XML::DTD)
413
544
  return true if n.is_a?(Nokogiri::XML::Comment) && opts[:ignore_comments]
414
545
  return true if n.is_a?(Nokogiri::XML::Text) && (opts[:ignore_text_nodes] || collapse(n.content).empty?)
@@ -417,84 +548,40 @@ module CompareXML
417
548
  end
418
549
 
419
550
  ##
420
- # Checks whether two given attributes should be excluded, based on a user-specified css rule.
421
- # If true, only the specified attributes are ignored; all remaining attributes are still compared.
422
- # The CSS rule is used to locate the node that contains the attributes to be excluded.
423
- # The CSS rule MUST contain the name of the attribute to be ignored.
424
- #
425
- # @param [Nokogiri::XML::Attr] a1 left attribute
426
- # @param [Nokogiri::XML::Attr] a2 right attribute
427
- # @param [Hash] opts user-overridden options
428
- #
429
- # @return true if excluded, false otherwise
551
+ # Determines whether a single attribute should be excluded from comparison, based on
552
+ # the +ignore_attrs_by_name+, +ignore_attr_content+ and +ignore_attrs+ options. The
553
+ # check is one-sided, so an attribute is dropped even when its counterpart is missing
554
+ # on the other element.
430
555
  #
431
- def attrsExcluded?(a1, a2, opts)
432
- opts[:ignore_attrs].each do |css|
433
- if css.include?(a1.name) && css.include?(a2.name)
434
- return true if a1.parent.xpath('../*').css(css).include?(a1.parent) && a2.parent.xpath('../*').css(css).include?(a2.parent)
435
- end
436
- end
437
- false
438
- end
439
-
440
- ##
441
- # Checks whether two given attributes should be excluded, based on their content.
442
- # Checks whether both attributes contain content that should be excluded, and
443
- # returns true only if an excluded string is contained in both attribute values.
556
+ # Name matchers are compared with +===+: a String matches the attribute name exactly,
557
+ # while a Regexp matches it as a pattern.
444
558
  #
445
- # @param [Nokogiri::XML::Attr] a1 left attribute
446
- # @param [Nokogiri::XML::Attr] a2 right attribute
559
+ # @param [Nokogiri::XML::Attr] attr the attribute being tested
447
560
  # @param [Hash] opts user-overridden options
448
561
  #
449
562
  # @return true if excluded, false otherwise
450
563
  #
451
- def attrContentExcluded?(a1, a2, opts)
452
- a1_excluded = false
453
- a2_excluded = false
454
- opts[:ignore_attr_content].each do |content|
455
- a1_excluded ||= a1.value.include?(content)
456
- a2_excluded ||= a2.value.include?(content)
457
- return true if a1_excluded && a2_excluded
458
- end
459
- false
460
- end
564
+ def attribute_excluded?(attr, opts)
565
+ return true if opts[:ignore_attrs_by_name].any? { |matcher| matcher === attr.name }
566
+ return true if opts[:ignore_attr_content].any? { |content| attr.value.include?(content) }
461
567
 
462
- ##
463
- # Checks whether two given attributes should be excluded, based on their content.
464
- # Checks whether both attributes contain content that should be excluded, and
465
- # returns true only if an excluded string is contained in both attribute values.
466
- #
467
- # @param [Nokogiri::XML::Attr] a1 left attribute
468
- # @param [Nokogiri::XML::Attr] a2 right attribute
469
- # @param [Hash] opts user-overridden options
470
- #
471
- # @return true if excluded, false otherwise
472
- #
473
- def attrNameExcluded?(a1, a2, opts)
474
- a1_excluded = false
475
- a2_excluded = false
476
- opts[:ignore_attrs_by_name].each do |name|
477
- a1_excluded ||= a1.to_s.include?(name)
478
- a2_excluded ||= a2.to_s.include?(name)
479
- return true if a1_excluded && a2_excluded
480
- end
481
- false
568
+ opts[:ignore_attrs].any? { |css| css.include?(attr.name) && attr.parent.xpath('../*').css(css).include?(attr.parent) }
482
569
  end
483
570
 
484
571
  ##
485
572
  # Strips the whitespace (from beginning and end) and collapses it,
486
573
  # i.e. multiple spaces, new lines and tabs are all collapsed to a single space.
487
574
  #
488
- # @param [Nokogiri::XML::Node] node1 left node
489
- # @param [Nokogiri::XML::Node] node2 right node
490
- # @param [String] diff1 left diffing value
491
- # @param [String] diff2 right diffing value
575
+ # @param [Nokogiri::XML::Element|NilClass] node1 left node
576
+ # @param [Nokogiri::XML::Element|NilClass] node2 right node
577
+ # @param [String|NilClass] diff1 left diffing value
578
+ # @param [String|NilClass] diff2 right diffing value
492
579
  # @param [Hash] opts user-overridden options
493
580
  # @param [Array] differences inequivalence messages
494
581
  #
495
582
  # @return collapsed string
496
583
  #
497
- def addDifference(node1, node2, diff1, diff2, opts, differences)
584
+ def add_difference(node1, node2, diff1, diff2, opts, differences)
498
585
  differences << { node1: node1, node2: node2, diff1: diff1, diff2: diff2 } if opts[:verbose]
499
586
  end
500
587
 
metadata CHANGED
@@ -1,55 +1,48 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: compare-xml
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.66'
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vadim Kononov
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2018-04-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: nokogiri
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - "~>"
16
+ - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '1.8'
18
+ version: '1.6'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
- - - "~>"
23
+ - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '1.8'
27
- description: CompareXML is a fast, lightweight and feature-rich tool that will solve
28
- your XML/HTML comparison or diffing needs. its purpose is to compare two instances
29
- of Nokogiri::XML::Node or Nokogiri::XML::NodeSet for equality or equivalency.
25
+ version: '1.6'
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.
30
28
  email:
31
- - vadim@poetic.com
29
+ - vadim@konoson.com
32
30
  executables: []
33
31
  extensions: []
34
32
  extra_rdoc_files: []
35
33
  files:
36
- - ".gitignore"
37
- - ".rubocop.yml"
38
- - Gemfile
39
34
  - LICENSE.txt
40
35
  - README.md
41
- - Rakefile
42
- - bin/console
43
- - bin/setup
44
- - compare-xml.gemspec
45
- - img/diffing.png
46
36
  - lib/compare-xml.rb
47
37
  - lib/compare-xml/version.rb
48
38
  homepage: https://github.com/vkononov/compare-xml
49
39
  licenses:
50
40
  - MIT
51
- metadata: {}
52
- post_install_message:
41
+ metadata:
42
+ homepage_uri: https://github.com/vkononov/compare-xml
43
+ source_code_uri: https://github.com/vkononov/compare-xml
44
+ changelog_uri: https://github.com/vkononov/compare-xml/releases
45
+ rubygems_mfa_required: 'true'
53
46
  rdoc_options: []
54
47
  require_paths:
55
48
  - lib
@@ -57,17 +50,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
50
  requirements:
58
51
  - - ">="
59
52
  - !ruby/object:Gem::Version
60
- version: '0'
53
+ version: 2.4.0
61
54
  required_rubygems_version: !ruby/object:Gem::Requirement
62
55
  requirements:
63
56
  - - ">="
64
57
  - !ruby/object:Gem::Version
65
58
  version: '0'
66
59
  requirements: []
67
- rubyforge_project:
68
- rubygems_version: 2.7.3
69
- signing_key:
60
+ rubygems_version: 4.0.10
70
61
  specification_version: 4
71
- summary: A customizable tool that compares two instances of Nokogiri::XML::Node for
72
- equality or equivalency.
62
+ summary: Compare XML and HTML documents or Nokogiri nodes for equality with a diff
73
63
  test_files: []
data/.gitignore DELETED
@@ -1,13 +0,0 @@
1
- *.DS_Store
2
- *thumbs.db
3
- /*.gem
4
- /.bundle/
5
- /.idea/
6
- /.yardoc
7
- /_yardoc/
8
- /coverage/
9
- /doc/
10
- /Gemfile.lock
11
- /pkg/
12
- /spec/reports/
13
- /tmp/
data/.rubocop.yml DELETED
@@ -1,45 +0,0 @@
1
- AllCops:
2
- # Exclude:
3
- # - config/routes.rb
4
- # - db/migrate/*
5
- # - db/schema.rb
6
- TargetRubyVersion: 2.5
7
-
8
- Metrics/AbcSize:
9
- Enabled: false
10
-
11
- Metrics/CyclomaticComplexity:
12
- Enabled: false
13
-
14
- Metrics/LineLength:
15
- Enabled: false
16
-
17
- Metrics/MethodLength:
18
- Enabled: false
19
-
20
- Metrics/ModuleLength:
21
- Enabled: false
22
-
23
- Metrics/ParameterLists:
24
- Enabled: false
25
-
26
- Metrics/PerceivedComplexity:
27
- Enabled: false
28
-
29
- Naming/FileName:
30
- Enabled: false
31
-
32
- Naming/MethodName:
33
- Enabled: false
34
-
35
- Naming/UncommunicativeMethodParamName:
36
- Enabled: false
37
-
38
- Style/Documentation:
39
- Enabled: false
40
-
41
- Style/FrozenStringLiteralComment:
42
- Enabled: false
43
-
44
- Style/Semicolon:
45
- Enabled: false
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in compare-xml-xml.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,2 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- task default: :spec
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'bundler/setup'
4
- require 'compare-xml/xml'
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require 'pry'
11
- # Pry.start
12
-
13
- require 'irb'
14
- IRB.start
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/compare-xml.gemspec DELETED
@@ -1,22 +0,0 @@
1
- lib = File.expand_path('lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'compare-xml/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'compare-xml'
7
- spec.version = CompareXML::VERSION
8
- spec.authors = ['Vadim Kononov']
9
- spec.email = ['vadim@poetic.com']
10
-
11
- spec.summary = 'A customizable tool that compares two instances of Nokogiri::XML::Node for equality or equivalency.'
12
- spec.description = '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.'
13
- spec.homepage = 'https://github.com/vkononov/compare-xml'
14
- spec.license = 'MIT'
15
-
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = 'exe'
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ['lib']
20
-
21
- spec.add_runtime_dependency 'nokogiri', '~> 1.8'
22
- end
data/img/diffing.png DELETED
Binary file