Create a Ruby Hash out of an xml string with the 'ox' gem


I am currently trying to create a hash out of an xml documen, with the help of the ox gem

Input xml:

<?xml version="1.0"?>
<expense>
  <payee>starbucks</payee>
  <amount>5.75</amount>
  <date>2017-06-10</date>
</expense> 

with the following ruby/ox code:

doc = Ox.parse(xml)
plist = doc.root.nodes

I get the following output:

=> [#<Ox::Element:0x00007f80d985a668 @value="payee", @attributes={}, @nodes=["starbucks"]>, #<Ox::Element:0x00007f80d9839198 @value="amount", @attributes={}, @nodes=["5.75"]>, #<Ox::Element:0x00007f80d9028788 @value="date", @attributes={}, @nodes=["2017-06-10"]>]

The output I want is a hash in the format:

{'payee'  => 'Starbucks',
'amount' => 5.75,
'date'   => '2017-06-10'}

to save in my sqllite database.
How can I transform the objects array into a hash like above.
Any help is highly appreciated.

- - Source

Answers

answered 12 mon ago SRack #1

The docs suggest you can use the following:

require 'ox'

xml = %{
<top name="sample">
  <middle name="second">
    <bottom name="third">Rock bottom</bottom>
  </middle>
</top>
}

puts Ox.load(xml, mode: :hash)
puts Ox.load(xml, mode: :hash_no_attrs)

#{:top=>[{:name=>"sample"}, {:middle=>[{:name=>"second"}, {:bottom=>[{:name=>"third"}, "Rock bottom"]}]}]}
#{:top=>{:middle=>{:bottom=>"Rock bottom"}}}

I'm not sure that's exactly what you're looking for though.

Otherwise, it really depends on the methods available on the Ox::Element instances in the array.

From the docs, it looks like there are two handy methods here: you can use [] and text.

Therefore, I'd use reduce to coerce the array into the hash format you're looking for, using something like the following:

ox_nodes = [#<Ox::Element:0x00007f80d985a668 @value="payee", @attributes={}, @nodes=["starbucks"]>, #<Ox::Element:0x00007f80d9839198 @value="amount", @attributes={}, @nodes=["5.75"]>, #<Ox::Element:0x00007f80d9028788 @value="date", @attributes={}, @nodes=["2017-06-10"]>]

ox_nodes.reduce({}) do |hash, node|
  hash[node['@value']] = node.text
  hash
end

I'm not sure whether node['@value'] will work, so you might need to experiment with that - otherwise perhaps node.instance_variable_get('@value') would do it.

node.text does the following, which sounds about right:

Returns the first String in the elements nodes array or nil if there is no String node.

N.B. I prefer to tidy the reduce block a little using tap, something like the following:

ox_nodes.reduce({}) do |hash, node|
  hash.tap { |h| h[node['@value']] = node.text } 
end

Hope that helps - let me know how you get on!

answered 12 mon ago Sebastian Peter #2

I found the answer to the question in my last comment by myself:

def create_xml(expense)
    Ox.default_options=({:with_xml => false})
    doc = Ox::Document.new(:version => '1.0')
    expense.each do |key, value|
        e = Ox::Element.new(key)
        e << value
        doc << e
    end
    Ox.dump(doc)
end

The next question would be how can i transform the value of the amount key from a string to an integer befopre saving it to the database

comments powered by Disqus