Nokogiri: Should namespaces be passed down to child elements?

Created on 17 May 2016  路  5Comments  路  Source: sparklemotion/nokogiri

I'm having a bit of a problem, I may be overlooking something quite obvious, but here is the gist of my problem https://gist.github.com/IceDragon200/873139e6c7d395dfcd9793949f9b2246

What's happening here, I've created an element with a namespace and then child elements without a namespace, expecting the children to be as such (namespace-less), however nokogiri slaps the label on anyway.

Expected behaviour:

# WHERE xml is the top level builder
xml['namespace'].element do |builder_with_namespace|
  xml.element_without_namespace
  builder_with_namespace.element_with_namespace
end

Though judging from that, it wouldn't exactly be efficient, since you'd create a new builder context per element, unless checking the block's arity to avoid it.

tl;dr

Expected

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:emer="http://dashcs.com/api/v1/emergency">
  <soapenv:Header/>
  <soapenv:Body>
    <emer:validateLocation>
      <location>
        <address1>Some place over the rainbow</address1>
        <community>Community</community>
        <postalcode>PS</postalcode>
        <state>AR</state>
        <type>ADDRESS</type>
      </location>
    </emer:validateLocation>
  </soapenv:Body>
</soapenv:Envelope>

Actual

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:emer="http://dashcs.com/api/v1/emergency">
  <soapenv:Header/>
  <soapenv:Body>
    <emer:validateLocation>
      <emer:location>
        <emer:address1>Some place over the rainbow</emer:address1>
        <emer:community>Community</emer:community>
        <emer:postalcode>PS</emer:postalcode>
        <emer:state>AR</emer:state>
        <emer:type>ADDRESS</emer:type>
      </emer:location>
    </emer:validateLocation>
  </soapenv:Body>
</soapenv:Envelope>

Test

require 'nokogiri' # nokogiri (1.6.7.2)

result = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
  xml['soapenv'].Envelope('xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:emer' => 'http://dashcs.com/api/v1/emergency') do
    xml['soapenv'].Header
    xml['soapenv'].Body do
      xml['emer'].validateLocation do
        # these do not require a namespace
        xml.location do
          xml.address1 'Some place over the rainbow'
          xml.community 'Community'
          xml.postalcode 'PS'
          xml.state 'AR'
          xml.type 'ADDRESS'
        end
      end
    end
  end
end.to_xml

# suddenly WILD NAMESPACES APPEAR!
puts result

Most helpful comment

The following workaround works if you need to keep the parent's namespace (set it to nil temporarily and then restore after you created all child elements that don't need a namespace):

require 'nokogiri'

result = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
  xml['soapenv'].Envelope('xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:emer' => 'http://dashcs.com/api/v1/emergency') do
    xml['soapenv'].Header
    xml['soapenv'].Body do
      xml['emer'].validateLocation do
        # these do not require a namespace

        parent_namespace = xml.parent.namespace
        xml.parent.namespace = nil

        xml.location do
          xml.address1 'Some place over the rainbow'
          xml.community 'Community'
          xml.postalcode 'PS'
          xml.state 'AR'
          xml.type 'ADDRESS'
        end

        xml.parent.namespace = parent_namespace

      end
    end
  end
end.to_xml

puts result

Produces:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:emer="http://dashcs.com/api/v1/emergency">
  <soapenv:Header/>
  <soapenv:Body>
    <emer:validateLocation>
      <location>
        <address1>Some place over the rainbow</address1>
        <community>Community</community>
        <postalcode>PS</postalcode>
        <state>AR</state>
        <type>ADDRESS</type>
      </location>
    </emer:validateLocation>
  </soapenv:Body>
</soapenv:Envelope>

All 5 comments

Is this same as #425 ?

@akostadinov Yes, it appears so, I'm surprised this issue has been brought up before...

For a temporary workaround, have you tried explicitly setting the namespace nil?

The following is working for me:

result = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
  xml['soapenv'].Envelope('xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:emer' => 'http://dashcs.com/api/v1/emergency') do
    xml['soapenv'].Header
    xml['soapenv'].Body do
      xml['emer'].validateLocation do
        xml.parent.namespace = nil # explicitly set namespace to nil
        xml.location do
          xml.address1 'Some place over the rainbow'
          xml.community 'Community'
          xml.postalcode 'PS'
          xml.state 'AR'
          xml.type 'ADDRESS'
        end
      end
    end
  end
end.to_xml

I had the same problem but setting the parent namespace to nil did not work for me.

This is what I initially did

Nokogiri::XML::Builder.new { |xml| xml["foo"].Root("xmlns:foo" => "localhost") { xml.Test "hello" } }.to_xml
<?xml version="1.0"?>
<foo:Root xmlns:foo="localhost">
  <foo:Test>hello</foo:Test>
</foo:Root>

But I only wanted the namespace on Root.

I then found this GH issue and tried the following

Nokogiri::XML::Builder.new { |xml| xml["foo"].Root("xmlns:foo" => "localhost") { xml.parent.namespace = nil; xml.Test "hello" } }.to_xml
<?xml version="1.0"?>
<Root xmlns:foo="localhost">
  <Test>hello</Test>
</Root>

The namespace then got removed from both Root and Test.

Ultimately I got the desired output by introducing a default namespace

Nokogiri::XML::Builder.new { |xml| xml["foo"].Root("xmlns:foo" => "localhost", "xmlns" => "default") { xml.Test "hello" } }.to_xml
<?xml version="1.0"?>
<foo:Root xmlns:foo="localhost" xmlns="default">
  <Test>hello</Test>
</foo:Root>

BUT the XML endpoint I was calling didn't want the default namespace, 馃槥 , soooo...

Nokogiri::XML::Builder
  .new { |xml| xml["foo"].Root("xmlns:foo" => "localhost", "xmlns" => "default") { xml.Test "hello" } }
  .to_xml
  .sub(' xmlns="default"', "")
<?xml version="1.0"?>
<foo:Root xmlns:foo="localhost">
  <Test>hello</Test>
</foo:Root>

The following workaround works if you need to keep the parent's namespace (set it to nil temporarily and then restore after you created all child elements that don't need a namespace):

require 'nokogiri'

result = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
  xml['soapenv'].Envelope('xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:emer' => 'http://dashcs.com/api/v1/emergency') do
    xml['soapenv'].Header
    xml['soapenv'].Body do
      xml['emer'].validateLocation do
        # these do not require a namespace

        parent_namespace = xml.parent.namespace
        xml.parent.namespace = nil

        xml.location do
          xml.address1 'Some place over the rainbow'
          xml.community 'Community'
          xml.postalcode 'PS'
          xml.state 'AR'
          xml.type 'ADDRESS'
        end

        xml.parent.namespace = parent_namespace

      end
    end
  end
end.to_xml

puts result

Produces:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:emer="http://dashcs.com/api/v1/emergency">
  <soapenv:Header/>
  <soapenv:Body>
    <emer:validateLocation>
      <location>
        <address1>Some place over the rainbow</address1>
        <community>Community</community>
        <postalcode>PS</postalcode>
        <state>AR</state>
        <type>ADDRESS</type>
      </location>
    </emer:validateLocation>
  </soapenv:Body>
</soapenv:Envelope>
Was this page helpful?
0 / 5 - 0 ratings