This is an automated email from the ASF dual-hosted git repository.
slawrence pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/daffodil.git
The following commit(s) were added to refs/heads/master by this push:
new 8771105 Update scala-library, scala-reflect to 2.12.13
8771105 is described below
commit 8771105a355fbe0aa3453c1535ff470fa5a0b591
Author: Scala Steward <[email protected]>
AuthorDate: Mon Mar 8 14:18:44 2021 +0100
Update scala-library, scala-reflect to 2.12.13
- Something changed in this version of scala that changed how data
structures were serialized, which revealed a potential recursive
serialization issue related to List's. To fix this, we can simply
apply a map identity to the lists, essentially copying the List and
getting around the serialization problem, at the expense of higher
memory usage.
- Add test that will fail if Scala fixes their List serialization issues
so that we can revert this change.
The minimizedScope NamespaceBinding is built from various Set's and
Map's which do not have a well defined order. The order seems to have
changed in Scala 2.12.13, and caused tests that depend on a certain
namespace orders to fail. To fix this, we now sort the minimizedScoep
namespace bindings when they are created to ensure consistent
behavior, independent of Set/Map implementation details.
This revealed other issues where we weren't consistent with outputting
namespace bindings in the correct order and had various workarounds to
try to get bindings in the right order. This mainly showed up with the
SAX content reader since it deals with NamespaceBindings and outputting
them in the correct order. Specific changes related to this:
- The SAXInfosetOutputter now calls start/endPrefixMapping in the same
order as in the minimizedScope namespace binding--that is the true
order so we should not change that.
- In the DaffodilParseOutputStreamContentHandler, we are now very
careful to maintain the order of NamespaceBindings. We do this by
gathering prefix mappings for a single element in reverse order and
then add them to the active mappings once complete, which re-reverses
them back into the right order.
- Also splits the process attribute into two functions, one to process
namespace mappings and add to the current element mappings, and one to
write the non-mapping attributes, potentially using the mappings added
in the previous step.
---
.github/workflows/main.yml | 2 +-
.github/workflows/sonarcloud.yml | 2 +-
build.sbt | 4 +-
daffodil-cli/bin.NOTICE | 4 +-
.../org/apache/daffodil/CLI/output/output9.txt | 2 +-
.../apache/daffodil/parsing/TestCLIParsing.scala | 7 ++
.../daffodil/dpath/DFDLExpressionParser.scala | 3 +
.../org/apache/daffodil/dsom/ElementBase.scala | 13 ++-
.../daffodil/processor/TestSAXParseAPI.scala | 12 +-
.../daffodil/util/TestListSerialization.scala | 102 ++++++++++++++++
.../apache/daffodil/dsom/CompiledExpression1.scala | 22 ++--
.../daffodil/infoset/SAXInfosetOutputter.scala | 9 +-
.../DaffodilParseOutputStreamContentHandler.scala | 130 +++++++++++++--------
13 files changed, 235 insertions(+), 77 deletions(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 07b86e5..ca263f2 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -24,7 +24,7 @@ jobs:
fail-fast: false
matrix:
java_version: [ 8, 11, 16 ]
- scala_version: [ 2.12.11 ]
+ scala_version: [ 2.12.13 ]
os: [ ubuntu-20.04, windows-2019 ]
include:
- os: ubuntu-20.04
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index d22007b..7abe70b 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -23,7 +23,7 @@ jobs:
name: SonarCloud Scan, Scala ${{ matrix.scala_version }}, ${{ matrix.os }}
strategy:
matrix:
- scala_version: [ 2.12.11 ]
+ scala_version: [ 2.12.13 ]
os: [ ubuntu-20.04 ]
runs-on: ${{ matrix.os }}
diff --git a/build.sbt b/build.sbt
index 658b8f6..27ea165 100644
--- a/build.sbt
+++ b/build.sbt
@@ -149,8 +149,8 @@ lazy val testStdLayout =
Project("daffodil-test-stdLayout", file("test-stdLay
lazy val commonSettings = Seq(
organization := "org.apache.daffodil",
version := "3.1.0-SNAPSHOT",
- scalaVersion := "2.12.11",
- crossScalaVersions := Seq("2.12.11"),
+ scalaVersion := "2.12.13",
+ crossScalaVersions := Seq("2.12.13"),
scalacOptions ++= Seq(
"-feature",
"-deprecation",
diff --git a/daffodil-cli/bin.NOTICE b/daffodil-cli/bin.NOTICE
index b8ce0c4..c4ee871 100644
--- a/daffodil-cli/bin.NOTICE
+++ b/daffodil-cli/bin.NOTICE
@@ -106,8 +106,8 @@ Scala (lib/org.scala-lang.scala-library-<VERSION>.jar)
Scala Parser Combinators
(lib/org.scala-lang.modules.scala-parser-combinators_<VERSION>.jar)
Scala parser combinators
- Copyright (c) 2002-2020 EPFL
- Copyright (c) 2011-2020 Lightbend, Inc.
+ Copyright (c) 2002-2021 EPFL
+ Copyright (c) 2011-2021 Lightbend, Inc.
Scala includes software developed at
LAMP/EPFL (https://lamp.epfl.ch/) and
diff --git
a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output9.txt
b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output9.txt
index 4d07bb4..dc021e8 100644
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output9.txt
+++ b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output9.txt
@@ -1,4 +1,4 @@
-<base14:rabbitHole xmlns:b14="http://b14.com" xmlns:a14="http://a14.com"
xmlns:base14="http://baseSchema.com">
+<base14:rabbitHole xmlns:a14="http://a14.com" xmlns:b14="http://b14.com"
xmlns:base14="http://baseSchema.com">
<a14:nestSequence>
<b14:nest>test</b14:nest>
</a14:nestSequence>
diff --git
a/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
b/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
index 33b6b59..657f212 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
@@ -223,6 +223,13 @@ class TestCLIparsing {
}
// See comment in DFDL-952
+ //
+ // Also note that this test is important in showing the expected existence
+ // and ordering of XML namespace prefix mappings. Daffodil ensures
+ // consistent and repeatable output of namespace prefix mappings, but normal
+ // TDML tests do not verify this part of expected infosets. This is one test
+ // verifies the expected output. If this test fails, it likely means we've
+ // broken our attempts to create consistent prefix mappings.
@Test def test_1585_CLI_Parsing_MultifileSchema_methodImportSameDir(): Unit
= {
val schemaFile =
Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_14.dfdl.xsd")
val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else
schemaFile
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dpath/DFDLExpressionParser.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dpath/DFDLExpressionParser.scala
index fec408e..85649a2 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/dpath/DFDLExpressionParser.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/dpath/DFDLExpressionParser.scala
@@ -277,6 +277,9 @@ class DFDLPathExpressionParser[T <: AnyRef](
SupportedForwardAxis ~ NodeTest ~ Predicate.? ^^ {
case "self" ~ qn ~ p => Self2(qn, p)
case "child" ~ qn ~ p => NamedStep(qn, p)
+ // $COVERAGE-OFF$
+ case _ => Assert.impossible()
+ // $COVERAGE-ON$
} |
UnsupportedForwardAxis ~ Predicate.? ^^ { case name ~ _ =>
context.SDE("'%s::' is an unsupported axis in DFDL Expression Syntax.", name) }
|
NodeTest ~ Predicate.? ^^ { case qn ~ p => { NamedStep(qn, p) } }
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
index 3bc515e..341ba72 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
@@ -274,7 +274,7 @@ trait ElementBase
}
// FIXME: DAFFODIL-2282 works only if there is no difference among usages.
- private def pairsToNSBinding(pairs: Set[(String, NS)], parentNS:
NamespaceBinding): NamespaceBinding = {
+ private def pairsToNSBinding(pairs: List[(String, NS)], parentNS:
NamespaceBinding): NamespaceBinding = {
if (pairs.isEmpty) parentNS
else {
val (pre, ns) = pairs.head
@@ -314,7 +314,16 @@ trait ElementBase
myUniquePairs
}
- pairsToNSBinding(uniquePairs, parentMinimizedScope)
+ // Sort alphabetically, with null NS going first. This is the order that
+ // NamespaceBinings will be output with the toString method. This ensures
+ // consistent ordering regardless of Set/Map implementation details of
+ // uniquePairs
+ val sortedPairs = uniquePairs.toList.sortWith { case (l, r) =>
+ (l._1 == null) || (r._1 == null) || (l._1.compareTo(r._1) < 0)
+ }
+
+ pairsToNSBinding(sortedPairs, parentMinimizedScope)
+
}.value
/**
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/processor/TestSAXParseAPI.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/processor/TestSAXParseAPI.scala
index a3aba83..89c3f6b 100644
---
a/daffodil-core/src/test/scala/org/apache/daffodil/processor/TestSAXParseAPI.scala
+++
b/daffodil-core/src/test/scala/org/apache/daffodil/processor/TestSAXParseAPI.scala
@@ -455,9 +455,9 @@ class TestSAXParseAPI {
|endElement($a02Uri, intx, )
|endElement($b02Uri, seq2, )
|endElement($b02Uri, seq, )
- |endPrefixMapping(xsi)
- |endPrefixMapping(b02)
|endPrefixMapping(a02)
+ |endPrefixMapping(b02)
+ |endPrefixMapping(xsi)
|endDocument
|""".stripMargin
assertEquals(expectedOutput, actualOutput)
@@ -479,7 +479,7 @@ class TestSAXParseAPI {
|startPrefixMapping(a02, $a02Uri)
|startPrefixMapping(b02, $b02Uri)
|startPrefixMapping(xsi, $xsiUri)
- |startElement($b02Uri, seq, b02:seq,
Attributes((,,xmlns:xsi,$xsiUri)(,,xmlns:b02,$b02Uri)(,,xmlns:a02,$a02Uri)))
+ |startElement($b02Uri, seq, b02:seq,
Attributes((,,xmlns:a02,$a02Uri)(,,xmlns:b02,$b02Uri)(,,xmlns:xsi,$xsiUri)))
|startElement($b02Uri, seq2, b02:seq2, Attributes())
|startElement($a02Uri, intx, a02:intx,
Attributes(($xsiUri,nil,xsi:nil,true)))
|endElement($a02Uri, intx, a02:intx)
@@ -500,9 +500,9 @@ class TestSAXParseAPI {
|endElement($a02Uri, intx, a02:intx)
|endElement($b02Uri, seq2, b02:seq2)
|endElement($b02Uri, seq, b02:seq)
- |endPrefixMapping(xsi)
- |endPrefixMapping(b02)
|endPrefixMapping(a02)
+ |endPrefixMapping(b02)
+ |endPrefixMapping(xsi)
|endDocument
|""".stripMargin
assertEquals(expectedOutput, actualOutput)
@@ -521,7 +521,7 @@ class TestSAXParseAPI {
val b02Uri = "http://b02.com"
val expectedOutput =
s"""startDocument
- |startElement(, , b02:seq,
Attributes((,,xmlns:xsi,$xsiUri)(,,xmlns:b02,$b02Uri)(,,xmlns:a02,$a02Uri)))
+ |startElement(, , b02:seq,
Attributes((,,xmlns:a02,$a02Uri)(,,xmlns:b02,$b02Uri)(,,xmlns:xsi,$xsiUri)))
|startElement(, , b02:seq2, Attributes())
|startElement(, , a02:intx, Attributes((,,xsi:nil,true)))
|endElement(, , a02:intx)
diff --git
a/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestListSerialization.scala
b/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestListSerialization.scala
new file mode 100644
index 0000000..08b2c8b
--- /dev/null
+++
b/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestListSerialization.scala
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.util
+
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+
+class Thing() extends Serializable {
+ var things: Seq[Thing] = _
+}
+
+class TestListSerialization {
+
+ /**
+ * Scala 2.12 handles List serialization in a special way using a
+ * SerializationProxy. In special cases such as recursion and self
+ * referential lists, this can lead to a ClassCastException during
+ * deserialization with an error like:
+ *
+ * java.lang.ClassCastException: cannot assign instance of
+ * scala.collection.immutable.List$SerializationProxy to field Thing.things
+ * of type scala.collection.Seq in instance of Thing
+ *
+ * We have implemented workarounds for this in CompiledExpression1.scala with
+ * uses of:
+ *
+ * .map(identity)
+ *
+ * If this test fails, it likely means Scala has fixed this issue and we can
+ * saftely remove the unnecessary .map(identity) workarounds.
+ */
+ @Test def testListSerializationProxyError(): Unit = {
+ val a = new Thing()
+ val b = new Thing()
+ val l = Seq(b)
+ a.things = l
+ b.things = l
+
+ // Serialize and Deserialize Thing A
+ val baos = new java.io.ByteArrayOutputStream()
+ val oos = new java.io.ObjectOutputStream(baos)
+ oos.writeObject(a)
+ oos.close()
+ baos.close()
+ val bytes = baos.toByteArray()
+ val bais = new java.io.ByteArrayInputStream(bytes)
+ val ois = new java.io.ObjectInputStream(bais)
+
+ val newA = try {
+ ois.readObject()
+ fail("Expected ClassCastException to be thrown")
+ } catch {
+ case e: ClassCastException => {
+ assertTrue(e.getMessage.contains("List$SerializationProxy"))
+ assertTrue(e.getMessage.contains("scala.collection.Seq"))
+ assertTrue(e.getMessage.contains("Thing"))
+ }
+ case _: Throwable => fail("Expected ClassCastException to be thrown")
+ }
+ }
+
+ /**
+ * Shows that the above issue can be worked around by creating a copy of the
+ * list via .map(identity) of the serialized list
+ */
+ @Test def testListSerializationProxyErrorWorkaround(): Unit = {
+ val a = new Thing()
+ val b = new Thing()
+ val l = Seq(b)
+ a.things = l.map(identity)
+ b.things = l.map(identity)
+
+ // Serialize and Deserialize Thing A
+ val baos = new java.io.ByteArrayOutputStream()
+ val oos = new java.io.ObjectOutputStream(baos)
+ oos.writeObject(a)
+ oos.close()
+ baos.close()
+ val bytes = baos.toByteArray()
+ val bais = new java.io.ByteArrayInputStream(bytes)
+ val ois = new java.io.ObjectInputStream(bais)
+
+ ois.readObject().asInstanceOf[Thing]
+ }
+
+}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dsom/CompiledExpression1.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dsom/CompiledExpression1.scala
index 3c6fc32..8fcd43d 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dsom/CompiledExpression1.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dsom/CompiledExpression1.scala
@@ -293,12 +293,15 @@ class DPathCompileInfo(
*
* Then we move outward to the enclosing element - and if there
* isn't one we return None. (Which most likely will lead to an SDE.)
+ *
+ * The map(identity) is used work around a bug related to serialization of
Lists
+ * and the SerializationProxy object.
*/
final lazy val enclosingElementCompileInfos: Seq[DPathElementCompileInfo] = {
val eci = elementCompileInfos.flatMap { _.parents }
val res = eci.flatMap { _.elementCompileInfos }
res
- }
+ }.map(identity)
/**
* The contract here supports the semantics of "." in paths.
@@ -309,14 +312,19 @@ class DPathCompileInfo(
* This is used because paths refer to elements, so we have to
* walk upward until we get elements. At that point we can
* then navigate element to element.
+ *
+ * The map(identity) is used work around a bug related to serialization of
+ * Lists and the SerializationProxy object.
*/
- final lazy val elementCompileInfos: Seq[DPathElementCompileInfo] = this
match {
- case e: DPathElementCompileInfo => Seq(e)
- case d: DPathCompileInfo => {
- val eci = d.parents
- eci flatMap { ci => ci.elementCompileInfos }
+ final lazy val elementCompileInfos: Seq[DPathElementCompileInfo] = {
+ this match {
+ case e: DPathElementCompileInfo => Seq(e)
+ case d: DPathCompileInfo => {
+ val eci = d.parents
+ eci flatMap { ci => ci.elementCompileInfos }
+ }
}
- }
+ }.map(identity)
}
/**
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/SAXInfosetOutputter.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/SAXInfosetOutputter.scala
index f7df299..99c6e5d 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/SAXInfosetOutputter.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/SAXInfosetOutputter.scala
@@ -145,18 +145,11 @@ class SAXInfosetOutputter(xmlReader:
DFDL.DaffodilParseXMLReader,
private def doStartPrefixMapping(diElem: DIElement, contentHandler:
ContentHandler): Unit = {
val (nsbStart: NamespaceBinding, nsbEnd: NamespaceBinding) =
getNsbStartAndEnd(diElem)
var n = nsbStart
- var mappingsList: Seq[(String, String)] = Seq()
while (n.ne(nsbEnd) && n.ne(null) && n.ne(scala.xml.TopScope)) {
val prefix = if (n.prefix == null) "" else n.prefix
val uri = if (n.uri == null) "" else n.uri
- // we generate a list here by prepending so can build the prefixMapping
in the
- // same order as it is in minimizedScope when we call
startPrefixMapping; we do this because
- // getPrefix in the contentHandler is order dependent
- mappingsList +:= (prefix, uri)
- n = n.parent
- }
- mappingsList.foreach{ case (prefix, uri) =>
contentHandler.startPrefixMapping(prefix, uri)
+ n = n.parent
}
}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DaffodilParseOutputStreamContentHandler.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DaffodilParseOutputStreamContentHandler.scala
index 4e8e658..12a9069 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DaffodilParseOutputStreamContentHandler.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DaffodilParseOutputStreamContentHandler.scala
@@ -93,7 +93,12 @@ class DaffodilParseOutputStreamContentHandler(out:
OutputStream, pretty: Boolean
override def startPrefixMapping(prefix: String, uri: String): Unit = {
val _prefix = if (prefix == "") null else prefix
- activePrefixMapping = NamespaceBinding(_prefix, uri, activePrefixMapping)
+ // only add this new prefix mapping to the currentElementMapping. The
+ // mappings in this variable will be added to the active mapping later.
+ // This is necessary because we essentially prepend NamespaceBindings when
+ // adding new ones, which effectively reverses the order. When we add these
+ // mappings to the activePrefixMapping, we'll undo that reversal so things
+ // are in the correct order
currentElementPrefixMapping = NamespaceBinding(_prefix, uri,
currentElementPrefixMapping)
}
@@ -102,39 +107,56 @@ class DaffodilParseOutputStreamContentHandler(out:
OutputStream, pretty: Boolean
}
/**
- * Uses Attributes, which is passed in to the startElement callback, to
gather element attributes
- * or in the case where namespacePrefixes is true, prefix mappings. Any new
prefix mappings are used
- * to update the activePrefixMapping and currentElementPrefixMapping.
- *
- * @return sequence of string attribute=val pairings
+ * Uses Attributes, which is passed in to the startElement callback, to
+ * gather prefix mappings in the case where namespacePrefixes is true. New
+ * prefix mappings are added to the currentElementPrefixMapping bindings
*/
- def processAttributes(atts: Attributes): Seq[String] = {
- var attrPairings: Seq[String] = Seq()
+ def processAttributePrefixMappings(atts: Attributes): Unit = {
+ var i = 0
+ while (i < atts.getLength) {
+ val qName = atts.getQName(i)
+ if (qName.nonEmpty) {
+ // if qName is populated that implies namespacePrefixes == true, and we
+ // might have prefix mappings if the qname is a special xmlns value
+ if (qName == "xmlns") {
+ val pre = null
+ val uri = atts.getValue(i)
+ currentElementPrefixMapping = NamespaceBinding(pre, uri,
currentElementPrefixMapping)
+ } else if (qName.startsWith("xmlns:")) {
+ val pre = qName.substring(6)
+ val uri = atts.getValue(i)
+ currentElementPrefixMapping = NamespaceBinding(pre, uri,
currentElementPrefixMapping)
+ } else {
+ // not a prefix mapping, ignore this attribute
+ }
+ } else {
+ // no qname, so namespacePrefixes == false, which means we get no
+ // prefix mappings in the Attributes, only regular attributes such as
xsi:nil.
+ // This can't be a prefix mapping, so ignore this attribute
+ }
+ i += 1
+ }
+ }
+
+ /**
+ * Uses Attributes, which is passed in to the startElement callback, to write
+ * element attributes. Prefix mappings are ignored and assumed to be written
+ * elsewhere. Uses activePrefixMappings to map uri's to prefixes, so prefix
+ * mappings in the Attributes must have already been processed and added to
+ * activePrefixMappings
+ */
+ def writeNonNamespaceAttributes(writer: OutputStreamWriter, atts:
Attributes): Unit = {
var i = 0
- var newMappingsList: Seq[(String, String)] = Seq()
while (i < atts.getLength) {
val qName = atts.getQName(i)
- val attrVal = atts.getValue(i)
if (qName.nonEmpty) {
- // if qName is populated; as in when namespacePrefixes == true
- // describing namespace mapping
if (qName.startsWith("xmlns:") || qName == "xmlns") {
- // get prefix
- val pre = if (qName.startsWith("xmlns:")) {
- qName.substring(6)
- } else {
- null
- }
- // we make this call to check if the prefix already exists. If it
doesn't exist, we get a
- // Nope, so we can add it to our list, but if it does exist, nothing
happens and it doesn't
- // get re-added and we instead proceed to the next item in Attributes
- val maybeUri = XMLUtils.maybeURI(activePrefixMapping, pre)
- if (maybeUri.isEmpty || maybeUri.get != attrVal) { // not found yet,
add it
- newMappingsList +:= (pre, attrVal)
- }
+ // namespace mapping, ignore
} else {
// regular attribute with qname such as xsi:nil
- attrPairings +:= s""" ${qName}="${attrVal}""""
+ val attrVal = atts.getValue(i)
+ val attr = s""" ${qName}="${attrVal}""""
+ writer.write(attr)
}
} else {
// no qname, so namespacePrefixes == false, which means we get no
@@ -148,7 +170,9 @@ class DaffodilParseOutputStreamContentHandler(out:
OutputStream, pretty: Boolean
// found a prefix; add to attribute pairings
if (maybePrefix.isDefined) {
val prefix = maybePrefix.get
- attrPairings +:= s""" $prefix:$localName="${attrVal}""""
+ val attrVal = atts.getValue(i)
+ val attr = s""" $prefix:$localName="${attrVal}""""
+ writer.write(attr)
} else {
// if an attribute has a URI, we must have a prefix, even if it is
null
Assert.invariantFailed("Cannot have URI with no prefix mapping")
@@ -160,11 +184,6 @@ class DaffodilParseOutputStreamContentHandler(out:
OutputStream, pretty: Boolean
}
i += 1
}
- newMappingsList.foreach{ case (prefix, uri) =>
- activePrefixMapping = NamespaceBinding(prefix, uri, activePrefixMapping)
- currentElementPrefixMapping = NamespaceBinding(prefix, uri,
currentElementPrefixMapping)
- }
- attrPairings.reverse
}
override def startElement(
@@ -179,11 +198,32 @@ class DaffodilParseOutputStreamContentHandler(out:
OutputStream, pretty: Boolean
outputIndentation(writer)
}
- /**
- * represents the attributes for the current element. We use this to
generate the attributes list
- * within the start tag
- */
- val currentElementAttributes = processAttributes(atts)
+ // scan the attributes for any prefix mappings, which will update
+ // currentElementPrefixMappings
+ processAttributePrefixMappings(atts)
+
+ // at this point, all prefix mappings specific to this element have been
+ // added to currentElementPrefixMapping (either from startPrefixMapping or
+ // processAttributePrefixMappings). Note that these new mappings are in the
+ // reverse order than they should be written because they were prepended to
+ // NamespaceBinding. We now add these new mappings to the
activePrefixMapping,
+ // and in doing so re-reverses the order so that they are in the correct
+ // order in the activePrefixMaping. This ensures we get find the right
+ // prefix during lookups and write the mappings in the correct order.
+ val previousPrefixMapping = activePrefixMapping
+ while (currentElementPrefixMapping != null) {
+ val prefix = currentElementPrefixMapping.prefix
+ val uri = currentElementPrefixMapping.uri
+
+ // check to see if the prefix is already mapped to the same URI. If it
+ // is, ignore this mapping since it adds nothing new
+ val maybeUri = XMLUtils.maybeURI(activePrefixMapping, prefix)
+ if (maybeUri.isEmpty || maybeUri.get != uri) {
+ activePrefixMapping = NamespaceBinding(prefix, uri,
activePrefixMapping)
+ }
+ currentElementPrefixMapping = currentElementPrefixMapping.parent
+ }
+
// we always push, but activePrefixMapping won't always be populated with
new information
// from startPrefixMapping or processAttributes
activePrefixMappingContextStack.push(activePrefixMapping)
@@ -192,19 +232,15 @@ class DaffodilParseOutputStreamContentHandler(out:
OutputStream, pretty: Boolean
writer.write("<")
outputTagName(uri, localName, qName)
- // this contains only xmlns prefixes and are populated via the
start/endPrefixMappings
- // or Attributes via processAttributes()
- if (currentElementPrefixMapping != null) {
- val pm = currentElementPrefixMapping.toString()
+ // write only the part of activePrefixMapping that is new for this element
+ if (activePrefixMapping ne previousPrefixMapping) {
+ val pm = activePrefixMapping.buildString(previousPrefixMapping)
writer.write(pm)
- currentElementPrefixMapping = null
}
- // handles attributes from the Attributes object. Example attributes is
xsi:nil
- if (currentElementAttributes.nonEmpty) {
- val attrs = currentElementAttributes.mkString(" ")
- writer.write(attrs)
- }
+ // write the non-namespace attributes from the Attributes object. Example
+ // attributes is xsi:nil
+ writeNonNamespaceAttributes(writer, atts)
// handle end of tag
writer.write(">")