Ruby - Nokogiri - 从内存中解析XML并将所有相同名称的节点值放入数组中

时间:2010-12-21 22:36:42

标签: ruby xml nokogiri

我有一个XML,我试图从内存中解析并使用Nokogiri获取每个心跳测试的状态。这是我的解决方案......

xml = 
<a:HBeat>
  <a:ElapsedTime>3 ms</a:ElapsedTime>
  <a:Name>Service 1</a:Name>
  <a:Status>true</a:Status>
</a:HBeat>
<a:HBeat>
  <a:ElapsedTime>4 ms</a:ElapsedTime>
  <a:Name>Service 2</a:Name>
  <a:Status>true</a:Status>
  </a:HBeat>
<a:HBeat>

我尝试使用css和xpath来拉回每个Status的值并将其放入数组中。代码如下:

doc = Nokogiri::XML.parse(xml)
#service_state = doc.css("a:HBeat, a:Status", 'a' => 'http://schemas.datacontract.org/2004/07/OpenAPI.Entity').map {|node| node.children.text}
service_state = doc.xpath("//*[@a:Status]", 'a' => 'http://schemas.datacontract.org/2004/07/OpenAPI.Entity').map(&:text)

两者都将返回service_state = []。有什么想法或建议吗?

另外,考虑到我有几乎相同的xml用于另一个测试,我使用了以下代码片段,它完全符合我的要求,但由于某种原因不能使用包含命名空间的xml。

service_state = doc.css("HBeat Status").map(&:text)

谢谢!

2 个答案:

答案 0 :(得分:2)

部分问题是您的XML示例不正确:虽然您正在使用名称空间,但您缺少名称空间声明,并且您缺少包含标记。第一个可以解决,但第二个需要调整XML。

require 'nokogiri'
require 'pp'

xml = <<EOT
<xml xmlns:a="http://schemas.datacontract.org/2004/07/OpenAPI.Entity"> # <-- changed
  <a:HBeat>
    <a:ElapsedTime>3 ms</a:ElapsedTime>
    <a:Name>Service 1</a:Name>
    <a:Status>true</a:Status>
  </a:HBeat>
  <a:HBeat>
    <a:ElapsedTime>4 ms</a:ElapsedTime>
    <a:Name>Service 2</a:Name>
    <a:Status>true</a:Status>
    </a:HBeat>
  <a:HBeat>
</xml>
EOT

doc = Nokogiri::XML(xml)
service_state = doc.css('a|Status').map(&:text)      # <-- changed to show CSS with namespace
pp service_state

service_state = doc.search('//a:Status').map(&:text) # <-- added
pp service_state                                     # <-- added

>> ruby test.rb
>> ["true", "true"]
>> ["true", "true"]                                  # <-- added

命名空间是一件好事,但是当你想要做的只是获取数据时,处理它们会很痛苦。 Nokogiri有一些技巧可以让它们变得不那么烦人,例如像我上面那样使用CSS访问器,这意味着“在所有命名空间中找到Status标签”,所以即使没有声明命名空间,它仍然会很好。

如果您控制XML,那么您可以取消命名空间。它们在处理可能的标记冲突时非常好,但是当您拥有生成文件的机制时,这种情况不太可能,因此,如果是这种情况,您可以放弃它们。如果你需要命名空间,那么它应该被声明为:

<xml xmlns:a="http://schemas.datacontract.org/2004/07/OpenAPI.Entity">

没有它,XML会解析大量的命名空间错误:

(rdb:1) pp doc.errors
[#<Nokogiri::XML::SyntaxError: Namespace prefix a on HBeat is not defined>,
#<Nokogiri::XML::SyntaxError: Namespace prefix a on ElapsedTime is not defined>,
#<Nokogiri::XML::SyntaxError: Namespace prefix a on Name is not defined>,
#<Nokogiri::XML::SyntaxError: Namespace prefix a on Status is not defined>,
#<Nokogiri::XML::SyntaxError: Namespace prefix a on HBeat is not defined>,
#<Nokogiri::XML::SyntaxError: Namespace prefix a on ElapsedTime is not defined>,
#<Nokogiri::XML::SyntaxError: Namespace prefix a on Name is not defined>,
#<Nokogiri::XML::SyntaxError: Namespace prefix a on Status is not defined>,
#<Nokogiri::XML::SyntaxError: Namespace prefix a on HBeat is not defined>,
#<Nokogiri::XML::SyntaxError: Opening and ending tag mismatch: HBeat line 12 and xml>,
#<Nokogiri::XML::SyntaxError: Premature end of data in tag xml line 1>]

但是在添加之后,doc的错误列表要小得多:

(rdb:1) pp doc.errors
[#<Nokogiri::XML::SyntaxError: Opening and ending tag mismatch: HBeat line 12 and xml>,
#<Nokogiri::XML::SyntaxError: Premature end of data in tag xml line 1>]

另请参阅“How to avoid joining all text from Nodes when scraping”。

答案 1 :(得分:2)

除了Greg的响应(XML需要包含元素)之外,你的XPath表达式选择了错误的东西:

 //*[@a:Status]

选择具有以下内容的所有元素:Status attributes 。如果您希望所有具有子项的元素为:Status元素,只需从节点测试中删除@

 //*[a:Status]