xqsr3-xml 0.1.1 → 0.1.3
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 +5 -5
- data/AUTHORS.md +21 -0
- data/CHANGES.md +40 -0
- data/CONTRIBUTING.md +36 -0
- data/EXAMPLES.md +8 -0
- data/FAQ.md +27 -0
- data/INSTALL.md +41 -0
- data/LICENSE +21 -18
- data/NEWS.md +12 -0
- data/README.md +89 -23
- data/Rakefile +21 -0
- data/SECURITY.md +23 -0
- data/TODO.md +23 -0
- data/examples/compare_xml.md +47 -0
- data/examples/compare_xml.rb +20 -0
- data/lib/xqsr3/xml/utilities/compare.rb +311 -311
- data/lib/xqsr3/xml/utilities/navigation.rb +31 -31
- data/lib/xqsr3/xml/version.rb +19 -20
- data/test/unit/tc_version.rb +15 -15
- data/test/unit/xml/utilities/tc_compare.rb +94 -94
- data/test/unit/xml/utilities/tc_navigation.rb +17 -17
- metadata +48 -33
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
# ######################################################################## #
|
|
3
|
-
# File:
|
|
3
|
+
# File: lib/xqsr3/xml/_utilities/compare.rb
|
|
4
4
|
#
|
|
5
|
-
# Purpose:
|
|
6
|
-
# module
|
|
5
|
+
# Purpose: Definition of the ::Xqsr3::XML::Utilities::Compare module
|
|
7
6
|
#
|
|
8
|
-
# Created:
|
|
9
|
-
# Updated:
|
|
7
|
+
# Created: 30th July 2017
|
|
8
|
+
# Updated: 19th August 2026
|
|
10
9
|
#
|
|
11
|
-
# Home:
|
|
10
|
+
# Home: http://github.com/synesissoftware/xqsr3
|
|
12
11
|
#
|
|
13
|
-
# Author:
|
|
12
|
+
# Author: Matthew Wilson
|
|
14
13
|
#
|
|
15
|
-
# Copyright (c)
|
|
14
|
+
# Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems
|
|
15
|
+
# Copyright (c) 2017-2019, Matthew Wilson and Synesis Software
|
|
16
16
|
# All rights reserved.
|
|
17
17
|
#
|
|
18
18
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -63,475 +63,475 @@ module Utilities
|
|
|
63
63
|
|
|
64
64
|
module Compare
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
# Class that represents the result of an XML comparison
|
|
67
|
+
#
|
|
68
|
+
# NOTE: Sadly, we cannot create instances of +FalseClass+/+TrueClass+,
|
|
69
|
+
# to which we could then add a +reason+ attribute, so instead we must
|
|
70
|
+
# have a results class
|
|
71
|
+
class Result
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
include ::Xqsr3::Quality::ParameterChecking
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
protected :check_parameter
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
77
|
+
#
|
|
78
|
+
# Options:
|
|
79
|
+
#
|
|
80
|
+
# +:different_attributes+
|
|
81
|
+
# +:different_attribute_count+
|
|
82
|
+
# +:different_attribute_order+
|
|
83
|
+
# +:different_child_node_count+
|
|
84
|
+
# +:different_child_node_order+
|
|
85
|
+
# +:different_child_nodes+
|
|
86
|
+
# +:different_node_names+
|
|
87
|
+
# +:different_node_contents+
|
|
88
|
+
# +:parameter_is_empty+
|
|
89
|
+
# +:parameter_is_nil+
|
|
90
|
+
# +:+
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
def initialize status, reason, **options
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
@call_stack = caller(2)
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
check_parameter status, 'status', types: [ ::FalseClass, ::TrueClass ]
|
|
97
|
+
check_parameter reason, 'reason', type: ::Symbol, allow_nil: true
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
@status = status
|
|
100
|
+
@reason = reason
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
@lhs_node = options[:lhs_node]
|
|
103
|
+
@rhs_node = options[:rhs_node]
|
|
104
|
+
end
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
def self.return status, reason, **options
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
return self.new status, reason, **options
|
|
109
|
+
end
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
def self.same reason = nil, **options
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
return self.new true, reason, **options
|
|
114
|
+
end
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
def self.different reason, **options
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
return self.new false, reason, **options
|
|
119
|
+
end
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
attr_reader :call_stack
|
|
122
|
+
attr_reader :status
|
|
123
|
+
attr_reader :reason
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
def different?
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
!status
|
|
128
|
+
end
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
def same?
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
status
|
|
133
|
+
end
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
def details
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
r = reason.to_s.gsub(/_/, ' ')
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
qualifying = ''
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
if @lhs_node
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
qualifying += '; ' unless qualifying.empty?
|
|
144
|
+
qualifying += "lhs-node=#{@lhs_node}"
|
|
145
|
+
end
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
if @rhs_node
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
qualifying += '; ' unless qualifying.empty?
|
|
150
|
+
qualifying += "rhs-node=#{@rhs_node}"
|
|
151
|
+
end
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
r = "#{r}: #{qualifying}" unless qualifying.empty?
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
r
|
|
156
|
+
end
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
def to_s
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
return 'same' if same?
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
"different, because: #{details}"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
module Internal_Compare_
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
include ::Xqsr3::XML::Utilities::Navigation
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
extend ::Xqsr3::Quality::ParameterChecking
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
DEFAULT_OPTIONS = {
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
#
|
|
185
|
-
|
|
186
|
-
|
|
174
|
+
debug: false,
|
|
175
|
+
# element_order: false,
|
|
176
|
+
equate_nil_and_empty: false,
|
|
177
|
+
ignore_attributes: false,
|
|
178
|
+
ignore_attribute_order: true,
|
|
179
|
+
ignore_child_node_order: true,
|
|
180
|
+
ignore_content: false,
|
|
181
|
+
ignore_content_case: false,
|
|
182
|
+
ignore_xml_declarations: true,
|
|
183
|
+
normalise_whitespace: true,
|
|
184
|
+
# normalize_whitespace: true,
|
|
185
|
+
validate_params: true,
|
|
186
|
+
}
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
ORDER_OPTIONS_SYMBOLS = [
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
:element_order,
|
|
191
|
+
:ignore_attribute_order,
|
|
192
|
+
:ignore_child_node_order,
|
|
193
|
+
:ignore_content,
|
|
194
|
+
:ignore_content_case,
|
|
195
|
+
]
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
WHITESPACE_OPTIONS_SYMBOLS = [
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
:normalise_whitespace,
|
|
200
|
+
:normalize_whitespace,
|
|
201
|
+
]
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
def self.derive_options_ given_options
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
default_options = DEFAULT_OPTIONS
|
|
206
|
+
derived_options = {}.merge given_options
|
|
207
207
|
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
# sort whitespace
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
if WHITESPACE_OPTIONS_SYMBOLS.any? { |sym| given_options.has_key? sym }
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
default_options = default_options.reject { |k, v| WHITESPACE_OPTIONS_SYMBOLS.include? k }
|
|
214
|
+
end
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
if given_options.has_key? :normalise_whitespace
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
derived_options.delete :normalize_whitespace
|
|
219
|
+
elsif given_options.has_key? :normalize_whitespace
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
derived_options[:normalise_whitespace] = given_options[:normalize_whitespace]
|
|
222
222
|
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
derived_options.delete :normalize_whitespace
|
|
224
|
+
end
|
|
225
225
|
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
# sort element-order
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
if ORDER_OPTIONS_SYMBOLS.any? { |sym| given_options.has_key? sym }
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
default_options = default_options.reject { |k, v| ORDER_OPTIONS_SYMBOLS.include? k }
|
|
232
|
+
end
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
if given_options.has_key? :element_order
|
|
235
235
|
|
|
236
|
-
|
|
236
|
+
element_order = given_options[:element_order]
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
derived_options[:ignore_attribute_order] = !element_order
|
|
239
|
+
derived_options[:ignore_child_node_order] = !element_order
|
|
240
|
+
end
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
derived_options[:ignore_attribute_order] = given_options[:ignore_attribute_order] if given_options.has_key? :ignore_attribute_order
|
|
243
|
+
derived_options[:ignore_child_node_order] = given_options[:ignore_child_node_order] if given_options.has_key? :ignore_child_node_order
|
|
244
244
|
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
default_options.merge derived_options
|
|
246
|
+
end
|
|
247
247
|
|
|
248
|
-
|
|
248
|
+
def self.one_line_ s
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
s = s.to_s.gsub(/\s+/, ' ')
|
|
251
|
+
end
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
253
|
+
#
|
|
254
|
+
# +:debug+
|
|
255
|
+
# +:element_order+
|
|
256
|
+
# +:equate_nil_and_empty+
|
|
257
|
+
# +:ignore_attributes+
|
|
258
|
+
# +:ignore_attribute_order+
|
|
259
|
+
# +:ignore_xml_declarations+
|
|
260
|
+
# +:normalise_whitespace+
|
|
261
|
+
# +:normalize_whitespace+
|
|
262
|
+
# +:validate_params+
|
|
263
|
+
#
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
def self.xml_compare_ lhs, rhs, options
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
$stderr.puts "#{self}#{__method__}(lhs (#{lhs.class})=#{self.one_line_ lhs}, rhs (#{rhs.class})=#{self.one_line_ rhs}, options (#{options.class})=#{options})" if $DEBUG
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
# validate parameter(s)
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
check_parameter options, 'options', type: ::Hash if $DEBUG
|
|
272
272
|
|
|
273
|
-
|
|
273
|
+
validate_params = $DEBUG || options[:debug] || options[:validate_params]
|
|
274
274
|
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
check_parameter lhs, 'lhs', types: [ ::String, ::Nokogiri::XML::Node ], allow_nil: true if validate_params
|
|
276
|
+
check_parameter rhs, 'rhs', types: [ ::String, ::Nokogiri::XML::Node ], allow_nil: true if validate_params
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
options = self.derive_options_ options
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
# deal with nil(s)
|
|
281
281
|
|
|
282
|
-
|
|
282
|
+
return Result.same if lhs.nil? && rhs.nil?
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
if lhs.nil?
|
|
285
285
|
|
|
286
|
-
|
|
286
|
+
return Result.same if options[:equate_nil_and_empty] && ::String === rhs && rhs.empty?
|
|
287
287
|
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
return Result.different :parameter_is_nil
|
|
289
|
+
end
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
if rhs.nil?
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
return Result.same if options[:equate_nil_and_empty] && ::String === lhs && lhs.empty?
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
return Result.different :parameter_is_nil
|
|
296
|
+
end
|
|
297
297
|
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
# deal with string(s)
|
|
300
300
|
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
lhs = Nokogiri::XML(lhs) if ::String === lhs
|
|
302
|
+
rhs = Nokogiri::XML(rhs) if ::String === rhs
|
|
303
303
|
|
|
304
304
|
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
# deal with XML Declaration(s)
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
if options[:ignore_xml_declarations]
|
|
309
309
|
|
|
310
|
-
|
|
310
|
+
if ::Nokogiri::XML::Document === lhs
|
|
311
311
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
lhs_root = lhs.root
|
|
313
|
+
lhs = lhs_root if lhs_root
|
|
314
|
+
end
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
if ::Nokogiri::XML::Document === rhs
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
318
|
+
rhs_root = rhs.root
|
|
319
|
+
rhs = rhs_root if rhs_root
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
322
|
|
|
323
323
|
|
|
324
|
-
|
|
325
|
-
|
|
324
|
+
self.xml_compare_nodes_ lhs, rhs, options
|
|
325
|
+
end
|
|
326
326
|
|
|
327
|
-
|
|
327
|
+
def self.xml_compare_nodes_ lhs, rhs, options
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
$stderr.puts "#{self}#{__method__}(lhs (#{lhs.class})=#{self.one_line_ lhs}, rhs (#{rhs.class})=#{self.one_line_ rhs}, options (#{options.class})=#{options})" if $DEBUG
|
|
330
330
|
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
332
|
+
# Compare:
|
|
333
|
+
#
|
|
334
|
+
# - name
|
|
335
|
+
# - attributes
|
|
336
|
+
# - content
|
|
337
|
+
# - children
|
|
338
|
+
# -
|
|
339
339
|
|
|
340
340
|
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
# ##########################
|
|
342
|
+
# name
|
|
343
343
|
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
lhs_name = lhs.name
|
|
345
|
+
rhs_name = rhs.name
|
|
346
346
|
|
|
347
|
-
|
|
347
|
+
return Result.different :different_node_names, lhs_node: lhs, rhs_node: rhs if lhs_name != rhs_name
|
|
348
348
|
|
|
349
349
|
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
# ##########################
|
|
351
|
+
# attributes
|
|
352
352
|
|
|
353
|
-
|
|
353
|
+
unless options[:ignore_attributes]
|
|
354
354
|
|
|
355
|
-
|
|
356
|
-
|
|
355
|
+
lhs_attributes = lhs.attribute_nodes
|
|
356
|
+
rhs_attributes = rhs.attribute_nodes
|
|
357
357
|
|
|
358
|
-
|
|
358
|
+
return Result.different :different_attribute_count, lhs_node: lhs, rhs_node: rhs if lhs_attributes.count != rhs_attributes.count
|
|
359
359
|
|
|
360
360
|
|
|
361
|
-
|
|
362
|
-
|
|
361
|
+
lhs_attr_list = lhs_attributes.map { |attr| [ attr.name, attr.content ] }
|
|
362
|
+
rhs_attr_list = rhs_attributes.map { |attr| [ attr.name, attr.content ] }
|
|
363
363
|
|
|
364
|
-
|
|
364
|
+
if lhs_attr_list != rhs_attr_list
|
|
365
365
|
|
|
366
|
-
|
|
366
|
+
# do the sort first
|
|
367
367
|
|
|
368
|
-
|
|
369
|
-
|
|
368
|
+
lhs_attr_list.sort! { |l, r| l[0] <=> r[0] }
|
|
369
|
+
rhs_attr_list.sort! { |l, r| l[0] <=> r[0] }
|
|
370
370
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
371
|
+
# Now there are four possibiliies:
|
|
372
|
+
#
|
|
373
|
+
# 1. Different attributes
|
|
374
|
+
# 2. Different attribute order
|
|
375
|
+
# 3. Same (when reordered)
|
|
376
376
|
|
|
377
|
-
|
|
377
|
+
if lhs_attr_list == rhs_attr_list
|
|
378
378
|
|
|
379
|
-
|
|
379
|
+
if options[:ignore_attribute_order]
|
|
380
380
|
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
# 3
|
|
382
|
+
else
|
|
383
383
|
|
|
384
|
-
|
|
384
|
+
# 2
|
|
385
385
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
386
|
+
return Result.different :different_attribute_order, lhs_node: lhs, rhs_node: rhs
|
|
387
|
+
end
|
|
388
|
+
else
|
|
389
389
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
return Result.different :different_attributes, lhs_node: lhs, rhs_node: rhs
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
394
|
|
|
395
|
-
|
|
396
|
-
|
|
395
|
+
# ##########################
|
|
396
|
+
# content
|
|
397
397
|
|
|
398
|
-
|
|
398
|
+
unless options[:ignore_content]
|
|
399
399
|
|
|
400
|
-
|
|
401
|
-
|
|
400
|
+
lhs_texts = self.get_descendants(lhs).select { |el| el.text? }.map { |el| el.content }
|
|
401
|
+
rhs_texts = self.get_descendants(rhs).select { |el| el.text? }.map { |el| el.content }
|
|
402
402
|
|
|
403
|
-
|
|
403
|
+
content_same = lhs_texts == rhs_texts
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
unless content_same
|
|
406
406
|
|
|
407
|
-
|
|
407
|
+
if options[:normalise_whitespace]
|
|
408
408
|
|
|
409
|
-
|
|
410
|
-
|
|
409
|
+
lhs_texts = lhs_texts.reject { |s| s.strip.empty? }
|
|
410
|
+
rhs_texts = rhs_texts.reject { |s| s.strip.empty? }
|
|
411
411
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
412
|
+
content_same = lhs_texts == rhs_texts
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
415
|
|
|
416
|
-
|
|
416
|
+
unless content_same
|
|
417
417
|
|
|
418
|
-
|
|
418
|
+
if options[:ignore_content_case]
|
|
419
419
|
|
|
420
|
-
|
|
421
|
-
|
|
420
|
+
lhs_texts = lhs_texts.reject { |s| s.downcase }
|
|
421
|
+
rhs_texts = rhs_texts.reject { |s| s.downcase }
|
|
422
422
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
423
|
+
content_same = lhs_texts == rhs_texts
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
unless content_same
|
|
428
428
|
|
|
429
|
-
|
|
429
|
+
if options[:ignore_child_node_order]
|
|
430
430
|
|
|
431
|
-
|
|
432
|
-
|
|
431
|
+
lhs_texts = lhs_texts.sort
|
|
432
|
+
rhs_texts = rhs_texts.sort
|
|
433
433
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
434
|
+
content_same = lhs_texts == rhs_texts
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
437
|
|
|
438
|
-
|
|
439
|
-
|
|
438
|
+
return Result.different :different_node_contents, lhs_node: lhs, rhs_node: rhs unless content_same
|
|
439
|
+
end
|
|
440
440
|
|
|
441
441
|
|
|
442
|
-
|
|
443
|
-
|
|
442
|
+
# ##########################
|
|
443
|
+
# children (preparation)
|
|
444
444
|
|
|
445
|
-
|
|
446
|
-
|
|
445
|
+
lhs_children = lhs.children.to_a
|
|
446
|
+
rhs_children = rhs.children.to_a
|
|
447
447
|
|
|
448
|
-
|
|
449
|
-
|
|
448
|
+
lhs_children.reject! { |child| child.text? && child.content.strip.empty? }
|
|
449
|
+
rhs_children.reject! { |child| child.text? && child.content.strip.empty? }
|
|
450
450
|
|
|
451
451
|
|
|
452
|
-
|
|
453
|
-
|
|
452
|
+
# ##########################
|
|
453
|
+
# children - count
|
|
454
454
|
|
|
455
|
-
|
|
456
|
-
|
|
455
|
+
lhs_children_count = lhs_children.count
|
|
456
|
+
rhs_children_count = rhs_children.count
|
|
457
457
|
|
|
458
|
-
|
|
458
|
+
return Result.different :different_child_node_count, lhs_node: lhs, rhs_node: rhs if lhs_children_count != rhs_children_count
|
|
459
459
|
|
|
460
460
|
|
|
461
|
-
|
|
462
|
-
|
|
461
|
+
# ##########################
|
|
462
|
+
# children - names
|
|
463
463
|
|
|
464
|
-
|
|
465
|
-
|
|
464
|
+
lhs_children_names = lhs_children.map { |ch| ch.name }
|
|
465
|
+
rhs_children_names = rhs_children.map { |ch| ch.name }
|
|
466
466
|
|
|
467
|
-
|
|
467
|
+
if lhs_children_names != rhs_children_names
|
|
468
468
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
469
|
+
# At this point, the lists of names of child elements are
|
|
470
|
+
# different. This may be because there are different
|
|
471
|
+
# elements or because they are in a different order. Either
|
|
472
|
+
# way, in order to provide detailed reasons for
|
|
473
|
+
# inequivalency, we must do an order-independent comparison
|
|
474
474
|
|
|
475
|
-
|
|
476
|
-
|
|
475
|
+
children_sorted_lhs = lhs_children.sort { |x, y| x.name <=> y.name }
|
|
476
|
+
children_sorted_rhs = rhs_children.sort { |x, y| x.name <=> y.name }
|
|
477
477
|
|
|
478
|
-
|
|
479
|
-
|
|
478
|
+
ch_names_sorted_lhs = children_sorted_lhs.map { |ch| ch.name }
|
|
479
|
+
ch_names_sorted_rhs = children_sorted_rhs.map { |ch| ch.name }
|
|
480
480
|
|
|
481
|
-
|
|
481
|
+
ignore_order = options[:ignore_child_node_order]
|
|
482
482
|
|
|
483
|
-
|
|
483
|
+
if ignore_order
|
|
484
484
|
|
|
485
|
-
|
|
485
|
+
return Result.different :different_child_nodes, lhs_node: lhs, rhs_node: rhs if ch_names_sorted_lhs != ch_names_sorted_rhs
|
|
486
486
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
487
|
+
# Since they are the same (when reordered), we need to
|
|
488
|
+
# adopt the ordered sequences so that the comparison of
|
|
489
|
+
# the children are meaningful
|
|
490
490
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
491
|
+
lhs_children = children_sorted_lhs
|
|
492
|
+
rhs_children = children_sorted_rhs
|
|
493
|
+
else
|
|
494
494
|
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
# failed, so need to determine whether it's due to
|
|
496
|
+
# different nodes or different order
|
|
497
497
|
|
|
498
|
-
|
|
498
|
+
if ch_names_sorted_lhs == ch_names_sorted_rhs
|
|
499
499
|
|
|
500
|
-
|
|
501
|
-
|
|
500
|
+
return Result.different :different_child_node_order, lhs_node: lhs, rhs_node: rhs
|
|
501
|
+
else
|
|
502
502
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
503
|
+
return Result.different :different_child_nodes, lhs_node: lhs, rhs_node: rhs
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
507
|
|
|
508
|
-
|
|
508
|
+
(0 ... lhs_children.count).each do |index|
|
|
509
509
|
|
|
510
|
-
|
|
511
|
-
|
|
510
|
+
ch_lhs = lhs_children[index]
|
|
511
|
+
ch_rhs = rhs_children[index]
|
|
512
512
|
|
|
513
|
-
|
|
513
|
+
r = self.xml_compare_nodes_ ch_lhs, ch_rhs, options
|
|
514
514
|
|
|
515
|
-
|
|
516
|
-
|
|
515
|
+
return r unless r.status
|
|
516
|
+
end
|
|
517
517
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
518
|
+
return Result.same
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
521
|
|
|
522
|
-
|
|
522
|
+
def self.xml_compare lhs, rhs, **options
|
|
523
523
|
|
|
524
|
-
|
|
524
|
+
$stderr.puts "#{self}#{__method__}(lhs (#{lhs.class})=#{Internal_Compare_.one_line_ lhs}, rhs (#{rhs.class})=#{Internal_Compare_.one_line_ rhs}, options (#{options.class})=#{options})" if $DEBUG
|
|
525
525
|
|
|
526
|
-
|
|
527
|
-
|
|
526
|
+
Internal_Compare_.xml_compare_ lhs, rhs, options
|
|
527
|
+
end
|
|
528
528
|
|
|
529
|
-
|
|
529
|
+
def xml_compare lhs, rhs, **options
|
|
530
530
|
|
|
531
|
-
|
|
531
|
+
$stderr.puts "#{self}#{__method__}(lhs (#{lhs.class})=#{Internal_Compare_.one_line_ lhs}, rhs (#{rhs.class})=#{Internal_Compare_.one_line_ rhs}, options (#{options.class})=#{options})" if $DEBUG
|
|
532
532
|
|
|
533
|
-
|
|
534
|
-
|
|
533
|
+
Internal_Compare_.xml_compare_ lhs, rhs, options
|
|
534
|
+
end
|
|
535
535
|
|
|
536
536
|
end # module Compare
|
|
537
537
|
|