34d2ae3f7fdcec853ec24cf85715da96

With ruby you could use REXML's Hash.from_xml, but I really don't like that. The reason for me to write this, was to convert various xml responses from different soap services to a hash. I don't want any namespaces, attributes, etc. The code includes a (pretty simple) example xml response. Other responses might include multiple nodes with the same name, as well as empty-element tags. This piece of code was heavily inspired by cobravsmongoose, but it doesn't follow the BadgerFish conventions and uses Hpricot instead of REXML.

[Update]
Now available as a gem at http://github.com/rubiii/apricoteatsgorilla

require 'hpricot'

class Parser

  # converts a given soap response xml into a hash.
  def self.soap_response_to_hash(xml)
    xml = prepare_xml(xml)
    doc = Hpricot.XML(xml)
    root = doc.at("//return")
    xml_node_to_hash(root)
  end

private

  # converts xml nodes into a hash. recursively calls
  # itself in case of nested xml nodes.
  def self.xml_node_to_hash(node)
    this_node = {}

    node.each_child do |child|
      if child.children.nil?
        key, value = child.name, nil
      elsif child.children.size == 1 && child.children.first.text?
        key, value = child.name, string_to_bool?(child.children.first.raw_string)
      else
        key, value = child.name, xml_node_to_hash(child)
      end

      current = this_node[key]
      case current
        when Array:
          this_node[key] << value
        when nil
          this_node[key] = value
        else
          this_node[key] = [current.dup, value]
      end
    end

    this_node
  end

  # removes line breaks and whitespace between xml nodes.
  def self.prepare_xml(xml)
    xml = xml.gsub(/\n+/, "")
    xml = xml.gsub(/(>)\s*(<)/, '\1\2')
  end

  # converts "true" and "false" to boolean values or
  # returns the given string otherwise.
  def self.string_to_bool?(string)
    return true if string == "true"
    return false if string == "false"
    string
  end

end

xml = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <ns2:authenticateResponse xmlns:ns2="http://v1_0.ws.some.namespace/">
         <return>
            <authenticationValue>
               <orderToken>dfgdfg-4356345-sdfsdf-2345234-234</orderToken>
               <tokenHash>000111DDD888444777NNNZZZ555000</tokenHash>
               <client>dude</client>
            </authenticationValue>
            <success>true</success>
         </return>
      </ns2:authenticateResponse>
   </soap:Body>
</soap:Envelope>'

hash = Parser::soap_response_to_hash(xml)

Refactorings

No refactoring yet !

D41d8cd98f00b204e9800998ecf8427e

kotrin

August 8, 2009, August 08, 2009 05:49, permalink

No rating. Login to rate!
require 'rubygems'
require 'hpricot'

class Parse
  def self.to_hash n
    h = {}
    n.each_child do |c|
      c.respond_to?(:children) ?
        h[c.name] = c.children.size>1 ? to_hash(c) : c.children[0].name :
        h[n.name] = c.name
    end
    h
  end
end

doc = Hpricot.XML xml.gsub(/(>)\s*(<)/, '\1\2').gsub(/\n+/, "")
node = doc%'//return'
hash = Parse.to_hash node

Your refactoring





Format Copy from initial code

or Cancel