libis-tools 0.9.20 → 0.9.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -233
  3. data/Rakefile +5 -0
  4. data/lib/libis/tools.rb +1 -0
  5. data/lib/libis/tools/assert.rb +11 -0
  6. data/lib/libis/tools/checksum.rb +22 -5
  7. data/lib/libis/tools/command.rb +24 -3
  8. data/lib/libis/tools/config.rb +61 -33
  9. data/lib/libis/tools/config_file.rb +0 -1
  10. data/lib/libis/tools/deep_struct.rb +10 -2
  11. data/lib/libis/tools/extend/empty.rb +2 -2
  12. data/lib/libis/tools/extend/hash.rb +37 -18
  13. data/lib/libis/tools/extend/kernel.rb +9 -0
  14. data/lib/libis/tools/extend/string.rb +17 -8
  15. data/lib/libis/tools/logger.rb +95 -44
  16. data/lib/libis/tools/metadata.rb +5 -1
  17. data/lib/libis/tools/metadata/dublin_core_record.rb +22 -4
  18. data/lib/libis/tools/metadata/field_format.rb +49 -9
  19. data/lib/libis/tools/metadata/fix_field.rb +5 -0
  20. data/lib/libis/tools/metadata/mapper.rb +2 -1
  21. data/lib/libis/tools/metadata/mappers/flandrica.rb +8 -1
  22. data/lib/libis/tools/metadata/mappers/kuleuven.rb +6 -2
  23. data/lib/libis/tools/metadata/marc21_record.rb +1 -0
  24. data/lib/libis/tools/metadata/marc_record.rb +31 -12
  25. data/lib/libis/tools/metadata/parser/basic_parser.rb +2 -0
  26. data/lib/libis/tools/metadata/parser/dublin_core_parser.rb +2 -1
  27. data/lib/libis/tools/metadata/parser/marc21_parser.rb +2 -1
  28. data/lib/libis/tools/metadata/parser/marc_format_parser.rb +2 -1
  29. data/lib/libis/tools/metadata/parser/marc_rules.rb +2 -1
  30. data/lib/libis/tools/metadata/parser/marc_select_parser.rb +2 -1
  31. data/lib/libis/tools/metadata/parser/patch.rb +1 -0
  32. data/lib/libis/tools/metadata/parser/subfield_criteria_parser.rb +2 -1
  33. data/lib/libis/tools/metadata/sharepoint_mapping.rb +1 -0
  34. data/lib/libis/tools/metadata/sharepoint_record.rb +2 -0
  35. data/lib/libis/tools/metadata/var_field.rb +8 -0
  36. data/lib/libis/tools/mets_dnx.rb +61 -0
  37. data/lib/libis/tools/mets_file.rb +87 -604
  38. data/lib/libis/tools/mets_objects.rb +534 -0
  39. data/lib/libis/tools/parameter.rb +144 -21
  40. data/lib/libis/tools/thread_safe.rb +31 -0
  41. data/lib/libis/tools/version.rb +1 -1
  42. data/lib/libis/tools/xml_document.rb +18 -24
  43. data/libis-tools.gemspec +6 -2
  44. data/spec/config_spec.rb +3 -4
  45. data/spec/logger_spec.rb +13 -30
  46. data/spec/mets_file_spec.rb +17 -17
  47. metadata +53 -7
@@ -1,33 +1,64 @@
1
1
  # encoding: utf-8
2
2
  require 'date'
3
3
  require 'libis/tools/extend/struct'
4
+ require 'concurrent/hash'
4
5
 
5
6
  module Libis
6
7
  module Tools
7
8
 
9
+ # Exception that will be raised when a parameter value does not pass the validation checks.
8
10
  class ParameterValidationError < RuntimeError;
9
11
  end
12
+
13
+ # Exception that will be raised when an attempt is made to change the value of a frozen parameter.
10
14
  class ParameterFrozenError < RuntimeError;
11
15
  end
12
16
 
13
17
  # noinspection RubyConstantNamingConvention
14
- Parameter = ::Struct.new(:name, :default, :datatype, :description, :constraint, :frozen, :options) do
15
18
 
19
+ # A {Parameter} is like a class instance attribute on steroids. Contrary to regular attributes, {Parameter}s are
20
+ # type-safe, can have a descriptive text explaining their use, a constraint that limits the values and any other
21
+ # properties for an application to use for their needs.
22
+ #
23
+ # Parameters are inherited from base classes and can be overwritten without affecting the parameters in the parent
24
+ # class. For instance, a regular parameter in the parent class can be given a fixed value in the child class by
25
+ # giving it a default value and setting it's frozen property to true. The same paremter in the parent class
26
+ # instances will still be modifieable. But the parameter in the child class instances will be frozen, even if
27
+ # accessed via the methods on parent class.
28
+ #
29
+ # Important: the parameter will exist both on the class level as on the instance level, but the parameter on the
30
+ # class level is the parameter definition as described in the {Parameter} class. On the instance level, there are
31
+ # merely some parameter methods that access the parameter instance values with the help of the parameter definitions
32
+ # on the class. The implementation of the parameter instances is dealt with by the {ParameterContainer} module.
33
+ class Parameter < Struct.new(:name, :default, :datatype, :description, :constraint, :frozen, :options)
34
+
35
+ # Create a Parameter instance.
36
+ # @param [Array] args The values for:
37
+ # * name - Required. String for the name of the parameter. Any valid attribute name is acceptable.
38
+ # * default value - Any value. Will be coverted to the given datatype if present. Default is nil.
39
+ # * datatype - String. One of: bool, string, int, float, datetime, array, hash. If omitted it will be derived
40
+ # from the default value or set to the default 'string'.
41
+ # * description - String describing the parameter's use.
42
+ # * constraint - Array, Range, RegEx or single value. Default is nil meaning no constraint.
43
+ # * frozen - Boolean. Default is false; if true the parameter value cannot be changed from the default value.
44
+ # * options - Any Hash. It's up to the applcation to interprete and use this info.
45
+ # datatype can be omitted if the type can be derived from the
16
46
  def initialize(*args)
17
- # noinspection RubySuperCallWithoutSuperclassInspection
18
47
  super(*args)
19
48
  self[:options] ||= {}
20
49
  self[:datatype] ||= guess_datatype
21
50
  end
22
51
 
52
+ # Duplicates the parameter
23
53
  def dup
24
- # noinspection RubySuperCallWithoutSuperclassInspection
25
54
  new_obj = super
26
55
  # noinspection RubyResolve
27
56
  new_obj[:options] = Marshal.load(Marshal.dump(self[:options]))
28
57
  new_obj
29
58
  end
30
59
 
60
+ # Merges other parameter data into the current parameter
61
+ # @param [::Libis::Tools::Parameter] other parameter definition to copy properties from
31
62
  def merge!(other)
32
63
  other.each do |k,v|
33
64
  if k == :options
@@ -39,39 +70,58 @@ module Libis
39
70
  self
40
71
  end
41
72
 
73
+ # Retrieve a specific property of the parameter.
74
+ # If not found in the regular properties, the options Hash is scanned for the property.
75
+ # @param [Symbol] key name of the property
42
76
  def [](key)
43
- # noinspection RubySuperCallWithoutSuperclassInspection
44
77
  return super(key) if members.include?(key)
45
78
  self[:options][key]
46
79
  end
47
80
 
81
+ # Set a property of the parameter.
82
+ # If the property is not one of the regular properties, the property will be set in the options Hash.
83
+ # @param (see #[])
84
+ # @param [Object] value value for the property. No type checking happens on this value
48
85
  def []=(key, value)
49
- # noinspection RubySuperCallWithoutSuperclassInspection
50
86
  return super(key, value) if members.include?(key)
51
87
  self[:options][key] = value
52
88
  end
53
89
 
90
+ # Convience method to create a new {Parameter} from a Hash.
91
+ # @param [Hash] h Hash with parameter definition properties
54
92
  def self.from_hash(h)
55
93
  h.each { |k, v| self[k.to_s.to_sym] = v }
56
94
  end
57
95
 
96
+ # Dumps the parameter properties into a Hash.
97
+ # The options properties are merged into the hash. If you do not want that, use Struct#to_h instead.
98
+ #
99
+ # @return [Hash] parameter definition properties
58
100
  def to_h
59
- # noinspection RubySuperCallWithoutSuperclassInspection
60
101
  super.inject({}) do |hash, key, value|
61
102
  key == :options ? value.each { |k, v| hash[k] = v } : hash[key] = value
62
103
  hash
63
104
  end
64
105
  end
65
106
 
107
+ # Valid input strings for boolean parameter value, all converted to 'true'
66
108
  TRUE_BOOL = %w'true yes t y 1'
109
+ # Valid input strings for boolean parameter value, all converted to 'false'
67
110
  FALSE_BOOL = %w'false no f n 0'
68
111
 
112
+ # Parse any value and try to convert to the correct datatype and check the constraints.
113
+ # Will throw an exception if not valid.
114
+ # @param [Object] value Any value to parse, strings are best supported.
115
+ # @return [Object] checked and converted value
69
116
  def parse(value = nil)
70
117
  result = value.nil? ? self[:default] : convert(value)
71
118
  check_constraint(result)
72
119
  result
73
120
  end
74
121
 
122
+ # Parse any value and try to convert to the correct datatype and check the constraints.
123
+ # Will return false if not valid, true otherwise.
124
+ # @param [Object] value Any value to check
75
125
  def valid_value?(value)
76
126
  begin
77
127
  parse(value)
@@ -80,7 +130,6 @@ module Libis
80
130
  end
81
131
  true
82
132
  end
83
-
84
133
  private
85
134
 
86
135
  def guess_datatype
@@ -105,14 +154,14 @@ module Libis
105
154
  end
106
155
 
107
156
  def convert(v)
108
- case self[:datatype]
157
+ case self[:datatype].to_s.downcase
109
158
  when 'boolean', 'bool'
110
159
  return true if TRUE_BOOL.include?(v.to_s.downcase)
111
160
  return false if FALSE_BOOL.include?(v.to_s.downcase)
112
161
  raise ParameterValidationError, "No boolean information in '#{v.to_s}'. " +
113
162
  "Valid values are: '#{TRUE_BOOL.join('\', \'')}" +
114
163
  "' and '#{FALSE_BOOL.join('\', \'')}'."
115
- when 'string', nil
164
+ when 'string', 'nil'
116
165
  return v.to_s
117
166
  when 'int'
118
167
  return Integer(v)
@@ -162,28 +211,88 @@ module Libis
162
211
 
163
212
  end # Parameter
164
213
 
214
+ # To use the parameters a class should include the ParameterContainer module and add parameter
215
+ # statements to the body of the class definition.
216
+ #
217
+ # Besides enabling the {::Libis::Tools::ParameterContainer::ClassMethods#parameter parameter} class method to
218
+ # define parameters, the module adds the class method
219
+ # {::Libis::Tools::ParameterContainer::ClassMethods#parameter_defs parameter_defs} that will return
220
+ # a Hash with parameter names as keys and their respective parameter definitions as values.
221
+ #
222
+ # On each class instance the {::Libis::Tools::ParameterContainer#parameter parameter} method is added and serves
223
+ # as both getter and setter for parameter values.
224
+ # The methods {::Libis::Tools::ParameterContainer#[] []} and {::Libis::Tools::ParameterContainer#[]= []=} serve as
225
+ # aliases for the getter and setter calls.
226
+ #
227
+ # Additionally two protected methods are available on the instance:
228
+ # * {::Libis::Tools::ParameterContainer#parameters parameters}: returns the Hash that keeps track of the current
229
+ # parameter values for the instance.
230
+ # * {::Libis::Tools::ParameterContainer#get_parameter_definition get_parameter_defintion}: retrieves the parameter
231
+ # definition from the instance's class for the given parameter name.
232
+ #
233
+ # Any class that derives from a class that included the ParameterContainer module will automatically inherit all
234
+ # parameter definitions from all of it's base classes and can override any of these parameter definitions e.g. to
235
+ # change the default values for the parameter.
236
+ #
165
237
  module ParameterContainer
166
238
 
239
+ # Methods created on class level.
167
240
  module ClassMethods
168
241
 
242
+ # Get a list of all parameter definitions.
243
+ # The list is initialized with duplicates of the parameter definitions of the parent class and
244
+ # each new parameter definition updates or appends the list.
245
+ # @return [Hash] with parameter names as keys and {Parameter} instance as value.
169
246
  def parameter_defs
170
247
  return @parameters if @parameters
171
- @parameters = Hash.new
172
- begin
173
- self.superclass.parameter_defs.
174
- each_with_object(@parameters) do |(name, param), hash|
175
- if hash.has_key?(name)
176
- hash[name].merge!(param)
177
- else
178
- hash[name] = param.dup
248
+ @parameters = ::Concurrent::Hash.new
249
+ begin
250
+ self.superclass.parameter_defs.
251
+ each_with_object(@parameters) do |(name, param), hash|
252
+ hash[name] = param.dup
179
253
  end
254
+ rescue NoMethodError
255
+ # ignored
180
256
  end
181
- rescue NoMethodError
182
- # ignored
183
- end
184
- @parameters
257
+ @parameters
185
258
  end
186
259
 
260
+ # DSL method that allows creating parameter definitions on the class level.
261
+ #
262
+ # It takes only one mandatory argument which is a Hash. The first entry is interpreted as '<name>: <default>'.
263
+ # The name for the parameter should be unique and the default value can be any value
264
+ # of type TrueClass, FalseClass, String, Integer, Float, Date, Time, DateTime, Array, Hash or nil.
265
+ #
266
+ # The second up to last Hash entries are optional properties for the parameter. These are:
267
+ # * datatype: the type of values the parameter will accept. Valid values are:
268
+ # * 'bool' or 'boolean'
269
+ # * 'string'
270
+ # * 'int'
271
+ # * 'float'
272
+ # * 'datetime'
273
+ # * 'array'
274
+ # * 'hash'
275
+ # Any other value will raise an Exception when the parameter is used. The value is case-insensitive and
276
+ # if not present, the datatype will be derived from the default value with 'string' being the default for
277
+ # NilClass. In any case the parameter will try its best to convert supplied values to the proper data type.
278
+ # For instance, an Integer parameter will accept 3, 3.1415, '3' and Rational(10/3) as valid values and
279
+ # store them as the integer value 3. Likewise DateTime parameters will try to interprete date and time strings.
280
+ # * description: any descriptive text you want to add to clarify what this parameter is used for.
281
+ # Any tool can ask the class for its parameters and - for instance - can use this property to provide help
282
+ # in a GUI when asking the user for input.
283
+ # * constraint: adds a validation condition to the parameter. The condition value can be:
284
+ # * an array: only values that convert to a value in the list are considered valid.
285
+ # * a range: only values that convert to a value in the given range are considered valid.
286
+ # * a regular expression: only values that match the regular expression are considered valid.
287
+ # * a string: only values that are '==' to the constraint are considered valid.
288
+ # * frozen: if set to true, prevents the class instance to set the parameter to any value other than
289
+ # the default. Mostly useful when a derived class needs a parameter in the parent class to be set to a
290
+ # specific value. Setting a value on a frozen parameter with the 'parameter(name,value)' method throws a
291
+ # {::Libis::Tools::ParameterFrozenError}.
292
+ # * options: a hash with any additional properties that you want to associate to the parameter. Any key-value pair in this
293
+ # hash is added to the retrievable properties of the parameter. Likewise any property defined, that is not in the list of
294
+ # known properties is added to the options hash. In this aspect the ::Libis::Tools::Parameter class behaves much like an
295
+ # OpenStruct even though it is implemented as a Struct.
187
296
  def parameter(options = {})
188
297
  return self.parameter_defs[options] unless options.is_a? Hash
189
298
  return nil if options.keys.empty?
@@ -198,12 +307,22 @@ module Libis
198
307
 
199
308
  end
200
309
 
310
+ # @!visibility private
201
311
  def self.included(base)
202
312
  base.extend(ClassMethods)
203
313
  end
204
314
 
315
+ # Special constant to indicate a parameter has no value set. Nil cannot be used as it is a valid value.
205
316
  NO_VALUE = '##NAV##'
206
317
 
318
+ # Getter/setter for parameter instances
319
+ # With only one argument (the parameter name) it returns the current value for the parameter, but the optional
320
+ # second argument will cause the method to set the parameter value. If the parameter is not available or
321
+ # the given value is not a valid value for the parameter, the method will return the special constant
322
+ # {::Libis::Tools::ParameterContainer::NO_VALUE NO_VALUE}.
323
+ #
324
+ # Setting a value on a frozen parameter with the 'parameter(name,value)' method throws a
325
+ # {::Libis::Tools::ParameterFrozenError} exception.
207
326
  def parameter(name, value = NO_VALUE)
208
327
  param_def = get_parameter_definition(name)
209
328
  return NO_VALUE unless param_def
@@ -219,10 +338,14 @@ module Libis
219
338
  end
220
339
  end
221
340
 
341
+ # Alias for the {#parameter} getter.
222
342
  def [](name)
223
343
  parameter(name)
224
344
  end
225
345
 
346
+ # Alias for the {#parameter} setter.
347
+ # The only difference is that in case of a frozen parameter, this method silently ignores the exception,
348
+ # but the default value still will not be changed.
226
349
  def []=(name, value)
227
350
  parameter name, value
228
351
  rescue ParameterFrozenError
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Libis
2
2
  module Tools
3
- VERSION = '0.9.20'
3
+ VERSION = '0.9.21'
4
4
  end
5
5
  end
@@ -9,21 +9,19 @@ module Libis
9
9
 
10
10
  # noinspection RubyTooManyMethodsInspection
11
11
 
12
-
13
12
  # This class embodies most used features of Nokogiri, Nori and Gyoku in one convenience class. The Nokogiri document
14
13
  # is stored in the class variable 'document' and can be accessed and manipulated directly - if required.
15
14
  #
16
15
  # In the examples we assume the following XML code:
17
16
  #
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
-
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>
27
25
  class XmlDocument
28
26
 
29
27
  attr_accessor :document
@@ -38,8 +36,9 @@ module Libis
38
36
  !invalid?
39
37
  end
40
38
 
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.
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.
43
42
  # @param [String] encoding character encoding for the XML content; default value is 'utf-8'
44
43
  # @return [XmlDocument] new instance
45
44
  def initialize(encoding = 'utf-8')
@@ -53,7 +52,6 @@ module Libis
53
52
  def self.open(file)
54
53
  doc = XmlDocument.new
55
54
  doc.document = Nokogiri::XML(File.open(file))
56
- # doc.document = Nokogiri::XML(File.open(file), &:noblanks)
57
55
  doc
58
56
  end
59
57
 
@@ -63,7 +61,6 @@ module Libis
63
61
  def self.parse(xml)
64
62
  doc = XmlDocument.new
65
63
  doc.document = Nokogiri::XML.parse(xml)
66
- # doc.document = Nokogiri::XML.parse(xml, &:noblanks)
67
64
  doc
68
65
  end
69
66
 
@@ -83,7 +80,6 @@ module Libis
83
80
  # @param [String] file name of the file to save to
84
81
  # @param [Integer] indent amount of space for indenting; default 2
85
82
  # @param [String] encoding character encoding; default 'utf-8'
86
- # @return [nil]
87
83
  def save(file, indent = 2, encoding = 'utf-8')
88
84
  fd = File.open(file, 'w')
89
85
  @document.write_xml_to(fd, :indent => indent, :encoding => encoding)
@@ -92,7 +88,7 @@ module Libis
92
88
 
93
89
  # Export the XML Document to an XML string.
94
90
  # @param [Hash] options options passed to the underlying Nokogiri::XML::Document#to_xml; default is:
95
- # {indent: 2, encoding: 'utf-8'}
91
+ # !{indent: 2, encoding: 'utf-8'}
96
92
  # @return [String] a string
97
93
  def to_xml(options = {})
98
94
  options = {indent: 2, encoding: 'utf-8', save_with: Nokogiri::XML::Node::SaveOptions::DEFAULT_XML}.merge(options)
@@ -102,7 +98,8 @@ module Libis
102
98
  # Export the XML Document to a Hash.
103
99
  #
104
100
  # @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
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
106
103
  # String class with an extra method #attributes that will return a Hash containing tag-value pairs for each
107
104
  # attribute of the XML element.
108
105
  #
@@ -252,6 +249,7 @@ module Libis
252
249
  # <book title="Quidditch Through the Ages" author="Kennilworthy Whisp" due_date="1992-4-23"/>
253
250
  # </books>
254
251
  # </patron>
252
+ #
255
253
  # @param [Code block] block Build instructions
256
254
  # @return [XmlDocument] the new XML Document
257
255
  def self.build(&block)
@@ -333,7 +331,7 @@ module Libis
333
331
  #
334
332
  # @param [Nokogiri::XML::Node] node node to add the attributes to
335
333
  # @param [Hash] attributes a Hash with tag - value pairs for each attribute
336
- # @return [{Nokogiri::XML::Node}] the node
334
+ # @return [Nokogiri::XML::Node] the node
337
335
  def add_attributes(node, attributes)
338
336
  XmlDocument.add_attributes node, attributes
339
337
  end
@@ -373,10 +371,6 @@ module Libis
373
371
  # @param [Hash] namespaces a Hash with prefix - URI pairs for each namespace definition that should be added. The
374
372
  # special key +:node_ns+ is reserved for specifying the prefix for the node itself. To set the default
375
373
  # namespace, use the prefix +nil+
376
- # Example:
377
- # node = xml_doc.create_text_node 'address', 'content'
378
- # xml_doc.add_namespaces node, node_ns: 'abc', abc: 'http://abc.org', xyz: 'http://xyz.org'
379
- # # node => <abc:sample abc="http://abc.org" xyz="http://xyz.org">content</abc:sample>
380
374
  def add_namespaces(node, namespaces)
381
375
  XmlDocument.add_namespaces node, namespaces
382
376
  end
@@ -565,7 +559,7 @@ module Libis
565
559
  node
566
560
  end
567
561
 
568
- # Get the first node matching the tag. The node will be seached with XPath search term = "//#{tag}".
562
+ # Get the first node matching the tag. The node will be seached with XPath search term = "//#!{tag}".
569
563
  #
570
564
  # @param [String] tag XML tag to look for; XPath syntax is allowed
571
565
  # @param [Node] parent
@@ -573,7 +567,7 @@ module Libis
573
567
  get_nodes(tag, parent).first
574
568
  end
575
569
 
576
- # Get all the nodes matching the tag. The node will be seached with XPath search term = "//#{tag}".
570
+ # Get all the nodes matching the tag. The node will be seached with XPath search term = "//#!{tag}".
577
571
  #
578
572
  # @param [String] tag XML tag to look for; XPath syntax is allowed
579
573
  # @param [Node] parent