libis-tools 1.0.5-java

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