xqsr3 0.30.3 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,544 +0,0 @@
1
-
2
- # ######################################################################## #
3
- # File: lib/xqsr3/xml/_utilities/compare.rb
4
- #
5
- # Purpose: Definition of the ::Xqsr3::XML::Utilities::Compare
6
- # module
7
- #
8
- # Created: 30th July 2017
9
- # Updated: 16th August 2018
10
- #
11
- # Home: http://github.com/synesissoftware/xqsr3
12
- #
13
- # Author: Matthew Wilson
14
- #
15
- # Copyright (c) 2017-2018, Matthew Wilson and Synesis Software
16
- # All rights reserved.
17
- #
18
- # Redistribution and use in source and binary forms, with or without
19
- # modification, are permitted provided that the following conditions are
20
- # met:
21
- #
22
- # * Redistributions of source code must retain the above copyright notice,
23
- # this list of conditions and the following disclaimer.
24
- #
25
- # * Redistributions in binary form must reproduce the above copyright
26
- # notice, this list of conditions and the following disclaimer in the
27
- # documentation and/or other materials provided with the distribution.
28
- #
29
- # * Neither the names of the copyright holder nor the names of its
30
- # contributors may be used to endorse or promote products derived from
31
- # this software without specific prior written permission.
32
- #
33
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
34
- # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
35
- # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
36
- # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
37
- # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
38
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
39
- # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
40
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
41
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
42
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
43
- # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44
- #
45
- # ######################################################################## #
46
-
47
-
48
- # ##########################################################
49
- # ::Xqsr3::XML::Utilities::Compare
50
-
51
- =begin
52
- =end
53
-
54
- require 'xqsr3/xml/utilities/navigation'
55
-
56
- require 'xqsr3/quality/parameter_checking'
57
-
58
- require 'nokogiri'
59
-
60
- module Xqsr3
61
- module XML
62
- module Utilities
63
-
64
- module Compare
65
-
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
-
73
- include ::Xqsr3::Quality::ParameterChecking
74
-
75
- protected :check_parameter
76
-
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
-
92
- def initialize status, reason, **options
93
-
94
- @call_stack = caller(2)
95
-
96
- check_parameter status, 'status', types: [ ::FalseClass, ::TrueClass ]
97
- check_parameter reason, 'reason', type: ::Symbol, allow_nil: true
98
-
99
- @status = status
100
- @reason = reason
101
-
102
- @lhs_node = options[:lhs_node]
103
- @rhs_node = options[:rhs_node]
104
- end
105
-
106
- def self.return status, reason, **options
107
-
108
- return self.new status, reason, **options
109
- end
110
-
111
- def self.same reason = nil, **options
112
-
113
- return self.new true, reason, **options
114
- end
115
-
116
- def self.different reason, **options
117
-
118
- return self.new false, reason, **options
119
- end
120
-
121
- attr_reader :call_stack
122
- attr_reader :status
123
- attr_reader :reason
124
-
125
- def different?
126
-
127
- !status
128
- end
129
-
130
- def same?
131
-
132
- status
133
- end
134
-
135
- def details
136
-
137
- r = reason.to_s.gsub(/_/, ' ')
138
-
139
- qualifying = ''
140
-
141
- if @lhs_node
142
-
143
- qualifying += '; ' unless qualifying.empty?
144
- qualifying += "lhs-node=#{@lhs_node}"
145
- end
146
-
147
- if @rhs_node
148
-
149
- qualifying += '; ' unless qualifying.empty?
150
- qualifying += "rhs-node=#{@rhs_node}"
151
- end
152
-
153
- r = "#{r}: #{qualifying}" unless qualifying.empty?
154
-
155
- r
156
- end
157
-
158
- def to_s
159
-
160
- return 'same' if same?
161
-
162
- "different, because: #{details}"
163
- end
164
- end
165
-
166
- module Internal_Compare_
167
-
168
- include ::Xqsr3::XML::Utilities::Navigation
169
-
170
- extend ::Xqsr3::Quality::ParameterChecking
171
-
172
- DEFAULT_OPTIONS = {
173
-
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
-
188
- ORDER_OPTIONS_SYMBOLS = [
189
-
190
- :element_order,
191
- :ignore_attribute_order,
192
- :ignore_child_node_order,
193
- :ignore_content,
194
- :ignore_content_case,
195
- ]
196
-
197
- WHITESPACE_OPTIONS_SYMBOLS = [
198
-
199
- :normalise_whitespace,
200
- :normalize_whitespace,
201
- ]
202
-
203
- def self.derive_options_ given_options
204
-
205
- default_options = DEFAULT_OPTIONS
206
- derived_options = {}.merge given_options
207
-
208
-
209
- # sort whitespace
210
-
211
- if WHITESPACE_OPTIONS_SYMBOLS.any? { |sym| given_options.has_key? sym }
212
-
213
- default_options = default_options.reject { |k, v| WHITESPACE_OPTIONS_SYMBOLS.include? k }
214
- end
215
-
216
- if given_options.has_key? :normalise_whitespace
217
-
218
- derived_options.delete :normalize_whitespace
219
- elsif given_options.has_key? :normalize_whitespace
220
-
221
- derived_options[:normalise_whitespace] = given_options[:normalize_whitespace]
222
-
223
- derived_options.delete :normalize_whitespace
224
- end
225
-
226
-
227
- # sort element-order
228
-
229
- if ORDER_OPTIONS_SYMBOLS.any? { |sym| given_options.has_key? sym }
230
-
231
- default_options = default_options.reject { |k, v| ORDER_OPTIONS_SYMBOLS.include? k }
232
- end
233
-
234
- if given_options.has_key? :element_order
235
-
236
- element_order = given_options[:element_order]
237
-
238
- derived_options[:ignore_attribute_order] = !element_order
239
- derived_options[:ignore_child_node_order] = !element_order
240
- end
241
-
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
-
245
- default_options.merge derived_options
246
- end
247
-
248
- def self.one_line_ s
249
-
250
- s = s.to_s.gsub(/\s+/, ' ')
251
- end
252
-
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
-
265
- def self.xml_compare_ lhs, rhs, options
266
-
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
-
269
- # validate parameter(s)
270
-
271
- check_parameter options, 'options', type: ::Hash if $DEBUG
272
-
273
- validate_params = $DEBUG || options[:debug] || options[:validate_params]
274
-
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
-
278
- options = self.derive_options_ options
279
-
280
- # deal with nil(s)
281
-
282
- return Result.same if lhs.nil? && rhs.nil?
283
-
284
- if lhs.nil?
285
-
286
- return Result.same if options[:equate_nil_and_empty] && ::String === rhs && rhs.empty?
287
-
288
- return Result.different :parameter_is_nil
289
- end
290
-
291
- if rhs.nil?
292
-
293
- return Result.same if options[:equate_nil_and_empty] && ::String === lhs && lhs.empty?
294
-
295
- return Result.different :parameter_is_nil
296
- end
297
-
298
-
299
- # deal with string(s)
300
-
301
- lhs = Nokogiri::XML(lhs) if ::String === lhs
302
- rhs = Nokogiri::XML(rhs) if ::String === rhs
303
-
304
-
305
-
306
- # deal with XML Declaration(s)
307
-
308
- if options[:ignore_xml_declarations]
309
-
310
- if ::Nokogiri::XML::Document === lhs
311
-
312
- lhs_root = lhs.root
313
- lhs = lhs_root if lhs_root
314
- end
315
-
316
- if ::Nokogiri::XML::Document === rhs
317
-
318
- rhs_root = rhs.root
319
- rhs = rhs_root if rhs_root
320
- end
321
- end
322
-
323
-
324
- self.xml_compare_nodes_ lhs, rhs, options
325
- end
326
-
327
- def self.xml_compare_nodes_ lhs, rhs, options
328
-
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
-
331
-
332
- # Compare:
333
- #
334
- # - name
335
- # - attributes
336
- # - content
337
- # - children
338
- # -
339
-
340
-
341
- # ##########################
342
- # name
343
-
344
- lhs_name = lhs.name
345
- rhs_name = rhs.name
346
-
347
- return Result.different :different_node_names, lhs_node: lhs, rhs_node: rhs if lhs_name != rhs_name
348
-
349
-
350
- # ##########################
351
- # attributes
352
-
353
- unless options[:ignore_attributes]
354
-
355
- lhs_attributes = lhs.attribute_nodes
356
- rhs_attributes = rhs.attribute_nodes
357
-
358
- return Result.different :different_attribute_count, lhs_node: lhs, rhs_node: rhs if lhs_attributes.count != rhs_attributes.count
359
-
360
-
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
-
364
- if lhs_attr_list != rhs_attr_list
365
-
366
- # do the sort first
367
-
368
- lhs_attr_list.sort! { |l, r| l[0] <=> r[0] }
369
- rhs_attr_list.sort! { |l, r| l[0] <=> r[0] }
370
-
371
- # Now there are four possibiliies:
372
- #
373
- # 1. Different attributes
374
- # 2. Different attribute order
375
- # 3. Same (when reordered)
376
-
377
- if lhs_attr_list == rhs_attr_list
378
-
379
- if options[:ignore_attribute_order]
380
-
381
- # 3
382
- else
383
-
384
- # 2
385
-
386
- return Result.different :different_attribute_order, lhs_node: lhs, rhs_node: rhs
387
- end
388
- else
389
-
390
- return Result.different :different_attributes, lhs_node: lhs, rhs_node: rhs
391
- end
392
- end
393
- end
394
-
395
- # ##########################
396
- # content
397
-
398
- unless options[:ignore_content]
399
-
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
-
403
- content_same = lhs_texts == rhs_texts
404
-
405
- unless content_same
406
-
407
- if options[:normalise_whitespace]
408
-
409
- lhs_texts = lhs_texts.reject { |s| s.strip.empty? }
410
- rhs_texts = rhs_texts.reject { |s| s.strip.empty? }
411
-
412
- content_same = lhs_texts == rhs_texts
413
- end
414
- end
415
-
416
- unless content_same
417
-
418
- if options[:ignore_content_case]
419
-
420
- lhs_texts = lhs_texts.reject { |s| s.downcase }
421
- rhs_texts = rhs_texts.reject { |s| s.downcase }
422
-
423
- content_same = lhs_texts == rhs_texts
424
- end
425
- end
426
-
427
- unless content_same
428
-
429
- if options[:ignore_child_node_order]
430
-
431
- lhs_texts = lhs_texts.sort
432
- rhs_texts = rhs_texts.sort
433
-
434
- content_same = lhs_texts == rhs_texts
435
- end
436
- end
437
-
438
- return Result.different :different_node_contents, lhs_node: lhs, rhs_node: rhs unless content_same
439
- end
440
-
441
-
442
- # ##########################
443
- # children (preparation)
444
-
445
- lhs_children = lhs.children.to_a
446
- rhs_children = rhs.children.to_a
447
-
448
- lhs_children.reject! { |child| child.text? && child.content.strip.empty? }
449
- rhs_children.reject! { |child| child.text? && child.content.strip.empty? }
450
-
451
-
452
- # ##########################
453
- # children - count
454
-
455
- lhs_children_count = lhs_children.count
456
- rhs_children_count = rhs_children.count
457
-
458
- return Result.different :different_child_node_count, lhs_node: lhs, rhs_node: rhs if lhs_children_count != rhs_children_count
459
-
460
-
461
- # ##########################
462
- # children - names
463
-
464
- lhs_children_names = lhs_children.map { |ch| ch.name }
465
- rhs_children_names = rhs_children.map { |ch| ch.name }
466
-
467
- if lhs_children_names != rhs_children_names
468
-
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
-
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
-
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
-
481
- ignore_order = options[:ignore_child_node_order]
482
-
483
- if ignore_order
484
-
485
- return Result.different :different_child_nodes, lhs_node: lhs, rhs_node: rhs if ch_names_sorted_lhs != ch_names_sorted_rhs
486
-
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
-
491
- lhs_children = children_sorted_lhs
492
- rhs_children = children_sorted_rhs
493
- else
494
-
495
- # failed, so need to determine whether it's due to
496
- # different nodes or different order
497
-
498
- if ch_names_sorted_lhs == ch_names_sorted_rhs
499
-
500
- return Result.different :different_child_node_order, lhs_node: lhs, rhs_node: rhs
501
- else
502
-
503
- return Result.different :different_child_nodes, lhs_node: lhs, rhs_node: rhs
504
- end
505
- end
506
- end
507
-
508
- (0 ... lhs_children.count).each do |index|
509
-
510
- ch_lhs = lhs_children[index]
511
- ch_rhs = rhs_children[index]
512
-
513
- r = self.xml_compare_nodes_ ch_lhs, ch_rhs, options
514
-
515
- return r unless r.status
516
- end
517
-
518
- return Result.same
519
- end
520
- end
521
-
522
- def self.xml_compare lhs, rhs, **options
523
-
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
-
526
- Internal_Compare_.xml_compare_ lhs, rhs, options
527
- end
528
-
529
- def xml_compare lhs, rhs, **options
530
-
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
-
533
- Internal_Compare_.xml_compare_ lhs, rhs, options
534
- end
535
-
536
- end # module Compare
537
-
538
- end # module Utilities
539
- end # module XML
540
- end # module Xqsr3
541
-
542
- # ############################## end of file ############################# #
543
-
544
-
@@ -1,108 +0,0 @@
1
-
2
- # ######################################################################## #
3
- # File: lib/xqsr3/xml/_utilities/navigation.rb
4
- #
5
- # Purpose: Definition of the ::Xqsr3::XML::Utilities::Navigation
6
- # module
7
- #
8
- # Created: 7th August 2018
9
- # Updated: 7th August 2018
10
- #
11
- # Home: http://github.com/synesissoftware/xqsr3
12
- #
13
- # Author: Matthew Wilson
14
- #
15
- # Copyright (c) 2018, Matthew Wilson and Synesis Software
16
- # All rights reserved.
17
- #
18
- # Redistribution and use in source and binary forms, with or without
19
- # modification, are permitted provided that the following conditions are
20
- # met:
21
- #
22
- # * Redistributions of source code must retain the above copyright notice,
23
- # this list of conditions and the following disclaimer.
24
- #
25
- # * Redistributions in binary form must reproduce the above copyright
26
- # notice, this list of conditions and the following disclaimer in the
27
- # documentation and/or other materials provided with the distribution.
28
- #
29
- # * Neither the names of the copyright holder nor the names of its
30
- # contributors may be used to endorse or promote products derived from
31
- # this software without specific prior written permission.
32
- #
33
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
34
- # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
35
- # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
36
- # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
37
- # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
38
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
39
- # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
40
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
41
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
42
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
43
- # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44
- #
45
- # ######################################################################## #
46
-
47
-
48
- # ##########################################################
49
- # ::Xqsr3::XML::Utilities::Navigation
50
-
51
- =begin
52
- =end
53
-
54
- require 'xqsr3/quality/parameter_checking'
55
-
56
- require 'nokogiri'
57
-
58
- module Xqsr3
59
- module XML
60
- module Utilities
61
-
62
- module Navigation
63
-
64
- module Internal_Compare_
65
-
66
- extend ::Xqsr3::Quality::ParameterChecking
67
-
68
- def self.get_descendants node
69
-
70
- descendants = []
71
-
72
- node.children.each do |child|
73
-
74
- descendants << child
75
-
76
- descendants += self.get_descendants child
77
- end
78
-
79
- descendants
80
- end
81
- end # module Internal_Compare_
82
-
83
- def self.included receiver
84
-
85
- def receiver.get_descendants node
86
-
87
- Internal_Compare_.get_descendants node
88
- end
89
- end
90
-
91
- def self.get_descendants node
92
-
93
- Internal_Compare_.get_descendants node
94
- end
95
-
96
- def get_descendants
97
-
98
- Internal_Compare_.get_descendants self
99
- end
100
- end # module Navigation
101
-
102
- end # module Utilities
103
- end # module XML
104
- end # module Xqsr3
105
-
106
- # ############################## end of file ############################# #
107
-
108
-
@@ -1,12 +0,0 @@
1
- #! /usr/bin/env ruby
2
- #
3
- # executes all other tests
4
-
5
- this_dir = File.expand_path(File.dirname(__FILE__))
6
-
7
- # all tc_*rb in current directory
8
- Dir[File.join(this_dir, 'tc_*rb')].each { |file| require file }
9
-
10
- # all ts_*rb in immediate sub-directories
11
- Dir[File.join(this_dir, '*', 'ts_*rb')].each { |file| require file }
12
-