How to create an XML document with a namespaced root element with Nokogiri Builder
Asked Answered
S

3

13

I'm implementing an exporter for XML data that requires namespaces. I'm using Nokogiri's XML Builder (version 1.4.0) to do this, however, I can't get Nokogiri to create a root node with a namespace.

This works:

Nokogiri::XML::Builder.new { |xml| xml.root('xmlns:foobar' => 'my-ns-url') }.to_xml

<?xml version="1.0"?>
<root xmlns:foobar="my-ns-url"/>

As does this:

Nokogiri::XML::Builder.new do |xml| 
  xml.root('xmlns:foobar' => 'my-ns-url') { xml['foobar'].child }
end.to_xml

<?xml version="1.0"?>
<root xmlns:foobar="my-ns-url">
  <foobar:child/>
</root>

However, I need something like <foo:root> and this doesn't work:

Nokogiri::XML::Builder.new { |xml| xml['foobar'].root('xmlns:foobar' => 'my-ns-url') }.to_xml

NoMethodError: undefined method `namespace_definitions' for #<Nokogiri::XML::Document:0x11bfef8 name="document">

Namespaces have to be defined before use, apparently, so there's no way to add one to the root node.

I found "Define root node with a namespace?" on the Nokogiri mailing list, but it had no replies.

Does anyone have a solution?

Superficies answered 1/12, 2009 at 22:35 Comment(1)
"# Namespaces" in the Nokogiri "Cheat sheet" has information in this.Neutralism
B
16
require 'rubygems'
require 'nokogiri'

puts Nokogiri::XML::Builder.new do |xml| 
  xml.root("xmlns:foo"=>"url") do
    xml.parent.namespace = xml.parent.namespace_definitions.find{|ns|ns.prefix=="foo"}
    xml['foo'].child
  end
end.to_xml

You cannot use xml['foo'] before the namespace is defined, I.E. before you pass it as an argument to the root node, thus, the code above added the namespace after-the-fact to the root node.

Bowstring answered 6/12, 2009 at 19:14 Comment(2)
You, sir, have spared me much frustrationGandhi
Note that this is fine if there's only one namespace, or maybe if you're working under Ruby 1.9 and thus can rely on the order of the hash arguments to be the same. Otherwise, I would recommend xml.parent.namespace = xml.parent.namespace_definitions.find{ |ns| ns.prefix=="foo" } (or use the ns.href if you're not sure what the prefix might have been named).Laylalayman
S
8

Two years later, I found a cleaner way to do this by using Nokogiri::XML::Builder's document reference to retrieve the root node and add the namespace(s) to that. Like the previous solution, it requires the root node to exist before the namespaces can be added to it.

I've changed the <root> element to <rootElement> so this is more clear:

builder = Nokogiri::XML::Builder.new do |xml| 
  xml.rootElement do
    # create a namespace and save it for later
    ns = xml.doc.root.add_namespace_definition('foo', 'my-ns-url')
    # you can also create multiple name spaces
    xml.doc.root.add_namespace_definition('bar', 'http://example.com/bar')

    # assign the saved namespace to rootElement
    xml.doc.root.namespace = ns

    xml['foo'].child
    xml['bar'].child
  end
end

Now, builder.to_xml will return:

<?xml version="1.0"?>
<foo:rootElement xmlns:foo="my-ns-url" xmlns:bar="http://example.com/bar">
  <foo:child/>
  <bar:child/>
</foo:rootElement>

I like this better because you don't have to search for the name space, and it's easier to add multiple namespaces.

Superficies answered 28/3, 2012 at 1:2 Comment(1)
This works great. Thanks. However, I am unable to get rid of the namespace inheritance. I want to achieve something like the following. <?xml version="1.0"?> <foo:rootElement xmlns:foo="my-ns-url" xmlns:bar="example.com/bar"> <child> value </child> </foo:rootElement>Norma
T
3

Three years after Luke's answer, it's gotten yet simpler. You can now use the namespace "before" it's defined. This code:

require 'nokogiri'

NAMESPACES = { 'xmlns:foo' => 'bar', 'xmlns:baz' => 'bat' }

builder = Nokogiri::XML::Builder.new { |xml|
  xml['foo'].RootElement(NAMESPACES) {
    xml['baz'].FirstChild
  }
}

puts builder.to_xml

Outputs this XML:

<?xml version="1.0"?>
<foo:RootElement xmlns:foo="bar" xmlns:baz="bat">
  <baz:FirstChild/>
</foo:RootElement>
Troat answered 1/12, 2015 at 17:2 Comment(3)
For some reason I get <baz:FirstChild xmlns:baz="bat"/> for child elementsLoireatlantique
I just tried it again and it's working as I described. Using nokogiri 1.6.7.2, Ruby 2.3.8.Troat
It is Nokogiri on JRuby issue if I remember correctly. On MRI it works fine.Loireatlantique

© 2022 - 2024 — McMap. All rights reserved.