How to convert XML to hash in Rails?

How to convert XML body to hash in Ruby?

I have an XML body that I would like to parse in a hash

<soap:Body> <TimesInMyDAY> <TIME_DATA> <StartTime>2010-11-10T09:00:00</StartTime> <EndTime>2010-11-10T09:20:00</EndTime> </TIME_DATA> <TIME_DATA> <StartTime>2010-11-10T09:20:00</StartTime> <EndTime>2010-11-10T09:40:00</EndTime> </TIME_DATA> <TIME_DATA> <StartTime>2010-11-10T09:40:00</StartTime> <EndTime>2010-11-10T10:00:00</EndTime> </TIME_DATA> <TIME_DATA> <StartTime>2010-11-10T10:00:00</StartTime> <EndTime>2010-11-10T10:20:00</EndTime> </TIME_DATA> <TIME_DATA> <StartTime>2010-11-10T10:40:00</StartTime> <EndTime>2010-11-10T11:00:00</EndTime> </TIME_DATA> </TimesInMyDAY> </soap:Body> 

I would like to convert it to a hash like this:

 { :times_in_my_day => { :time_data = > [ {:start_time=>"2010-11-10T09:00:00", :end_time => "2010-11-10T09:20:00" }, {:start_time=>"2010-11-10T09:20:00", :end_time => "2010-11-10T09:40:00" }, {:start_time=>"2010-11-10T09:40:00", :end_time => "2010-11-10T10:00:00" }, {:start_time=>"2010-11-10T10:00:00", :end_time => "2010-11-10T10:20:00" }, {:start_time=>"2010-11-10T10:40:00", :end_time => "2010-11-10T11:00:00" } ] } } 

Ideally, tags will be converted to snake_case characters and become keys in the hash.

In addition, there are no time zone offsets in datetime. They are in the local time zone (not UTC). So I would like to parse it to show the local offset, and then convert the xml datetime strings to Rails DateTime objects. The resulting array will look something like this:

 { :times_in_my_day => { :time_data = > [ {:start_time=>Wed Nov 10 09:00:00 -0800 2010, :end_time => Wed Nov 10 9:20:00 -0800 2010 }, {:start_time=>Wed Nov 10 09:20:00 -0800 2010, :end_time => Wed Nov 10 9:40:00 -0800 2010 }, {:start_time=>Wed Nov 10 09:40:00 -0800 2010, :end_time => Wed Nov 10 10:00:00 -0800 2010 }, {:start_time=>Wed Nov 10 10:00:00 -0800 2010, :end_time => Wed Nov 10 10:20:00 -0800 2010 }, {:start_time=>Wed Nov 10 10:40:00 -0800 2010, :end_time => Wed Nov 10 11:00:00 -0800 2010 } ] } } 

I managed to convert a single datetime with the parse and in_time_zone as follows:

 Time.parse(xml_datetime).in_time_zone(current_user.time_zone) 

But I'm not quite sure what is the best way to parse the time when converting XML to hash.

I would be grateful for any advice. Thanks!

Edit

The code for converting a datetime string to a Rails DateTime object is incorrect. This will parse the xml datetime string at the system offset of the time zone and then convert that time to the user's time zone. The correct code is:

Time.zone.parse(xml_datetime)

If the user has a different time zone than the system, this will add the user's time zone offset to the original date string. There's a Railscast on how to enable custom timezone settings here: http://railscasts.com/episodes/106-time-zones-in-rails-2-1 .

+7
xml datetime ruby-on-rails hash
source share
5 answers

I used XML :: Simple in Perl because XML parsing using Perl was PITA.

When I switched to Ruby, I ended up using Nokogiri and found it very easy to use for parsing HTML and XML. It's so simple that I think in terms of CSS or XPath selectors and don't miss the XML hash converter.

 require 'ap' require 'date' require 'time' require 'nokogiri' xml = %{ <soap:Body> <TimesInMyDAY> <TIME_DATA> <StartTime>2010-11-10T09:00:00</StartTime> <EndTime>2010-11-10T09:20:00</EndTime> </TIME_DATA> <TIME_DATA> <StartTime>2010-11-10T09:20:00</StartTime> <EndTime>2010-11-10T09:40:00</EndTime> </TIME_DATA> <TIME_DATA> <StartTime>2010-11-10T09:40:00</StartTime> <EndTime>2010-11-10T10:00:00</EndTime> </TIME_DATA> <TIME_DATA> <StartTime>2010-11-10T10:00:00</StartTime> <EndTime>2010-11-10T10:20:00</EndTime> </TIME_DATA> <TIME_DATA> <StartTime>2010-11-10T10:40:00</StartTime> <EndTime>2010-11-10T11:00:00</EndTime> </TIME_DATA> </TimesInMyDAY> </soap:Body> } time_data = [] doc = Nokogiri::XML(xml) doc.search('//TIME_DATA').each do |t| start_time = t.at('StartTime').inner_text end_time = t.at('EndTime').inner_text time_data << { :start_time => DateTime.parse(start_time), :end_time => Time.parse(end_time) } end puts time_data.first[:start_time].class puts time_data.first[:end_time].class ap time_data[0, 2] 

the output of which is as follows:

 DateTime Time [ [0] { :start_time => #<DateTime: 2010-11-10T09:00:00+00:00 (19644087/8,0/1,2299161)>, :end_time => 2010-11-10 09:20:00 -0700 }, [1] { :start_time => #<DateTime: 2010-11-10T09:20:00+00:00 (22099598/9,0/1,2299161)>, :end_time => 2010-11-10 09:40:00 -0700 } ] 

Time values ​​are intentionally parsed in DateTime and Time objects to show that they can be used.

+5
source share

Hash.from_xml(xml) is an easy way to solve this problem. His Asset Support Method

+15
source share

ActiveSupport adds Hash.from_xml , which performs the conversion in a single call. Described in another question: Stack Overflow

Example:

 require 'open-uri' remote_xml_file = "https://www.example.com/some_file.xml" data = Hash.from_xml(open(remote_xml_file)) 
+3
source share

The original question was asked some time ago, but I found a simpler solution than using Nokogiri and searching for specific names in XML.

Nori.parse(your_xml) will parse the XML into a hash, and the keys will have the same names as your XML elements.

+2
source share

If you don't mind using a gem, crack does a pretty good job.

Crack processes the XML hash code, then you can loop the resulting hash to normalize datetimes.

edit Using REXML, you can try the following (it should be close to work, but I do not have access to the terminal, so some configuration may be required):

 require 'rexml/document' arr = [] doc = REXML::XPath.first(REXML::Document.new(xml), "//soap:Body/TimesInMyDAY").text REXML::XPath.each(doc, "//TIME_DATA") do |el| start = REXML::XPath.first(el, "//StartTime").text end = REXML::XPath.first(el, "//EndTime").text arr.push({:start_time => Time.parse(start).in_time_zone(current_user.time_zone), :end_time => Time.parse(end).in_time_zone(current_user.time_zone)}) end hash = { :times_in_my_day => { :time_data => arr } } 

Of course, this assumes that the structure is ALWAYS the same, and that the example you published was not invented for simplicity (as often happens).

0
source share

All Articles