简体   繁体   English

如何将XML转换为Rails中的哈希?

[英]How do I convert XML into a hash in Rails?

How do I convert an XML body to a hash in Ruby? 如何在Ruby中将XML主体转换为哈希?

I have an XML body which I'd like to parse into a hash 我有一个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>

I'd like to convert it into 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, the tags would convert to snake_case symbols and become keys within the hash. 理想情况下,标记会转换为snake_case符号并成为哈希中的键。

Also, the datetimes are missing their time zone offsets. 此外,日期时间缺少其时区偏移。 They are in the local time zone (not UTC). 它们位于当地时区(不是UTC)。 So I'd like to parse it to show the local offset and then convert the xml datetime strings into Rails DateTime objects. 所以我想解析它以显示本地偏移量,然后将xml日期时间字符串转换为Rails DateTime对象。 The resulting array would be something like: 结果数组将是这样的:

{ :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 was able to convert a single datetime with the parse and in_time_zone methods this way: 我能够通过这种方式使用parsein_time_zone方法转换单个日期时间:

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

But I'm not quite sure the best way to parse the times while converting the XML into a hash. 但是我不太确定在将XML转换为哈希时解析时间的最佳方法。

I'd appreciate any advice. 我很感激任何建议。 Thanks! 谢谢!

Edit 编辑

The code for converting the datetime string into a Rails DateTime object is wrong. 将datetime字符串转换为Rails DateTime对象的代码是错误的。 That will parse the xml datetime string to the system's timezone offset and then convert that time to the user's timezone. 这会将xml日期时间字符串解析为系统的时区偏移量,然后将该时间转换为用户的时区。 The correct code is: 正确的代码是:

Time.zone.parse(xml_datetime)

If the user has a different time zone other than the system, this will add the user's time zone offset to the original datetime string. 如果用户具有不同于系统的时区,则会将用户的时区偏移量添加到原始日期时间字符串。 There's a Railscast on how to enable user timezone preferences here: http://railscasts.com/episodes/106-time-zones-in-rails-2-1 . 有关如何在此处启用用户时区首选项的Railscast: http//railscasts.com/episodes/106-time-zones-in-rails-2-1

Hash.from_xml(xml) is simple way to solve this. Hash.from_xml(xml)是解决此问题的简单方法。 Its activesupport method 它的activesupport方法

I used to use XML::Simple in Perl because parsing XML using Perl was a PITA. 我以前在Perl中使用XML :: Simple,因为使用Perl解析XML是一个PITA。

When I switched to Ruby I ended up using Nokogiri, and found it to be very easy to use for parsing HTML and XML. 当我切换到Ruby时,我最终使用了Nokogiri,发现它非常容易用于解析HTML和XML。 It's so easy that I think in terms of CSS or XPath selectors and don't miss a XML-to-hash converter. 我认为在CSS或XPath选择器方面非常容易,并且不要错过XML-to-hash转换器。

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]

with the output looking like: 输出看起来像:

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
    }
]

The time values are deliberately parsed into DateTime and Time objects to show that either could be used. 故意将时间值解析为DateTime和Time对象,以显示可以使用其中任何一个。

ActiveSupport adds a Hash.from_xml , which does the conversion in a single call. ActiveSupport添加了一个Hash.from_xml ,它在一次调用中进行转换。 Described in another question: https://stackoverflow.com/a/7488299/937595 另一个问题描述: https//stackoverflow.com/a/7488299/937595

Example: 例:

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

The original question was asked some time ago, but I found a simpler solution than using Nokogiri and searching for specific names in the XML. 最初的问题是在前一段时间被问到的,但我找到了比使用Nokogiri并在XML中搜索特定名称更简单的解决方案。

Nori.parse(your_xml) will parse the XML into a hash and the keys will have the same names as your XML items. Nori.parse(your_xml)将XML解析为散列,并且键将与XML项具有相同的名称。

If you don't mind using a gem, crack does a pretty good job at this. 如果你不介意使用宝石,那么破解就可以了。

Crack does the XML to hash processing, then you can loop over the resulting hash to normalize the datetimes. Crack对XML进行哈希处理,然后您可以循环生成的哈希以规范化日期时间。

edit Using REXML, you could try the following (should be close to working, but I do not have access to a terminal so it may need some tweaking): 编辑使用REXML,你可以尝试以下(应该接近工作,但我没有访问终端,所以它可能需要一些调整):

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 the structure is ALWAYS the same, and that the example you posted was not contrived for simplicity sake (as examples often are). 当然,这假设结构始终是相同的,并且您发布的示例并非为简单起见而设计(通常是示例)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM