lib/happymapper.rb in happymapper-0.3.2 vs lib/happymapper.rb in happymapper-0.4.0
- old
+ new
@@ -1,5 +1,6 @@
+require 'rubygems'
require 'date'
require 'time'
require 'xml'
class Boolean; end
@@ -9,10 +10,12 @@
DEFAULT_NS = "happymapper"
def self.included(base)
base.instance_variable_set("@attributes", {})
base.instance_variable_set("@elements", {})
+ base.instance_variable_set("@registered_namespaces", {})
+
base.extend ClassMethods
end
module ClassMethods
def attribute(name, type, options={})
@@ -63,10 +66,14 @@
# defined element.
def namespace(namespace = nil)
@namespace = namespace if namespace
@namespace
end
+
+ def register_namespace(namespace, ns)
+ @registered_namespaces.merge!(namespace => ns)
+ end
def tag(new_tag_name)
@tag_name = new_tag_name.to_s
end
@@ -98,16 +105,16 @@
collection = nodes.collect do |n|
obj = new
attributes.each do |attr|
obj.send("#{attr.method_name}=",
- attr.from_xml_node(n, namespace))
+ attr.from_xml_node(n, namespace))
end
elements.each do |elem|
obj.send("#{elem.method_name}=",
- elem.from_xml_node(n, namespace))
+ elem.from_xml_node(n, namespace))
end
obj.send("#{@content}=", n.content) if @content
obj.class.after_parse_callbacks.each { |callback| callback.call(obj) }
@@ -122,11 +129,187 @@
collection.first
else
collection
end
end
+
end
+
+ #
+ # Create an xml representation of the specified class based on defined
+ # HappyMapper elements and attributes. The method is defined in a way
+ # that it can be called recursively by classes that are also HappyMapper
+ # classes, allowg for the composition of classes.
+ #
+ def to_xml(parent_node = nil, default_namespace = nil)
+
+ #
+ # Create a tag that uses the tag name of the class that has no contents
+ # but has the specified namespace or uses the default namespace
+ #
+ current_node = XML::Node.new(self.class.tag_name)
+
+
+ if parent_node
+ #
+ # if #to_xml has been called with a parent_node that means this method
+ # is being called recursively (or a special case) and we want to return
+ # the parent_node with the new node as a child
+ #
+ parent_node << current_node
+ else
+ #
+ # If #to_xml has been called without a Node (and namespace) that
+ # means we want to return an xml document
+ #
+ write_out_to_xml = true
+ end
+
+ #
+ # Add all the registered namespaces to the current node and the current node's
+ # root element. Without adding it to the root element it is not possible to
+ # parse or use xpath to find elements.
+ #
+ if self.class.instance_variable_get('@registered_namespaces')
+
+ # Given a node, continue moving up to parents until there are no more parents
+ find_root_node = lambda {|node| while node.parent? ; node = node.parent ; end ; node }
+ root_node = find_root_node.call(current_node)
+
+ # Add the registered namespace to the found root node only if it does not already have one defined
+ self.class.instance_variable_get('@registered_namespaces').each_pair do |prefix,href|
+ XML::Namespace.new(current_node,prefix,href)
+ XML::Namespace.new(root_node,prefix,href) unless root_node.namespaces.find_by_prefix(prefix)
+ end
+ end
+
+ #
+ # Determine the tag namespace if one has been specified. This value takes
+ # precendence over one that is handed down to composed sub-classes.
+ #
+ tag_namespace = current_node.namespaces.find_by_prefix(self.class.namespace) || default_namespace
+
+ # Set the namespace of the current node to the specified namespace
+ current_node.namespaces.namespace = tag_namespace if tag_namespace
+
+ #
+ # Add all the attribute tags to the current node with their namespace, if one
+ # is defined, or the namespace handed down to the node.
+ #
+ self.class.attributes.each do |attribute|
+ attribute_namespace = current_node.namespaces.find_by_prefix(attribute.options[:namespace]) || default_namespace
+
+ value = send(attribute.method_name)
+
+ #
+ # If the attribute has a :on_save attribute defined that is a proc or
+ # a defined method, then call those with the current value.
+ #
+ if on_save_operation = attribute.options[:on_save]
+ if on_save_operation.is_a?(Proc)
+ value = on_save_operation.call(value)
+ elsif respond_to?(on_save_operation)
+ value = send(on_save_operation,value)
+ end
+ end
+
+ current_node[ "#{attribute_namespace ? "#{attribute_namespace.prefix}:" : ""}#{attribute.tag}" ] = value
+ end
+
+ #
+ # All all the elements defined (e.g. has_one, has_many, element) ...
+ #
+ self.class.elements.each do |element|
+
+ tag = element.tag || element.name
+
+ element_namespace = current_node.namespaces.find_by_prefix(element.options[:namespace]) || tag_namespace
+
+ value = send(element.name)
+
+ #
+ # 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_operation = element.options[:on_save]
+ if on_save_operation.is_a?(Proc)
+ value = on_save_operation.call(value)
+ elsif respond_to?(on_save_operation)
+ value = send(on_save_operation,value)
+ end
+ end
+
+ #
+ # 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[:state_when_nil]
+ current_node << XML::Node.new(tag,nil,element_namespace)
+ 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
+
+
+ values.each do |item|
+
+ if item.is_a?(HappyMapper)
+
+ #
+ # Other HappyMapper items that are convertable should not be called
+ # with the current node and the namespace defined for the element.
+ #
+ item.to_xml(current_node,element_namespace)
+
+ elsif item
+
+ #
+ # When a value exists we should append the value for the tag
+ #
+ current_node << XML::Node.new(tag,item.to_s,element_namespace)
+
+ else
+
+ #
+ # Normally a nil value would be ignored, however if specified then
+ # an empty element will be written to the xml
+ #
+ current_node << XML.Node.new(tag,nil,element_namespace) if element.options[:state_when_nil]
+
+ end
+
+ end
+
+ end
+
+
+ #
+ # Generate xml from a document if no node was passed as a parameter. Otherwise
+ # this method is being called recursively (or special case) and we should
+ # return the node with this node attached as a child.
+ #
+ if write_out_to_xml
+ document = XML::Document.new
+ document.root = current_node
+ document.to_s
+ else
+ parent_node
+ end
+
+ end
+
+
end
-require 'happymapper/item'
-require 'happymapper/attribute'
-require 'happymapper/element'
+require File.dirname(__FILE__) + '/happymapper/item'
+require File.dirname(__FILE__) + '/happymapper/attribute'
+require File.dirname(__FILE__) + '/happymapper/element'