用Ruby解析XML

60

我对处理XML完全不熟悉,但是突然需要处理一种我不熟悉的XML格式。标签中包含冒号。

<THING1:things type="Container">
  <PART1:Id type="Property">1234</PART1:Id>
  <PART1:Name type="Property">The Name</PART1:Name>
</THING1:things>

这是一个大文件,除了这里展示的内容外还有很多。希望有人对这种格式比较熟悉。有人知道如何处理这种类型的XML文档吗?

我不想采用一种蛮力解析文本的方式,但我似乎无法通过REXML或Hpricot取得任何进展,并且我怀疑这归咎于这些不寻常的标记。

我的Ruby代码:

    require 'hpricot'
    xml = File.open( "myfile.xml" )

    doc = Hpricot::XML( xml )

   (doc/:things).each do |thg|
     [ 'Id', 'Name' ].each do |el|
       puts "#{el}: #{thg.at(el).innerHTML}"
     end
   end

这段代码仅仅是从http://railstips.org/blog/archives/2006/12/09/parsing-xml-with-hpricot/中提取的。

我本来认为我可以从中找出一些有用的东西,但是这段代码没有返回任何内容。它没有产生错误,只是没有返回结果。


我理解第一行应该是 <THING1:things type="Container"> 而不是 THING1:thingstype="Container">,对吗? - Benjamin Cox
你能展示一下你的 Hpricot 尝试吗?它可以处理命名空间,我们帮助你找出问题比起重新开始更容易且可能更有教育意义。 - Benjamin Cox
我现在真的没有太多尝试。只是试图模仿一个教程。我会编辑帖子以包含它。 - n8gard
4
记录一下,hpricot已经不再维护了。现在我们都使用nokogiri。 - pguardiario
THING1PART1是XML命名空间,需要进行声明。请参考http://en.wikipedia.org/wiki/XML_namespace。 - Jonas Elfström
2个回答

93

正如 @pguardiario 提到的,Nokogiri 是事实上的 XML 和 HTML 解析库。如果您想要打印出您的示例中的 IdName 值,以下是您应该这样做的方法:

require 'nokogiri'

xml_str = <<EOF
<THING1:things type="Container">
  <PART1:Id type="Property">1234</PART1:Id>
  <PART1:Name type="Property">The Name</PART1:Name>
</THING1:things>
EOF

doc = Nokogiri::XML(xml_str)

thing = doc.at_xpath('//things')
puts "ID   = " + thing.at_xpath('//Id').content
puts "Name = " + thing.at_xpath('//Name').content

一些注意事项:

  • at_xpath 用于匹配单个元素。如果你知道有多个元素,应该使用 xpath
  • 根据你的文档不同,命名空间可能会成为一个问题,调用 doc.remove_namespaces! 可以帮助解决(参见这个回答进行简要讨论)。
  • 如果你更熟悉 css 方法,可以使用它们代替 xpath
  • 一定要在 irb 或者 pry 中尝试一下方法来调查。

资源

更新

要处理多个元素,你需要一个根元素,并且需要在 xpath 查询中去掉 //

require 'nokogiri'

xml_str = <<EOF
<root>
  <THING1:things type="Container">
    <PART1:Id type="Property">1234</PART1:Id>
    <PART1:Name type="Property">The Name1</PART1:Name>
  </THING1:things>
  <THING2:things type="Container">
    <PART2:Id type="Property">2234</PART2:Id>
    <PART2:Name type="Property">The Name2</PART2:Name>
  </THING2:things>
</root>
EOF

doc = Nokogiri::XML(xml_str)
doc.xpath('//things').each do |thing|
  puts "ID   = " + thing.at_xpath('Id').content
  puts "Name = " + thing.at_xpath('Name').content
end
这将给你:
Id   = 1234
Name = The Name1

ID   = 2234
Name = The Name2

如果您更熟悉CSS选择器,您可以使用这段几乎相同的代码:

doc.css('things').each do |thing|
  puts "ID   = " + thing.at_css('Id').content
  puts "Name = " + thing.at_css('Name').content
end

这太棒了。我已经让它针对一个项目工作了。它似乎拉取了第一个项目,但没有其他的 - 就像你说的那样。你能给出一个输出所有“事物”的示例吗?大约有10个。 - n8gard
1
@B5Fan74 我已经更新了我的回答,并提供了一个示例。这有帮助吗?您需要添加某种根元素,您的XML文件可能已经有了。然后,您需要从xpath查询中删除//(或使用我更喜欢的CSS接口)。在Nokogiri的搜索文档中有一个方便的示例,希望对您有用。 - jmdeldin
更新中...链接已移至:https://www.engineyard.com/blog/getting-started-with-nokogiri - digitalextremist
1
@jayqui 这完全取决于您的文档以及您编写选择器的方式。文档提供了更多关于行为的信息。 - jmdeldin
运行你的第一个示例会出现 undefined method at_xpath' for nil:NilClass` 的错误。 - reducing activity
显示剩余2条评论

50
如果在Rails环境中,Hash对象会被扩展,这样可以利用from_xml方法:
xml = File.open("myfile.xml")
data = Hash.from_xml(xml)

15
from_xml 不是 Hash 的原生方法,它是 Rails / ActiveSupport 的一部分。如果你在这个环境中,它可以正常工作。 - Trashpanda
1
这只在 Rails 环境中起作用,不能在纯 Ruby 中使用。 - Yakob Ubaidi
11
@YakobUbaidi 注意帖子的前五个单词。 - IliasT
YMMV。这并不总是将您所期望/需要成为哈希的子数据结构转换为哈希。它可能会将这些嵌套的子项转换为数组,这样您就会失去键。 - WillHaslett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接