nokogiri-happymapper 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df07426975e91e50aa2aec4abd1583c85021a314296b10b06079e09126198120
4
- data.tar.gz: 910f91cb9ed649317e82605c8571c9a59744e23d53a327e2b228f32a8f275758
3
+ metadata.gz: 4037488d0324e3ef30b397bb88363d45724dbfff725c3fa34c011d6530463cce
4
+ data.tar.gz: 8db235cf492da7f19b42ff3b10b59556fd580039a31d7e0afe1041f613db67fa
5
5
  SHA512:
6
- metadata.gz: 9e2c3a0b37ab88d2887b6c5b3e6043c33606102a2e8389e591f259d26b5bd1cc973dcdcb73316b287c0132f2ce5e9c9f4fd3ecfa8904f779eccc5833bfab4342
7
- data.tar.gz: ba72beaa7d76b55268c0da2e0957604a280d66cba3b334a6fc4a0ee483afc21e3f696d5fd0c5c5ac6d103f94f9a6cab9b20c6541fd5ef36043b5a28a59aa9e05
6
+ metadata.gz: e74d65ab359a268fe0c0af0adba002cea497280111479f523858b063d36d6e182e1594b2cece9728f3ee10fd7d36fa024df1583e568042b915ebe502773d92d5
7
+ data.tar.gz: d5b197a8d3163ccbd27ed542546b61b6e364301ef43503c38636da40fec50f0c1a0e78f88ca161690dc2b495ea645684247c78faf9053b3ab555380750f0e91b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 / 2024-01-05
4
+
5
+ * Fix typo in README code sample ([#198] by [Spone])
6
+ * Improve custom parser option ([#219] by [dmke])
7
+ * Force namespace to be specified separately from tag ([#222] by [mvz])
8
+ * Pass options into wrapping element ([#225] by [jbennett])
9
+ * Support Ruby 3.0 through 3.3 and JRuby 9.4, dropping support for Ruby 2.6 and 2.7
10
+ ([#209], [#210], [#211] and [#230] by [mvz])
11
+
12
+ [Spone]: https://github.com/Spone
13
+ [dmke]: https://github.com/dmke
14
+ [jbennett]: https://github.com/jbennett
15
+ [mvz]: https://github.com/mvz
16
+
17
+ [#198]: https://github.com/mvz/happymapper/pull/198
18
+ [#209]: https://github.com/mvz/happymapper/pull/209
19
+ [#210]: https://github.com/mvz/happymapper/pull/210
20
+ [#211]: https://github.com/mvz/happymapper/pull/211
21
+ [#219]: https://github.com/mvz/happymapper/pull/219
22
+ [#222]: https://github.com/mvz/happymapper/pull/222
23
+ [#225]: https://github.com/mvz/happymapper/pull/225
24
+ [#230]: https://github.com/mvz/happymapper/pull/230
25
+
3
26
  ## 0.9.0 / 2022-01-21
4
27
 
5
28
  * Add official support for Ruby 2.7, 3.0 and 3.1
data/README.md CHANGED
@@ -241,7 +241,7 @@ the instance variable `@streets` if we ever need to the values as an array.
241
241
  Attributes are absolutely the same as `element` or `has_many`
242
242
 
243
243
  ```ruby
244
- attribute :location, String, tag: 'location
244
+ attribute :location, String, tag: 'location'
245
245
  ```
246
246
 
247
247
  Again, you can omit the tag if the attribute accessor symbol matches the name
@@ -3,8 +3,9 @@
3
3
  module HappyMapper
4
4
  class AnonymousMapper
5
5
  def parse(xml_content)
6
- # TODO: this should be able to handle all the types of functionality that parse is able
7
- # to handle which includes the text, xml document, node, fragment, etc.
6
+ # TODO: this should be able to handle all the types of functionality that
7
+ # parse is able to handle which includes the text, xml document, node,
8
+ # fragment, etc.
8
9
  xml = Nokogiri::XML(xml_content)
9
10
 
10
11
  klass = create_happymapper_class_from_node(xml.root)
@@ -26,7 +27,7 @@ module HappyMapper
26
27
  word = camel_cased_word.to_s.dup
27
28
  word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
28
29
  word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
29
- word.tr!('-', '_')
30
+ word.tr!("-", "_")
30
31
  word.downcase!
31
32
  word
32
33
  end
@@ -80,7 +81,7 @@ module HappyMapper
80
81
  # some content.
81
82
 
82
83
  if node.text?
83
- klass.content :content, String if node.content.strip != ''
84
+ klass.content :content, String if node.content.strip != ""
84
85
  return
85
86
  end
86
87
 
@@ -101,7 +102,7 @@ module HappyMapper
101
102
  options[:tag] = node.name
102
103
  namespace = node.namespace
103
104
  options[:namespace] = namespace.prefix if namespace
104
- options[:xpath] = './' unless element_type == String
105
+ options[:xpath] = "./" unless element_type == String
105
106
 
106
107
  klass.send(method, element_name, element_type, options)
107
108
  end
@@ -0,0 +1,466 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "happymapper/syntax_error"
4
+
5
+ module HappyMapper
6
+ # Class methods to be applied to classes that include the HappyMapper module.
7
+ module ClassMethods
8
+ #
9
+ # The xml has the following attributes defined.
10
+ #
11
+ # @example
12
+ #
13
+ # "<country code='de'>Germany</country>"
14
+ #
15
+ # # definition of the 'code' attribute within the class
16
+ # attribute :code, String
17
+ #
18
+ # @param [Symbol] name the name of the accessor that is created
19
+ # @param [String,Class] type the class name of the name of the class whcih
20
+ # the object will be converted upon parsing
21
+ # @param [Hash] options additional parameters to send to the relationship
22
+ #
23
+ def attribute(name, type, options = {})
24
+ attribute = Attribute.new(name, type, options)
25
+ @attributes[name] = attribute
26
+ attr_accessor attribute.method_name.intern
27
+ end
28
+
29
+ #
30
+ # The elements defined through {#attribute}.
31
+ #
32
+ # @return [Array<Attribute>] a list of the attributes defined for this class;
33
+ # an empty array is returned when there have been no attributes defined.
34
+ #
35
+ def attributes
36
+ @attributes.values
37
+ end
38
+
39
+ #
40
+ # Register a namespace that is used to persist the object namespace back to
41
+ # XML.
42
+ #
43
+ # @example
44
+ #
45
+ # register_namespace 'prefix', 'http://www.unicornland.com/prefix'
46
+ #
47
+ # # the output will contain the namespace defined
48
+ #
49
+ # "<outputXML xmlns:prefix="http://www.unicornland.com/prefix">
50
+ # ...
51
+ # </outputXML>"
52
+ #
53
+ # @param [String] name the xml prefix
54
+ # @param [String] href url for the xml namespace
55
+ #
56
+ def register_namespace(name, href)
57
+ @registered_namespaces.merge!(name => href)
58
+ end
59
+
60
+ #
61
+ # An element defined in the XML that is parsed.
62
+ #
63
+ # @example
64
+ #
65
+ # "<address location='home'>
66
+ # <city>Oldenburg</city>
67
+ # </address>"
68
+ #
69
+ # # definition of the 'city' element within the class
70
+ #
71
+ # element :city, String
72
+ #
73
+ # @param [Symbol] name the name of the accessor that is created
74
+ # @param [String,Class] type the class name of the name of the class whcih
75
+ # the object will be converted upon parsing
76
+ # @param [Hash] options additional parameters to send to the relationship
77
+ #
78
+ def element(name, type, options = {})
79
+ element = Element.new(name, type, options)
80
+ attr_accessor element.method_name.intern unless @elements[name]
81
+ @elements[name] = element
82
+ end
83
+
84
+ #
85
+ # The elements defined through {#element}, {#has_one}, and {#has_many}.
86
+ #
87
+ # @return [Array<Element>] a list of the elements contained defined for this
88
+ # class; an empty array is returned when there have been no elements
89
+ # defined.
90
+ #
91
+ def elements
92
+ @elements.values
93
+ end
94
+
95
+ #
96
+ # The value stored in the text node of the current element.
97
+ #
98
+ # @example
99
+ #
100
+ # "<firstName>Michael Jackson</firstName>"
101
+ #
102
+ # # definition of the 'firstName' text node within the class
103
+ #
104
+ # content :first_name, String
105
+ #
106
+ # @param [Symbol] name the name of the accessor that is created
107
+ # @param [String,Class] type the class name of the name of the class whcih
108
+ # the object will be converted upon parsing. By Default String class will be taken.
109
+ # @param [Hash] options additional parameters to send to the relationship
110
+ #
111
+ def content(name, type = String, options = {})
112
+ @content = TextNode.new(name, type, options)
113
+ attr_accessor @content.method_name.intern
114
+ end
115
+
116
+ #
117
+ # Sets the object to have xml content, this will assign the XML contents
118
+ # that are parsed to the attribute accessor xml_content. The object will
119
+ # respond to the method #xml_content and will return the XML data that
120
+ # it has parsed.
121
+ #
122
+ def has_xml_content
123
+ attr_accessor :xml_content
124
+ end
125
+
126
+ #
127
+ # The object has one of these elements in the XML. If there are multiple,
128
+ # the last one will be set to this value.
129
+ #
130
+ # @param [Symbol] name the name of the accessor that is created
131
+ # @param [String,Class] type the class name of the name of the class whcih
132
+ # the object will be converted upon parsing
133
+ # @param [Hash] options additional parameters to send to the relationship
134
+ #
135
+ # @see #element
136
+ #
137
+ def has_one(name, type, options = {})
138
+ element name, type, { single: true }.merge(options)
139
+ end
140
+
141
+ #
142
+ # The object has many of these elements in the XML.
143
+ #
144
+ # @param [Symbol] name the name of accessor that is created
145
+ # @param [String,Class] type the class name or the name of the class which
146
+ # the object will be converted upon parsing.
147
+ # @param [Hash] options additional parameters to send to the relationship
148
+ #
149
+ # @see #element
150
+ #
151
+ def has_many(name, type, options = {})
152
+ element name, type, { single: false }.merge(options)
153
+ end
154
+
155
+ #
156
+ # The list of registered after_parse callbacks.
157
+ #
158
+ def after_parse_callbacks
159
+ @after_parse_callbacks ||= []
160
+ end
161
+
162
+ #
163
+ # Register a new after_parse callback, given as a block.
164
+ #
165
+ # @yield [object] Yields the newly-parsed object to the block after parsing.
166
+ # Sub-objects will be already populated.
167
+ def after_parse(&block)
168
+ after_parse_callbacks.push(block)
169
+ end
170
+
171
+ #
172
+ # Specify a namespace if a node and all its children are all namespaced
173
+ # elements. This is simpler than passing the :namespace option to each
174
+ # defined element.
175
+ #
176
+ # @param [String] namespace the namespace to set as default for the class
177
+ # element.
178
+ #
179
+ def namespace(namespace = nil)
180
+ @namespace = namespace if namespace
181
+ @namespace if defined? @namespace
182
+ end
183
+
184
+ #
185
+ # @param [String] new_tag_name the name for the tag
186
+ #
187
+ def tag(new_tag_name)
188
+ return if new_tag_name.nil? || (name = new_tag_name.to_s).empty?
189
+
190
+ raise SyntaxError, "Unexpected ':' in tag name #{new_tag_name}" if name.include? ":"
191
+
192
+ @tag_name = name
193
+ end
194
+
195
+ #
196
+ # The name of the tag
197
+ #
198
+ # @return [String] the name of the tag as a string, downcased
199
+ #
200
+ def tag_name
201
+ @tag_name ||= name && name.to_s.split("::")[-1].downcase
202
+ end
203
+
204
+ # There is an XML tag that needs to be known for parsing and should be generated
205
+ # during a to_xml. But it doesn't need to be a class and the contained elements should
206
+ # be made available on the parent class
207
+ #
208
+ # @param [String] name the name of the element that is just a place holder
209
+ # @param [Proc] blk the element definitions inside the place holder tag
210
+ #
211
+ def wrap(name, options = {}, &blk)
212
+ # Get an anonymous HappyMapper that has 'name' as its tag and defined
213
+ # in '&blk'. Then save that to a class instance variable for later use
214
+ wrapper = AnonymousWrapperClassFactory.get(name, &blk)
215
+ wrapper_key = wrapper.inspect
216
+ @wrapper_anonymous_classes[wrapper_key] = wrapper
217
+
218
+ # Create getter/setter for each element and attribute defined on the
219
+ # anonymous HappyMapper onto this class. They get/set the value by
220
+ # passing thru to the anonymous class.
221
+ passthrus = wrapper.attributes + wrapper.elements
222
+ passthrus.each do |item|
223
+ method_name = item.method_name
224
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
225
+ def #{method_name} # def property
226
+ @#{name} ||= # @wrapper ||=
227
+ wrapper_anonymous_classes['#{wrapper_key}'].new # wrapper_anonymous_classes['#<Class:0x0000555b7d0b9220>'].new
228
+ @#{name}.#{method_name} # @wrapper.property
229
+ end # end
230
+
231
+ def #{method_name}=(value) # def property=(value)
232
+ @#{name} ||= # @wrapper ||=
233
+ wrapper_anonymous_classes['#{wrapper_key}'].new # wrapper_anonymous_classes['#<Class:0x0000555b7d0b9220>'].new
234
+ @#{name}.#{method_name} = value # @wrapper.property = value
235
+ end # end
236
+ RUBY
237
+ end
238
+
239
+ has_one name, wrapper, options
240
+ end
241
+
242
+ # The callback defined through {.with_nokogiri_config}.
243
+ #
244
+ # @return [Proc] the proc to pass to Nokogiri to setup parse options. nil if empty.
245
+ #
246
+ attr_reader :nokogiri_config_callback
247
+
248
+ # Register a config callback according to the block Nokogori expects when
249
+ # calling Nokogiri::XML::Document.parse().
250
+ #
251
+ # See http://nokogiri.org/Nokogiri/XML/Document.html#method-c-parse
252
+ #
253
+ # @param [Proc] the proc to pass to Nokogiri to setup parse options
254
+ #
255
+ def with_nokogiri_config(&blk)
256
+ @nokogiri_config_callback = blk
257
+ end
258
+
259
+ #
260
+ # @param [Nokogiri::XML::Node,Nokogiri:XML::Document,String] xml the XML
261
+ # contents to convert into Object.
262
+ # @param [Hash] options additional information for parsing.
263
+ # :single => true if requesting a single object, otherwise it defaults
264
+ # to retuning an array of multiple items.
265
+ # :xpath information where to start the parsing
266
+ # :namespace is the namespace to use for additional information.
267
+ #
268
+ def parse(xml, options = {})
269
+ # Capture any provided namespaces and merge in any namespaces that have
270
+ # been registered on the object.
271
+ namespaces = options[:namespaces] || {}
272
+ namespaces = namespaces.merge(@registered_namespaces)
273
+
274
+ # If the XML specified is an Node then we have what we need.
275
+ if xml.is_a?(Nokogiri::XML::Node) && !xml.is_a?(Nokogiri::XML::Document)
276
+ node = xml
277
+ else
278
+
279
+ unless xml.is_a?(Nokogiri::XML::Document)
280
+ # Attempt to parse the xml value with Nokogiri XML as a document
281
+ # and select the root element
282
+ xml = Nokogiri::XML(
283
+ xml, nil, nil,
284
+ Nokogiri::XML::ParseOptions::STRICT,
285
+ &nokogiri_config_callback
286
+ )
287
+ end
288
+ # Now xml is certainly an XML document: Select the root node of the document
289
+ node = xml.root
290
+
291
+ # merge any namespaces found on the xml node into the namespace hash
292
+ namespaces = namespaces.merge(xml.collect_namespaces)
293
+
294
+ # if the node name is equal to the tag name then the we are parsing the
295
+ # root element and that is important to record so that we can apply
296
+ # the correct xpath on the elements of this document.
297
+
298
+ root = node.name == tag_name
299
+ end
300
+
301
+ # If the :single option has been specified or we are at the root element
302
+ # then we are going to return a single element or nil if no nodes are found
303
+ single = root || options[:single]
304
+
305
+ # if a namespace has been provided then set the current namespace to it
306
+ # or use the namespace provided by the class
307
+ # or use the 'xmlns' namespace if defined
308
+
309
+ namespace =
310
+ options[:namespace] ||
311
+ self.namespace ||
312
+ namespaces.key?("xmlns") && "xmlns"
313
+
314
+ # from the options grab any nodes present and if none are present then
315
+ # perform the following to find the nodes for the given class
316
+
317
+ nodes = options.fetch(:nodes) do
318
+ find_nodes_to_parse(options, namespace, tag_name, namespaces, node, root)
319
+ end
320
+
321
+ # Nothing matching found, we can go ahead and return
322
+ return (single ? nil : []) if nodes.empty?
323
+
324
+ # If the :limit option has been specified then we are going to slice
325
+ # our node results by that amount to allow us the ability to deal with
326
+ # a large result set of data.
327
+
328
+ limit = options[:in_groups_of] || nodes.size
329
+
330
+ # If the limit of 0 has been specified then the user obviously wants
331
+ # none of the nodes that we are serving within this batch of nodes.
332
+
333
+ return [] if limit == 0
334
+
335
+ collection = []
336
+
337
+ nodes.each_slice(limit) do |slice|
338
+ part = slice.map do |n|
339
+ parse_node(n, options, namespace, namespaces)
340
+ end
341
+
342
+ # If a block has been provided and the user has requested that the objects
343
+ # be handled in groups then we should yield the slice of the objects to them
344
+ # otherwise continue to lump them together
345
+
346
+ if block_given? && options[:in_groups_of]
347
+ yield part
348
+ else
349
+ collection += part
350
+ end
351
+ end
352
+
353
+ # If we're parsing a single element then we are going to return the first
354
+ # item in the collection. Otherwise the return response is going to be an
355
+ # entire array of items.
356
+
357
+ if single
358
+ collection.first
359
+ else
360
+ collection
361
+ end
362
+ end
363
+
364
+ # @private
365
+ def defined_content
366
+ @content if defined? @content
367
+ end
368
+
369
+ private
370
+
371
+ def find_nodes_to_parse(options, namespace, tag_name, namespaces, node, root)
372
+ # when at the root use the xpath '/' otherwise use a more gready './/'
373
+ # unless an xpath has been specified, which should overwrite default
374
+ # and finally attach the current namespace if one has been defined
375
+ #
376
+
377
+ xpath = if options[:xpath]
378
+ options[:xpath].to_s.sub(%r{([^/])$}, '\1/')
379
+ elsif root
380
+ "/"
381
+ else
382
+ ".//"
383
+ end
384
+ if namespace
385
+ unless namespaces.find { |name, _| ["xmlns:#{namespace}", namespace].include? name }
386
+ return []
387
+ end
388
+
389
+ xpath += "#{namespace}:"
390
+ end
391
+
392
+ nodes = []
393
+
394
+ # when finding nodes, do it in this order:
395
+ # 1. specified tag if one has been provided
396
+ # 2. name of element
397
+ # 3. tag_name (derived from class name by default)
398
+
399
+ # If a tag has been provided we need to search for it.
400
+
401
+ if options.key?(:tag)
402
+ nodes = node.xpath(xpath + options[:tag].to_s, namespaces)
403
+ else
404
+
405
+ # This is the default case when no tag value is provided.
406
+ # First we use the name of the element `items` in `has_many items`
407
+ # Second we use the tag name which is the name of the class cleaned up
408
+
409
+ [options[:name], tag_name].compact.each do |xpath_ext|
410
+ nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
411
+ break if nodes && !nodes.empty?
412
+ end
413
+
414
+ end
415
+
416
+ nodes
417
+ end
418
+
419
+ def parse_node(node, options, namespace, namespaces)
420
+ # If an existing HappyMapper object is provided, update it with the
421
+ # values from the xml being parsed. Otherwise, create a new object
422
+
423
+ obj = options[:update] || new
424
+
425
+ attributes.each do |attr|
426
+ value = attr.from_xml_node(node, namespace, namespaces)
427
+ value = attr.default if value.nil?
428
+ obj.send(:"#{attr.method_name}=", value)
429
+ end
430
+
431
+ elements.each do |elem|
432
+ obj.send(:"#{elem.method_name}=", elem.from_xml_node(node, namespace, namespaces))
433
+ end
434
+
435
+ if (content = defined_content)
436
+ obj.send(:"#{content.method_name}=",
437
+ content.from_xml_node(node, namespace, namespaces))
438
+ end
439
+
440
+ # If the HappyMapper class has the method #xml_value=,
441
+ # attr_writer :xml_value, or attr_accessor :xml_value then we want to
442
+ # assign the current xml that we just parsed to the xml_value
443
+
444
+ if obj.respond_to?(:xml_value=)
445
+ obj.xml_value = node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
446
+ end
447
+
448
+ # If the HappyMapper class has the method #xml_content=,
449
+ # attr_write :xml_content, or attr_accessor :xml_content then we want to
450
+ # assign the child xml that we just parsed to the xml_content
451
+
452
+ if obj.respond_to?(:xml_content=)
453
+ node = node.children if node.respond_to?(:children)
454
+ obj.xml_content = node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
455
+ end
456
+
457
+ # Call any registered after_parse callbacks for the object's class
458
+
459
+ obj.class.after_parse_callbacks.each { |callback| callback.call(obj) }
460
+
461
+ # collect the object that we have created
462
+
463
+ obj
464
+ end
465
+ end
466
+ end
@@ -44,7 +44,7 @@ module HappyMapper
44
44
  attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options)
45
45
  .from_xml_node(result, namespace, xpath_options)
46
46
 
47
- method_name = xml_attribute.name.tr('-', '_')
47
+ method_name = xml_attribute.name.tr("-", "_")
48
48
  value.define_singleton_method(method_name) { attribute_value }
49
49
  end
50
50
  end
@@ -20,7 +20,7 @@ module HappyMapper
20
20
  self.tag = options[:tag] || name.to_s
21
21
  self.options = { single: true }.merge(options.merge(name: self.name))
22
22
 
23
- @xml_type = self.class.to_s.split('::').last.downcase
23
+ @xml_type = self.class.to_s.split("::").last.downcase
24
24
  end
25
25
 
26
26
  def constant
@@ -35,20 +35,20 @@ module HappyMapper
35
35
  def from_xml_node(node, namespace, xpath_options)
36
36
  namespace = options[:namespace] if options.key?(:namespace)
37
37
 
38
- if suported_type_registered?
38
+ if custom_parser_defined?
39
+ find(node, namespace, xpath_options) { |n| process_node_with_custom_parser(n) }
40
+ elsif suported_type_registered?
39
41
  find(node, namespace, xpath_options) { |n| process_node_as_supported_type(n) }
40
42
  elsif constant == XmlContent
41
43
  find(node, namespace, xpath_options) { |n| process_node_as_xml_content(n) }
42
- elsif custom_parser_defined?
43
- find(node, namespace, xpath_options) { |n| process_node_with_custom_parser(n) }
44
44
  else
45
45
  process_node_with_default_parser(node, namespaces: xpath_options)
46
46
  end
47
47
  end
48
48
 
49
49
  def xpath(namespace = self.namespace)
50
- xpath = ''
51
- xpath += './/' if options[:deep]
50
+ xpath = ""
51
+ xpath += ".//" if options[:deep]
52
52
  xpath += "#{namespace}:" if namespace
53
53
  xpath += tag
54
54
  # puts "xpath: #{xpath}"
@@ -56,7 +56,7 @@ module HappyMapper
56
56
  end
57
57
 
58
58
  def method_name
59
- @method_name ||= name.tr('-', '_')
59
+ @method_name ||= name.tr("-", "_")
60
60
  end
61
61
 
62
62
  #
@@ -118,11 +118,17 @@ module HappyMapper
118
118
  node.to_s
119
119
  end
120
120
 
121
- begin
122
- constant.send(options[:parser].to_sym, value)
123
- rescue StandardError
124
- nil
125
- end
121
+ custom_parser = create_custom_parser(options[:parser])
122
+
123
+ custom_parser.call(value)
124
+ end
125
+
126
+ def create_custom_parser(parser)
127
+ return parser if parser.respond_to?(:call)
128
+
129
+ proc { |value|
130
+ constant.send(parser.to_sym, value)
131
+ }
126
132
  end
127
133
 
128
134
  def process_node_with_default_parser(node, parse_options)
@@ -142,7 +148,7 @@ module HappyMapper
142
148
  end
143
149
 
144
150
  def convert_string_to_constant(type)
145
- names = type.split('::')
151
+ names = type.split("::")
146
152
  constant = Object
147
153
  names.each do |name|
148
154
  constant =
@@ -104,7 +104,7 @@ module HappyMapper
104
104
 
105
105
  register_type Time do |value|
106
106
  Time.parse(value.to_s)
107
- rescue StandardError
107
+ rescue ArgumentError
108
108
  Time.at(value.to_i)
109
109
  end
110
110
 
@@ -122,7 +122,7 @@ module HappyMapper
122
122
 
123
123
  register_type Integer do |value|
124
124
  value_to_i = value.to_i
125
- if value_to_i == 0 && !value.to_s.start_with?('0')
125
+ if value_to_i == 0 && !value.to_s.start_with?("0")
126
126
  nil
127
127
  else
128
128
  value_to_i
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HappyMapper
4
+ class SyntaxError < ::StandardError
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HappyMapper
4
- VERSION = '0.9.0'
4
+ VERSION = "0.10.0"
5
5
  end
data/lib/happymapper.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'nokogiri'
4
- require 'date'
5
- require 'time'
6
- require 'happymapper/version'
7
- require 'happymapper/anonymous_mapper'
3
+ require "nokogiri"
4
+ require "date"
5
+ require "time"
6
+ require "happymapper/version"
7
+ require "happymapper/anonymous_mapper"
8
+ require "happymapper/class_methods"
8
9
 
9
10
  module HappyMapper
10
11
  class Boolean; end
@@ -39,460 +40,11 @@ module HappyMapper
39
40
  base.extend ClassMethods
40
41
  end
41
42
 
42
- module ClassMethods
43
- #
44
- # The xml has the following attributes defined.
45
- #
46
- # @example
47
- #
48
- # "<country code='de'>Germany</country>"
49
- #
50
- # # definition of the 'code' attribute within the class
51
- # attribute :code, String
52
- #
53
- # @param [Symbol] name the name of the accessor that is created
54
- # @param [String,Class] type the class name of the name of the class whcih
55
- # the object will be converted upon parsing
56
- # @param [Hash] options additional parameters to send to the relationship
57
- #
58
- def attribute(name, type, options = {})
59
- attribute = Attribute.new(name, type, options)
60
- @attributes[name] = attribute
61
- attr_accessor attribute.method_name.intern
62
- end
63
-
64
- #
65
- # The elements defined through {#attribute}.
66
- #
67
- # @return [Array<Attribute>] a list of the attributes defined for this class;
68
- # an empty array is returned when there have been no attributes defined.
69
- #
70
- def attributes
71
- @attributes.values
72
- end
73
-
74
- #
75
- # Register a namespace that is used to persist the object namespace back to
76
- # XML.
77
- #
78
- # @example
79
- #
80
- # register_namespace 'prefix', 'http://www.unicornland.com/prefix'
81
- #
82
- # # the output will contain the namespace defined
83
- #
84
- # "<outputXML xmlns:prefix="http://www.unicornland.com/prefix">
85
- # ...
86
- # </outputXML>"
87
- #
88
- # @param [String] name the xml prefix
89
- # @param [String] href url for the xml namespace
90
- #
91
- def register_namespace(name, href)
92
- @registered_namespaces.merge!(name => href)
93
- end
94
-
95
- #
96
- # An element defined in the XML that is parsed.
97
- #
98
- # @example
99
- #
100
- # "<address location='home'>
101
- # <city>Oldenburg</city>
102
- # </address>"
103
- #
104
- # # definition of the 'city' element within the class
105
- #
106
- # element :city, String
107
- #
108
- # @param [Symbol] name the name of the accessor that is created
109
- # @param [String,Class] type the class name of the name of the class whcih
110
- # the object will be converted upon parsing
111
- # @param [Hash] options additional parameters to send to the relationship
112
- #
113
- def element(name, type, options = {})
114
- element = Element.new(name, type, options)
115
- attr_accessor element.method_name.intern unless @elements[name]
116
- @elements[name] = element
117
- end
118
-
119
- #
120
- # The elements defined through {#element}, {#has_one}, and {#has_many}.
121
- #
122
- # @return [Array<Element>] a list of the elements contained defined for this
123
- # class; an empty array is returned when there have been no elements
124
- # defined.
125
- #
126
- def elements
127
- @elements.values
128
- end
129
-
130
- #
131
- # The value stored in the text node of the current element.
132
- #
133
- # @example
134
- #
135
- # "<firstName>Michael Jackson</firstName>"
136
- #
137
- # # definition of the 'firstName' text node within the class
138
- #
139
- # content :first_name, String
140
- #
141
- # @param [Symbol] name the name of the accessor that is created
142
- # @param [String,Class] type the class name of the name of the class whcih
143
- # the object will be converted upon parsing. By Default String class will be taken.
144
- # @param [Hash] options additional parameters to send to the relationship
145
- #
146
- def content(name, type = String, options = {})
147
- @content = TextNode.new(name, type, options)
148
- attr_accessor @content.method_name.intern
149
- end
150
-
151
- #
152
- # Sets the object to have xml content, this will assign the XML contents
153
- # that are parsed to the attribute accessor xml_content. The object will
154
- # respond to the method #xml_content and will return the XML data that
155
- # it has parsed.
156
- #
157
- def has_xml_content
158
- attr_accessor :xml_content
159
- end
160
-
161
- #
162
- # The object has one of these elements in the XML. If there are multiple,
163
- # the last one will be set to this value.
164
- #
165
- # @param [Symbol] name the name of the accessor that is created
166
- # @param [String,Class] type the class name of the name of the class whcih
167
- # the object will be converted upon parsing
168
- # @param [Hash] options additional parameters to send to the relationship
169
- #
170
- # @see #element
171
- #
172
- def has_one(name, type, options = {})
173
- element name, type, { single: true }.merge(options)
174
- end
175
-
176
- #
177
- # The object has many of these elements in the XML.
178
- #
179
- # @param [Symbol] name the name of accessor that is created
180
- # @param [String,Class] type the class name or the name of the class which
181
- # the object will be converted upon parsing.
182
- # @param [Hash] options additional parameters to send to the relationship
183
- #
184
- # @see #element
185
- #
186
- def has_many(name, type, options = {})
187
- element name, type, { single: false }.merge(options)
188
- end
189
-
190
- #
191
- # The list of registered after_parse callbacks.
192
- #
193
- def after_parse_callbacks
194
- @after_parse_callbacks ||= []
195
- end
196
-
197
- #
198
- # Register a new after_parse callback, given as a block.
199
- #
200
- # @yield [object] Yields the newly-parsed object to the block after parsing.
201
- # Sub-objects will be already populated.
202
- def after_parse(&block)
203
- after_parse_callbacks.push(block)
204
- end
205
-
206
- #
207
- # Specify a namespace if a node and all its children are all namespaced
208
- # elements. This is simpler than passing the :namespace option to each
209
- # defined element.
210
- #
211
- # @param [String] namespace the namespace to set as default for the class
212
- # element.
213
- #
214
- def namespace(namespace = nil)
215
- @namespace = namespace if namespace
216
- @namespace if defined? @namespace
217
- end
218
-
219
- #
220
- # @param [String] new_tag_name the name for the tag
221
- #
222
- def tag(new_tag_name)
223
- @tag_name = new_tag_name.to_s unless new_tag_name.nil? || new_tag_name.to_s.empty?
224
- end
225
-
226
- #
227
- # The name of the tag
228
- #
229
- # @return [String] the name of the tag as a string, downcased
230
- #
231
- def tag_name
232
- @tag_name ||= name && name.to_s.split('::')[-1].downcase
233
- end
234
-
235
- # There is an XML tag that needs to be known for parsing and should be generated
236
- # during a to_xml. But it doesn't need to be a class and the contained elements should
237
- # be made available on the parent class
238
- #
239
- # @param [String] name the name of the element that is just a place holder
240
- # @param [Proc] blk the element definitions inside the place holder tag
241
- #
242
- def wrap(name, &blk)
243
- # Get an anonymous HappyMapper that has 'name' as its tag and defined
244
- # in '&blk'. Then save that to a class instance variable for later use
245
- wrapper = AnonymousWrapperClassFactory.get(name, &blk)
246
- wrapper_key = wrapper.inspect
247
- @wrapper_anonymous_classes[wrapper_key] = wrapper
248
-
249
- # Create getter/setter for each element and attribute defined on the anonymous HappyMapper
250
- # onto this class. They get/set the value by passing thru to the anonymous class.
251
- passthrus = wrapper.attributes + wrapper.elements
252
- passthrus.each do |item|
253
- method_name = item.method_name
254
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
255
- def #{method_name} # def property
256
- @#{name} ||= # @wrapper ||=
257
- wrapper_anonymous_classes['#{wrapper_key}'].new # wrapper_anonymous_classes['#<Class:0x0000555b7d0b9220>'].new
258
- @#{name}.#{method_name} # @wrapper.property
259
- end # end
260
-
261
- def #{method_name}=(value) # def property=(value)
262
- @#{name} ||= # @wrapper ||=
263
- wrapper_anonymous_classes['#{wrapper_key}'].new # wrapper_anonymous_classes['#<Class:0x0000555b7d0b9220>'].new
264
- @#{name}.#{method_name} = value # @wrapper.property = value
265
- end # end
266
- RUBY
267
- end
268
-
269
- has_one name, wrapper
270
- end
271
-
272
- # The callback defined through {.with_nokogiri_config}.
273
- #
274
- # @return [Proc] the proc to pass to Nokogiri to setup parse options. nil if empty.
275
- #
276
- attr_reader :nokogiri_config_callback
277
-
278
- # Register a config callback according to the block Nokogori expects when
279
- # calling Nokogiri::XML::Document.parse().
280
- #
281
- # See http://nokogiri.org/Nokogiri/XML/Document.html#method-c-parse
282
- #
283
- # @param [Proc] the proc to pass to Nokogiri to setup parse options
284
- #
285
- def with_nokogiri_config(&blk)
286
- @nokogiri_config_callback = blk
287
- end
288
-
289
- #
290
- # @param [Nokogiri::XML::Node,Nokogiri:XML::Document,String] xml the XML
291
- # contents to convert into Object.
292
- # @param [Hash] options additional information for parsing.
293
- # :single => true if requesting a single object, otherwise it defaults
294
- # to retuning an array of multiple items.
295
- # :xpath information where to start the parsing
296
- # :namespace is the namespace to use for additional information.
297
- #
298
- def parse(xml, options = {})
299
- # Capture any provided namespaces and merge in any namespaces that have
300
- # been registered on the object.
301
- namespaces = options[:namespaces] || {}
302
- namespaces = namespaces.merge(@registered_namespaces)
303
-
304
- # If the XML specified is an Node then we have what we need.
305
- if xml.is_a?(Nokogiri::XML::Node) && !xml.is_a?(Nokogiri::XML::Document)
306
- node = xml
307
- else
308
-
309
- unless xml.is_a?(Nokogiri::XML::Document)
310
- # Attempt to parse the xml value with Nokogiri XML as a document
311
- # and select the root element
312
- xml = Nokogiri::XML(
313
- xml, nil, nil,
314
- Nokogiri::XML::ParseOptions::STRICT,
315
- &nokogiri_config_callback
316
- )
317
- end
318
- # Now xml is certainly an XML document: Select the root node of the document
319
- node = xml.root
320
-
321
- # merge any namespaces found on the xml node into the namespace hash
322
- namespaces = namespaces.merge(xml.collect_namespaces)
323
-
324
- # if the node name is equal to the tag name then the we are parsing the
325
- # root element and that is important to record so that we can apply
326
- # the correct xpath on the elements of this document.
327
-
328
- root = node.name == tag_name
329
- end
330
-
331
- # If the :single option has been specified or we are at the root element
332
- # then we are going to return a single element or nil if no nodes are found
333
- single = root || options[:single]
334
-
335
- # if a namespace has been provided then set the current namespace to it
336
- # or use the namespace provided by the class
337
- # or use the 'xmlns' namespace if defined
338
-
339
- namespace = options[:namespace] || self.namespace || namespaces.key?('xmlns') && 'xmlns'
340
-
341
- # from the options grab any nodes present and if none are present then
342
- # perform the following to find the nodes for the given class
343
-
344
- nodes = options.fetch(:nodes) do
345
- find_nodes_to_parse(options, namespace, tag_name, namespaces, node, root)
346
- end
347
-
348
- # Nothing matching found, we can go ahead and return
349
- return (single ? nil : []) if nodes.empty?
350
-
351
- # If the :limit option has been specified then we are going to slice
352
- # our node results by that amount to allow us the ability to deal with
353
- # a large result set of data.
354
-
355
- limit = options[:in_groups_of] || nodes.size
356
-
357
- # If the limit of 0 has been specified then the user obviously wants
358
- # none of the nodes that we are serving within this batch of nodes.
359
-
360
- return [] if limit == 0
361
-
362
- collection = []
363
-
364
- nodes.each_slice(limit) do |slice|
365
- part = slice.map do |n|
366
- parse_node(n, options, namespace, namespaces)
367
- end
368
-
369
- # If a block has been provided and the user has requested that the objects
370
- # be handled in groups then we should yield the slice of the objects to them
371
- # otherwise continue to lump them together
372
-
373
- if block_given? && options[:in_groups_of]
374
- yield part
375
- else
376
- collection += part
377
- end
378
- end
379
-
380
- # If we're parsing a single element then we are going to return the first
381
- # item in the collection. Otherwise the return response is going to be an
382
- # entire array of items.
383
-
384
- if single
385
- collection.first
386
- else
387
- collection
388
- end
389
- end
390
-
391
- # @private
392
- def defined_content
393
- @content if defined? @content
394
- end
395
-
396
- private
397
-
398
- def find_nodes_to_parse(options, namespace, tag_name, namespaces, node, root)
399
- # when at the root use the xpath '/' otherwise use a more gready './/'
400
- # unless an xpath has been specified, which should overwrite default
401
- # and finally attach the current namespace if one has been defined
402
- #
403
-
404
- xpath = if options[:xpath]
405
- options[:xpath].to_s.sub(%r{([^/])$}, '\1/')
406
- elsif root
407
- '/'
408
- else
409
- './/'
410
- end
411
- if namespace
412
- return [] unless namespaces.find { |name, _url| ["xmlns:#{namespace}", namespace].include? name }
413
-
414
- xpath += "#{namespace}:"
415
- end
416
-
417
- nodes = []
418
-
419
- # when finding nodes, do it in this order:
420
- # 1. specified tag if one has been provided
421
- # 2. name of element
422
- # 3. tag_name (derived from class name by default)
423
-
424
- # If a tag has been provided we need to search for it.
425
-
426
- if options.key?(:tag)
427
- nodes = node.xpath(xpath + options[:tag].to_s, namespaces)
428
- else
429
-
430
- # This is the default case when no tag value is provided.
431
- # First we use the name of the element `items` in `has_many items`
432
- # Second we use the tag name which is the name of the class cleaned up
433
-
434
- [options[:name], tag_name].compact.each do |xpath_ext|
435
- nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
436
- break if nodes && !nodes.empty?
437
- end
438
-
439
- end
440
-
441
- nodes
442
- end
443
-
444
- def parse_node(node, options, namespace, namespaces)
445
- # If an existing HappyMapper object is provided, update it with the
446
- # values from the xml being parsed. Otherwise, create a new object
447
-
448
- obj = options[:update] || new
449
-
450
- attributes.each do |attr|
451
- value = attr.from_xml_node(node, namespace, namespaces)
452
- value = attr.default if value.nil?
453
- obj.send("#{attr.method_name}=", value)
454
- end
455
-
456
- elements.each do |elem|
457
- obj.send("#{elem.method_name}=", elem.from_xml_node(node, namespace, namespaces))
458
- end
459
-
460
- if (content = defined_content)
461
- obj.send("#{content.method_name}=", content.from_xml_node(node, namespace, namespaces))
462
- end
463
-
464
- # If the HappyMapper class has the method #xml_value=,
465
- # attr_writer :xml_value, or attr_accessor :xml_value then we want to
466
- # assign the current xml that we just parsed to the xml_value
467
-
468
- if obj.respond_to?(:xml_value=)
469
- obj.xml_value = node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
470
- end
471
-
472
- # If the HappyMapper class has the method #xml_content=,
473
- # attr_write :xml_content, or attr_accessor :xml_content then we want to
474
- # assign the child xml that we just parsed to the xml_content
475
-
476
- if obj.respond_to?(:xml_content=)
477
- node = node.children if node.respond_to?(:children)
478
- obj.xml_content = node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
479
- end
480
-
481
- # Call any registered after_parse callbacks for the object's class
482
-
483
- obj.class.after_parse_callbacks.each { |callback| callback.call(obj) }
484
-
485
- # collect the object that we have created
486
-
487
- obj
488
- end
489
- end
490
-
491
43
  # Set all attributes with a default to their default values
492
44
  def initialize
493
45
  super
494
46
  self.class.attributes.reject { |attr| attr.default.nil? }.each do |attr|
495
- send("#{attr.method_name}=", attr.default)
47
+ send(:"#{attr.method_name}=", attr.default)
496
48
  end
497
49
  end
498
50
 
@@ -549,7 +101,9 @@ module HappyMapper
549
101
  # in a recursive call from the parent doc. Then append
550
102
  # any attributes to the element that were defined above.
551
103
  #
552
- builder.send("#{tag_from_parent || self.class.tag_name}_", attributes) do |xml|
104
+
105
+ tag_name = tag_from_parent || self.class.tag_name
106
+ builder.send(:"#{tag_name}_", attributes) do |xml|
553
107
  register_namespaces_with_builder(builder)
554
108
 
555
109
  xml.parent.namespace =
@@ -581,7 +135,7 @@ module HappyMapper
581
135
  # xml generated from the object. If an XML builder instance was specified
582
136
  # then we assume that has been called recursively to generate a larger
583
137
  # XML document.
584
- write_out_to_xml ? builder.to_xml.force_encoding('UTF-8') : builder
138
+ write_out_to_xml ? builder.to_xml.force_encoding("UTF-8") : builder
585
139
  end
586
140
 
587
141
  # Parse the xml and update this instance. This does not update instances
@@ -631,7 +185,7 @@ module HappyMapper
631
185
  # Find the attributes for the class and collect them into an array
632
186
  # that will be placed into a Hash structure
633
187
  #
634
- attributes = self.class.attributes.collect do |attribute|
188
+ attributes = self.class.attributes.filter_map do |attribute|
635
189
  #
636
190
  # If an attribute is marked as read_only then we want to ignore the attribute
637
191
  # when it comes to saving the xml document; so we will not go into any of
@@ -654,8 +208,8 @@ module HappyMapper
654
208
  next if value.nil? && !attribute.options[:state_when_nil]
655
209
 
656
210
  attribute_namespace = attribute.options[:namespace]
657
- ["#{attribute_namespace ? "#{attribute_namespace}:" : ''}#{attribute.tag}", value]
658
- end.compact
211
+ ["#{attribute_namespace ? "#{attribute_namespace}:" : ""}#{attribute.tag}", value]
212
+ end
659
213
 
660
214
  attributes.to_h
661
215
  end
@@ -672,7 +226,7 @@ module HappyMapper
672
226
  return unless self.class.instance_variable_get(:@registered_namespaces)
673
227
 
674
228
  self.class.instance_variable_get(:@registered_namespaces).sort.each do |name, href|
675
- name = nil if name == 'xmlns'
229
+ name = nil if name == "xmlns"
676
230
  builder.doc.root.add_namespace(name, href)
677
231
  end
678
232
  end
@@ -723,16 +277,19 @@ module HappyMapper
723
277
 
724
278
  elsif !item.nil? || element.options[:state_when_nil]
725
279
 
726
- item_namespace = element.options[:namespace] || self.class.namespace || default_namespace
280
+ item_namespace =
281
+ element.options[:namespace] ||
282
+ self.class.namespace ||
283
+ default_namespace
727
284
 
728
285
  #
729
286
  # When a value exists or the tag should always be emitted,
730
287
  # we should append the value for the tag
731
288
  #
732
289
  if item_namespace
733
- xml[item_namespace].send("#{tag}_", item.to_s)
290
+ xml[item_namespace].send(:"#{tag}_", item.to_s)
734
291
  else
735
- xml.send("#{tag}_", item.to_s)
292
+ xml.send(:"#{tag}_", item.to_s)
736
293
  end
737
294
  end
738
295
  end
@@ -743,8 +300,8 @@ module HappyMapper
743
300
  end
744
301
  end
745
302
 
746
- require 'happymapper/supported_types'
747
- require 'happymapper/item'
748
- require 'happymapper/attribute'
749
- require 'happymapper/element'
750
- require 'happymapper/text_node'
303
+ require "happymapper/supported_types"
304
+ require "happymapper/item"
305
+ require "happymapper/attribute"
306
+ require "happymapper/element"
307
+ require "happymapper/text_node"
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # TODO: Deprecate requiring 'happymapper' in favor of 'nokogiri-happymapper'.
4
- require 'happymapper'
4
+ require "happymapper"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nokogiri-happymapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damien Le Berrigaud
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2022-01-21 00:00:00.000000000 Z
17
+ date: 2024-01-05 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: nokogiri
@@ -92,70 +92,70 @@ dependencies:
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: 1.25.0
95
+ version: '1.56'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: 1.25.0
102
+ version: '1.56'
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: rubocop-packaging
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: 0.5.0
109
+ version: 0.5.2
110
110
  type: :development
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: 0.5.0
116
+ version: 0.5.2
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: rubocop-performance
119
119
  requirement: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: 1.13.0
123
+ version: '1.19'
124
124
  type: :development
125
125
  prerelease: false
126
126
  version_requirements: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: 1.13.0
130
+ version: '1.19'
131
131
  - !ruby/object:Gem::Dependency
132
132
  name: rubocop-rspec
133
133
  requirement: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: 2.7.0
137
+ version: '2.24'
138
138
  type: :development
139
139
  prerelease: false
140
140
  version_requirements: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: 2.7.0
144
+ version: '2.24'
145
145
  - !ruby/object:Gem::Dependency
146
146
  name: simplecov
147
147
  requirement: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - "~>"
150
150
  - !ruby/object:Gem::Version
151
- version: 0.21.1
151
+ version: 0.22.0
152
152
  type: :development
153
153
  prerelease: false
154
154
  version_requirements: !ruby/object:Gem::Requirement
155
155
  requirements:
156
156
  - - "~>"
157
157
  - !ruby/object:Gem::Version
158
- version: 0.21.1
158
+ version: 0.22.0
159
159
  description: Object to XML Mapping Library, using Nokogiri (fork from John Nunemaker's
160
160
  Happymapper)
161
161
  email: matijs@matijs.net
@@ -172,9 +172,11 @@ files:
172
172
  - lib/happymapper.rb
173
173
  - lib/happymapper/anonymous_mapper.rb
174
174
  - lib/happymapper/attribute.rb
175
+ - lib/happymapper/class_methods.rb
175
176
  - lib/happymapper/element.rb
176
177
  - lib/happymapper/item.rb
177
178
  - lib/happymapper/supported_types.rb
179
+ - lib/happymapper/syntax_error.rb
178
180
  - lib/happymapper/text_node.rb
179
181
  - lib/happymapper/version.rb
180
182
  - lib/nokogiri-happymapper.rb
@@ -191,14 +193,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
191
193
  requirements:
192
194
  - - ">="
193
195
  - !ruby/object:Gem::Version
194
- version: 2.6.0
196
+ version: 3.0.0
195
197
  required_rubygems_version: !ruby/object:Gem::Requirement
196
198
  requirements:
197
199
  - - ">="
198
200
  - !ruby/object:Gem::Version
199
201
  version: '0'
200
202
  requirements: []
201
- rubygems_version: 3.3.3
203
+ rubygems_version: 3.5.3
202
204
  signing_key:
203
205
  specification_version: 4
204
206
  summary: Provides a simple way to map XML to Ruby Objects and back again.