Consider the following XML file:

<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://ns1" xmlns:ns2="http://ns2">
  <element a="1" ns2:b="2"/>
  <ns2:element a="3" ns2:b="4"/>
</document>

We can use Ruby and Nokogiri to parse it:

irb(main):001:0> require "nokogiri"
=> true
irb(main):003:0> doc = Nokogiri::XML.parse(File.read("x.xml")); nil
=> nil

Then we try to look for the first element in document:

irb(main):004:0> doc.xpath("//element").size
=> 0

But we get nothing. That’s because of namespaces. We could, of course, call remove_namespaces!, but sometimes that’s not the best choice. We can instead be more specific:

irb(main):007:0> doc.xpath("//ns1:element", {"ns1" => "http://ns1"}).size
=> 1

That surely works, but it’s a lot of code just to evaluate an XPath expression. And, if we are looking for the second element, things are simpler:

irb(main):009:0> doc.xpath("//ns2:element").size
=> 1

The reason is that the namespace http://ns2 was given a name in the XML file, and Nokogiri automatically registers such namespaces. And we can’t give the default namespace a name in the XML file, but we can tell Nokogiri about it:

irb(main):010:0> doc.root.add_namespace("ns1", "http://ns1"); nil
=> nil
irb(main):011:0> doc.xpath("//ns1:element").size
=> 1

That’s much shorter and more symmetric: all namespaces are treated more equally this way. Now we’ll go looking for the attributes:

irb(main):015:0> doc.at_xpath("//ns1:element")["ns1:a"]
=> nil

Well, that’s weird. All namespaces are properly registered. If this works for elements, it should work for attributes, too. So it shouldn’t work with the second namespace, should it?

irb(main):017:0> doc.at_xpath("//ns1:element")["ns2:b"]
=> "2"

Except it does. What happened to that a attribute? Where did it go?

irb(main):018:0> doc.at_xpath("//ns1:element")["a"]
=> "1"

Oh, it’s there, except it has no namespace. That must be a bug in Nokogiri, right? Well, no, it’s the expected behavior:

The namespace name for an unprefixed attribute name always has no value.

There’s also a relevant question in Stack Overflow.

I’m sure there’s a perfectly good reason for this behavior, but I find it counterintuitive.