Ruby on rails 如何在Rails中将XML转换为哈希?

Ruby on rails 如何在Rails中将XML转换为哈希?,ruby-on-rails,xml,datetime,hash,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> <

如何在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>
理想情况下,标记将转换为snake_case符号,并成为散列中的键

此外,日期时间缺少其时区偏移。它们位于本地时区(非UTC)。所以我想解析它以显示本地偏移量,然后将xml datetime字符串转换为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 }
        ]
    } 
}
我能够用
parse
在时区中转换单个日期时间,方法如下:

Time.parse(xml_datetime).in_time_zone(current_user.time_zone)
但我不太确定在将XML转换为哈希时解析时间的最佳方法

如果有任何建议,我将不胜感激。谢谢

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

Time.zone.parse(xml\u datetime)


如果用户的时区与系统不同,则会将用户的时区偏移量添加到原始日期时间字符串中。这里有一个关于如何启用用户时区首选项的Railscast:。

如果您不介意使用gem,那么在这方面做得很好

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

编辑 使用REXML,您可以尝试以下操作(应该可以正常工作,但我无法访问终端,因此可能需要进行一些调整):


当然,这是假设结构总是相同的,并且您发布的示例不是为了简单而设计的(通常是示例)。

我过去在Perl中使用XML::Simple,因为使用Perl解析XML是一种PITA

当我切换到Ruby时,我最终使用了Nokogiri,并发现它非常容易用于解析HTML和XML。它非常简单,我用CSS或XPath选择器来思考,而且不会错过XML到哈希转换器

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]
需要“ap”
需要“日期”
需要“时间”
需要“nokogiri”
xml=%{
2010-11-10T09:00:00
2010-11-10T09:20:00
2010-11-10T09:20:00
2010-11-10T09:40:00
2010-11-10T09:40:00
2010-11-10T10:00:00
2010-11-10T10:00:00
2010-11-10T10:20:00
2010-11-10T10:40:00
2010-11-10T11:00:00
}
时间_数据=[]
doc=Nokogiri::XML(XML)
doc.search('//TIME_DATA')。每个dot|
开始时间=t.at('StartTime')。内部文本
结束时间=t.at('EndTime')。内部文本
time\u data DateTime.parse(开始时间),
:end\u time=>time.parse(end\u time)
}
结束
将时间\数据放在第一个[:开始\时间].class
将时间\数据放在第一个[:结束\时间].class
ap时间单位数据[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
时间
[
[0] {
:开始时间=>#,
:结束时间=>2010-11-10 09:20:00-0700
},
[1] {
:开始时间=>#,
:结束时间=>2010-11-10 09:40:00-0700
}
]

时间值被故意解析为DateTime和time对象,以表明两者都可以使用。

最初的问题是在一段时间前提出的,但我找到了一个比使用Nokogiri和在XML中搜索特定名称更简单的解决方案


Nori.parse(您的xml)
将xml解析为散列,并且键将与您的xml项具有相同的名称。

ActiveSupport添加了一个
hash.from_xml
,它在单个调用中进行转换。在另一个问题中描述:

示例:

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

Hash.from_xml(xml)
是解决这个问题的简单方法。它的activesupport方法

不介意使用gem,但我尝试了使用savongem,其中包括使用Crack的to_散列方法。。。但是,我在日期解析方面遇到了问题。似乎Savon/Crack会假设没有偏移量的xml日期时间字符串是UTC格式的,而不是本地用户的时区。所以所有的时间都被无意中转移了。因此,
2010-11-10T09:00:00
变成了
2010年11月10日星期三01:00:00-0800
当我真正想要
2010年11月10日星期三09:00:00-0800
:-(我在尝试
doc=REXML::XPath.first(REXML::Document.new(xml),“//soap:Body/TimesInMyDAY”)时遇到了一个奇怪的错误.text
。错误是
REXML::UndefinedNamespaceException:Undefined prefix soap found
很酷,现在尝试一下。有没有办法将Nokogiri xml文档转换成散列?类似于
doc.to_hash
?。我有一个xml源嵌套很深的例子,所以想知道是否有一种优雅的方法可以不用编写每个级别都有很多迭代器。看起来我可以做
result=Hash.from\u xml(xml\u source)
,但不会将标记转换为snake\u case符号:-(整个想法是避免将整个XML文件转换为散列。它适用于小文件,但与大文件分开。XPATH访问器非常强大,可以将一些搜索和迭代转移到XML解析器,这非常快。有关更多信息,请参阅Nokogiri的文档。有意义的是,我在文档,我正试图将其映射到数据库中,因此我认为将它们作为散列进行迭代将是一种可行的方法。但这可能是Nokogiri的搜索功能中不必要的一步!这只是一种不同的迭代方式。习惯于使用Nokogiri,您会发现从HTML页面获取数据同样容易,假设HTML不是病态的。
Time.zone.parse(xml_datetime)
它在背后使用Nokogiri。那么为什么要在gem上使用gem呢?@TaimoorChangaiz它也使用其他的。你可以用它来抽象复杂度。
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
    }
]
require 'open-uri'
remote_xml_file = "https://www.example.com/some_file.xml"
data = Hash.from_xml(open(remote_xml_file))