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

时间:2010-11-11 02:08:04

标签: ruby-on-rails xml datetime hash

如何在Ruby中将XML主体转换为哈希?

我有一个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>

我想把它转换成这样的哈希:

{ :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" }
        ]
    } 
}

理想情况下,标记会转换为snake_case符号并成为哈希中的键。

此外,日期时间缺少时区偏移。它们位于当地时区(不是UTC)。所以我想解析它以显示本地偏移量,然后将xml日期时间字符串转换为Rails DateTime对象。结果数组将类似于:

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

我能够通过这种方式使用parsein_time_zone方法转换单个日期时间:

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

但是我不太确定在将XML转换为哈希时解析时间的最佳方法。

我很感激任何建议。谢谢!

修改

将datetime字符串转换为Rails DateTime对象的代码是错误的。这会将xml日期时间字符串解析为系统的时区偏移量,然后将该时间转换为用户的时区。正确的代码是:

Time.zone.parse(xml_datetime)

如果用户具有与系统不同的时区,则会将用户的时区偏移量添加到原始日期时间字符串。有关如何在此处启用用户时区首选项的Railscast:http://railscasts.com/episodes/106-time-zones-in-rails-2-1

5 个答案:

答案 0 :(得分:15)

Hash.from_xml(xml)是解决此问题的简单方法。其主动支持方法

答案 1 :(得分:5)

我曾经在Perl中使用XML :: Simple,因为使用Perl解析XML是一个PITA。

当我切换到Ruby时,我最终使用了Nokogiri,发现它非常容易用于解析HTML和XML。我认为在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]

输出看起来像:

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

时间值被故意解析为DateTime和Time对象,以显示可以使用它们。

答案 2 :(得分:3)

ActiveSupport添加了Hash.from_xml,可在一次通话中完成转换。在另一个问题中描述:https://stackoverflow.com/a/7488299/937595

示例:

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

答案 3 :(得分:2)

前一段时间问了原始问题,但我找到了比使用Nokogiri并在XML中搜索特定名称更简单的解决方案。

Nori.parse(your_xml)会将XML解析为哈希值,并且键的名称与XML项目的名称相同。

答案 4 :(得分:0)

如果你不介意使用宝石,crack在这方面做得很好。

Crack对XML执行哈希处理,然后您可以循环生成的哈希以规范化日期时间。

修改 使用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 } }

当然,这假设结构始终是相同的,并且您发布的示例并非为简单起见而设计(通常是示例)。