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(">")

Reply via email to