Given this XML in a string called "string":
<Guide>
<Master>
<Part>12345</Part>
<Sub>
<Name>A</Name>
</Sub>
<Sub>
<Name>B</Name>
</Sub>
</Master>
<Master>
<Part>XYZABC</Part>
<Sub>
<Name>A</Name>
</Sub>
<Sub>
<Name>C</Name>
</Sub>
</Master>
</Guide>
And this line of code:
bgdoc = Nokogiri::XML::DocumentFragment.parse(xstring.to_xml)
I want to loop through all nodes named "Part".
I tried the following:
bgdoc.xpath("//Part").each do |node|
and:
bgdoc.children.each do |node|
next unless node.name=="Part"
But that didn't work.
The problem is that parsing the XML as a fragment returns a partial XML document, i.e., a DocumentFragment
, which doesn't have a root:
1.9.2-p290 :002 > doc = Nokogiri::XML::DocumentFragment.parse('<a><b>foo</b></a>').root
NoMethodError: undefined method `root' for #<Nokogiri::XML::DocumentFragment:0x00000100b34448>
from (irb):2
from /Users/greg/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'
whereas a full XML document does have a root:
1.9.2-p290 :003 > doc = Nokogiri::XML('<a><b>foo</b></a>').root
=> #<Nokogiri::XML::Element:0x8058b350 name="a" children=[#<Nokogiri::XML::Element:0x80587b10 name="b" children=[#<Nokogiri::XML::Text:0x80587818 "foo">]>]>
By default Nokogiri is going to search from the root of the document with an XPath like //Path
:
1.9.2-p290 :004 > doc = Nokogiri::XML('<a><Path>foo</Path></a>').search('//Path')
=> [#<Nokogiri::XML::Element:0x8055465c name="Path" children=[#<Nokogiri::XML::Text:0x805543c8 "foo">]>]
But that fails with the fragment because of the missing root:
1.9.2-p290 :005 > doc = Nokogiri::XML::DocumentFragment.parse('<a><Path>foo</Path></a>').search('//Path')
=> []
The trick is to tell Nokogiri where to look when dealing with the fragment. Using a relative search or a wildcard:
1.9.2-p290 :006 > doc = Nokogiri::XML::DocumentFragment.parse('<a><Path>foo</Path></a>').search('.//Path')
=> [#<Nokogiri::XML::Element:0x8053c69c name="Path" children=[#<Nokogiri::XML::Text:0x8053c46c "foo">]>]
or
1.9.2-p290 :007 > doc = Nokogiri::XML::DocumentFragment.parse('<a><Path>foo</Path></a>').search('*//Path')
=> [#<Nokogiri::XML::Element:0x8052a208 name="Path" children=[#<Nokogiri::XML::Text:0x80529fec "foo">]>]
bgdoc = Nokogiri::XML::DocumentFragment.parse(<<EOF)
<xml stuff>
EOF
bgdoc.xpath(".//Part").each do |node|
# some instruction
end
If you do have just a string, simply parsing with Nokogiri:XML
instead:
bgdoc = Nokogiri::XML.parse(string)
will give you a root from which you can use the xpath //Part
as you expected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With