nokogiri-happymapper 0.9.0 → 0.10.0

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 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.