libis-tools 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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