How to parse XML and put all the same named node values into an array
I have XML I'm trying to parse and get the status of each of my heart beat tests using Nokogiri.
Here is my code:
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>
I have tried using both css
and xpath
to retrieve the value for each Status and put it into an array:
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)
Both will return service_state = []
I have almost identical XML for another test and I used the following snippet of code which does exactly what I wanted but for some reason isn't working with the XML that contains namespaces:
service_state = doc.css("HBeat Status").map(&:text)
In addition to Greg's response (that the XML needs a containing element), your XPath expression is selecting the wrong thing:
//*[@a:Status]
selects all elements that have a:Status
attributes. If you want all elements that have a child a:Status
element, just remove the @
from the node test:
//*[a:Status]
Part of the problem is that your XML sample is not correct: You're missing the namespace declaration though you're using namespaces, and you're missing a containing tag. The first can be worked around but the second needs a tweak to the 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
Namespaces are a good thing, but dealing with them can be a pain when all you want to do is get at the data. Nokogiri has some tricks for making them less annoying, such as using CSS accessors like I did above, which means "find the Status tag in all namespaces", so even if the namespaces aren't declared it'll still be good.
If you are in control of the XML then you can do away with the namespaces. They're great when dealing with possible tag collisions but that's not too likely when you own the mechanism generating the file, so, if that's the case, you can probably do away with them. If you need the namespace, then it should be declared something like this:
<xml xmlns:a="http://schemas.datacontract.org/2004/07/OpenAPI.Entity">
Without it the XML parses with a lot of namespace errors:
(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>]
But after adding it the doc's error list is much smaller:
(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>]
See "How to avoid joining all text from Nodes when scraping" also.
精彩评论