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 !
kotrin
August 8, 2009, August 08, 2009 05:49, permalink
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
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