7

I have been wrecking my head for a few hours but I can't seem to determine how to add XMLNS namespace whilst using the Nokogiri XML Builder class to construct a XML structure.

For instance, consider the XML sample below: I can create everything between the GetQuote tags but creating the "p:ACMRequest" remains a mystery.

I came across this reference, https://gist.github.com/428455/7a15f84cc08c05b73fcec2af49947d458ae3b96a, that still doesn't make sense to me. Even referring to the XML documentation,http://www.w3.org/TR/xml-names/, didn't make much sense either.

<?xml version="1.0" encoding="UTF-8"?>
<p:ACMRequest xmlns:p="http://www.acme.com" xmlns:p1="http://www.acme.com/datatypes" xmlns:p2="http://www.acme.com/ACMRequestdatatypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.acme.com ACM-req.xsd ">
  <GetQuote>
    <Request>
      <ServiceHeader>
        ...
        ...
      </ServiceHeader>
    </Request>
    <From>
      ...
      ...
    </From>
    <Details>
      ...
      ...
    </Details>
  </GetQuote>
</p:ACMRequest>

2 Answers 2

16

The Nokogiri documentation page says:

Namespaces are added similarly to attributes. Nokogiri::XML::Builder assumes that when an attribute starts with “xmlns”, it is meant to be a namespace:

   builder = Nokogiri::XML::Builder.new { |xml|
     xml.root('xmlns' => 'default', 'xmlns:foo' => 'bar') do
       xml.tenderlove
     end
   }
   puts builder.to_xml

Will output XML like this:

   <?xml version="1.0"?>
   <root xmlns:foo="bar" xmlns="default">
     <tenderlove/>
   </root>

Applying this to your specific question, simply do:

require 'nokogiri'
NS = {
  "xmlns:p"   => "http://www.acme.com",
  "xmlns:p1"  => "http://www.acme.com/datatypes",
  "xmlns:p2"  => "http://www.acme.com/ACMRequestdatatypes",
  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
}
builder = Nokogiri::XML::Builder.new { |xml|
  xml.ACMRequest(NS) do
    xml.GetQuote
  end
}
puts builder.to_xml

#=> <?xml version="1.0"?>
#=> <ACMRequest xmlns:p="http://www.acme.com" xmlns:p1="http://www.acme.com/datatypes" xmlns:p2="http://www.acme.com/ACMRequestdatatypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
#=>   <GetQuote/>
#=> </ACMRequest>

As for the namespace prefix on the root element itself…

<p:ACMRequest xmlns:p="…">…</p:ACMRequest>

…I cannot figure out how to apply a namespace prefix to the first element in Nokogiri during creation. Instead, you have to apply the namespace after creating the document:

root = builder.doc.root
acme = root.namespace_definitions.find{ |ns| ns.href==NS["xmlns:p"] }
root.namespace = acme
puts builder.to_xml

#=> <?xml version="1.0"?>
#=> <p:ACMRequest xmlns:p="http://www.acme.com" xmlns:p1="http://www.acme.com/datatypes" xmlns:p2="http://www.acme.com/ACMRequestdatatypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">atypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
#=>   <GetQuote/>
#=> </p:ACMRequest>

Alternatively, you can cheat:

# This happens to work for now, but I doubt you should rely upon it.
builder.doc.root.name = "p:ACMRequest"

Per "How to create an XML document with a namespaced root element with Nokogiri Builder" you can alternatively do this during creation via a small hack:

builder = Nokogiri::XML::Builder.new { |xml|
  xml.ACMRequest(NS) do
   xml.parent.namespace = … # find the ns in xml.parent.namespace_definitions
   # …
  end
end
Sign up to request clarification or add additional context in comments.

4 Comments

@muistooshort No; the first four blocks (quote, code, quote, code) are all from the page. I'll indent them all for increased clarity.
It looked like an editing mistake but it was just a Markdown artifact. You can make a "code block inside a blockquote" work if you use enough leading spaces on the code block and enough > for the blockquote. I think I fixed it, I'm just feeling a bit OCD this morning
Sorry for the late response. @Phrogz thanks for your extensive response. My experience with XML has been rather limited but your response did clear up a few other questions I had as well. I have lost count of how many times I have looked at the Nokogiri documentation but its not till someone else explains it that things makes sense. :-) yup i did notice the typo in the code block too :-)
Congratulations, now the first hit on Google for that is this page.
13
require 'nokogiri'
NS = {
  "xmlns:p"   => "http://www.acme.com",
  "xmlns:p1"  => "http://www.acme.com/datatypes",
  "xmlns:p2"  => "http://www.acme.com/ACMRequestdatatypes",
  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
}
builder = Nokogiri::XML::Builder.new { |xml|
  xml['p'].ACMRequest(NS) do
    xml.GetQuote
  end
}
puts builder.to_xml

Produces:

<?xml version="1.0"?>
<p:ACMRequest xmlns:p="http://www.acme.com" xmlns:p1="http://www.acme.com/datatypes" xmlns:p2="http://www.acme.com/ACMRequestdatatypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <p:GetQuote/>
</p:ACMRequest>

This is documented in the builder API: http://nokogiri.org/Nokogiri/XML/Builder.html:

Referencing declared namespaces

Tags that reference non-default namespaces (i.e. a tag “foo:bar”) can be built by using the Nokogiri::XML::Builder#[] method.

1 Comment

Caution: This adds the namespace (p) in all the nested tags.

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.