Skip to content

Commit

Permalink
Fix attribute's default namespace behavior
Browse files Browse the repository at this point in the history
NOTE: It's a backward incompatible change. If we have any serious
problems with this change, we may revert this change.

The XML namespace specification says the default namespace doesn't
apply to attribute names but it does in REXML without this change:

https://www.w3.org/TR/xml-names/#uniqAttrs

> the default namespace does not apply to attribute names

REXML reports a parse error for the following XML that is described as
a valid XML in the XML nsmaspace specification without this change:

    <!-- http://www.w3.org is bound to n1 and is the default -->
    <x xmlns:n1="http://www.w3.org"
       xmlns="http://www.w3.org" >
      <good a="1"     b="2" />
      <good a="1"     n1:a="2" />
    </x>

If attribute doesn't have prefix, the attribute should return "" for
both #prefix and #namespace.
  • Loading branch information
kou committed Dec 31, 2018
1 parent 2384586 commit 9e4fd55
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 22 deletions.
21 changes: 10 additions & 11 deletions lib/rexml/attribute.rb
Expand Up @@ -67,15 +67,11 @@ def initialize( first, second=nil, parent=nil )
# e.add_attribute( "nsa:a", "aval" )
# e.add_attribute( "b", "bval" )
# e.attributes.get_attribute( "a" ).prefix # -> "nsa"
# e.attributes.get_attribute( "b" ).prefix # -> "elns"
# e.attributes.get_attribute( "b" ).prefix # -> ""
# a = Attribute.new( "x", "y" )
# a.prefix # -> ""
def prefix
pf = super
if pf == ""
pf = @element.prefix if @element
end
pf
super
end

# Returns the namespace URL, if defined, or nil otherwise
Expand All @@ -87,9 +83,8 @@ def prefix
# e.attribute("ns:a").namespace # => "http://url"
# e.attribute("nsx:a").namespace # => nil
#
# TODO: This method should always return nil for no namespace
# attribute. Because the default namespace doesn't apply to
# attribute name.
# This method always returns "" for no namespace attribute. Because
# the default namespace doesn't apply to attribute names.
#
# From https://www.w3.org/TR/xml-names/#uniqAttrs
#
Expand All @@ -99,10 +94,14 @@ def prefix
# e.add_namespace("", "http://example.com/")
# e.namespace # => "http://example.com/"
# e.add_attribute("a", "b")
# e.attribute("a").namespace # => nil
# e.attribute("a").namespace # => ""
def namespace arg=nil
arg = prefix if arg.nil?
@element.namespace arg
if arg == ""
""
else
@element.namespace(arg)
end
end

# Returns true if other is an Attribute and has the same name and value,
Expand Down
4 changes: 2 additions & 2 deletions lib/rexml/element.rb
Expand Up @@ -1133,8 +1133,8 @@ def []=( name, value )
elsif old_attr.prefix != value.prefix
# Check for conflicting namespaces
if value.prefix != "xmlns" and old_attr.prefix != "xmlns"
old_namespace = @element.namespace(old_attr.prefix)
new_namespace = @element.namespace(value.prefix)
old_namespace = old_attr.namespace
new_namespace = value.namespace
if old_namespace == new_namespace
raise ParseException.new(
"Namespace conflict in adding attribute \"#{value.name}\": "+
Expand Down
10 changes: 2 additions & 8 deletions lib/rexml/xpath_parser.rb
Expand Up @@ -493,17 +493,11 @@ def node_test(path_stack, nodesets, any_type: :element)
if prefix.nil?
raw_node.name == name
elsif prefix.empty?
# FIXME: This DOUBLES the time XPath searches take
raw_node.name == name and
raw_node.namespace == raw_node.element.namespace
raw_node.name == name and raw_node.namespace == ""
else
# FIXME: This DOUBLES the time XPath searches take
ns = get_namespace(raw_node.element, prefix)
if ns.empty?
raw_node.name == name and raw_node.prefix.empty?
else
raw_node.name == name and raw_node.namespace == ns
end
raw_node.name == name and raw_node.namespace == ns
end
else
false
Expand Down
50 changes: 49 additions & 1 deletion test/rexml/test_core.rb
@@ -1,4 +1,4 @@
# coding: binary
# -*- coding: utf-8 -*-
# frozen_string_literal: false

require_relative "rexml_test_utils"
Expand Down Expand Up @@ -116,6 +116,54 @@ def test_attribute
name4='test4'/>).join(' '), e.to_s
end

def test_attribute_namespace_conflict
# https://www.w3.org/TR/xml-names/#uniqAttrs
message = <<-MESSAGE
Duplicate attribute "a"
Line: 4
Position: 140
Last 80 unconsumed characters:
MESSAGE
assert_raise_with_message(REXML::ParseException, message) do
Document.new(<<-XML)
<!-- http://www.w3.org is bound to n1 and n2 -->
<x xmlns:n1="http://www.w3.org"
xmlns:n2="http://www.w3.org" >
<bad a="1" a="2" />
<bad n1:a="1" n2:a="2" />
</x>
XML
end
end

def test_attribute_default_namespace
# https://www.w3.org/TR/xml-names/#uniqAttrs
document = Document.new(<<-XML)
<!-- http://www.w3.org is bound to n1 and is the default -->
<x xmlns:n1="http://www.w3.org"
xmlns="http://www.w3.org" >
<good a="1" b="2" />
<good a="1" n1:a="2" />
</x>
XML
attributes = document.root.elements.collect do |element|
element.attributes.each_attribute.collect do |attribute|
[attribute.prefix, attribute.namespace, attribute.name]
end
end
assert_equal([
[
["", "", "a"],
["", "", "b"],
],
[
["", "", "a"],
["n1", "http://www.w3.org", "a"],
],
],
attributes)
end

def test_cdata
test = "The quick brown fox jumped
& < & < \" '
Expand Down

0 comments on commit 9e4fd55

Please sign in to comment.