【问题标题】:How do I convert XML into a hash in Rails?如何在 Rails 中将 XML 转换为哈希?
【发布时间】:2011-05-08 05:49:33
【问题描述】:

如何在 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 转换为哈希时解析时间的最佳方法。

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

编辑

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

Time.zone.parse(xml_datetime)

如果用户的时区与系统不同,这会将用户的时区偏移量添加到原始日期时间字符串中。这里有一个关于如何启用用户时区偏好的 Railscast:http://railscasts.com/episodes/106-time-zones-in-rails-2-1

【问题讨论】:

  • Time.zone.parse(xml_datetime)

标签: ruby-on-rails xml datetime hash


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    我曾经在 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 对象,以表明两者都可以使用。

    【讨论】:

    • 酷,现在试试这个。有没有办法将 Nokogiri xml 文档转换为哈希?像doc.to_hash?。我有一个 XML 源代码嵌套很深的情况,所以想知道是否有一种优雅的方法可以做到这一点,而无需为每个级别编写大量迭代器。
    • 看起来我可以做到result = Hash.from_xml(xml_source),但不会将标签转换为snake_case符号:-(
    • 整个想法是避免将整个 XML 文件转换为散列。它适用于小文件,但与大文件分开。 XPATH 访问器非常强大,可以将一些搜索和迭代卸载到 XML 解析器,这非常快。有关更多信息,请参阅 Nokogiri 的 Searching an HTML / XML Document 文档。
    • 有道理,我在文档中有一些其他级别和元素,我正在尝试将映射映射到数据库中,所以我认为将它们作为哈希进行迭代将是要走的路。但这对于 Nokogiri 的搜索功能来说可能是不必要的步骤!
    • 这只是一种不同的迭代方式。习惯用 Nokogiri 做这件事,你会发现从 HTML 页面抓取数据同样容易,假设 HTML 不是病态的。
    【解决方案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))
    

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 它在背后使用Nokogiri。那么为什么要在 gem 上使用 gem?
      • @TaimoorChangaiz 它也使用其他人。您可以使用它来抽象出复杂性。
      【解决方案5】:

      如果您不介意使用 gem,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 } }
      

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

      【讨论】:

      • 不介意使用 gem,但我尝试使用 Savon gem,其中包含使用 Crack 的 to_hash 方法......但是,我在日期解析时遇到了问题。似乎 Savon/Crack 会假设没有偏移量的 xml 日期时间字符串是 UTC,而不是本地用户的时区。所以所有的时间都在无意中改变。所以2010-11-10T09:00:00 在我真正想要Wed Nov 10 09:00:00 -0800 2010 的时候变成了Wed Nov 10 01:00:00 -0800 2010 :-(
      • 我在尝试doc = REXML::XPath.first(REXML::Document.new(xml), "//soap:Body/TimesInMyDAY").text 时遇到了一个奇怪的错误。错误是REXML::UndefinedNamespaceException: Undefined prefix soap found
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-16
      • 2021-07-13
      • 2016-05-27
      • 2015-07-03
      • 1970-01-01
      • 2016-08-26
      • 2012-11-11
      相关资源
      最近更新 更多