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.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.travis.yml +40 -0
- data/Gemfile +7 -0
- data/README.md +202 -0
- data/Rakefile +11 -0
- data/bin/libis_tool +5 -0
- data/lib/libis-tools.rb +1 -0
- data/lib/libis/tools.rb +25 -0
- data/lib/libis/tools/assert.rb +52 -0
- data/lib/libis/tools/checksum.rb +106 -0
- data/lib/libis/tools/cli/cli_helper.rb +189 -0
- data/lib/libis/tools/cli/reorg.rb +416 -0
- data/lib/libis/tools/command.rb +133 -0
- data/lib/libis/tools/command_line.rb +23 -0
- data/lib/libis/tools/config.rb +147 -0
- data/lib/libis/tools/config_file.rb +85 -0
- data/lib/libis/tools/csv.rb +38 -0
- data/lib/libis/tools/deep_struct.rb +71 -0
- data/lib/libis/tools/extend/array.rb +16 -0
- data/lib/libis/tools/extend/empty.rb +7 -0
- data/lib/libis/tools/extend/hash.rb +147 -0
- data/lib/libis/tools/extend/kernel.rb +25 -0
- data/lib/libis/tools/extend/ostruct.rb +3 -0
- data/lib/libis/tools/extend/roo.rb +91 -0
- data/lib/libis/tools/extend/string.rb +94 -0
- data/lib/libis/tools/extend/struct.rb +29 -0
- data/lib/libis/tools/extend/symbol.rb +8 -0
- data/lib/libis/tools/logger.rb +130 -0
- data/lib/libis/tools/mets_dnx.rb +61 -0
- data/lib/libis/tools/mets_file.rb +504 -0
- data/lib/libis/tools/mets_objects.rb +547 -0
- data/lib/libis/tools/parameter.rb +372 -0
- data/lib/libis/tools/spreadsheet.rb +196 -0
- data/lib/libis/tools/temp_file.rb +42 -0
- data/lib/libis/tools/thread_safe.rb +31 -0
- data/lib/libis/tools/version.rb +5 -0
- data/lib/libis/tools/xml_document.rb +583 -0
- data/libis-tools.gemspec +55 -0
- data/spec/assert_spec.rb +65 -0
- data/spec/checksum_spec.rb +68 -0
- data/spec/command_spec.rb +90 -0
- data/spec/config_file_spec.rb +83 -0
- data/spec/config_spec.rb +113 -0
- data/spec/csv_spec.rb +159 -0
- data/spec/data/test-headers.csv +2 -0
- data/spec/data/test-headers.tsv +2 -0
- data/spec/data/test-noheaders.csv +1 -0
- data/spec/data/test-noheaders.tsv +1 -0
- data/spec/data/test.data +9 -0
- data/spec/data/test.xlsx +0 -0
- data/spec/data/test.xml +8 -0
- data/spec/data/test.yml +2 -0
- data/spec/data/test_config.yml +15 -0
- data/spec/deep_struct_spec.rb +138 -0
- data/spec/logger_spec.rb +165 -0
- data/spec/mets_file_spec.rb +223 -0
- data/spec/parameter_container_spec.rb +152 -0
- data/spec/parameter_spec.rb +148 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/spreadsheet_spec.rb +1820 -0
- data/spec/temp_file_spec.rb +76 -0
- data/spec/test.xsd +20 -0
- data/spec/thread_safe_spec.rb +64 -0
- data/spec/xmldocument_spec.rb +421 -0
- data/test/test_helper.rb +7 -0
- data/test/webservices/test_ca_item_info.rb +59 -0
- data/test/webservices/test_ca_search.rb +35 -0
- 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,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
|