How can I iterate through child nodes of a particular name in a Nokogiri XML DocumentFragment?

15,769

Solution 1

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">]>] 

Solution 2

bgdoc = Nokogiri::XML::DocumentFragment.parse(<<EOF)
  <xml stuff>
EOF

bgdoc.xpath(".//Part").each do |node|
  # some instruction
end

Solution 3

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.

Share:
15,769
RubyRedGrapefruit
Author by

RubyRedGrapefruit

I have been developing in Ruby on Rails for over 10 years. I'm focusing on developing reusable APIs with Grape API in Ruby. I have 25 years of experience with relational databases including Oracle, MySQL, and Postgres. I have been programming computers since I received a Commodore 64 in the early 1980s.

Updated on July 19, 2022

Comments

  • RubyRedGrapefruit
    RubyRedGrapefruit almost 2 years

    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.