From 4a1a2ab362dbe696a0a8c1284a8f9063642de193 Mon Sep 17 00:00:00 2001 From: NAITOH Jun Date: Mon, 17 Aug 2026 16:35:35 +0900 Subject: [PATCH] Document how Element#attribute matches a namespace REXML::Element#attribute matches its +namespace+ argument loosely: an unprefixed attribute is taken to be in the default namespace, and a namespace that no prefix is bound to falls back to the unprefixed attribute. attribute("a", "ns0") -> a='a' attribute("a", "nosuch") -> a='a' The XML Namespaces specification says an unprefixed attribute has no namespace and does not take the default one, so neither of those should match. The behavior is kept for compatibility -- it is what tickets 102 and 121 asked for, and the tests for both are still in place -- but nothing said so, which has left people reading the method unsure whether what they saw was the contract or a bug. Say it in the documentation of both methods, and point each at the other: REXML::Attributes#get_attribute_ns matches as the XML Namespaces specification says and is what to reach for when the namespace has to be matched strictly. It has behaved that way at least as far back as GH-151, but that discussion never mentioned it. GitHub: GH-151 --- lib/rexml/element.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/rexml/element.rb b/lib/rexml/element.rb index a002db07..7ce03df6 100644 --- a/lib/rexml/element.rb +++ b/lib/rexml/element.rb @@ -1268,6 +1268,22 @@ def [](name_or_index) # document.root.attribute("x") # => x='x' # document.root.attribute("x", "a") # => a:x='a:x' # + # This method matches +namespace+ loosely. An unprefixed attribute is + # taken to be in the default namespace, and a +namespace+ that no prefix + # is bound to falls back to the unprefixed attribute: + # + # xml_string = "" + # document = REXML::Document.new(xml_string) + # document.root.attribute("a", "ns0") # => a='a' + # document.root.attribute("a", "nosuch") # => a='a' + # + # The XML Namespaces specification says that an unprefixed attribute has + # no namespace, so neither of those should match. + # REXML::Attributes#get_attribute_ns follows the XML Namespaces + # specification and returns +nil+ for both; use it when you need the + # namespace to be matched strictly. The looser behavior here is kept for + # compatibility. + # def attribute( name, namespace=nil ) prefix = namespaces.key(namespace) if namespace prefix = nil if prefix == 'xmlns' @@ -2493,6 +2509,18 @@ def delete_all( name ) # attrs.get_attribute_ns('http://foo', 'att') # => foo:att='1' # attrs.get_attribute_ns('http://foo', 'nosuch') # => nil # + # +namespace+ is matched as the XML Namespaces specification defines it, + # where an unprefixed attribute has no namespace of its own and does not + # take the default one: + # + # xml_string = "" + # attrs = REXML::Document.new(xml_string).root.attributes + # attrs.get_attribute_ns('ns0', 'a') # => nil + # attrs.get_attribute_ns('', 'a') # => a='a' + # + # REXML::Element#attribute matches more loosely and returns the attribute + # for both of those. + # def get_attribute_ns(namespace, name) each_effective_attribute.find do |attribute| if attribute.namespace_declaration?