lib/happymapper.rb in nokogiri-happymapper-0.6.0 vs lib/happymapper.rb in nokogiri-happymapper-0.7.0

- old
+ new

@@ -1,44 +1,44 @@ +# frozen_string_literal: true + require 'nokogiri' require 'date' require 'time' +require 'happymapper/version' require 'happymapper/anonymous_mapper' module HappyMapper class Boolean; end class XmlContent; end extend AnonymousMapper - DEFAULT_NS = "happymapper" - def self.included(base) - if !(base.superclass <= HappyMapper) + if base.superclass <= HappyMapper base.instance_eval do - @attributes = {} - @elements = {} - @registered_namespaces = {} - @wrapper_anonymous_classes = {} - end - else - base.instance_eval do @attributes = - superclass.instance_variable_get(:@attributes).dup + superclass.instance_variable_get(:@attributes).dup @elements = - superclass.instance_variable_get(:@elements).dup + superclass.instance_variable_get(:@elements).dup @registered_namespaces = - superclass.instance_variable_get(:@registered_namespaces).dup + superclass.instance_variable_get(:@registered_namespaces).dup @wrapper_anonymous_classes = - superclass.instance_variable_get(:@wrapper_anonymous_classes).dup + superclass.instance_variable_get(:@wrapper_anonymous_classes).dup end + else + base.instance_eval do + @attributes = {} + @elements = {} + @registered_namespaces = {} + @wrapper_anonymous_classes = {} + end end base.extend ClassMethods end module ClassMethods - # # The xml has the following attributes defined. # # @example # @@ -50,11 +50,11 @@ # @param [Symbol] name the name of the accessor that is created # @param [String,Class] type the class name of the name of the class whcih # the object will be converted upon parsing # @param [Hash] options additional parameters to send to the relationship # - def attribute(name, type, options={}) + def attribute(name, type, options = {}) attribute = Attribute.new(name, type, options) @attributes[name] = attribute attr_accessor attribute.method_name.intern end @@ -80,15 +80,15 @@ # # "<outputXML xmlns:prefix="http://www.unicornland.com/prefix"> # ... # </outputXML>" # - # @param [String] namespace the xml prefix - # @param [String] ns url for the xml namespace + # @param [String] name the xml prefix + # @param [String] href url for the xml namespace # - def register_namespace(namespace, ns) - @registered_namespaces.merge!({namespace => ns}) + def register_namespace(name, href) + @registered_namespaces.merge!(name => href) end # # An element defined in the XML that is parsed. # @@ -105,11 +105,11 @@ # @param [Symbol] name the name of the accessor that is created # @param [String,Class] type the class name of the name of the class whcih # the object will be converted upon parsing # @param [Hash] options additional parameters to send to the relationship # - def element(name, type, options={}) + def element(name, type, options = {}) element = Element.new(name, type, options) attr_accessor element.method_name.intern unless @elements[name] @elements[name] = element end @@ -138,11 +138,11 @@ # @param [Symbol] name the name of the accessor that is created # @param [String,Class] type the class name of the name of the class whcih # the object will be converted upon parsing. By Default String class will be taken. # @param [Hash] options additional parameters to send to the relationship # - def content(name, type=String, options={}) + def content(name, type = String, options = {}) @content = TextNode.new(name, type, options) attr_accessor @content.method_name.intern end # @@ -164,12 +164,12 @@ # the object will be converted upon parsing # @param [Hash] options additional parameters to send to the relationship # # @see #element # - def has_one(name, type, options={}) - element name, type, {:single => true}.merge(options) + def has_one(name, type, options = {}) + element name, type, { single: true }.merge(options) end # # The object has many of these elements in the XML. # @@ -178,12 +178,12 @@ # the object will be converted upon parsing. # @param [Hash] options additional parameters to send to the relationship # # @see #element # - def has_many(name, type, options={}) - element name, type, {:single => false}.merge(options) + def has_many(name, type, options = {}) + element name, type, { single: false }.merge(options) end # # The list of registered after_parse callbacks. # @@ -224,11 +224,11 @@ # The name of the tag # # @return [String] the name of the tag as a string, downcased # def tag_name - @tag_name ||= to_s.split('::')[-1].downcase + @tag_name ||= name && name.to_s.split('::')[-1].downcase end # There is an XML tag that needs to be known for parsing and should be generated # during a to_xml. But it doesn't need to be a class and the contained elements should # be made available on the parent class @@ -238,26 +238,27 @@ # def wrap(name, &blk) # Get an anonymous HappyMapper that has 'name' as its tag and defined # in '&blk'. Then save that to a class instance variable for later use wrapper = AnonymousWrapperClassFactory.get(name, &blk) - @wrapper_anonymous_classes[wrapper.inspect] = wrapper + wrapper_key = wrapper.inspect + @wrapper_anonymous_classes[wrapper_key] = wrapper # Create getter/setter for each element and attribute defined on the anonymous HappyMapper # onto this class. They get/set the value by passing thru to the anonymous class. passthrus = wrapper.attributes + wrapper.elements passthrus.each do |item| - class_eval %{ + class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{item.method_name} - @#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper.inspect}'].new + @#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper_key}'].new @#{name}.#{item.method_name} end def #{item.method_name}=(value) - @#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper.inspect}'].new + @#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper_key}'].new @#{name}.#{item.method_name} = value end - } + RUBY end has_one name, wrapper end @@ -265,11 +266,13 @@ # # @return [Proc] the proc to pass to Nokogiri to setup parse options. nil if empty. # attr_reader :nokogiri_config_callback - # Register a config callback according to the block Nokogori expects when calling Nokogiri::XML::Document.parse(). + # Register a config callback according to the block Nokogori expects when + # calling Nokogiri::XML::Document.parse(). + # # See http://nokogiri.org/Nokogiri/XML/Document.html#method-c-parse # # @param [Proc] the proc to pass to Nokogiri to setup parse options # def with_nokogiri_config(&blk) @@ -283,116 +286,64 @@ # if requesting a single object, otherwise it defaults to retuning an # array of multiple items. :xpath information where to start the parsing # :namespace is the namespace to use for additional information. # def parse(xml, options = {}) - # create a local copy of the objects namespace value for this parse execution namespace = (@namespace if defined? @namespace) + # Capture any provided namespaces and merge in any namespaces that have + # been registered on the object. + namespaces = options[:namespaces] || {} + namespaces = namespaces.merge(@registered_namespaces) + # If the XML specified is an Node then we have what we need. if xml.is_a?(Nokogiri::XML::Node) && !xml.is_a?(Nokogiri::XML::Document) node = xml else - # If xml is an XML document select the root node of the document - if xml.is_a?(Nokogiri::XML::Document) - node = xml.root - else - + unless xml.is_a?(Nokogiri::XML::Document) # Attempt to parse the xml value with Nokogiri XML as a document # and select the root element xml = Nokogiri::XML( xml, nil, nil, Nokogiri::XML::ParseOptions::STRICT, &nokogiri_config_callback ) - node = xml.root end + # Now xml is certainly an XML document: Select the root node of the document + node = xml.root + # merge any namespaces found on the xml node into the namespace hash + namespaces = namespaces.merge(xml.collect_namespaces) + # if the node name is equal to the tag name then the we are parsing the # root element and that is important to record so that we can apply # the correct xpath on the elements of this document. root = node.name == tag_name end - # if any namespaces have been provied then we should capture those and then - # merge them with any namespaces found on the xml node and merge all that - # with any namespaces that have been registered on the object - - namespaces = options[:namespaces] || {} - namespaces = namespaces.merge(xml.collect_namespaces) if xml.respond_to?(:collect_namespaces) - namespaces = namespaces.merge(@registered_namespaces) - # if a namespace has been provided then set the current namespace to it # or set the default namespace to the one defined under 'xmlns' # or set the default namespace to the namespace that matches 'happymapper's if options[:namespace] namespace = options[:namespace] - elsif namespaces.has_key?("xmlns") - namespace ||= DEFAULT_NS - namespaces[DEFAULT_NS] = namespaces.delete("xmlns") - elsif namespaces.has_key?(DEFAULT_NS) - namespace ||= DEFAULT_NS + elsif namespaces.has_key?('xmlns') + namespace ||= 'xmlns' end # from the options grab any nodes present and if none are present then # perform the following to find the nodes for the given class nodes = options.fetch(:nodes) do - - # when at the root use the xpath '/' otherwise use a more gready './/' - # unless an xpath has been specified, which should overwrite default - # and finally attach the current namespace if one has been defined - # - - xpath = (root ? '/' : './/') - xpath = options[:xpath].to_s.sub(/([^\/])$/, '\1/') if options[:xpath] - xpath += "#{namespace}:" if namespace - - nodes = [] - - # when finding nodes, do it in this order: - # 1. specified tag if one has been provided - # 2. name of element - # 3. tag_name (derived from class name by default) - - # If a tag has been provided we need to search for it. - - if options.key?(:tag) - begin - nodes = node.xpath(xpath + options[:tag].to_s, namespaces) - rescue - # This exception takes place when the namespace is often not found - # and we should continue on with the empty array of nodes. - end - else - - # This is the default case when no tag value is provided. - # First we use the name of the element `items` in `has_many items` - # Second we use the tag name which is the name of the class cleaned up - - [options[:name], tag_name].compact.each do |xpath_ext| - begin - nodes = node.xpath(xpath + xpath_ext.to_s, namespaces) - rescue - break - # This exception takes place when the namespace is often not found - # and we should continue with the empty array of nodes or keep looking - end - break if nodes && !nodes.empty? - end - - end - - nodes + find_nodes_to_parse(options, namespace, tag_name, namespaces, node, root) end # Nothing matching found, we can go ahead and return - return ( ( options[:single] || root ) ? nil : [] ) if nodes.size == 0 + return (options[:single] || root ? nil : []) if nodes.size == 0 # If the :limit option has been specified then we are going to slice # our node results by that amount to allow us the ability to deal with # a large result set of data. @@ -404,90 +355,134 @@ return [] if limit == 0 collection = [] nodes.each_slice(limit) do |slice| - part = slice.map do |n| + parse_node(n, options, namespace, namespaces) + end - # If an existing HappyMapper object is provided, update it with the - # values from the xml being parsed. Otherwise, create a new object + # If a block has been provided and the user has requested that the objects + # be handled in groups then we should yield the slice of the objects to them + # otherwise continue to lump them together - obj = options[:update] ? options[:update] : new + if block_given? && options[:in_groups_of] + yield part + else + collection += part + end + end - attributes.each do |attr| - value = attr.from_xml_node(n, namespace, namespaces) - value = attr.default if value.nil? - obj.send("#{attr.method_name}=", value) - end + # If the :single option has been specified or we are at the root element + # then we are going to return the first item in the collection. Otherwise + # the return response is going to be an entire array of items. - elements.each do |elem| - obj.send("#{elem.method_name}=",elem.from_xml_node(n, namespace, namespaces)) - end + if options[:single] || root + collection.first + else + collection + end + end - if (defined? @content) && @content - obj.send("#{@content.method_name}=",@content.from_xml_node(n, namespace, namespaces)) - end + # @private + def defined_content + @content if defined? @content + end - # If the HappyMapper class has the method #xml_value=, - # attr_writer :xml_value, or attr_accessor :xml_value then we want to - # assign the current xml that we just parsed to the xml_value + private - if obj.respond_to?('xml_value=') - n.namespaces.each {|name,path| n[name] = path } - obj.xml_value = n.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML) - end + def find_nodes_to_parse(options, namespace, tag_name, namespaces, node, root) + # when at the root use the xpath '/' otherwise use a more gready './/' + # unless an xpath has been specified, which should overwrite default + # and finally attach the current namespace if one has been defined + # - # If the HappyMapper class has the method #xml_content=, - # attr_write :xml_content, or attr_accessor :xml_content then we want to - # assign the child xml that we just parsed to the xml_content + xpath = (root ? '/' : './/') + xpath = options[:xpath].to_s.sub(/([^\/])$/, '\1/') if options[:xpath] + if namespace + return [] unless namespaces.find { |name, _url| ["xmlns:#{namespace}", namespace].include? name } + xpath += "#{namespace}:" + end - if obj.respond_to?('xml_content=') - n = n.children if n.respond_to?(:children) - obj.xml_content = n.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML) - end + nodes = [] - # Call any registered after_parse callbacks for the object's class + # when finding nodes, do it in this order: + # 1. specified tag if one has been provided + # 2. name of element + # 3. tag_name (derived from class name by default) - obj.class.after_parse_callbacks.each { |callback| callback.call(obj) } + # If a tag has been provided we need to search for it. - # collect the object that we have created + if options.key?(:tag) + nodes = node.xpath(xpath + options[:tag].to_s, namespaces) + else - obj + # This is the default case when no tag value is provided. + # First we use the name of the element `items` in `has_many items` + # Second we use the tag name which is the name of the class cleaned up + + [options[:name], tag_name].compact.each do |xpath_ext| + nodes = node.xpath(xpath + xpath_ext.to_s, namespaces) + break if nodes && !nodes.empty? end - # If a block has been provided and the user has requested that the objects - # be handled in groups then we should yield the slice of the objects to them - # otherwise continue to lump them together + end - if block_given? and options[:in_groups_of] - yield part - else - collection += part - end + nodes + end + def parse_node(node, options, namespace, namespaces) + # If an existing HappyMapper object is provided, update it with the + # values from the xml being parsed. Otherwise, create a new object + + obj = options[:update] || new + + attributes.each do |attr| + value = attr.from_xml_node(node, namespace, namespaces) + value = attr.default if value.nil? + obj.send("#{attr.method_name}=", value) end - # per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354 - nodes = nil + elements.each do |elem| + obj.send("#{elem.method_name}=", elem.from_xml_node(node, namespace, namespaces)) + end - # If the :single option has been specified or we are at the root element - # then we are going to return the first item in the collection. Otherwise - # the return response is going to be an entire array of items. + if (content = defined_content) + obj.send("#{content.method_name}=", content.from_xml_node(node, namespace, namespaces)) + end - if options[:single] or root - collection.first - else - collection + # If the HappyMapper class has the method #xml_value=, + # attr_writer :xml_value, or attr_accessor :xml_value then we want to + # assign the current xml that we just parsed to the xml_value + + if obj.respond_to?('xml_value=') + obj.xml_value = node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML) end + + # If the HappyMapper class has the method #xml_content=, + # attr_write :xml_content, or attr_accessor :xml_content then we want to + # assign the child xml that we just parsed to the xml_content + + if obj.respond_to?('xml_content=') + node = node.children if node.respond_to?(:children) + obj.xml_content = node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML) + end + + # Call any registered after_parse callbacks for the object's class + + obj.class.after_parse_callbacks.each { |callback| callback.call(obj) } + + # collect the object that we have created + + obj end end # Set all attributes with a default to their default values def initialize super - self.class.attributes.reject {|attr| attr.default.nil?}.each do |attr| + self.class.attributes.reject { |attr| attr.default.nil? }.each do |attr| send("#{attr.method_name}=", attr.default) end end # @@ -517,257 +512,237 @@ tag_from_parent = nil) # # If to_xml has been called without a passed in builder instance that # means we are going to return xml output. When it has been called with + # a builder instance that means we most likely being called recursively # and will return the end product as a builder instance. # unless builder write_out_to_xml = true builder = Nokogiri::XML::Builder.new end + attributes = collect_writable_attributes + # - # Find the attributes for the class and collect them into an array - # that will be placed into a Hash structure + # If the object we are serializing has a namespace declaration we will want + # to use that namespace or we will use the default namespace. + # When neither are specifed we are simply using whatever is default to the + # builder # - attributes = self.class.attributes.collect do |attribute| + namespace_name = namespace_override || self.class.namespace || default_namespace - # - # If an attribute is marked as read_only then we want to ignore the attribute - # when it comes to saving the xml document; so we wiill not go into any of - # the below process - # - unless attribute.options[:read_only] - - value = send(attribute.method_name) - value = nil if value == attribute.default - - # - # If the attribute defines an on_save lambda/proc or value that maps to - # a method that the class has defined, then call it with the value as a - # parameter. - # - if on_save_action = attribute.options[:on_save] - if on_save_action.is_a?(Proc) - value = on_save_action.call(value) - elsif respond_to?(on_save_action) - value = send(on_save_action,value) - end - end - - # - # Attributes that have a nil value should be ignored unless they explicitly - # state that they should be expressed in the output. - # - if not value.nil? || attribute.options[:state_when_nil] - attribute_namespace = attribute.options[:namespace] - [ "#{attribute_namespace ? "#{attribute_namespace}:" : ""}#{attribute.tag}", value ] - else - [] - end - - else - [] - end - - end.flatten - - attributes = Hash[ *attributes ] - # # Create a tag in the builder that matches the class's tag name unless a tag was passed # in a recursive call from the parent doc. Then append # any attributes to the element that were defined above. # - builder.send("#{tag_from_parent || self.class.tag_name}_",attributes) do |xml| + builder.send("#{tag_from_parent || self.class.tag_name}_", attributes) do |xml| + register_namespaces_with_builder(builder) - # - # Add all the registered namespaces to the root element. - # When this is called recurisvely by composed classes the namespaces - # are still added to the root element - # - # However, we do not want to add the namespace if the namespace is 'xmlns' - # which means that it is the default namesapce of the code. - # - if self.class.instance_variable_get('@registered_namespaces') && builder.doc.root - self.class.instance_variable_get('@registered_namespaces').each_pair do |name,href| - name = nil if name == "xmlns" - builder.doc.root.add_namespace(name,href) - end - end - - # - # If the object we are serializing has a namespace declaration we will want - # to use that namespace or we will use the default namespace. - # When neither are specifed we are simply using whatever is default to the - # builder - # - namespace_for_parent = namespace_override - if self.class.respond_to?(:namespace) && self.class.namespace - namespace_for_parent ||= self.class.namespace - end - namespace_for_parent ||= default_namespace - xml.parent.namespace = - builder.doc.root.namespace_definitions.find { |x| x.prefix == namespace_for_parent } + builder.doc.root.namespace_definitions.find { |x| x.prefix == namespace_name } - # # When a content has been defined we add the resulting value # the output xml # - if self.class.instance_variable_defined?('@content') - if content = self.class.instance_variable_get('@content') + if (content = self.class.defined_content) - unless content.options[:read_only] - text_accessor = content.tag || content.name - value = send(text_accessor) + unless content.options[:read_only] + value = send(content.name) + value = apply_on_save_action(content, value) - if on_save_action = content.options[:on_save] - if on_save_action.is_a?(Proc) - value = on_save_action.call(value) - elsif respond_to?(on_save_action) - value = send(on_save_action,value) - end - end - - builder.text(value) - end - + builder.text(value) end + end # # for every define element (i.e. has_one, has_many, element) we are # going to persist each one # self.class.elements.each do |element| + element_to_xml(element, xml, default_namespace) + end + end - # - # If an element is marked as read only do not consider at all when - # saving to XML. - # - unless element.options[:read_only] + # Write out to XML, this value was set above, based on whether or not an XML + # builder object was passed to it as a parameter. When there was no parameter + # we assume we are at the root level of the #to_xml call and want the actual + # xml generated from the object. If an XML builder instance was specified + # then we assume that has been called recursively to generate a larger + # XML document. + write_out_to_xml ? builder.to_xml.force_encoding('UTF-8') : builder + end - tag = element.tag || element.name + # Parse the xml and update this instance. This does not update instances + # of HappyMappers that are children of this object. New instances will be + # created for any HappyMapper children of this object. + # + # Params and return are the same as the class parse() method above. + def parse(xml, options = {}) + self.class.parse(xml, options.merge!(update: self)) + end - # - # The value to store is the result of the method call to the element, - # by default this is simply utilizing the attr_accessor defined. However, - # this allows for this method to be overridden - # - value = send(element.name) + # Factory for creating anonmyous HappyMappers + class AnonymousWrapperClassFactory + def self.get(name, &blk) + Class.new do + include HappyMapper + tag name + instance_eval(&blk) + end + end + end - # - # If the element defines an on_save lambda/proc then we will call that - # operation on the specified value. This allows for operations to be - # performed to convert the value to a specific value to be saved to the xml. - # - if on_save_action = element.options[:on_save] - if on_save_action.is_a?(Proc) - value = on_save_action.call(value) - elsif respond_to?(on_save_action) - value = send(on_save_action,value) - end - end + private - # - # Normally a nil value would be ignored, however if specified then - # an empty element will be written to the xml - # - if value.nil? && element.options[:single] && element.options[:state_when_nil] - xml.send("#{tag}_","") - end + # + # If the item defines an on_save lambda/proc or value that maps to a method + # that the class has defined, then call it with the value as a parameter. + # This allows for operations to be performed to convert the value to a + # specific value to be saved to the xml. + # + def apply_on_save_action(item, value) + if (on_save_action = item.options[:on_save]) + if on_save_action.is_a?(Proc) + value = on_save_action.call(value) + elsif respond_to?(on_save_action) + value = send(on_save_action, value) + end + end + value + end - # - # To allow for us to treat both groups of items and singular items - # equally we wrap the value and treat it as an array. - # - if value.nil? - values = [] - elsif value.respond_to?(:to_ary) && !element.options[:single] - values = value.to_ary - else - values = [value] - end + # + # Find the attributes for the class and collect them into a Hash structure + # + def collect_writable_attributes + # + # Find the attributes for the class and collect them into an array + # that will be placed into a Hash structure + # + attributes = self.class.attributes.collect do |attribute| + # + # If an attribute is marked as read_only then we want to ignore the attribute + # when it comes to saving the xml document; so we will not go into any of + # the below process + # + if attribute.options[:read_only] + [] + else - values.each do |item| + value = send(attribute.method_name) + value = nil if value == attribute.default - if item.is_a?(HappyMapper) + # + # Apply any on_save lambda/proc or value defined on the attribute. + # + value = apply_on_save_action(attribute, value) - # - # Other items are convertable to xml through the xml builder - # process should have their contents retrieved and attached - # to the builder structure - # - item.to_xml(xml, self.class.namespace || default_namespace, - element.options[:namespace], - element.options[:tag] || nil) + # + # Attributes that have a nil value should be ignored unless they explicitly + # state that they should be expressed in the output. + # + if not value.nil? || attribute.options[:state_when_nil] + attribute_namespace = attribute.options[:namespace] + ["#{attribute_namespace ? "#{attribute_namespace}:" : ''}#{attribute.tag}", value] + else + [] + end - elsif !item.nil? + end + end.flatten - item_namespace = element.options[:namespace] || self.class.namespace || default_namespace + Hash[*attributes] + end - # - # When a value exists we should append the value for the tag - # - if item_namespace - xml[item_namespace].send("#{tag}_",item.to_s) - else - xml.send("#{tag}_",item.to_s) - end + # + # Add all the registered namespaces to the builder's root element. + # When this is called recursively by composed classes the namespaces + # are still added to the root element + # + # However, we do not want to add the namespace if the namespace is 'xmlns' + # which means that it is the default namespace of the code. + # + def register_namespaces_with_builder(builder) + return unless self.class.instance_variable_get('@registered_namespaces') + self.class.instance_variable_get('@registered_namespaces').sort.each do |name, href| + name = nil if name == 'xmlns' + builder.doc.root.add_namespace(name, href) + end + end - else + # Persist a single nested element as xml + def element_to_xml(element, xml, default_namespace) + # + # If an element is marked as read only do not consider at all when + # saving to XML. + # + return if element.options[:read_only] - # - # Normally a nil value would be ignored, however if specified then - # an empty element will be written to the xml - # - xml.send("#{tag}_","") if element.options[:state_when_nil] + tag = element.tag || element.name - end + # + # The value to store is the result of the method call to the element, + # by default this is simply utilizing the attr_accessor defined. However, + # this allows for this method to be overridden + # + value = send(element.name) - end + # + # Apply any on_save action defined on the element. + # + value = apply_on_save_action(element, value) - end - end + # + # To allow for us to treat both groups of items and singular items + # equally we wrap the value and treat it as an array. + # + values = if value.respond_to?(:to_ary) && !element.options[:single] + value.to_ary + else + [value] + end - end + values.each do |item| + if item.is_a?(HappyMapper) - # Write out to XML, this value was set above, based on whether or not an XML - # builder object was passed to it as a parameter. When there was no parameter - # we assume we are at the root level of the #to_xml call and want the actual - # xml generated from the object. If an XML builder instance was specified - # then we assume that has been called recursively to generate a larger - # XML document. - write_out_to_xml ? builder.to_xml : builder + # + # Other items are convertable to xml through the xml builder + # process should have their contents retrieved and attached + # to the builder structure + # + item.to_xml(xml, self.class.namespace || default_namespace, + element.options[:namespace], + element.options[:tag] || nil) - end + elsif !item.nil? - # Parse the xml and update this instance. This does not update instances - # of HappyMappers that are children of this object. New instances will be - # created for any HappyMapper children of this object. - # - # Params and return are the same as the class parse() method above. - def parse(xml, options = {}) - self.class.parse(xml, options.merge!(:update => self)) - end + item_namespace = element.options[:namespace] || self.class.namespace || default_namespace - private + # + # When a value exists we should append the value for the tag + # + if item_namespace + xml[item_namespace].send("#{tag}_", item.to_s) + else + xml.send("#{tag}_", item.to_s) + end - # Factory for creating anonmyous HappyMappers - class AnonymousWrapperClassFactory - def self.get(name, &blk) - Class.new do - include HappyMapper - tag name - instance_eval(&blk) - end - end - end + elsif element.options[:state_when_nil] + # + # Normally a nil value would be ignored, however if specified then + # an empty element will be written to the xml + # + xml.send("#{tag}_", '') + end + end + end end require 'happymapper/supported_types' require 'happymapper/item' require 'happymapper/attribute'