require 'rexml/document' class Object def to_xml_value to_s end end class NilClass def to_xml_value nil end end class TrueClass def to_xml_value to_s end end class FalseClass def to_xml_value to_s end end class Time def to_xml_value self.xmlschema end end class DateTime def to_xml_value self.xmlschema end end class Date def to_xml_value self.to_time.xmlschema end end class REXML::Element def to_xml_element self end end class XmlNode attr_accessor :child_nodes attr_reader :element class List include Enumerable def initialize(parent) @parent = parent @children = {} end def [](value) node_for @parent.element.elements[value] end def []=(value, key) @parent.element.elements[value.to_s] = key.to_xml_element end def each(&block) @parent.element.each_element { |e| yield node_for(e) } end private def node_for(element) @parent.child_nodes[element] ||= XmlNode.new(element) end end # Allows for very pretty xml generation akin to xml builder. # # Example: # # # Create an atom like document # doc = Document.new # doc.root = XmlNode.new 'feed' do |feed| # # feed << XmlNode.new('id', 'tag:atom.com,2007:1') # feed << XmlNode.new('title', 'Atom test feed') # feed << XmlNode.new('author') do |author| # author << XmlNode.new("name", "tobi") # author << XmlNode.new("email", "tobi@gmail.com") # end # # feed << XmlNode.new('entry') do |entry| # entry << XmlNode.new('title', 'First post') # entry << XmlNode.new('summary', 'Lorem ipsum', :type => 'xhtml') # entry << XmlNode.new('created_at', Time.now) # end # # feed << XmlNode.new('dc:published', Time.now) # end # def initialize(node, *args) @element = if node.is_a?(REXML::Element) node else REXML::Element.new(node) end @child_nodes = {} if attributes = args.last.is_a?(Hash) ? args.pop : nil attributes.each { |k,v| @element.add_attribute(k.to_s, v.to_xml_value) } end if !args[0].nil? @element.text = args[0].to_xml_value end if block_given? yield self end end def self.parse(xml) self.new(REXML::Document.new(xml).root) end def children XmlNode::List.new(self) end def []=(key, value) @element.attributes[key.to_s] = value.to_xml_value end def [](key) @element.attributes[key] end # Add a namespace to the node # Example # # node.namespace 'http://www.w3.org/2005/Atom' # node.namespace :opensearch, 'http://a9.com/-/spec/opensearch/1.1/' # def namespace(*args) args[0] = args[0].to_s if args[0].is_a?(Symbol) @element.add_namespace(*args) end def cdata=(value) new_cdata = REXML::CData.new( value ) @element.children.each do |c| if c.is_a?(REXML::CData) return @element.replace_child(c,new_cdata) end end @element << new_cdata rescue RuntimeError => e @element << REXML::Text.new(e.message) end def cdata @element.cdatas.first.to_s end def name @element.name end def text=(value) @element.text = REXML::Text.new( value ) end def text @element.text end def find(scope, xpath) case scope when :first elem = @element.elements[xpath] elem.nil? ? nil : child_nodes[elem] ||= XmlNode.new(elem) when :all @element.elements.to_a(xpath).collect { |e| child_nodes[e] ||= XmlNode.new(e) } end end def <<(elem) case elem when nil then return when Array elem.each { |e| @element << e.to_xml_element } else @element << elem.to_xml_element end end def to_xml_element @element end def to_s @element.to_s end # Use to get pretty formatted xml including DECL # instructions def to_xml xml = [] document = REXML::Document.new document << REXML::XMLDecl.new('1.0') document << @element document.write( xml, 0) xml.join end end