nokogiri-happymapper 0.8.1 → 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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +1 -4
  4. data/lib/happymapper/anonymous_mapper.rb +6 -5
  5. data/lib/happymapper/class_methods.rb +466 -0
  6. data/lib/happymapper/element.rb +3 -3
  7. data/lib/happymapper/item.rb +19 -13
  8. data/lib/happymapper/supported_types.rb +4 -6
  9. data/lib/happymapper/syntax_error.rb +6 -0
  10. data/lib/happymapper/version.rb +1 -1
  11. data/lib/happymapper.rb +54 -505
  12. data/lib/nokogiri-happymapper.rb +1 -1
  13. metadata +100 -107
  14. data/spec/features/after_parse_callbacks_spec.rb +0 -32
  15. data/spec/features/attribute_default_value_spec.rb +0 -48
  16. data/spec/features/attributes_spec.rb +0 -35
  17. data/spec/features/has_many_empty_array_spec.rb +0 -44
  18. data/spec/features/ignay_spec.rb +0 -92
  19. data/spec/features/inheritance_spec.rb +0 -121
  20. data/spec/features/mixed_namespaces_spec.rb +0 -60
  21. data/spec/features/parse_with_object_to_update_spec.rb +0 -116
  22. data/spec/features/same_tag_different_meaning_spec.rb +0 -44
  23. data/spec/features/to_xml_spec.rb +0 -205
  24. data/spec/features/to_xml_with_namespaces_spec.rb +0 -237
  25. data/spec/features/wildcard_tag_name_spec.rb +0 -110
  26. data/spec/features/wrap_spec.rb +0 -87
  27. data/spec/features/xpath_spec.rb +0 -84
  28. data/spec/fixtures/address.xml +0 -9
  29. data/spec/fixtures/ambigous_items.xml +0 -22
  30. data/spec/fixtures/analytics.xml +0 -61
  31. data/spec/fixtures/analytics_profile.xml +0 -127
  32. data/spec/fixtures/atom.xml +0 -19
  33. data/spec/fixtures/commit.xml +0 -52
  34. data/spec/fixtures/current_weather.xml +0 -89
  35. data/spec/fixtures/current_weather_missing_elements.xml +0 -18
  36. data/spec/fixtures/default_namespace_combi.xml +0 -6
  37. data/spec/fixtures/dictionary.xml +0 -20
  38. data/spec/fixtures/family_tree.xml +0 -21
  39. data/spec/fixtures/inagy.xml +0 -85
  40. data/spec/fixtures/lastfm.xml +0 -355
  41. data/spec/fixtures/multiple_namespaces.xml +0 -170
  42. data/spec/fixtures/multiple_primitives.xml +0 -5
  43. data/spec/fixtures/optional_attributes.xml +0 -6
  44. data/spec/fixtures/pita.xml +0 -133
  45. data/spec/fixtures/posts.xml +0 -23
  46. data/spec/fixtures/product_default_namespace.xml +0 -18
  47. data/spec/fixtures/product_no_namespace.xml +0 -10
  48. data/spec/fixtures/product_single_namespace.xml +0 -10
  49. data/spec/fixtures/quarters.xml +0 -19
  50. data/spec/fixtures/radar.xml +0 -21
  51. data/spec/fixtures/set_config_options.xml +0 -3
  52. data/spec/fixtures/statuses.xml +0 -422
  53. data/spec/fixtures/subclass_namespace.xml +0 -50
  54. data/spec/fixtures/unformatted_address.xml +0 -1
  55. data/spec/fixtures/wrapper.xml +0 -11
  56. data/spec/happymapper/anonymous_mapper_spec.rb +0 -158
  57. data/spec/happymapper/attribute_spec.rb +0 -12
  58. data/spec/happymapper/item_spec.rb +0 -177
  59. data/spec/happymapper_spec.rb +0 -1208
  60. data/spec/spec_helper.rb +0 -25
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HappyMapper
4
- VERSION = '0.8.1'
4
+ VERSION = "0.10.0"
5
5
  end
data/lib/happymapper.rb CHANGED
@@ -1,13 +1,15 @@
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
12
+
11
13
  class XmlContent; end
12
14
 
13
15
  def self.parse(xml_content)
@@ -38,456 +40,11 @@ module HappyMapper
38
40
  base.extend ClassMethods
39
41
  end
40
42
 
41
- module ClassMethods
42
- #
43
- # The xml has the following attributes defined.
44
- #
45
- # @example
46
- #
47
- # "<country code='de'>Germany</country>"
48
- #
49
- # # definition of the 'code' attribute within the class
50
- # attribute :code, String
51
- #
52
- # @param [Symbol] name the name of the accessor that is created
53
- # @param [String,Class] type the class name of the name of the class whcih
54
- # the object will be converted upon parsing
55
- # @param [Hash] options additional parameters to send to the relationship
56
- #
57
- def attribute(name, type, options = {})
58
- attribute = Attribute.new(name, type, options)
59
- @attributes[name] = attribute
60
- attr_accessor attribute.method_name.intern
61
- end
62
-
63
- #
64
- # The elements defined through {#attribute}.
65
- #
66
- # @return [Array<Attribute>] a list of the attributes defined for this class;
67
- # an empty array is returned when there have been no attributes defined.
68
- #
69
- def attributes
70
- @attributes.values
71
- end
72
-
73
- #
74
- # Register a namespace that is used to persist the object namespace back to
75
- # XML.
76
- #
77
- # @example
78
- #
79
- # register_namespace 'prefix', 'http://www.unicornland.com/prefix'
80
- #
81
- # # the output will contain the namespace defined
82
- #
83
- # "<outputXML xmlns:prefix="http://www.unicornland.com/prefix">
84
- # ...
85
- # </outputXML>"
86
- #
87
- # @param [String] name the xml prefix
88
- # @param [String] href url for the xml namespace
89
- #
90
- def register_namespace(name, href)
91
- @registered_namespaces.merge!(name => href)
92
- end
93
-
94
- #
95
- # An element defined in the XML that is parsed.
96
- #
97
- # @example
98
- #
99
- # "<address location='home'>
100
- # <city>Oldenburg</city>
101
- # </address>"
102
- #
103
- # # definition of the 'city' element within the class
104
- #
105
- # element :city, String
106
- #
107
- # @param [Symbol] name the name of the accessor that is created
108
- # @param [String,Class] type the class name of the name of the class whcih
109
- # the object will be converted upon parsing
110
- # @param [Hash] options additional parameters to send to the relationship
111
- #
112
- def element(name, type, options = {})
113
- element = Element.new(name, type, options)
114
- attr_accessor element.method_name.intern unless @elements[name]
115
- @elements[name] = element
116
- end
117
-
118
- #
119
- # The elements defined through {#element}, {#has_one}, and {#has_many}.
120
- #
121
- # @return [Array<Element>] a list of the elements contained defined for this
122
- # class; an empty array is returned when there have been no elements
123
- # defined.
124
- #
125
- def elements
126
- @elements.values
127
- end
128
-
129
- #
130
- # The value stored in the text node of the current element.
131
- #
132
- # @example
133
- #
134
- # "<firstName>Michael Jackson</firstName>"
135
- #
136
- # # definition of the 'firstName' text node within the class
137
- #
138
- # content :first_name, String
139
- #
140
- # @param [Symbol] name the name of the accessor that is created
141
- # @param [String,Class] type the class name of the name of the class whcih
142
- # the object will be converted upon parsing. By Default String class will be taken.
143
- # @param [Hash] options additional parameters to send to the relationship
144
- #
145
- def content(name, type = String, options = {})
146
- @content = TextNode.new(name, type, options)
147
- attr_accessor @content.method_name.intern
148
- end
149
-
150
- #
151
- # Sets the object to have xml content, this will assign the XML contents
152
- # that are parsed to the attribute accessor xml_content. The object will
153
- # respond to the method #xml_content and will return the XML data that
154
- # it has parsed.
155
- #
156
- def has_xml_content
157
- attr_accessor :xml_content
158
- end
159
-
160
- #
161
- # The object has one of these elements in the XML. If there are multiple,
162
- # the last one will be set to this value.
163
- #
164
- # @param [Symbol] name the name of the accessor that is created
165
- # @param [String,Class] type the class name of the name of the class whcih
166
- # the object will be converted upon parsing
167
- # @param [Hash] options additional parameters to send to the relationship
168
- #
169
- # @see #element
170
- #
171
- def has_one(name, type, options = {})
172
- element name, type, { single: true }.merge(options)
173
- end
174
-
175
- #
176
- # The object has many of these elements in the XML.
177
- #
178
- # @param [Symbol] name the name of accessor that is created
179
- # @param [String,Class] type the class name or the name of the class which
180
- # the object will be converted upon parsing.
181
- # @param [Hash] options additional parameters to send to the relationship
182
- #
183
- # @see #element
184
- #
185
- def has_many(name, type, options = {})
186
- element name, type, { single: false }.merge(options)
187
- end
188
-
189
- #
190
- # The list of registered after_parse callbacks.
191
- #
192
- def after_parse_callbacks
193
- @after_parse_callbacks ||= []
194
- end
195
-
196
- #
197
- # Register a new after_parse callback, given as a block.
198
- #
199
- # @yield [object] Yields the newly-parsed object to the block after parsing.
200
- # Sub-objects will be already populated.
201
- def after_parse(&block)
202
- after_parse_callbacks.push(block)
203
- end
204
-
205
- #
206
- # Specify a namespace if a node and all its children are all namespaced
207
- # elements. This is simpler than passing the :namespace option to each
208
- # defined element.
209
- #
210
- # @param [String] namespace the namespace to set as default for the class
211
- # element.
212
- #
213
- def namespace(namespace = nil)
214
- @namespace = namespace if namespace
215
- @namespace if defined? @namespace
216
- end
217
-
218
- #
219
- # @param [String] new_tag_name the name for the tag
220
- #
221
- def tag(new_tag_name)
222
- @tag_name = new_tag_name.to_s unless new_tag_name.nil? || new_tag_name.to_s.empty?
223
- end
224
-
225
- #
226
- # The name of the tag
227
- #
228
- # @return [String] the name of the tag as a string, downcased
229
- #
230
- def tag_name
231
- @tag_name ||= name && name.to_s.split('::')[-1].downcase
232
- end
233
-
234
- # There is an XML tag that needs to be known for parsing and should be generated
235
- # during a to_xml. But it doesn't need to be a class and the contained elements should
236
- # be made available on the parent class
237
- #
238
- # @param [String] name the name of the element that is just a place holder
239
- # @param [Proc] blk the element definitions inside the place holder tag
240
- #
241
- def wrap(name, &blk)
242
- # Get an anonymous HappyMapper that has 'name' as its tag and defined
243
- # in '&blk'. Then save that to a class instance variable for later use
244
- wrapper = AnonymousWrapperClassFactory.get(name, &blk)
245
- wrapper_key = wrapper.inspect
246
- @wrapper_anonymous_classes[wrapper_key] = wrapper
247
-
248
- # Create getter/setter for each element and attribute defined on the anonymous HappyMapper
249
- # onto this class. They get/set the value by passing thru to the anonymous class.
250
- passthrus = wrapper.attributes + wrapper.elements
251
- passthrus.each do |item|
252
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
253
- def #{item.method_name}
254
- @#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper_key}'].new
255
- @#{name}.#{item.method_name}
256
- end
257
- def #{item.method_name}=(value)
258
- @#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper_key}'].new
259
- @#{name}.#{item.method_name} = value
260
- end
261
- RUBY
262
- end
263
-
264
- has_one name, wrapper
265
- end
266
-
267
- # The callback defined through {.with_nokogiri_config}.
268
- #
269
- # @return [Proc] the proc to pass to Nokogiri to setup parse options. nil if empty.
270
- #
271
- attr_reader :nokogiri_config_callback
272
-
273
- # Register a config callback according to the block Nokogori expects when
274
- # calling Nokogiri::XML::Document.parse().
275
- #
276
- # See http://nokogiri.org/Nokogiri/XML/Document.html#method-c-parse
277
- #
278
- # @param [Proc] the proc to pass to Nokogiri to setup parse options
279
- #
280
- def with_nokogiri_config(&blk)
281
- @nokogiri_config_callback = blk
282
- end
283
-
284
- #
285
- # @param [Nokogiri::XML::Node,Nokogiri:XML::Document,String] xml the XML
286
- # contents to convert into Object.
287
- # @param [Hash] options additional information for parsing.
288
- # :single => true if requesting a single object, otherwise it defaults
289
- # to retuning an array of multiple items.
290
- # :xpath information where to start the parsing
291
- # :namespace is the namespace to use for additional information.
292
- #
293
- def parse(xml, options = {})
294
- # Capture any provided namespaces and merge in any namespaces that have
295
- # been registered on the object.
296
- namespaces = options[:namespaces] || {}
297
- namespaces = namespaces.merge(@registered_namespaces)
298
-
299
- # If the XML specified is an Node then we have what we need.
300
- if xml.is_a?(Nokogiri::XML::Node) && !xml.is_a?(Nokogiri::XML::Document)
301
- node = xml
302
- else
303
-
304
- unless xml.is_a?(Nokogiri::XML::Document)
305
- # Attempt to parse the xml value with Nokogiri XML as a document
306
- # and select the root element
307
- xml = Nokogiri::XML(
308
- xml, nil, nil,
309
- Nokogiri::XML::ParseOptions::STRICT,
310
- &nokogiri_config_callback
311
- )
312
- end
313
- # Now xml is certainly an XML document: Select the root node of the document
314
- node = xml.root
315
-
316
- # merge any namespaces found on the xml node into the namespace hash
317
- namespaces = namespaces.merge(xml.collect_namespaces)
318
-
319
- # if the node name is equal to the tag name then the we are parsing the
320
- # root element and that is important to record so that we can apply
321
- # the correct xpath on the elements of this document.
322
-
323
- root = node.name == tag_name
324
- end
325
-
326
- # If the :single option has been specified or we are at the root element
327
- # then we are going to return a single element or nil if no nodes are found
328
- single = root || options[:single]
329
-
330
- # if a namespace has been provided then set the current namespace to it
331
- # or use the namespace provided by the class
332
- # or use the 'xmlns' namespace if defined
333
-
334
- namespace = options[:namespace] || self.namespace || namespaces.key?('xmlns') && 'xmlns'
335
-
336
- # from the options grab any nodes present and if none are present then
337
- # perform the following to find the nodes for the given class
338
-
339
- nodes = options.fetch(:nodes) do
340
- find_nodes_to_parse(options, namespace, tag_name, namespaces, node, root)
341
- end
342
-
343
- # Nothing matching found, we can go ahead and return
344
- return (single ? nil : []) if nodes.empty?
345
-
346
- # If the :limit option has been specified then we are going to slice
347
- # our node results by that amount to allow us the ability to deal with
348
- # a large result set of data.
349
-
350
- limit = options[:in_groups_of] || nodes.size
351
-
352
- # If the limit of 0 has been specified then the user obviously wants
353
- # none of the nodes that we are serving within this batch of nodes.
354
-
355
- return [] if limit == 0
356
-
357
- collection = []
358
-
359
- nodes.each_slice(limit) do |slice|
360
- part = slice.map do |n|
361
- parse_node(n, options, namespace, namespaces)
362
- end
363
-
364
- # If a block has been provided and the user has requested that the objects
365
- # be handled in groups then we should yield the slice of the objects to them
366
- # otherwise continue to lump them together
367
-
368
- if block_given? && options[:in_groups_of]
369
- yield part
370
- else
371
- collection += part
372
- end
373
- end
374
-
375
- # If we're parsing a single element then we are going to return the first
376
- # item in the collection. Otherwise the return response is going to be an
377
- # entire array of items.
378
-
379
- if single
380
- collection.first
381
- else
382
- collection
383
- end
384
- end
385
-
386
- # @private
387
- def defined_content
388
- @content if defined? @content
389
- end
390
-
391
- private
392
-
393
- def find_nodes_to_parse(options, namespace, tag_name, namespaces, node, root)
394
- # when at the root use the xpath '/' otherwise use a more gready './/'
395
- # unless an xpath has been specified, which should overwrite default
396
- # and finally attach the current namespace if one has been defined
397
- #
398
-
399
- xpath = if options[:xpath]
400
- options[:xpath].to_s.sub(%r{([^/])$}, '\1/')
401
- elsif root
402
- '/'
403
- else
404
- './/'
405
- end
406
- if namespace
407
- return [] unless namespaces.find { |name, _url| ["xmlns:#{namespace}", namespace].include? name }
408
-
409
- xpath += "#{namespace}:"
410
- end
411
-
412
- nodes = []
413
-
414
- # when finding nodes, do it in this order:
415
- # 1. specified tag if one has been provided
416
- # 2. name of element
417
- # 3. tag_name (derived from class name by default)
418
-
419
- # If a tag has been provided we need to search for it.
420
-
421
- if options.key?(:tag)
422
- nodes = node.xpath(xpath + options[:tag].to_s, namespaces)
423
- else
424
-
425
- # This is the default case when no tag value is provided.
426
- # First we use the name of the element `items` in `has_many items`
427
- # Second we use the tag name which is the name of the class cleaned up
428
-
429
- [options[:name], tag_name].compact.each do |xpath_ext|
430
- nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
431
- break if nodes && !nodes.empty?
432
- end
433
-
434
- end
435
-
436
- nodes
437
- end
438
-
439
- def parse_node(node, options, namespace, namespaces)
440
- # If an existing HappyMapper object is provided, update it with the
441
- # values from the xml being parsed. Otherwise, create a new object
442
-
443
- obj = options[:update] || new
444
-
445
- attributes.each do |attr|
446
- value = attr.from_xml_node(node, namespace, namespaces)
447
- value = attr.default if value.nil?
448
- obj.send("#{attr.method_name}=", value)
449
- end
450
-
451
- elements.each do |elem|
452
- obj.send("#{elem.method_name}=", elem.from_xml_node(node, namespace, namespaces))
453
- end
454
-
455
- if (content = defined_content)
456
- obj.send("#{content.method_name}=", content.from_xml_node(node, namespace, namespaces))
457
- end
458
-
459
- # If the HappyMapper class has the method #xml_value=,
460
- # attr_writer :xml_value, or attr_accessor :xml_value then we want to
461
- # assign the current xml that we just parsed to the xml_value
462
-
463
- if obj.respond_to?('xml_value=')
464
- obj.xml_value = node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
465
- end
466
-
467
- # If the HappyMapper class has the method #xml_content=,
468
- # attr_write :xml_content, or attr_accessor :xml_content then we want to
469
- # assign the child xml that we just parsed to the xml_content
470
-
471
- if obj.respond_to?('xml_content=')
472
- node = node.children if node.respond_to?(:children)
473
- obj.xml_content = node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
474
- end
475
-
476
- # Call any registered after_parse callbacks for the object's class
477
-
478
- obj.class.after_parse_callbacks.each { |callback| callback.call(obj) }
479
-
480
- # collect the object that we have created
481
-
482
- obj
483
- end
484
- end
485
-
486
43
  # Set all attributes with a default to their default values
487
44
  def initialize
488
45
  super
489
46
  self.class.attributes.reject { |attr| attr.default.nil? }.each do |attr|
490
- send("#{attr.method_name}=", attr.default)
47
+ send(:"#{attr.method_name}=", attr.default)
491
48
  end
492
49
  end
493
50
 
@@ -544,7 +101,9 @@ module HappyMapper
544
101
  # in a recursive call from the parent doc. Then append
545
102
  # any attributes to the element that were defined above.
546
103
  #
547
- 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|
548
107
  register_namespaces_with_builder(builder)
549
108
 
550
109
  xml.parent.namespace =
@@ -554,15 +113,11 @@ module HappyMapper
554
113
  # When a content has been defined we add the resulting value
555
114
  # the output xml
556
115
  #
557
- if (content = self.class.defined_content)
558
-
559
- unless content.options[:read_only]
560
- value = send(content.name)
561
- value = apply_on_save_action(content, value)
562
-
563
- builder.text(value)
564
- end
116
+ if (content = self.class.defined_content) && !content.options[:read_only]
117
+ value = send(content.name)
118
+ value = apply_on_save_action(content, value)
565
119
 
120
+ builder.text(value)
566
121
  end
567
122
 
568
123
  #
@@ -580,7 +135,7 @@ module HappyMapper
580
135
  # xml generated from the object. If an XML builder instance was specified
581
136
  # then we assume that has been called recursively to generate a larger
582
137
  # XML document.
583
- 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
584
139
  end
585
140
 
586
141
  # Parse the xml and update this instance. This does not update instances
@@ -630,39 +185,33 @@ module HappyMapper
630
185
  # Find the attributes for the class and collect them into an array
631
186
  # that will be placed into a Hash structure
632
187
  #
633
- attributes = self.class.attributes.collect do |attribute|
188
+ attributes = self.class.attributes.filter_map do |attribute|
634
189
  #
635
190
  # If an attribute is marked as read_only then we want to ignore the attribute
636
191
  # when it comes to saving the xml document; so we will not go into any of
637
192
  # the below process
638
193
  #
639
- if attribute.options[:read_only]
640
- []
641
- else
194
+ next if attribute.options[:read_only]
642
195
 
643
- value = send(attribute.method_name)
644
- value = nil if value == attribute.default
196
+ value = send(attribute.method_name)
197
+ value = nil if value == attribute.default
645
198
 
646
- #
647
- # Apply any on_save lambda/proc or value defined on the attribute.
648
- #
649
- value = apply_on_save_action(attribute, value)
199
+ #
200
+ # Apply any on_save lambda/proc or value defined on the attribute.
201
+ #
202
+ value = apply_on_save_action(attribute, value)
650
203
 
651
- #
652
- # Attributes that have a nil value should be ignored unless they explicitly
653
- # state that they should be expressed in the output.
654
- #
655
- if !(value.nil? || attribute.options[:state_when_nil])
656
- attribute_namespace = attribute.options[:namespace]
657
- ["#{attribute_namespace ? "#{attribute_namespace}:" : ''}#{attribute.tag}", value]
658
- else
659
- []
660
- end
204
+ #
205
+ # Attributes that have a nil value should be ignored unless they explicitly
206
+ # state that they should be expressed in the output.
207
+ #
208
+ next if value.nil? && !attribute.options[:state_when_nil]
661
209
 
662
- end
663
- end.flatten
210
+ attribute_namespace = attribute.options[:namespace]
211
+ ["#{attribute_namespace ? "#{attribute_namespace}:" : ""}#{attribute.tag}", value]
212
+ end
664
213
 
665
- Hash[*attributes]
214
+ attributes.to_h
666
215
  end
667
216
 
668
217
  #
@@ -674,10 +223,10 @@ module HappyMapper
674
223
  # which means that it is the default namespace of the code.
675
224
  #
676
225
  def register_namespaces_with_builder(builder)
677
- return unless self.class.instance_variable_get('@registered_namespaces')
226
+ return unless self.class.instance_variable_get(:@registered_namespaces)
678
227
 
679
- self.class.instance_variable_get('@registered_namespaces').sort.each do |name, href|
680
- name = nil if name == 'xmlns'
228
+ self.class.instance_variable_get(:@registered_namespaces).sort.each do |name, href|
229
+ name = nil if name == "xmlns"
681
230
  builder.doc.root.add_namespace(name, href)
682
231
  end
683
232
  end
@@ -726,33 +275,33 @@ module HappyMapper
726
275
  element.options[:namespace],
727
276
  element.options[:tag] || nil)
728
277
 
729
- elsif !item.nil?
278
+ elsif !item.nil? || element.options[:state_when_nil]
730
279
 
731
- item_namespace = element.options[:namespace] || self.class.namespace || default_namespace
280
+ item_namespace =
281
+ element.options[:namespace] ||
282
+ self.class.namespace ||
283
+ default_namespace
732
284
 
733
285
  #
734
- # When a value exists we should append the value for the tag
286
+ # When a value exists or the tag should always be emitted,
287
+ # we should append the value for the tag
735
288
  #
736
289
  if item_namespace
737
- xml[item_namespace].send("#{tag}_", item.to_s)
290
+ xml[item_namespace].send(:"#{tag}_", item.to_s)
738
291
  else
739
- xml.send("#{tag}_", item.to_s)
292
+ xml.send(:"#{tag}_", item.to_s)
740
293
  end
741
-
742
- elsif element.options[:state_when_nil]
743
-
744
- #
745
- # Normally a nil value would be ignored, however if specified then
746
- # an empty element will be written to the xml
747
- #
748
- xml.send("#{tag}_", '')
749
294
  end
750
295
  end
751
296
  end
297
+
298
+ def wrapper_anonymous_classes
299
+ self.class.instance_variable_get(:@wrapper_anonymous_classes)
300
+ end
752
301
  end
753
302
 
754
- require 'happymapper/supported_types'
755
- require 'happymapper/item'
756
- require 'happymapper/attribute'
757
- require 'happymapper/element'
758
- 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"