Skip to content

Commit 63ebca1

Browse files
eed3si9nadriaanm
authored andcommitted
SI-6939 Fix namespace binding (xmlns) not overriding outer binding
Given a nested XML literal to the compiler Elem instance is generated with namespace binding of the inner element copying that of the outer element: val foo = <x:foo xmlns:x="http://foo.com/"> <x:bar xmlns:x="http://bar.com/"><x:baz/></x:bar></x:foo> With the above example, `foo.child.head.scope.toString` returns " xmlns:x="http://bar.com/" xmlns:x="http://foo.com/"" This is incorrect since the outer xmls:x should be overridden by the inner binding. XML library also parses XML document in a similar manner: val foo2 = scala.xml.XML.loadString("""<x:foo xmlns:x="http://foo.com/"><x:bar xmlns:x="http://bar.com/"><x:baz/></x:bar></x:foo>""") Despite this erroneous behavior, since the structure of NamespaceBinding class is designed to be singly-linked list, the stacking of namespace bindings allows constant-time creation with simple implementation. Since the inner namespace binding comes before the outer one, query methods like `getURI` method behave correctly. Because this bug is manifested when Elem is turned back into XML string, it could be fixed by shadowing the redefined namespace binding right when buildString is called. With this change `foo.child.head.scope.toString` now returns: " xmlns:x="http://bar.com/""
1 parent 091de12 commit 63ebca1

File tree

2 files changed

+35
-2
lines changed

2 files changed

+35
-2
lines changed

src/library/scala/xml/NamespaceBinding.scala

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ case class NamespaceBinding(prefix: String, uri: String, parent: NamespaceBindin
3838

3939
override def toString(): String = sbToString(buildString(_, TopScope))
4040

41+
private def shadowRedefined: NamespaceBinding = shadowRedefined(TopScope)
42+
43+
private def shadowRedefined(stop: NamespaceBinding): NamespaceBinding = {
44+
def prefixList(x: NamespaceBinding): List[String] =
45+
if ((x == null) || (x eq stop)) Nil
46+
else x.prefix :: prefixList(x.parent)
47+
def fromPrefixList(l: List[String]): NamespaceBinding = l match {
48+
case Nil => stop
49+
case x :: xs => new NamespaceBinding(x, this.getURI(x), fromPrefixList(xs))
50+
}
51+
val ps0 = prefixList(this).reverse
52+
val ps = ps0.distinct
53+
if (ps.size == ps0.size) this
54+
else fromPrefixList(ps)
55+
}
56+
4157
override def canEqual(other: Any) = other match {
4258
case _: NamespaceBinding => true
4359
case _ => false
@@ -53,12 +69,16 @@ case class NamespaceBinding(prefix: String, uri: String, parent: NamespaceBindin
5369
def buildString(stop: NamespaceBinding): String = sbToString(buildString(_, stop))
5470

5571
def buildString(sb: StringBuilder, stop: NamespaceBinding) {
56-
if (this eq stop) return // contains?
72+
shadowRedefined(stop).doBuildString(sb, stop)
73+
}
74+
75+
private def doBuildString(sb: StringBuilder, stop: NamespaceBinding) {
76+
if ((this == null) || (this eq stop)) return // contains?
5777

5878
val s = " xmlns%s=\"%s\"".format(
5979
(if (prefix != null) ":" + prefix else ""),
6080
(if (uri != null) uri else "")
6181
)
62-
parent.buildString(sb append s, stop) // copy(ignore)
82+
parent.doBuildString(sb append s, stop) // copy(ignore)
6383
}
6484
}

test/files/run/t6939.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
object Test extends App {
2+
val foo = <x:foo xmlns:x="http://foo.com/"><x:bar xmlns:x="http://bar.com/"><x:baz/></x:bar></x:foo>
3+
assert(foo.child.head.scope.toString == """ xmlns:x="http://bar.com/"""")
4+
5+
val fooDefault = <foo xmlns="http://foo.com/"><bar xmlns="http://bar.com/"><baz/></bar></foo>
6+
assert(fooDefault.child.head.scope.toString == """ xmlns="http://bar.com/"""")
7+
8+
val foo2 = scala.xml.XML.loadString("""<x:foo xmlns:x="http://foo.com/"><x:bar xmlns:x="http://bar.com/"><x:baz/></x:bar></x:foo>""")
9+
assert(foo2.child.head.scope.toString == """ xmlns:x="http://bar.com/"""")
10+
11+
val foo2Default = scala.xml.XML.loadString("""<foo xmlns="http://foo.com/"><bar xmlns="http://bar.com/"><baz/></bar></foo>""")
12+
assert(foo2Default.child.head.scope.toString == """ xmlns="http://bar.com/"""")
13+
}

0 commit comments

Comments
 (0)