xqsr3-xml 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1995665f234ee13f105bf6316a5ff88c91d49a22
4
+ data.tar.gz: f75620b304904ff142f92ef8576cc32b7ce1bba1
5
+ SHA512:
6
+ metadata.gz: 9719a1c7e803abf401646f4934d06231ff06cb0f371921d1cae3e442fd7408d7197e0ea3bafa03524f230a71638ff7f2ded0beabdfb449befe7e31c3aff0ed76
7
+ data.tar.gz: 9fad44e02ea4784441a2e170f476e7f593799339354ab05d7cb861b89c355b8d1a376c7b3b345457a1c69f408e6c10b146da60e90107140abbad9fc39d82e01f
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ xqsr3-xml
2
+
3
+ Copyright (c) 2005-2016, Matthew Wilson and Synesis Software
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the names of xqsr3 and xqsr3-xml nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # xqsr3-xml
2
+ e**X**tensions by fine **Q**uantum for **S**tandard **R**uby and **3**rd-party libraries, for **XML**
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/xqsr3-xml.svg)](https://badge.fury.io/rb/xqsr3-xml)
5
+
6
+ ## Introduction
7
+
8
+ [**xqsr3**](https://github.com/synesissoftware/xqsr3/) is a lightweight, low-coupling library of assorted extensions to standard ruby and 3rd-party libraries. As of version **0.31** of **xqsr3**, the **XML Utilites** components were moved out into **xqsr3-xml** in order that the core **xqsr** can be independent of any non-standard libraries.
9
+
10
+ ## Table of Contents
11
+
12
+ 1. [Introduction](#introduction)
13
+ 2. [Installation](#installation)
14
+ 3. [Components](#components)
15
+ 4. [Project Information](#project-information)
16
+
17
+ ## Installation
18
+
19
+ Install using `gem install xqsr3-xml` or add it to your `Gemfile`.
20
+
21
+ ## Components
22
+
23
+ **xqsr3** provides components in the following categories:
24
+
25
+ * Array Utilities
26
+ * Command-line Utilities
27
+ * Containers
28
+ * Conversion
29
+ * Diagnostics
30
+ * Hash Utilities
31
+ * IO
32
+ * Quality
33
+ * String Utilities
34
+
35
+ **xqsr3-xml** provides components in the following categories:
36
+
37
+ * XML Utilities
38
+
39
+ ## Project Information
40
+
41
+ ### Where to get help
42
+
43
+ [GitHub Page](https://github.com/synesissoftware/xqsr3-xml "GitHub Page")
44
+
45
+ ### Contribution guidelines
46
+
47
+ Defect reports, feature requests, and pull requests are welcome on https://github.com/synesissoftware/xqsr3-xml.
48
+
49
+ ### Dependencies
50
+
51
+ * [**Nokogiri**](http://nokogiri.org/)
52
+ * [**xqsr3**](https://github.com/synesissoftware/xqsr3/)
53
+
54
+ ### Related projects
55
+
56
+ * [**xqsr3**](https://github.com/synesissoftware/xqsr3/)
57
+
58
+ ### License
59
+
60
+ **xqsr3-xml** is released under the 3-clause BSD license. See [LICENSE](./LICENSE) for details.
@@ -0,0 +1,544 @@
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
+
@@ -0,0 +1,108 @@
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
+
@@ -0,0 +1,71 @@
1
+
2
+ # ######################################################################## #
3
+ # File: lib/xqsr3/xml/version.rb
4
+ #
5
+ # Purpose: Version for xqsr3-xml library
6
+ #
7
+ # Created: 1st March 2019
8
+ # Updated: 2nd March 2019
9
+ #
10
+ # Home: http://github.com/synesissoftware/xqsr3
11
+ #
12
+ # Author: Matthew Wilson
13
+ #
14
+ # Copyright (c) 2019, Matthew Wilson and Synesis Software
15
+ # All rights reserved.
16
+ #
17
+ # Redistribution and use in source and binary forms, with or without
18
+ # modification, are permitted provided that the following conditions are
19
+ # met:
20
+ #
21
+ # * Redistributions of source code must retain the above copyright
22
+ # notice, this list of conditions and the following disclaimer.
23
+ #
24
+ # * Redistributions in binary form must reproduce the above copyright
25
+ # notice, this list of conditions and the following disclaimer in the
26
+ # documentation and/or other materials provided with the distribution.
27
+ #
28
+ # * Neither the names of the copyright holder nor the names of its
29
+ # contributors may be used to endorse or promote products derived from
30
+ # this software without specific prior written permission.
31
+ #
32
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
33
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
34
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
35
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
36
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
37
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
38
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
39
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
40
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
41
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
42
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43
+ #
44
+ # ######################################################################## #
45
+
46
+
47
+ =begin
48
+ =end
49
+
50
+ module Xqsr3
51
+ module XML
52
+
53
+ # Current version of the Xqsr3 library
54
+ VERSION = '0.1.0.1'
55
+
56
+ private
57
+ VERSION_PARTS_ = VERSION.split(/[.]/).collect { |n| n.to_i } # :nodoc:
58
+ public
59
+ # Major version of the Xqsr3 library
60
+ VERSION_MAJOR = VERSION_PARTS_[0] # :nodoc:
61
+ # Minor version of the Xqsr3 library
62
+ VERSION_MINOR = VERSION_PARTS_[1] # :nodoc:
63
+ # Revision version of the Xqsr3 library
64
+ VERSION_REVISION = VERSION_PARTS_[2] # :nodoc:
65
+
66
+ end # module XML
67
+ end # module Xqsr3
68
+
69
+ # ############################## end of file ############################# #
70
+
71
+
@@ -0,0 +1,36 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), '../../lib')
4
+
5
+ require 'xqsr3/xml/version'
6
+
7
+ require 'test/unit'
8
+
9
+ class Test_version < Test::Unit::TestCase
10
+
11
+ def test_has_VERSION
12
+
13
+ assert defined? Xqsr3::XML::VERSION
14
+ end
15
+
16
+ def test_has_VERSION_MAJOR
17
+
18
+ assert defined? Xqsr3::XML::VERSION_MAJOR
19
+ end
20
+
21
+ def test_has_VERSION_MINOR
22
+
23
+ assert defined? Xqsr3::XML::VERSION_MINOR
24
+ end
25
+
26
+ def test_has_VERSION_REVISION
27
+
28
+ assert defined? Xqsr3::XML::VERSION_REVISION
29
+ end
30
+
31
+ def test_VERSION_has_consistent_format
32
+
33
+ assert_equal Xqsr3::XML::VERSION, "#{Xqsr3::XML::VERSION_MAJOR}.#{Xqsr3::XML::VERSION_MINOR}.#{Xqsr3::XML::VERSION_REVISION}"
34
+ end
35
+ end
36
+
@@ -0,0 +1,12 @@
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
+
@@ -0,0 +1,12 @@
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
+
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *(['..'] * 4), 'lib')
4
+
5
+ require 'xqsr3/xml/utilities/compare'
6
+
7
+ require 'xqsr3/extensions/test/unit'
8
+ require 'test/unit'
9
+
10
+ class Test_Xqsr3_XML_Utilities_Compare < Test::Unit::TestCase
11
+
12
+ include ::Xqsr3::XML::Utilities::Compare
13
+
14
+ def test_compare_nil
15
+
16
+ assert xml_compare(nil, nil).same?
17
+
18
+ assert_false xml_compare('', nil).same?
19
+ assert_false xml_compare(nil, '').same?
20
+
21
+ assert xml_compare('', nil, equate_nil_and_empty: true).same?
22
+ assert xml_compare(nil, '', equate_nil_and_empty: true).same?
23
+ end
24
+
25
+ def test_compare_empty
26
+
27
+ assert xml_compare('', '').same?
28
+
29
+ assert_false xml_compare('<abc/>', '').same?
30
+ assert_false xml_compare('', '<abc/>').same?
31
+ end
32
+
33
+ def test_compare_one_level_1
34
+
35
+ assert xml_compare('<abc/>', '<abc/>').same?
36
+ assert xml_compare('<abc/>', '<abc></abc>').same?
37
+ assert_false xml_compare('<abc/>', '<def/>').same?
38
+ end
39
+
40
+ def test_compare_two_level_1
41
+
42
+ assert xml_compare('<parent><child1></child1></parent>', '<parent><child1></child1></parent>').same?
43
+ assert xml_compare('<parent><child1/></parent>', '<parent><child1></child1></parent>').same?
44
+
45
+ r = xml_compare('<parent><child1/></parent>', '<parent><child2/></parent>')
46
+
47
+ assert_false r.same?
48
+ end
49
+
50
+ def test_compare_attributes_1
51
+
52
+ lhs = <<END_OF_lhs
53
+ <node name="John Smith" age="21" />
54
+ END_OF_lhs
55
+
56
+ rhs_same = <<END_OF_lhs
57
+ <node age="21" name="John Smith" />
58
+ END_OF_lhs
59
+
60
+ rhs_diff = <<END_OF_lhs
61
+ <node name="John Smith" age="22" />
62
+ END_OF_lhs
63
+
64
+ r = xml_compare lhs, rhs_same, ignore_attribute_order: false
65
+
66
+ assert r.different?, r.details
67
+ assert_equal :different_attribute_order, r.reason
68
+
69
+ r = xml_compare lhs, rhs_same, ignore_attribute_order: true
70
+
71
+ assert r.same?
72
+
73
+ r = xml_compare lhs, rhs_same, element_order: false
74
+
75
+ assert r.same?
76
+
77
+ r = xml_compare lhs, rhs_diff
78
+
79
+ assert r.different?
80
+ assert_equal :different_attributes, r.reason
81
+ end
82
+
83
+ def test_compare_two_level_2
84
+
85
+ lhs = <<END_OF_lhs
86
+ <parent>
87
+ <child1/>
88
+ </parent>
89
+ END_OF_lhs
90
+ rhs = <<END_OF_rhs
91
+ <parent>
92
+ <child1>
93
+ </child1>
94
+ </parent>
95
+ END_OF_rhs
96
+
97
+ r = xml_compare lhs, rhs, normalize_whitespace: false
98
+
99
+ assert r.different?, "#{r.details}"
100
+ assert_equal :different_node_contents, r.reason
101
+
102
+ r = xml_compare(lhs, rhs, normalize_whitespace: true)
103
+
104
+ assert r.same?, "#{r.details}"
105
+ end
106
+
107
+ def test_compare_two_level_3
108
+
109
+ lhs = <<END_OF_lhs
110
+ <parent>
111
+ <child1/>
112
+ <child2>
113
+ <grandchild2a/>
114
+ </child2>
115
+ </parent>
116
+ END_OF_lhs
117
+ rhs = <<END_OF_rhs
118
+ <parent>
119
+ <child2><grandchild2a/></child2>
120
+ <child1>
121
+ </child1>
122
+ </parent>
123
+ END_OF_rhs
124
+
125
+ r = xml_compare lhs, rhs, normalize_whitespace: true
126
+
127
+ assert r.same?, "#{r.details}"
128
+ end
129
+
130
+ def test_different_declarations_and_dont_ignore
131
+
132
+ lhs_str = <<END_OF_lhs_doc
133
+ <?xml version="1.0"?>
134
+ <outer>
135
+ <mid>
136
+ <inner>some text</inner>
137
+ </mid>
138
+ </outer>
139
+ END_OF_lhs_doc
140
+
141
+ rhs_str = <<END_OF_rhs_doc
142
+ <?xml version="1.0"?>
143
+ <mid>
144
+ <inner>some text</inner>
145
+ </mid>
146
+ END_OF_rhs_doc
147
+
148
+ lhs_doc = Nokogiri::XML lhs_str
149
+ rhs_doc = Nokogiri::XML rhs_str
150
+
151
+ expected = rhs_doc
152
+ actual = lhs_doc.at_xpath('/outer/mid')
153
+
154
+ r = xml_compare expected, actual, normalise_whitespace: true, ignore_xml_declarations: false
155
+
156
+ assert !r.same?
157
+ end
158
+
159
+ def test_different_declarations_and_do_ignore
160
+
161
+ lhs_str = <<END_OF_lhs_doc
162
+ <?xml version="1.0"?>
163
+ <outer>
164
+ <mid>
165
+ <inner>some text</inner>
166
+ </mid>
167
+ </outer>
168
+ END_OF_lhs_doc
169
+
170
+ rhs_str = <<END_OF_rhs_doc
171
+ <?xml version="1.0"?>
172
+ <mid>
173
+ <inner>some text</inner>
174
+ </mid>
175
+ END_OF_rhs_doc
176
+
177
+ lhs_doc = Nokogiri::XML lhs_str
178
+ rhs_doc = Nokogiri::XML rhs_str
179
+
180
+ expected = rhs_doc
181
+ actual = lhs_doc.at_xpath('/outer/mid')
182
+
183
+ r = xml_compare expected, actual, normalise_whitespace: true, ignore_xml_declarations: true
184
+
185
+ assert r.same?, "#{r.details}"
186
+ end
187
+
188
+ def test_different_node_contents_by_child_node_order
189
+
190
+ lhs_str = <<END_OF_lhs_doc
191
+ <?xml version="1.0"?>
192
+ <outer>
193
+ <mid_1>
194
+ <inner>some text</inner>
195
+ </mid_1>
196
+ <mid_2>
197
+ <inner>some more text</inner>
198
+ </mid_2>
199
+ </outer>
200
+ END_OF_lhs_doc
201
+
202
+ rhs_str = <<END_OF_rhs_doc
203
+ <?xml version="1.0"?>
204
+ <outer>
205
+ <mid_2>
206
+ <inner>some more text</inner>
207
+ </mid_2>
208
+ <mid_1>
209
+ <inner>some text</inner>
210
+ </mid_1>
211
+ </outer>
212
+ END_OF_rhs_doc
213
+
214
+ lhs_doc = Nokogiri::XML lhs_str
215
+ rhs_doc = Nokogiri::XML rhs_str
216
+
217
+ expected = rhs_doc
218
+ actual = lhs_doc
219
+
220
+ r = xml_compare expected, actual, ignore_child_node_order: true, normalise_whitespace: true, ignore_xml_declarations: true
221
+
222
+ assert r.same?, "#{r.details}"
223
+ end
224
+
225
+ def test_different_node_contents_by_child_node_order_and_whitespace
226
+
227
+ lhs_str = <<END_OF_lhs_doc
228
+ <?xml version="1.0"?>
229
+ <outer><mid_1><inner>some text</inner></mid_1><mid_2><inner>some more text</inner></mid_2></outer>
230
+ END_OF_lhs_doc
231
+
232
+ rhs_str = <<END_OF_rhs_doc
233
+ <?xml version="1.0"?>
234
+ <outer>
235
+ <mid_2>
236
+ <inner>some more text</inner>
237
+ </mid_2>
238
+ <mid_1>
239
+ <inner>some text</inner>
240
+ </mid_1>
241
+ </outer>
242
+ END_OF_rhs_doc
243
+
244
+ lhs_doc = Nokogiri::XML lhs_str
245
+ rhs_doc = Nokogiri::XML rhs_str
246
+
247
+ expected = rhs_doc
248
+ actual = lhs_doc
249
+
250
+ r = xml_compare expected, actual, ignore_child_node_order: true, normalise_whitespace: true, ignore_xml_declarations: true
251
+
252
+ assert r.same?, "#{r.details}"
253
+ end
254
+ end
255
+
256
+
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *(['..'] * 4), 'lib')
4
+
5
+ require 'xqsr3/xml/utilities/navigation'
6
+
7
+ require 'xqsr3/extensions/test/unit'
8
+ require 'test/unit'
9
+
10
+ require 'nokogiri'
11
+
12
+ class Test_Xqsr3_XML_Utilities_Navigation < Test::Unit::TestCase
13
+
14
+ include ::Xqsr3::XML::Utilities::Navigation
15
+
16
+ def test_get_descendants_1
17
+
18
+ xml_s = <<END_OF_rhs_doc
19
+ <?xml version="1.0"?>
20
+ <document>
21
+ <outer>
22
+ <mid_2>
23
+ <inner>some more text</inner>
24
+ </mid_2>
25
+ <mid_1>
26
+ <inner>some text</inner>
27
+ </mid_1>
28
+ </outer>
29
+ </document>
30
+ END_OF_rhs_doc
31
+
32
+ xml = ::Nokogiri.XML(xml_s)
33
+ doc = xml.children.first
34
+
35
+ descs = self.class.get_descendants xml.children.first
36
+
37
+ assert_not_nil descs
38
+ assert_kind_of ::Array, descs
39
+
40
+ assert_operator 7, :<=, descs.size
41
+ %w{ outer mid_1 mid_2 }.each do |name|
42
+
43
+ assert(descs.find { |el| name == el.name }, "did not find an element named '#{name}' in the collection #{descs}")
44
+ end
45
+
46
+ texts = descs.select { |el| el.text? }
47
+
48
+ [ 'some text', 'some more text' ].each do |text|
49
+
50
+ assert(texts.find { |el| text == el.text }, "did not find an element with the text '#{text}' in the collection #{texts}")
51
+ end
52
+ end
53
+ end
54
+
55
+
@@ -0,0 +1,12 @@
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
+
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xqsr3-xml
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Wilson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: xqsr3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.31.0
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: '1.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.31.0
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: nokogiri
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: '1.6'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '1.6'
47
+ description: |
48
+ xqsr3 - eXtensions by fine Quantum for Standard Ruby and 3rd-party libraries
49
+ - is a lightweight, low-coupling library of assorted extensions to standard
50
+ ruby and 3rd-party libraries.
51
+
52
+ xqsr3-xml contains the XML-related components for xqsr3, so that the core
53
+ library remains independent of any non-standard libraries.
54
+ email: matthew@synesis.com.au
55
+ executables: []
56
+ extensions: []
57
+ extra_rdoc_files: []
58
+ files:
59
+ - lib/xqsr3/xml/utilities/compare.rb
60
+ - lib/xqsr3/xml/utilities/navigation.rb
61
+ - lib/xqsr3/xml/version.rb
62
+ - test/unit/tc_version.rb
63
+ - test/unit/ts_all.rb
64
+ - test/unit/xml/ts_all.rb
65
+ - test/unit/xml/utilities/tc_compare.rb
66
+ - test/unit/xml/utilities/tc_navigation.rb
67
+ - test/unit/xml/utilities/ts_all.rb
68
+ - README.md
69
+ - LICENSE
70
+ homepage: http://github.com/synesissoftware/xqsr3-xml
71
+ licenses:
72
+ - BSD-3-Clause
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.0.14.1
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: xqsr3-xml
94
+ test_files: []