10

How can I add a Nokogiri::XML::Element to a XML document that is being created with Nokogiri::XML::Buider?

My current solution is to serialize the element and use the << method to have the Builder reinterpret it.

orig_doc = Nokogiri::XML('<root xmlns="foobar"><a>test</a></root>')
node = orig_doc.at('/*/*[1]')

puts Nokogiri::XML::Builder.new do |doc|
    doc.another {
        # FIXME: this is the round-trip I would like to avoid
        xml_text = node.to_xml(:skip_instruct => true).to_s
        doc << xml_text

        doc.second("hi")
    }
end.to_xml

# The expected result is
#
# <another>
#    <a xmlns="foobar">test</a>
#    <second>hi</second>
# </another>

However the Nokogiri::XML::Element is a quite big node (in the order of kilobytes and thousands of nodes) and this code is in the hot path. Profiling shows that the serialization/parsing round trip is very expensive.

How can I instruct the Nokogiri Builder to add the existing XML element node in the "current" position?

2 Answers 2

12

Without using a private method you can get a handle on the current parent element using the parent method of the Builder instance. Then you can append an element to that (even from another document). For example:

require 'nokogiri'
doc1 = Nokogiri.XML('<r><a>success!</a></r>')
a = doc1.at('a')

# note that `xml` is not a Nokogiri::XML::Document,
#  but rather a Nokogiri::XML::Builder instance.
doc2 = Nokogiri::XML::Builder.new do |xml|
  xml.some do
    xml.more do
      xml.parent << a
    end
  end
end.doc

puts doc2
#=> <?xml version="1.0"?>
#=> <some>
#=>   <more>
#=>     <a>success!</a>
#=>   </more>
#=> </some>
Sign up to request clarification or add additional context in comments.

2 Comments

This is much better than my kludge with #insert.
When I tried this I was losing a namespace for an xml attribute
2

After looking at the Nokogiri source I have found this fragile solution: using the protected #insert(node) method.

The code, modified to use that private method looks like this:

doc.another {
    xml_text = node.to_xml(:skip_instruct => true).to_s
    doc.send('insert', xml_text) # <= use `#insert` instead of `<<`

    doc.second("hi")
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.