libis-tools 0.9.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +37 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +289 -0
  9. data/Rakefile +6 -0
  10. data/lib/libis-tools.rb +1 -0
  11. data/lib/libis/tools.rb +16 -0
  12. data/lib/libis/tools/assert.rb +41 -0
  13. data/lib/libis/tools/checksum.rb +84 -0
  14. data/lib/libis/tools/command.rb +40 -0
  15. data/lib/libis/tools/config.rb +160 -0
  16. data/lib/libis/tools/dc_record.rb +47 -0
  17. data/lib/libis/tools/extend/empty.rb +7 -0
  18. data/lib/libis/tools/extend/hash.rb +107 -0
  19. data/lib/libis/tools/extend/ostruct.rb +3 -0
  20. data/lib/libis/tools/extend/string.rb +85 -0
  21. data/lib/libis/tools/extend/struct.rb +29 -0
  22. data/lib/libis/tools/logger.rb +71 -0
  23. data/lib/libis/tools/mets_file.rb +575 -0
  24. data/lib/libis/tools/parameter.rb +172 -0
  25. data/lib/libis/tools/sharepoint_mapping.rb +118 -0
  26. data/lib/libis/tools/sharepoint_record.rb +260 -0
  27. data/lib/libis/tools/version.rb +5 -0
  28. data/lib/libis/tools/xml_document.rb +574 -0
  29. data/libis-tools.gemspec +39 -0
  30. data/spec/assert_spec.rb +65 -0
  31. data/spec/checksum_spec.rb +132 -0
  32. data/spec/command_spec.rb +68 -0
  33. data/spec/config_spec.rb +86 -0
  34. data/spec/data/test.data +9 -0
  35. data/spec/data/test.xml +8 -0
  36. data/spec/data/test.yml +1 -0
  37. data/spec/logger_spec.rb +107 -0
  38. data/spec/parameter_container_spec.rb +83 -0
  39. data/spec/parameter_spec.rb +139 -0
  40. data/spec/spec_helper.rb +12 -0
  41. data/spec/test.xsd +20 -0
  42. data/spec/xmldocument_spec.rb +413 -0
  43. data/test/test_helper.rb +7 -0
  44. data/test/webservices/test_ca_item_info.rb +59 -0
  45. data/test/webservices/test_ca_search.rb +35 -0
  46. metadata +244 -0
@@ -0,0 +1,5 @@
1
+ module Libis
2
+ module Tools
3
+ VERSION = '0.9.1'
4
+ end
5
+ end
@@ -0,0 +1,574 @@
1
+ # coding: utf-8
2
+
3
+ require 'nokogiri'
4
+ require 'gyoku'
5
+ require 'nori'
6
+
7
+ module Libis
8
+ module Tools
9
+
10
+ # noinspection RubyTooManyMethodsInspection
11
+
12
+
13
+ # This class embodies most used features of Nokogiri, Nori and Gyoku in one convenience class. The Nokogiri document
14
+ # is stored in the class variable 'document' and can be accessed and manipulated directly - if required.
15
+ #
16
+ # In the examples we assume the following XML code:
17
+ #
18
+ # <?xml version="1.0" encoding="utf-8"?>
19
+ # <patron>
20
+ # <name>Harry Potter</name>
21
+ # <barcode library='Hogwarts Library'>1234567890</barcode>
22
+ # <access_level>student</access_level>
23
+ # <email>harry.potter@hogwarts.edu</email>
24
+ # <email>hpotter@JKRowling.com</email>
25
+ # </patron>
26
+
27
+ class XmlDocument
28
+
29
+ attr_accessor :document
30
+
31
+ # Check if the embedded XML document is not present or invalid.
32
+ def invalid?
33
+ @document.nil? or !document.is_a?(::Nokogiri::XML::Document) or @document.root.nil?
34
+ end
35
+
36
+ # Check if the embedded XML document is present and valid
37
+ def valid?
38
+ !invalid?
39
+ end
40
+
41
+ # Create new XmlDocument instance. The object will contain a new and emtpy Nokogiri XML Document. The object will
42
+ # not be valid until a root node is added.
43
+ # @param [String] encoding character encoding for the XML content; default value is 'utf-8'
44
+ # @return [XmlDocument] new instance
45
+ def initialize(encoding = 'utf-8')
46
+ @document = Nokogiri::XML::Document.new
47
+ @document.encoding = encoding
48
+ end
49
+
50
+ # Create a new instance initialized with the content of an XML file.
51
+ # @param [String] file path to the XML file
52
+ # @return [XmlDocument] new instance
53
+ def self.open(file)
54
+ doc = XmlDocument.new
55
+ doc.document = Nokogiri::XML(File.open(file))
56
+ # doc.document = Nokogiri::XML(File.open(file), &:noblanks)
57
+ doc
58
+ end
59
+
60
+ # Create a new instance initialized with an XML String.
61
+ # @param [String] xml XML string
62
+ # @return [XmlDocument] new instance
63
+ def self.parse(xml)
64
+ doc = XmlDocument.new
65
+ doc.document = Nokogiri::XML.parse(xml)
66
+ # doc.document = Nokogiri::XML.parse(xml, &:noblanks)
67
+ doc
68
+ end
69
+
70
+ # Create a new instance initialized with a Hash.
71
+ # @note The Hash will be converted with Gyoku. See the Gyoku documentation for the Hash format requirements.
72
+ # @param [Hash] hash the content
73
+ # @param [Hash] options options passed to Gyoku upon parsing the Hash into XML
74
+ # @return [XmlDocument] new instance
75
+ def self.from_hash(hash, options = {})
76
+ doc = XmlDocument.new
77
+ doc.document = Nokogiri::XML(Gyoku.xml(hash, options))
78
+ doc.document.encoding = 'utf-8'
79
+ doc
80
+ end
81
+
82
+ # Save the XML Document to a given XML file.
83
+ # @param [String] file name of the file to save to
84
+ # @param [Integer] indent amount of space for indenting; default 2
85
+ # @param [String] encoding character encoding; default 'utf-8'
86
+ # @return [nil]
87
+ def save(file, indent = 2, encoding = 'utf-8')
88
+ fd = File.open(file, 'w')
89
+ @document.write_xml_to(fd, :indent => indent, :encoding => encoding)
90
+ fd.close
91
+ end
92
+
93
+ # Export the XML Document to an XML string.
94
+ # @param [Hash] options options passed to the underlying Nokogiri::XML::Document#to_xml; default is:
95
+ # {indent: 2, encoding: 'utf-8'}
96
+ # @return [String] a string
97
+ def to_xml(options = {})
98
+ options = {indent: 2, encoding: 'utf-8', save_with: Nokogiri::XML::Node::SaveOptions::DEFAULT_XML}.merge(options)
99
+ @document.to_xml(options)
100
+ end
101
+
102
+ # Export the XML Document to a Hash.
103
+ #
104
+ # @note The hash is generated using the Nori gem. The options passed to this call are used to configure Nori in
105
+ # the constructor call. For content and syntax see the {Nori} documentation. Nori also uses an enhanced
106
+ # String class with an extra method #attributes that will return a Hash containing tag-value pairs for each
107
+ # attribute of the XML element.
108
+ #
109
+ # Example:
110
+ #
111
+ # h = xml_doc.to_hash
112
+ # # => { "patron" =>
113
+ # { "name" => "Harry Potter",
114
+ # "barcode" => "1234567890",
115
+ # "access_level" => "student",
116
+ # "email" => ["harry.potter@hogwarts.edu", "hpotter@JKRowling.com"],
117
+ # } }
118
+ # h['patron']['barcode']
119
+ # # => "12345678890"
120
+ # h['patron']['barcode'].attributes
121
+ # # => {"library" => "Hogwarts Library"}
122
+ # h['patron']['barcode'].attributes['library']
123
+ # # => "Hogwarts Library"
124
+ #
125
+ # @param [Hash] options
126
+ # @return [Hash]
127
+ def to_hash(options = {})
128
+ Nori.new(options).parse(to_xml)
129
+ end
130
+
131
+ # Check if the document validates against a given XML schema file.
132
+ # @param [String] schema the file path of the XML schema
133
+ # @return [Boolean]
134
+ def validates_against?(schema)
135
+ schema_doc = Nokogiri::XML::Schema.new(File.open(schema))
136
+ schema_doc.valid?(@document)
137
+ end
138
+
139
+ # Check if the document validates against a given XML schema file.
140
+ # @return [Array<{Nokogiri::XML::SyntaxError}>] a list of validation errors
141
+ def validate(schema)
142
+ schema_doc = Nokogiri::XML::Schema.new(File.open(schema))
143
+ schema_doc.validate(@document)
144
+ end
145
+
146
+ # Add a processing instruction to the current XML document.
147
+ # @note unlike regular nodes, these nodes are automatically added to the document.
148
+ # @param [String] name instruction name
149
+ # @param [String] content instruction content
150
+ # @return [Nokogiri::XML::Node] the processing instruction node
151
+ def add_processing_instruction(name, content)
152
+ processing_instruction = Nokogiri::XML::ProcessingInstruction.new(@document, name, content)
153
+ @document.root.add_previous_sibling processing_instruction
154
+ processing_instruction
155
+ end
156
+
157
+ # Get the root node of the XML Document.
158
+ #
159
+ # Example:
160
+ #
161
+ # puts xml_doc.root.to_xml
162
+ # # =>
163
+ # <patron>
164
+ # ...
165
+ # </patron>
166
+ #
167
+ # @return [{Nokogiri::XML::Node}] the root node of the XML Document
168
+ def root
169
+ raise ArgumentError, 'XML document not valid.' if @document.nil?
170
+ @document.root
171
+ end
172
+
173
+ # Set the root node of the XML Document.
174
+ #
175
+ # Example:
176
+ #
177
+ # patron = ::Nokogiri::XML::Node.new 'patron', xml_doc.document
178
+ # xml_doc.root = patron
179
+ # puts xml_doc.to_xml
180
+ # # =>
181
+ # <?xml version="1.0" encoding="utf-8"?>
182
+ # <patron/>
183
+ #
184
+ # @param [{Nokogiri::XML::Node}] node new root node
185
+ # @return [{Nokogiri::XML::Node}] the new root node
186
+ def root=(node)
187
+ raise ArgumentError, 'XML document not valid.' if @document.nil?
188
+ #noinspection RubyArgCount
189
+ @document.root = node
190
+ end
191
+
192
+ # Creates nodes using the Nokogiri build short syntax.
193
+ #
194
+ # Example:
195
+ #
196
+ # xml_doc.build(xml_doc.root) do |xml|
197
+ # xml.books do
198
+ # xml.book title: 'Quidditch Through the Ages', author: 'Kennilworthy Whisp', due_date: '1992-4-23'
199
+ # end
200
+ # end
201
+ # p xml_doc.to_xml
202
+ # # =>
203
+ # <?xml version="1.0" encoding="utf-8"?>
204
+ # <patron>
205
+ # ...
206
+ # <books>
207
+ # <book title="Quidditch Through the Ages" author="Kennilworthy Whisp" due_date="1992-4-23"/>
208
+ # </books>
209
+ # </patron>
210
+ #
211
+ # @param [Nokogiri::XML::Node] at_node the node to attach the new nodes to;
212
+ # optional - if missing or nil the new nodes will replace the entire document
213
+ # @param [Code block] block Build instructions
214
+ # @return [XmlDocument] the XML Document itself
215
+ def build(at_node = nil, options = {}, &block)
216
+ options = {encoding: 'utf-8' }.merge options
217
+ if at_node
218
+ Nokogiri::XML::Builder.new(options,at_node, &block)
219
+ else
220
+ xml = Nokogiri::XML::Builder.new(options, &block)
221
+ @document = xml.doc
222
+ end
223
+ self
224
+ end
225
+
226
+ # Creates a new XML document with contents supplied in Nokogiri build short syntax.
227
+ #
228
+ # Example:
229
+ #
230
+ # xml_doc = ::Libis::Tools::XmlDocument.build do
231
+ # patron {
232
+ # name 'Harry Potter'
233
+ # barcode( '1234567890', library: 'Hogwarts Library')
234
+ # access_level 'student'
235
+ # email 'harry.potter@hogwarts.edu'
236
+ # email 'hpotter@JKRowling.com'
237
+ # books {
238
+ # book title: 'Quidditch Through the Ages', author: 'Kennilworthy Whisp', due_date: '1992-4-23'
239
+ # }
240
+ # }
241
+ # end
242
+ # p xml_doc.to_xml
243
+ # # =>
244
+ # <?xml version="1.0" encoding="utf-8"?>
245
+ # <patron>
246
+ # <name>Harry Potter</name>
247
+ # <barcode library="Hogwarts Library">1234567890</barcode>
248
+ # <access_level>student</access_level>
249
+ # <email>harry.potter@hogwarts.edu</email>
250
+ # <email>hpotter@JKRowling.com</email>
251
+ # <books>
252
+ # <book title="Quidditch Through the Ages" author="Kennilworthy Whisp" due_date="1992-4-23"/>
253
+ # </books>
254
+ # </patron>
255
+ # @param [Code block] block Build instructions
256
+ # @return [XmlDocument] the new XML Document
257
+ def self.build(&block)
258
+ self.new.build(nil, &block)
259
+ end
260
+
261
+ # Adds a new XML node to the document.
262
+ #
263
+ # Example:
264
+ #
265
+ # xml_doc = ::Libis::Tools::XmlDocument.new
266
+ # xml_doc.valid? # => false
267
+ # xml_doc.add_node :patron
268
+ # xml_doc.add_node :name, 'Harry Potter'
269
+ # books = xml_doc.add_node :books, nil, nil, namespaces: { jkr: 'http://JKRowling.com', node_ns: 'jkr' }
270
+ # xml_doc.add_node :book, nil, books,
271
+ # title: 'Quidditch Through the Ages', author: 'Kennilworthy Whisp', due_date: '1992-4-23',
272
+ # namespaces: { node_ns: 'jkr' }
273
+ # p xml_doc.to_xml
274
+ # # =>
275
+ # <?xml version="1.0" encoding="utf-8"?>
276
+ # <patron>
277
+ # <name>Harry Potter</name>
278
+ # <jkr:books xmlns:jkr="http://JKRowling.com">
279
+ # <jkr:book author="Kennilworthy Whisp" due_date="1992-4-23" title="Quidditch Through the Ages"/>
280
+ # </jkr:books>
281
+ # </patron>
282
+ #
283
+ # @param [String] name tag for the new node
284
+ # @param [String] value optional content for new node; empty if nil
285
+ # @param [Node] parent optional parent node for new node; root if nil; xml document if root is not defined
286
+ # @param [Hash] attributes a Hash containing tag-value pairs for each attribute; the special key ':namespaces'
287
+ # contains a Hash of namespace definitions as in {#add_namespaces}
288
+ # @return [Nokogiri::XML::Node] the new node
289
+ def add_node(name, value = nil, parent = nil, attributes = {})
290
+
291
+ node = Nokogiri::XML::Node.new name.to_s, @document
292
+ node.content = value
293
+
294
+ if !parent.nil?
295
+ parent << node
296
+ elsif !self.root.nil?
297
+ self.root << node
298
+ else
299
+ self.root = node
300
+ end
301
+
302
+ return node if attributes.empty?
303
+
304
+ namespaces = attributes.delete :namespaces
305
+ add_namespaces(node, namespaces) if namespaces
306
+
307
+ add_attributes(node, attributes) if attributes
308
+
309
+ node
310
+
311
+ end
312
+
313
+ # Add attributes to a node.
314
+ # @note The Nokogiri method Node#[]= is probably easier to use if you only want to add a single attribute ;the
315
+ # main purpose of this method is to make it easier to add attributes in bulk or if you have them already
316
+ # available as a Hash
317
+ #
318
+ # Example:
319
+ #
320
+ # xml_doc.add_attributes xml_doc.root, status: 'active', id: '123456'
321
+ # xml_doc.to_xml
322
+ # # =>
323
+ # <?xml version="1.0" encoding="utf-8"?>
324
+ # <patron id="123456" status="active">
325
+ # ...
326
+ # </patron>
327
+ #
328
+ # @param [Nokogiri::XML::Node] node node to add the attributes to
329
+ # @param [Hash] attributes a Hash with tag - value pairs for each attribute
330
+ # @return [{Nokogiri::XML::Node}] the node
331
+ def add_attributes(node, attributes)
332
+ XmlDocument.add_attributes node, attributes
333
+ end
334
+
335
+ # (see #add_attributes)
336
+ def self.add_attributes(node, attributes)
337
+
338
+ attributes.each do |name, value|
339
+ node.set_attribute name.to_s, value
340
+ end
341
+
342
+ node
343
+
344
+ end
345
+
346
+ # Add namespace information to a node
347
+ #
348
+ # Example:
349
+ #
350
+ # xml_doc.add_namespaces xml_doc.root, jkr: 'http://JKRowling.com', node_ns: 'jkr'
351
+ # xml_doc.to_xml
352
+ # # =>
353
+ # <?xml version="1.0" encoding="utf-8"?>
354
+ # <jkr:patron xmlns:jkr="http://JKRowling.com">
355
+ # ...
356
+ # </jkr:patron>
357
+ #
358
+ # xml_doc.add_namespaces xml_doc.root, nil => 'http://JKRowling.com'
359
+ # xml_doc.to_xml
360
+ # # =>
361
+ # <?xml version="1.0" encoding="utf-8"?>
362
+ # <patron xmlns="http://JKRowling.com">
363
+ # ...
364
+ # </patron>
365
+ #
366
+ # @param [Nokogiri::XML::Node] node the node where the namespace info should be added to
367
+ # @param [Hash] namespaces a Hash with prefix - URI pairs for each namespace definition that should be added. The
368
+ # special key +:node_ns+ is reserved for specifying the prefix for the node itself. To set the default
369
+ # namespace, use the prefix +nil+
370
+ # Example:
371
+ # node = xml_doc.create_text_node 'address', 'content'
372
+ # xml_doc.add_namespaces node, node_ns: 'abc', abc: 'http://abc.org', xyz: 'http://xyz.org'
373
+ # # node => <abc:sample abc="http://abc.org" xyz="http://xyz.org">content</abc:sample>
374
+ def add_namespaces(node, namespaces)
375
+ XmlDocument.add_namespaces node, namespaces
376
+ end
377
+
378
+ # (see #add_namespaces)
379
+ def self.add_namespaces(node, namespaces)
380
+
381
+ node_ns = namespaces.delete :node_ns
382
+ default_ns = namespaces.delete nil
383
+
384
+ namespaces.each do |prefix, prefix_uri|
385
+ node.add_namespace prefix.to_s, prefix_uri
386
+ end
387
+
388
+ node.namespace_scopes.each do |ns|
389
+ node.namespace = ns if ns.prefix == node_ns
390
+ end if node_ns
391
+
392
+ node.default_namespace = default_ns if default_ns
393
+
394
+ node
395
+
396
+ end
397
+
398
+ # Search for nodes in the current document root.
399
+ #
400
+ # Example:
401
+ #
402
+ # nodes = xml_doc.xpath('//email')
403
+ # nodes.size # => 2
404
+ # nodes.map(&:content) # => ["harry.potter@hogwarts.edu", "hpotter@JKRowling.com"]
405
+ #
406
+ # @param [String] path XPath search string
407
+ # @return [{Nokogiri::XML::NodeSet}] set of nodes found
408
+ def xpath(path)
409
+ raise ArgumentError, 'XML document not valid.' if self.invalid?
410
+ @document.xpath(path.to_s)
411
+ end
412
+
413
+ # Check if the XML document contains certain element(s) anywhere in the XML document.
414
+ #
415
+ # Example:
416
+ #
417
+ # xml_doc.has_element? 'barcode[@library="Hogwarts Library"]' # => true
418
+ #
419
+ # @param [String] element_name name of the element(s) to search
420
+ # @return [Integer] number of elements found
421
+ def has_element?(element_name)
422
+ list = xpath("//#{element_name}")
423
+ list.nil? ? 0 : list.size
424
+ end
425
+
426
+ # Return the content of the first element found.
427
+ #
428
+ # Example:
429
+ #
430
+ # xml_doc.value('//email') # => "harry.potter@hogwarts.edu"
431
+ #
432
+ # @param [String] path the name or XPath term to search the node(s)
433
+ # @return [String] content or nil if not found
434
+ def value(path)
435
+ xpath(path).first.content rescue nil
436
+ end
437
+
438
+ alias_method :[], :value
439
+
440
+ # Return the content of all elements found.
441
+ # Example:
442
+ #
443
+ # xml_doc.values('//email') # => [ "harry.potter@hogwarts.edu", "hpotter@JKRowling.com" ]
444
+ #
445
+ # @param (see #value)
446
+ # @return [Array<String>] content
447
+ def values(path)
448
+ xpath(path).map &:content
449
+ end
450
+
451
+ # Return the content of the first element in the set of nodes.
452
+ #
453
+ # Example:
454
+ #
455
+ # ::Libis::Tools::XmlDocument.get_content(xml_doc.xpath('//email')) # => "harry.potter@hogwarts.edu"
456
+ #
457
+ # @param [{Nokogiri::XML::NodeSet}] nodelist set of nodes to get content from
458
+ # @return [String] content of the first node; always returns at least an empty string
459
+ def self.get_content(nodelist)
460
+ (nodelist.first && nodelist.first.content) || ''
461
+ end
462
+
463
+ # Find a node and set its content.
464
+ #
465
+ # Example:
466
+ #
467
+ # xml_doc['//access_level'] = 'postgraduate'
468
+ # p xml_doc.to_xml
469
+ # # =>
470
+ # <?xml version="1.0" encoding="utf-8"?>
471
+ # <patron>
472
+ # ...
473
+ # <access_level>postgraduate</access_level>
474
+ # ...
475
+ # </patron>
476
+ #
477
+ # @param (see #value)
478
+ # @param [String] value the content
479
+ # @return [String] the value
480
+ def []=(path, value)
481
+ begin
482
+ nodes = xpath(path)
483
+ nodes.first.content = value
484
+ rescue
485
+ # ignored
486
+ end
487
+ end
488
+
489
+ # Node access by method name.
490
+ #
491
+ # Nodes can be accessed through a method with signature the tag name of the node. There are several ways to use
492
+ # this shorthand method:
493
+ #
494
+ # * without arguments it simply returns the first node found
495
+ # * with one argument it retrieves the node's attribute
496
+ # * with one argument and '=' sign it sets the content of the node
497
+ # * with two arguments it sets the value of the node's attribute
498
+ # * with a code block it implements the build pattern
499
+ #
500
+ #
501
+ # Examples:
502
+ #
503
+ # xml_doc.email
504
+ # # => "harry.potter@hogwarts.edu"
505
+ # p xml_doc.barcode 'library'
506
+ # # => "Hogwarts Library"
507
+ # xml_doc.access_level = 'postgraduate'
508
+ # xml_doc.barcode 'library', 'Hogwarts Dumbledore Library'
509
+ # xml_doc.dates do |dates|
510
+ # dates.birth_date '1980-07-31'
511
+ # dates.member_since '1991-09-01'
512
+ # end
513
+ # p xml_doc.to_xml
514
+ # # => <patron>
515
+ # ...
516
+ # <barcode library='Hogwarts Dumbledore Library'>1234567890</barcode>
517
+ # <access_level>postgraduate</access_level>
518
+ # ...
519
+ # <dates>
520
+ # <birth_date>1980-07-31</birth_date>
521
+ # <member_since>1991-09-01</member_since>
522
+ # </dates>
523
+ # </patron>
524
+ #
525
+ #
526
+ def method_missing(method, *args, &block)
527
+ super unless method.to_s =~ /^([a-z_][a-z_0-9]*)(=)?$/i
528
+ node = get_node($1)
529
+ node = add_node($1) unless node
530
+ case args.size
531
+ when 0
532
+ node = get_node($1)
533
+ if block_given?
534
+ build(node, &block)
535
+ end
536
+ when 1
537
+ if $2
538
+ node.content = args.first.to_s
539
+ else
540
+ return node[args.first.to_s]
541
+ end
542
+ when 2
543
+ node[args.first.to_s] = args[1].to_s
544
+ return node[args.first.to_s]
545
+ else
546
+ raise ArgumentError, 'Too many arguments.'
547
+ end
548
+ node
549
+ end
550
+
551
+ protected
552
+
553
+ # Get the first node matching the tag. The node will be seached with XPath search term = "//#{tag}".
554
+ #
555
+ # @param [String] tag XML tag to look for; XPath syntax is allowed
556
+ # @param [Node] parent
557
+ def get_node(tag, parent = nil)
558
+ get_nodes(tag, parent).first
559
+ end
560
+
561
+ # Get all the nodes matching the tag. The node will be seached with XPath search term = "//#{tag}".
562
+ #
563
+ # @param [String] tag XML tag to look for; XPath syntax is allowed
564
+ # @param [Node] parent
565
+ def get_nodes(tag, parent = nil)
566
+ parent ||= root
567
+ term = "#{tag.to_s =~ /^\// ? '' : '//'}#{tag.to_s}"
568
+ parent.xpath(term)
569
+ end
570
+
571
+ end
572
+
573
+ end
574
+ end