You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by sl...@apache.org on 2022/08/12 17:37:54 UTC

[daffodil] branch main updated: Support XML strings in XMLTextInfosetInputter/Outputter

This is an automated email from the ASF dual-hosted git repository.

slawrence pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new 3b213ce30 Support XML strings in XMLTextInfosetInputter/Outputter
3b213ce30 is described below

commit 3b213ce30b1974ecd9fc2260e4f081240da89874
Author: Steve Lawrence <sl...@apache.org>
AuthorDate: Fri Aug 12 12:06:32 2022 -0400

    Support XML strings in XMLTextInfosetInputter/Outputter
    
    - When outputting string elements with the stringAsXml runtime property
      set to true, instead of escaping the simple element content, we
      instead output the content to raw XML as if it were part of the
      infoset. When inputting, we read the XML and convert it back to a
      string as the simple content. When the XML is added to the infoset, a
      wrapper element called "stringAsXml" is added to ensure the default
      namespace is reset. As an example:
    
      Infoset without the stringAsXml property:
    
        <foo>&lt;payload&gt;content&lt;/payload&gt;</foo>
    
      Infoset with the stringAsXml property:
    
        <foo><stringAsXml xmlns=""><payload>content</payload></stringAsXml></foo>
    
      Note that there are multiple ways to read/write XML that are
      syntactically different but semantically the same, making it unlikely
      that unparsed XML will be byte-for-byte the same as the original XML.
    
      Also note that the result of the XMLTextInfosetOutputter is used for
      "full" validation. Because this changes its output, essentially
      converting a simple string type into a complex type, this will break
      full validation if stringAsXml is used. If full validation is needed,
      one must use external validation with a modified schema. And example
      of this schema is included in new tests.
    
    - We currently ignore the return value of InfosetOutputter functions,
      and any exceptions thrown just bubble to the top and appear as an
      unexpected exception. Instead, if the InfosetOutputter throws an
      exception, we create an SDE. The logic for finalizing the walker is
      moved into the doParse function so that the SDE is caught and
      correctly added as a diagnostic. This is need to handle
      non-well-formed XML. Additionally, InfosetOutputter's returning false
      has been deprecated and will result in a usage error.
    
    - We cannot use normal TDML tests to test this behavior because the
      XMLTextInfosetOutputter outputs a different infoset than the other
      infoset outputters, hand written tests added to verify the correct
      behavior.
    
    DAFFODIL-2708
---
 .../apache/daffodil/infoset/InfosetWalker.scala    |  39 ++-
 .../daffodil/infoset/XMLTextInfosetInputter.scala  | 262 ++++++++++++++++++---
 .../daffodil/infoset/XMLTextInfosetOutputter.scala |  70 +++++-
 .../apache/daffodil/processors/DataProcessor.scala |  21 +-
 .../stringAsXml/namespaced/binMessage_01.dat       | Bin 0 -> 732 bytes
 .../stringAsXml/namespaced/binMessage_01.dat.xml   |  38 +++
 .../namespaced/binMessage_01.dat.xml.dat           | Bin 0 -> 679 bytes
 .../stringAsXml/namespaced/binMessage_02.xml       |  24 ++
 .../stringAsXml/namespaced/binMessage_03.dat       | Bin 0 -> 98 bytes
 .../stringAsXml/namespaced/binMessage_04.xml       |  14 ++
 .../stringAsXml/namespaced/binMessage_05.xml       |  14 ++
 .../stringAsXml/namespaced/binMessage_06.xml       |  15 ++
 .../stringAsXml/namespaced/binMessage_07.xml       |  14 ++
 .../stringAsXml/namespaced/binMessage_08.dat       | Bin 0 -> 121 bytes
 .../stringAsXml/namespaced/xsd/binMessage.dfdl.xsd |  76 ++++++
 .../namespaced/xsd/binMessageWithXmlPayload.xsd    |  55 +++++
 .../namespaced/xsd/stringAsXmlWrapper.xsd          |  82 +++++++
 .../stringAsXml/namespaced/xsd/xmlPayload.xsd      |  41 ++++
 .../stringAsXml/nonamespace/binMessage_01.dat      | Bin 0 -> 56 bytes
 .../stringAsXml/nonamespace/binMessage_01.dat.xml  |  14 ++
 .../nonamespace/xsd/binMessage.dfdl.xsd            |  76 ++++++
 .../nonamespace/xsd/binMessageWithXmlPayload.xsd   |  48 ++++
 .../nonamespace/xsd/stringAsXmlWrapper.xsd         |  38 +++
 .../stringAsXml/nonamespace/xsd/xmlPayload.xsd     |  39 +++
 .../apache/daffodil/infoset/TestStringAsXml.scala  | 201 ++++++++++++++++
 project/Rat.scala                                  |  13 +
 26 files changed, 1135 insertions(+), 59 deletions(-)

diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetWalker.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetWalker.scala
index 5968f1478..d99b809c5 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetWalker.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetWalker.scala
@@ -17,7 +17,12 @@
 
 package org.apache.daffodil.infoset
 
+import scala.util.Failure
+import scala.util.Success
+import scala.util.Try
+
 import org.apache.daffodil.exceptions.Assert
+import org.apache.daffodil.exceptions.ThrowsSDE
 import org.apache.daffodil.util.MStackOfInt
 import org.apache.daffodil.util.MStackOf
 
@@ -408,6 +413,20 @@ class InfosetWalker private (
     containerIndexStack.setTop(top + 1)
   }
 
+  private def doOutputter(outputterFunc: => Boolean, desc: String, context: ThrowsSDE): Unit = {
+    Try(outputterFunc) match {
+      case Success(true) => // success
+      // $COVERAGE-OFF$
+      case Success(false) => Assert.usageError("InfosetOutputter false return value is deprecated. Throw an Exception instead.")
+      // $COVERAGE-ON$
+      case Failure(e) => {
+        val cause = e.getCause
+        val msg = if (cause == null) e.toString else cause.toString
+        context.SDE("Failed to %s: %s", desc, msg)
+      }
+    }
+  }
+
   /**
    * Start the document. Note that because the top of container index is
    * initialized to one less that the starting index, we also call
@@ -415,7 +434,7 @@ class InfosetWalker private (
    * position.
    */
   private def infosetWalkerStepStart(): Unit = {
-    outputter.startDocument()
+    doOutputter(outputter.startDocument(), "start infoset document", startingContainerNode.erd)
     moveToNextSibling()
   }
 
@@ -425,7 +444,7 @@ class InfosetWalker private (
    * should not call walk() again because it is finished.
    */
   private def infosetWalkerStepEnd(): Unit = {
-    outputter.endDocument()
+    doOutputter(outputter.endDocument(), "end infoset document", startingContainerNode.erd)
     containerNodeStack = null
     containerIndexStack = null
     finished = true
@@ -452,8 +471,8 @@ class InfosetWalker private (
       if (child.isSimple) {
         if (!child.isHidden || walkHidden) {
           val simple = child.asInstanceOf[DISimple]
-          outputter.startSimple(simple)
-          outputter.endSimple(simple)
+          doOutputter(outputter.startSimple(simple), "start infoset simple element", simple.erd)
+          doOutputter(outputter.endSimple(simple), "end infoset simple element", simple.erd)
         }
         // now we can remove this simple element to free up memory
         containerNode.freeChildIfNoLongerNeeded(containerIndex, releaseUnneededInfoset)
@@ -462,9 +481,11 @@ class InfosetWalker private (
         // must be complex or array, exact same logic for both
         if (!child.isHidden || walkHidden) {
           if (child.isComplex) {
-            outputter.startComplex(child.asInstanceOf[DIComplex])
+            val complex = child.asInstanceOf[DIComplex]
+            doOutputter(outputter.startComplex(complex), "start infoset complex element", complex.erd)
           } else {
-            outputter.startArray(child.asInstanceOf[DIArray])
+            val array = child.asInstanceOf[DIArray]
+            doOutputter(outputter.startArray(array), "start infoset array", array.erd)
           }
           moveToFirstChild(child)
         } else {
@@ -485,9 +506,11 @@ class InfosetWalker private (
 
       // create appropriate end event
       if (containerNode.isComplex) {
-        outputter.endComplex(containerNode.asInstanceOf[DIComplex])
+        val complex = containerNode.asInstanceOf[DIComplex]
+        doOutputter(outputter.endComplex(complex), "end infoset complex element", complex.erd)
       } else {
-        outputter.endArray(containerNode.asInstanceOf[DIArray])
+        val array = containerNode.asInstanceOf[DIArray]
+        doOutputter(outputter.endArray(array), "end infoset array", array.erd)
       }
 
       // we've ended this array/complex associated with the container, so we
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/XMLTextInfosetInputter.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/XMLTextInfosetInputter.scala
index bb11f4126..7efbfc4a4 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/XMLTextInfosetInputter.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/XMLTextInfosetInputter.scala
@@ -17,24 +17,36 @@
 
 package org.apache.daffodil.infoset
 
+import java.io.StringWriter
+import java.nio.charset.StandardCharsets
+import javax.xml.XMLConstants
+import javax.xml.stream.XMLInputFactory
+import javax.xml.stream.XMLStreamConstants._
+import javax.xml.stream.XMLStreamException
+import javax.xml.stream.XMLStreamReader
+import javax.xml.stream.XMLStreamWriter
+import javax.xml.stream.util.XMLEventAllocator
+
+import org.apache.daffodil.dpath.NodeInfo
 import org.apache.daffodil.exceptions.Assert
+import org.apache.daffodil.infoset.InfosetInputterEventType._
+import org.apache.daffodil.util.MaybeBoolean
 import org.apache.daffodil.util.Misc
 import org.apache.daffodil.xml.XMLUtils
-import org.apache.daffodil.util.MaybeBoolean
-import org.apache.daffodil.dpath.NodeInfo
-import org.apache.daffodil.infoset.InfosetInputterEventType._
-
-import javax.xml.stream.XMLStreamReader
-import javax.xml.stream.XMLStreamConstants._
-import javax.xml.stream.XMLInputFactory
-import javax.xml.stream.util.XMLEventAllocator
-import javax.xml.stream.XMLStreamException
-import javax.xml.XMLConstants
 
-object XMLTextInfosetInputter {
+object XMLTextInfoset {
   lazy val xmlInputFactory = {
     val fact = new com.ctc.wstx.stax.WstxInputFactory()
-    fact.setProperty(XMLInputFactory.IS_COALESCING, true)
+    // Disable coalescing. We use getElementText() almost everywhere in this
+    // inputter, which coalesces simple type content regardless of this
+    // property. This is what we want in most cases since we need to coalesce
+    // simple type content into a single string for Daffodil, so we might as
+    // well let the XMLStreamReader do it. The one exception to this is in our
+    // gatherXmlAsString() function. In this function, we want XML events to
+    // match the actual XML as closely as possible to avoid making changes
+    // where possible. We can achieve this by disabling coalescing, and not use
+    // getElementText() when needed.
+    fact.setProperty(XMLInputFactory.IS_COALESCING, false)
     fact.setEventAllocator(com.ctc.wstx.evt.DefaultEventAllocator.getDefaultInstance)
 
     // Woodstox has it's own properties for turning off DTDs to provide
@@ -48,6 +60,132 @@ object XMLTextInfosetInputter {
     fact.setProperty(XMLInputFactory.IS_VALIDATING, false) // no DTD validation
     fact
   }
+
+  lazy val xmlOutputFactory = {
+    val fact = new com.ctc.wstx.stax.WstxOutputFactory()
+    fact
+  }
+
+  /**
+   * When the stringAsXml runtime property is set to true we treat the simple
+   * content as if it were XML and output it to the infoset. We use this value
+   * as the runtime property name. Additionally, when we output the XML we need
+   * a wrapper element to reset the default XML namespace in case the infoset
+   * has a default namespace. We also use this variable as the name of that
+   * wrapper element.
+   */
+  lazy val stringAsXml = "stringAsXml"
+
+  /**
+   * Read an event from an XMLStreamReader and write the same event to an
+   * XMLStreamWriter. For example, this can be used for convert XML from one
+   * type of input to a different type (e.g. InputStream to String), check for
+   * well-formed XML, and perform some basic normalization. Example usage of
+   * this function is to call this in a loop like this:
+   *
+   *   while (xsr.hasNext()) {
+   *     xsr.next()
+   *     XMLTextInfoset.writeStreamEvent(xsr, xsw)
+   *   }
+   *
+   * This approach allows callers to skip certain events by conditionally
+   * calling this function.
+   *
+   * It is assumed that the caller calls xsw.startDocument() if needed. This
+   * function does not handle the START_DOCUMENT event.
+   *
+   * Because we disable coalescing in our XMLStreamReader, we will actually get
+   * events for CHARACTERS, CDATA, SPACE, and ENTITY_REFERENCE, which would
+   * otherwise be lost. This allows us to convert XML to strings (e.g. in
+   * gatherXmlAsString) that are much closer to the original XML, and in some
+   * cases exactly the same.
+   *
+   * This function drops all DTD events. This is because this function is used
+   * to either embed XML into the middle of an XML infoset, or read an embedded
+   * XML from the middle of an XML infoset. In either case DTD's aren't allowed
+   * because they are only allowed at the top of XML, and so we drop them.
+   *
+   * Spaces inside element tags (e.g. in between attributes/namespce
+   * declarations) are not preserved.
+   *
+   * Attribute and namespace definitions order is preserved, but all namespace
+   * definitions are output first followed by all attributes.
+   *
+   * Element/attribute prefixes are preserved
+   *
+   * Comments are preserved
+   *
+   * CDATA regions are preserved
+   *
+   * Processing instructions are preserved.
+   *
+   * The XML declaration is always output when unparsing, and never output when
+   * parsing (handled by the callers).
+   *
+   * Double quotes are always used for attribute/namespace definitions, except
+   * in the XML declaration which always uses single quotes.
+   *
+   * The following character entities are preserved: &lt; &amp; &#xd;. The
+   * decimal entitity &#10; is converted to the &#xd; hex entity. All others
+   * are converted to thir UTF-8 characters. Because DTD is disabled, custom
+   * entities are not supported and result in an error.
+   *
+   * Whitespace, both inbetween complex elements and inside simple elements, are
+   * preserved. However, whitespace in the epilog or prolog is ignored.
+   *
+   * Elements with no content but with an open and close tag are converted to a
+   * an empty-element tag.
+   *
+   * Both a lone CR and CRLF are converted to LF.
+   */
+  def writeXMLStreamEvent(xsr: XMLStreamReader, xsw: XMLStreamWriter): Unit = {
+    xsr.getEventType() match {
+      case START_ELEMENT => {
+        xsw.writeStartElement(
+          xsr.getPrefix(),
+          xsr.getLocalName(),
+          xsr.getNamespaceURI())
+        for (i <- 0 until xsr.getNamespaceCount()) {
+          xsw.writeNamespace(
+            xsr.getNamespacePrefix(i),
+            xsr.getNamespaceURI(i))
+        }
+        for (i <- 0 until xsr.getAttributeCount()) {
+          xsw.writeAttribute(
+            xsr.getAttributePrefix(i),
+            xsr.getAttributeNamespace(i),
+            xsr.getAttributeLocalName(i),
+            xsr.getAttributeValue(i))
+        }
+      }
+      case END_ELEMENT => xsw.writeEndElement()
+      case CHARACTERS => xsw.writeCharacters(xsr.getText())
+      case COMMENT => xsw.writeComment(xsr.getText())
+      case CDATA => xsw.writeCData(xsr.getText())
+      case PROCESSING_INSTRUCTION => xsw.writeProcessingInstruction(xsr.getPITarget(), xsr.getPIData())
+      case END_DOCUMENT => xsw.writeEndDocument()
+      case DTD => {
+        // even though we disable DTD in the XMLInputFactory, we still get DTD
+        // events when parsing XML. Silently ignore these events
+      }
+      // $COVERAGE-OFF$
+      case START_DOCUMENT | ATTRIBUTE | NAMESPACE | ENTITY_REFERENCE | SPACE | _ => {
+        // START_DOCUMENT events are expected to be handled and skipped by
+        // the caller.
+        //
+        // Woodstox does not create ATTRIBUTE or NAMESPACE events. Namespace
+        // prefix definition and attributes are accessable only from the
+        // START_ELEMENT.
+        //
+        // Woodstox does not create SPACE events.
+        //
+        // ENTITY_REFERENCE events are never created. This event is only used
+        // for custom entities, which error because we disable DTD.
+        Assert.invariantFailed("Unexpected XML event while XML stream: " + xsr.getEventType())
+      }
+      // $COVERAGE-ON$
+    }
+  }
 }
 
 class XMLTextInfosetInputter private (input: Either[java.io.Reader, java.io.InputStream])
@@ -65,15 +203,15 @@ class XMLTextInfosetInputter private (input: Either[java.io.Reader, java.io.Inpu
    */
   private lazy val (xsr: XMLStreamReader, evAlloc: XMLEventAllocator) = {
     val xsr = input match {
-      case Left(reader) => XMLTextInfosetInputter.xmlInputFactory.createXMLStreamReader(reader)
-      case Right(is) => XMLTextInfosetInputter.xmlInputFactory.createXMLStreamReader(is)
+      case Left(reader) => XMLTextInfoset.xmlInputFactory.createXMLStreamReader(reader)
+      case Right(is) => XMLTextInfoset.xmlInputFactory.createXMLStreamReader(is)
     }
 
     //
     // This gets the event allocator corresponding to the xmlStreamReader just created.
     // Strange API. They should let you get this from the xmlStreamReader itself.
     //
-    val evAlloc = XMLTextInfosetInputter.xmlInputFactory.getEventAllocator
+    val evAlloc = XMLTextInfoset.xmlInputFactory.getEventAllocator
 
     // no need for UnparseError here. If the XML syntax is bad, parser catches it before we get here.
     Assert.invariant(xsr.hasNext())
@@ -115,25 +253,83 @@ class XMLTextInfosetInputter private (input: Either[java.io.Reader, java.io.Inpu
     xsr.getNamespaceURI()
   }
 
+  /**
+  * Consumes all events inside a "stringAsXml" element and forwards those
+  * events to an XMLStreamWriter to write them to a string. That string is
+  * returned and used as the value of the simple element.
+   */
+  private def gatherXmlAsString(): String = {
+    // move past the START_ELEMENT event for this simple type, ignoring all
+    // whitespace/comments
+    xsr.nextTag()
+
+    // we should now be at the START_ELEMENT event for the wrapper element.
+    // We need to skip it. Error if that's not the case.
+    if (xsr.getEventType() != START_ELEMENT || xsr.getLocalName() != XMLTextInfoset.stringAsXml) {
+      throw new XMLStreamException("Expected start of " + XMLTextInfoset.stringAsXml)
+    }
+    xsr.next()
+   
+    // we are now at the first event inside the wrapper element. Convert this
+    // and all following events we see to a string until we find the closing
+    // wrapper tag. We trim the result to remove whitespace that the outputter
+    // may have written with pretty mode enabled.
+    val sw = new StringWriter()
+    val xsw = XMLTextInfoset.xmlOutputFactory.createXMLStreamWriter(sw, StandardCharsets.UTF_8.toString)
+    xsw.writeStartDocument()
+    while (xsr.getEventType() != END_ELEMENT || xsr.getLocalName() != XMLTextInfoset.stringAsXml) {
+      XMLTextInfoset.writeXMLStreamEvent(xsr, xsw)
+      xsr.next()
+    }
+    xsw.writeEndDocument()
+    val xmlString = sw.toString.trim
+
+    // skip the END_ELEMENT event for the wrapper element and any following whitespace
+    xsr.nextTag()
+
+    // should now be at the END_ELEMENT for our simple type
+    if (xsr.getEventType() != END_ELEMENT) {
+      throw new XMLStreamException("Expected end of element following end of " + XMLTextInfoset.stringAsXml)
+    }
+
+    xmlString
+  }
+
   override def getSimpleText(primType: NodeInfo.Kind, runtimeProperties: java.util.Map[String, String]): String = {
+
     val txt =
-      try {
-        xsr.getElementText()
-      } catch {
-        case xse: XMLStreamException => {
-          throw new NonTextFoundInSimpleContentException("Error on line " + evAlloc.allocate(xsr).getLocation.getLineNumber)
+      if (primType == NodeInfo.String && runtimeProperties.get(XMLTextInfoset.stringAsXml) == "true") {
+        try {
+          gatherXmlAsString()
+        } catch {
+          case xse: XMLStreamException => {
+            val lineNum = evAlloc.allocate(xsr).getLocation.getLineNumber
+            throw new InvalidInfosetException("Error on line " + lineNum + ": " + xse.getMessage)
+          }
+        }
+      } else {
+        val elementText = try {
+          xsr.getElementText()
+        } catch {
+          case xse: XMLStreamException => {
+            throw new NonTextFoundInSimpleContentException("Error on line " + evAlloc.allocate(xsr).getLocation.getLineNumber)
+          }
+        }
+        if (primType == NodeInfo.String) {
+          XMLUtils.remapPUAToXMLIllegalCharacters(elementText)
+        } else {
+          elementText
         }
       }
+
+    // getElementText and gatherXmlAsString move the current event to the
+    // EndElement. We want to stay on StartElement until next is called. So set
+    // fakeStartEvent to true so that any calls to getEventType will return
+    // StartElement.
     Assert.invariant(xsr.getEventType() == END_ELEMENT)
-    // getElementText moves the current event to the EndElement. We want to
-    // stay on StartElement until next is called. So set fakeStartEvent to true
-    // so that any calls to getEventType will return StartElement.
     fakeStartEvent = true
-    if (primType.isInstanceOf[NodeInfo.String.Kind]) {
-      XMLUtils.remapPUAToXMLIllegalCharacters(txt)
-    } else {
-      txt
-    }
+
+    txt
   }
 
   override def isNilled(): MaybeBoolean = {
@@ -163,11 +359,11 @@ class XMLTextInfosetInputter private (input: Either[java.io.Reader, java.io.Inpu
 
   override def next(): Unit = {
     if (fakeStartEvent) {
-      // we are faking a StartElement event due to a call to getSimpleText. Now
-      // that we have called next() we need to return an EndElement event. The
-      // xsr is already on the end event for this element (due to
-      // getSimpleText), so all we need to to is flip the fakeStartEvent
-      // variable and we'll get the correct EndElement event
+      // we are faking a StartElement event due to a call to getSimpleText or
+      // gatherXmlAsString. Now that we have called next() we need to return an
+      // EndElement event. The xsr is already on the end event for this
+      // element, so all we need to to is flip the fakeStartEvent flag and
+      // we'll get the correct EndElement event
       fakeStartEvent = false
     } else {
       val next = nextTagOrEndDocument()
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/XMLTextInfosetOutputter.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/XMLTextInfosetOutputter.scala
index b88e9a34a..62154196f 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/XMLTextInfosetOutputter.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/XMLTextInfosetOutputter.scala
@@ -17,10 +17,13 @@
 
 package org.apache.daffodil.infoset
 
+import java.io.StringReader
 import java.nio.charset.StandardCharsets
+import javax.xml.stream.XMLStreamConstants._
 
-import org.apache.daffodil.util.Indentable
 import org.apache.daffodil.dpath.NodeInfo
+import org.apache.daffodil.exceptions.Assert
+import org.apache.daffodil.util.Indentable
 
 /**
  * Writes the infoset to a java.io.Writer as XML text.
@@ -99,6 +102,56 @@ class XMLTextInfosetOutputter private (writer: java.io.Writer, pretty: Boolean,
     writer.write(">")
   }
 
+  private def writeStringAsXml(str: String): Unit = {
+    // create a wrapper element that allows us to reset the default XML
+    // namespace. This ensures the embedded XML does not inherit the default
+    // namespaces if one is defined in the infoset
+    incrementIndentation()
+    if (pretty) {
+      writer.write(System.lineSeparator())
+      outputIndentation(writer)
+    }
+    writer.write("<")
+    writer.write(XMLTextInfoset.stringAsXml)
+    writer.write(" xmlns=\"\">")
+
+    if (pretty) {
+      writer.write(System.lineSeparator())
+    }
+
+    // Parse the string as XML and then write all events out to the
+    // XMLStreamWriter. This performs basic validation of the XML so we do not
+    // create an invalid infoset, though it may contain XML features that
+    // aren't normally in an XML infoset (e.g. elements with attributes). This
+    // logic also skips the START_DOCUMENT event so that the XML declaration is
+    // not written in the middle of our XML infoset
+    val sr = new StringReader(str)
+    val xsr = XMLTextInfoset.xmlInputFactory.createXMLStreamReader(sr)
+    val xsw = XMLTextInfoset.xmlOutputFactory.createXMLStreamWriter(writer, StandardCharsets.UTF_8.toString)
+    Assert.invariant(xsr.getEventType() == START_DOCUMENT)
+    while (xsr.hasNext()) {
+      xsr.next()
+      XMLTextInfoset.writeXMLStreamEvent(xsr, xsw)
+    }
+
+    // write the closing wrapper element
+    if (pretty) {
+      writer.write(System.lineSeparator())
+      outputIndentation(writer)
+    }
+    writer.write("</")
+    writer.write(XMLTextInfoset.stringAsXml)
+    writer.write(">")
+    decrementIndentation()
+
+    // if pretty, write indentation so that the closing tag of the simple
+    // element is indented as if it were complex
+    if (pretty) {
+      writer.write(System.lineSeparator())
+      outputIndentation(writer)
+    }
+  }
+
   override def startSimple(simple: DISimple): Boolean = {
     if (pretty) {
       writer.write(System.lineSeparator())
@@ -107,15 +160,16 @@ class XMLTextInfosetOutputter private (writer: java.io.Writer, pretty: Boolean,
     outputStartTag(simple)
 
     if (!isNilled(simple) && simple.hasValue) {
-      val text =
-        if (simple.erd.optPrimType.get.isInstanceOf[NodeInfo.String.Kind]) {
-          val s = remapped(simple.dataValueAsString)
-          scala.xml.Utility.escape(s)
+      if (simple.erd.optPrimType.get == NodeInfo.String) {
+        val simpleVal = simple.dataValueAsString
+        if (simple.erd.runtimeProperties.get(XMLTextInfoset.stringAsXml) == "true") {
+          writeStringAsXml(simpleVal)
         } else {
-          simple.dataValueAsString
+          writer.write(scala.xml.Utility.escape(remapped(simpleVal)))
         }
-
-      writer.write(text)
+      } else {
+        writer.write(simple.dataValueAsString)
+      }
     }
 
     outputEndTag(simple)
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
index 52292fd70..557a67fca 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
@@ -447,16 +447,6 @@ class DataProcessor private (
     doParse(ssrd.parser, state)
 
     val pr = if (state.processorStatus == Success) {
-      // By the time we get here, all infoset nodes have been set final, all
-      // walker blocks released, and all elements walked. The one exception
-      // is that the root node has not been set final because isFinal is
-      // handled by the sequence parser and there is no sequence around the
-      // root node. So mark it as final and do one last walk to end the
-      // document.
-      state.infoset.contents(0).isFinal = true
-      state.walker.walk(lastWalk = true)
-      Assert.invariant(state.walker.isFinished)
-
       // validate infoset, errors are added to the PState diagnostics
       val vr = maybeValidationBytes.toScalaOption.map { bytes =>
         val bis = new java.io.ByteArrayInputStream(bytes.toByteArray)
@@ -512,6 +502,17 @@ class DataProcessor private (
         // so that subsequent use of the state can generally work and have a context.
         //
         state.setMaybeProcessor(Maybe(p))
+
+        if (state.processorStatus == Success) {
+          // At this point all infoset nodes have been set final, all infoset
+          // walker blocks released, and all elements walked. The one exception
+          // is the root node has not been set final because isFinal is handled
+          // by the sequence parser and there is no sequence around the root
+          // node. So mark it final and do one last walk to end the document.
+          state.infoset.contents(0).isFinal = true
+          state.walker.walk(lastWalk = true)
+          Assert.invariant(state.walker.isFinished)
+        }
       } catch {
         //We will actually be handling all errors in the outer loop
         //However, there is a chance that our finally block will itself throw.
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat
new file mode 100644
index 000000000..f7910589c
Binary files /dev/null and b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat differ
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml
new file mode 100644
index 000000000..1ba957668
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<b:binMessage xmlns:b="urn:bin">
+  <record>
+    <id>1</id>
+    <xmlStr>
+      <stringAsXml xmlns="">
+<p:root xmlns:q="urn:payload" xmlns:p="urn:payload" attr2="4" attr1=" value ">
+  <!-- commment -->
+  <?processing instruction?>
+  <field><![CDATA[Field ]]> with <![CDATA[cdata]]> </field>
+  <field>spaces</field>    <field>   spaces   </field>
+  <field/>
+  <field/>
+  <field>entity references: &lt; > &amp; " ' ©</field>
+  <field>CR</field>&#xd;<field>LF</field>
+<field>CRLF</field>&#xd;
+<field>end</field>
+  <field>CR</field>
+  <field>LF</field>
+  <field>CRLF</field>
+  <field>=invalid field</field>
+</p:root>
+      </stringAsXml>
+    </xmlStr>
+    <priority>5</priority>
+  </record>
+  <record>
+    <id>2</id>
+    <xmlStr>
+      <stringAsXml xmlns="">
+<r:root xmlns:r="urn:payload">
+  <field>second record</field>
+</r:root>
+      </stringAsXml>
+    </xmlStr>
+    <priority>1</priority>
+  </record>
+</b:binMessage>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml.dat b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml.dat
new file mode 100644
index 000000000..3498ce7ae
Binary files /dev/null and b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml.dat differ
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_02.xml b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_02.xml
new file mode 100644
index 000000000..53ddbb811
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_02.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<b:binMessage xmlns:b="urn:bin">
+  <record>
+    <id>1</id>
+    <xmlStr>
+      <stringAsXml xmlns="">
+<p:root   attr2="4"   xmlns:q='urn:payload' xmlns:p="urn:payload" attr1=' value '>
+  <!-- commment -->
+  <?processing instruction?>
+  <field><![CDATA[Field ]]> with <![CDATA[cdata]]> </field>
+  <field>spaces</field>    <field>   spaces   </field>
+  <field />
+  <field></field>
+  <field>entity references: &lt; &gt; &amp; &quot; &apos; &#169;</field>
+  <field>CR</field>&#13;<field>LF</field>&#10;<field>CRLF</field>&#13;&#10;<field>end</field>
+  <field>CR</field>
  <field>LF</field>
+  <field>CRLF</field>
+  <field>=invalid field</field>
+</p:root>
+      </stringAsXml>
+    </xmlStr>
+    <priority>5</priority>
+  </record>
+</b:binMessage>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_03.dat b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_03.dat
new file mode 100644
index 000000000..cd61581d2
Binary files /dev/null and b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_03.dat differ
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_04.xml b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_04.xml
new file mode 100644
index 000000000..42d37faa0
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_04.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<b:binMessage xmlns:b="urn:bin">
+  <record>
+    <id>1</id>
+    <xmlStr>
+      <stringAsXml xmlns="">
+<p:root xmlns:p="urn:payload">
+  <field foo="unclosed attr>invalid xml</field>
+</p:root>
+      </stringAsXml>
+    </xmlStr>
+    <priority>1</priority>
+  </record>
+</b:binMessage>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_05.xml b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_05.xml
new file mode 100644
index 000000000..105749c6c
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_05.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<b:binMessage xmlns:b="urn:bin">
+  <record>
+    <id>1</id>
+    <xmlStr>
+      <notStringAsXml xmlns="">
+<p:root xmlns:p="urn:payload">
+  <field>test</field>
+</p:root>
+      </notStringAsXml>
+    </xmlStr>
+    <priority>1</priority>
+  </record>
+</b:binMessage>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_06.xml b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_06.xml
new file mode 100644
index 000000000..8a214e53a
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_06.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<b:binMessage xmlns:b="urn:bin">
+  <record>
+    <id>1</id>
+    <xmlStr>
+      <stringAsXml xmlns="">
+<p:root xmlns:p="urn:payload">
+  <field>test</field>
+</p:root>
+      </stringAsXml>
+      <extraElement />
+    </xmlStr>
+    <priority>1</priority>
+  </record>
+</b:binMessage>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_07.xml b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_07.xml
new file mode 100644
index 000000000..ba815de81
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_07.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<b:binMessage xmlns:b="urn:bin">
+  <record>
+    <id>
+      <stringAsXml xmlns="">
+<p:root xmlns:p="urn:payload">
+  <field>stringAsXml on wrong element</field>
+</p:root>
+      </stringAsXml>
+    </id>
+    <xmlStr />
+    <priority>1</priority>
+  </record>
+</b:binMessage>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_08.dat b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_08.dat
new file mode 100644
index 000000000..be28e57a6
Binary files /dev/null and b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_08.dat differ
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd
new file mode 100644
index 000000000..557d8f5de
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<schema
+  xmlns="http://www.w3.org/2001/XMLSchema" 
+  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
+  xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"
+  xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions"
+  xmlns:b="urn:bin"
+  targetNamespace="urn:bin">
+
+  <include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+  
+  <annotation>
+    <appinfo source="http://www.ogf.org/dfdl/">
+      <dfdl:format ref="b:GeneralFormat"
+        lengthKind="delimited"
+        encoding="UTF-8"/>
+    </appinfo>
+  </annotation>
+
+  <group name="strLenHG">
+    <sequence>
+      <element name="strLen" type="xs:int"
+        dfdl:outputValueCalc="{ dfdl:valueLength(../xmlStr, 'bytes') }" />
+    </sequence>
+  </group>
+
+  <!--
+    Because the data is all text, git might convert LF to CRLF on windows. This
+    breaks the data because the strLen will be incorect if CR's are added . To
+    prevent this, we append a NUL byte and LF at the end of the data,
+    represented here by a terminator. This NUL byte tricks git into thinking
+    this is a binary file and will not convert line feeds
+  -->
+  <element name="binMessage" dfdl:terminator="%#r00;%#r0A;">
+    <complexType>
+      <sequence>
+        <element name="record" maxOccurs="unbounded">
+          <complexType>
+            <sequence dfdl:separator="%NL;" dfdl:separatorPosition="postfix">
+              <element name="id" type="xs:int" />
+              <sequence dfdl:hiddenGroupRef="b:strLenHG" />
+              <element name="xmlStr"
+                dfdl:lengthKind="explicit" dfdl:length="{ ../strLen }"
+                dfdlx:runtimeProperties="stringAsXml=true">
+                <simpleType>
+                  <restriction base="xs:string">
+                    <maxLength value="0" />
+                  </restriction>
+                </simpleType>
+              </element>
+              <element name="priority" type="xs:int" />
+            </sequence>
+          </complexType>
+        </element>
+      </sequence>
+    </complexType>
+  </element>
+  
+</schema>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessageWithXmlPayload.xsd b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessageWithXmlPayload.xsd
new file mode 100644
index 000000000..3b46c663c
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessageWithXmlPayload.xsd
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<schema
+  xmlns="http://www.w3.org/2001/XMLSchema" 
+  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
+  xmlns:b="urn:bin"
+  targetNamespace="urn:bin">
+
+  <import namespace="urn:stringAsXml" schemaLocation="stringAsXmlWrapper.xsd" />
+
+  <element name="binMessage">
+    <complexType>
+      <sequence>
+        <element name="record" maxOccurs="unbounded">
+          <complexType>
+            <sequence>
+              <element name="id" type="xs:int" />
+              <!--
+              The XMLTextInfosetOutputter converts this xmlStr element, which used to be a
+              simple element with xs:string type, to a complex element. This complex element
+              contains a "wrapper" element defined in the stringAsXmlWrapper.xsd schema file,
+              which references the schema that defines the payload XML. See the
+              stringAsXMLWrapper.xsd file for more information.
+              -->
+              <element name="xmlStr">
+                <complexType>
+                  <group xmlns:s="urn:stringAsXml" ref="s:stringAsXmlGroup" />
+                </complexType>
+              </element>
+              <element name="priority" type="xs:int" />
+            </sequence>
+          </complexType>
+        </element>
+      </sequence>
+    </complexType>
+  </element>
+  
+</schema>
+
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/stringAsXmlWrapper.xsd b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/stringAsXmlWrapper.xsd
new file mode 100644
index 000000000..3c7cd0bff
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/stringAsXmlWrapper.xsd
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<!--
+When using the stringAsXml=true runtime property, the infoset no longer
+validates against the DFDL schema. This is because what used to be a simple
+element with type xs:string is now a complex element with an "xmlString" child
+and a grand child depending on the payload XML.
+
+To modify a DFDL schema into an XSD schema to validate a this kind of infoset,
+the DFDL schema should have the following changes:
+
+1. Import this file:
+
+   <xs:import namespace="urn:stringAsXml" schemaLocation="stringAsXmlWrapper.xsd" />
+
+2. Simple elements with type="xs:string" and dfdlx:runtimeProperty="stringAsXml=true"
+   should be changed to a complex type referencing the group in this schema.
+   For example, if the DFDl schema looks like this:
+
+   <xs:element name="xmlString" type="xs:string" dfdlx:runtimeProperties="stringAsXml=true" />
+
+   Then the validation schema should be changed to this:
+
+   <xs:element name="xmlString">
+     <xs:complexType>
+       <xs:group xmlns:s="urn:stringAsXml" ref="s:stringAsXmlGroup" />
+     </xs:complexType>
+   </xs:element>
+
+3. Modify this file to import the payload schema and reference the correct root
+   element represented by the xmlString string.
+
+Note that the stringAsXml element will have no namespace associated with it,
+and in the infoset will be output like this to ensure this is the case:
+
+  <stringAsXml xmlns="">...</stringAsXml>
+
+This is necessary to reset the default XML namespace to ensure the default
+namespace in the infoset (if defined) does not leak into the namespace of
+the XML payload and prevent validation.
+
+Using this approach, the payload schema can be used without modification to
+validate XML embedded in data and subsequently embedded into the infoset.
+-->
+
+<schema
+  xmlns="http://www.w3.org/2001/XMLSchema"
+  targetNamespace="urn:stringAsXml"
+  elementFormDefault="unqualified">
+
+  <import namespace="urn:payload" schemaLocation="xmlPayload.xsd" />
+
+  <group name="stringAsXmlGroup">
+    <sequence>
+      <element name="stringAsXml">
+        <complexType>
+          <sequence>
+            <element xmlns:p="urn:payload" ref="p:root" />
+          </sequence>
+        </complexType>
+      </element>
+    </sequence>
+  </group>
+  
+</schema>
+
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/xmlPayload.xsd b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/xmlPayload.xsd
new file mode 100644
index 000000000..0a7fd0e76
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/xmlPayload.xsd
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<schema
+  xmlns="http://www.w3.org/2001/XMLSchema"
+  xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  xmlns:p="urn:payload"
+  targetNamespace="urn:payload">
+
+  <element name="root">
+    <complexType>
+      <sequence>
+        <element name="field" maxOccurs="unbounded">
+          <simpleType>
+            <restriction base="xs:string">
+              <pattern value="[^=]*" /> <!-- field containing an equal sign is invalid -->
+            </restriction>
+          </simpleType>
+        </element>
+      </sequence>
+      <attribute name="attr1" type="xs:string" />
+      <attribute name="attr2" type="xs:int" />
+    </complexType>
+  </element>
+  
+</schema>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat
new file mode 100644
index 000000000..743f1d594
Binary files /dev/null and b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat differ
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat.xml b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat.xml
new file mode 100644
index 000000000..a70d6b382
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binMessage xmlns="urn:bin" xmlns:b="urn:bin">
+  <record>
+    <id>1</id>
+    <xmlStr>
+      <stringAsXml xmlns="">
+<root>
+  <field>=invalid field</field>
+</root>
+      </stringAsXml>
+    </xmlStr>
+    <priority>5</priority>
+  </record>
+</binMessage>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/binMessage.dfdl.xsd b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/binMessage.dfdl.xsd
new file mode 100644
index 000000000..7ec2d0b9f
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/binMessage.dfdl.xsd
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<schema
+  xmlns="http://www.w3.org/2001/XMLSchema" 
+  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
+  xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"
+  xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions"
+  targetNamespace="urn:bin"
+  elementFormDefault="qualified">
+
+  <include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+  
+  <annotation>
+    <appinfo source="http://www.ogf.org/dfdl/">
+      <dfdl:format xmlns:b="urn:bin" ref="b:GeneralFormat"
+        lengthKind="delimited"
+        encoding="UTF-8"/>
+    </appinfo>
+  </annotation>
+
+  <group name="strLenHG">
+    <sequence xmlns:b="urn:bin">
+      <element name="strLen" type="xs:int"
+        dfdl:outputValueCalc="{ dfdl:valueLength(../b:xmlStr, 'bytes') }" />
+    </sequence>
+  </group>
+
+  <!--
+    Because the data is all text, git might convert LF to CRLF on windows. This
+    breaks the data because the strLen will be incorect if CR's are added . To
+    prevent this, we append a NUL byte and LF at the end of the data,
+    represented here by a terminator. This NUL byte tricks git into thinking
+    this is a binary file and will not convert line feeds
+  -->
+  <element name="binMessage" dfdl:terminator="%#r00;%#r0A;">
+    <complexType>
+      <sequence>
+        <element name="record" maxOccurs="unbounded">
+          <complexType>
+            <sequence xmlns:b="urn:bin" dfdl:separator="%NL;" dfdl:separatorPosition="postfix">
+              <element name="id" type="xs:int" />
+              <sequence dfdl:hiddenGroupRef="b:strLenHG" />
+              <element name="xmlStr"
+                dfdl:lengthKind="explicit" dfdl:length="{ ../b:strLen }"
+                dfdlx:runtimeProperties="stringAsXml=true">
+                <simpleType>
+                  <restriction base="xs:string">
+                    <maxLength value="0" />
+                  </restriction>
+                </simpleType>
+              </element>
+              <element name="priority" type="xs:int" />
+            </sequence>
+          </complexType>
+        </element>
+      </sequence>
+    </complexType>
+  </element>
+  
+</schema>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/binMessageWithXmlPayload.xsd b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/binMessageWithXmlPayload.xsd
new file mode 100644
index 000000000..620ab7011
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/binMessageWithXmlPayload.xsd
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<schema
+  xmlns="http://www.w3.org/2001/XMLSchema" 
+  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
+  targetNamespace="urn:bin"
+  elementFormDefault="qualified">
+
+  <import namespace="urn:stringAsXml" schemaLocation="stringAsXmlWrapper.xsd" />
+
+  <element name="binMessage">
+    <complexType>
+      <sequence>
+        <element name="record" maxOccurs="unbounded">
+          <complexType>
+            <sequence>
+              <element name="id" type="xs:int" />
+              <element name="xmlStr">
+                <complexType>
+                  <group xmlns:s="urn:stringAsXml" ref="s:stringAsXmlGroup" />
+                </complexType>
+              </element>
+              <element name="priority" type="xs:int" />
+            </sequence>
+          </complexType>
+        </element>
+      </sequence>
+    </complexType>
+  </element>
+  
+</schema>
+
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/stringAsXmlWrapper.xsd b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/stringAsXmlWrapper.xsd
new file mode 100644
index 000000000..e6b697241
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/stringAsXmlWrapper.xsd
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<xs:schema
+  xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  targetNamespace="urn:stringAsXml"
+  elementFormDefault="unqualified">
+
+  <xs:import schemaLocation="xmlPayload.xsd" />
+
+  <xs:group name="stringAsXmlGroup">
+    <xs:sequence>
+      <xs:element name="stringAsXml">
+        <xs:complexType>
+          <xs:sequence>
+            <xs:element ref="root" />
+          </xs:sequence>
+        </xs:complexType>
+      </xs:element>
+    </xs:sequence>
+  </xs:group>
+  
+</xs:schema>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/xmlPayload.xsd b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/xmlPayload.xsd
new file mode 100644
index 000000000..d9f8610bf
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/xmlPayload.xsd
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<schema
+  xmlns="http://www.w3.org/2001/XMLSchema"
+  xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+  <element name="root">
+    <complexType>
+      <sequence>
+        <element name="field" maxOccurs="unbounded">
+          <simpleType>
+            <restriction base="xs:string">
+              <pattern value="[a-zA-Z].*" />
+            </restriction>
+          </simpleType>
+        </element>
+      </sequence>
+      <attribute name="attr1" type="xs:string" />
+      <attribute name="attr2" type="xs:int" />
+    </complexType>
+  </element>
+  
+</schema>
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/infoset/TestStringAsXml.scala b/daffodil-test/src/test/scala/org/apache/daffodil/infoset/TestStringAsXml.scala
new file mode 100644
index 000000000..de35a2c7f
--- /dev/null
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/infoset/TestStringAsXml.scala
@@ -0,0 +1,201 @@
+/*
+ * 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.infoset
+
+import java.io.ByteArrayOutputStream
+import java.io.ByteArrayInputStream
+import java.io.File
+import java.io.InputStream
+import java.net.URI
+import java.nio.charset.StandardCharsets
+import javax.xml.XMLConstants
+import javax.xml.transform.stream.StreamSource
+import javax.xml.validation.SchemaFactory
+
+import org.junit.Test
+import org.junit.Assert._
+
+import org.xml.sax.SAXParseException
+
+import org.apache.commons.io.IOUtils
+
+import org.apache.daffodil.api.DFDL.DataProcessor
+import org.apache.daffodil.api.URISchemaSource
+import org.apache.daffodil.api.ValidationMode
+import org.apache.daffodil.compiler.Compiler
+import org.apache.daffodil.io.InputSourceDataInputStream
+import org.apache.daffodil.util.Misc
+import org.apache.daffodil.Implicits.intercept
+
+
+class TestStringAsXml {
+
+  private def compileSchema(dfdlSchemaURI: URI) = {
+    val c = Compiler()
+    val pf = c.compileSource(URISchemaSource(dfdlSchemaURI))
+    val dp = pf.onPath("/")
+    dp.withValidationMode(ValidationMode.Full)
+  }
+
+  private def doParse(dp: DataProcessor, data: InputStream) = {
+    val parseIn = InputSourceDataInputStream(data)
+    val parseBos = new ByteArrayOutputStream()
+    val parseOut = new XMLTextInfosetOutputter(parseBos, pretty = true)
+    val parseRes = dp.parse(parseIn, parseOut)
+    val parseDiags = parseRes.getDiagnostics.map(_.toString)
+    val parseStrOpt = if (parseRes.isProcessingError) None else Some(parseBos.toString)
+    (parseDiags, parseStrOpt)
+  }
+
+  private def doUnparse(dp: DataProcessor, infoset: InputStream) = {
+    val unparseIn = new XMLTextInfosetInputter(infoset)
+    val unparseBos = new ByteArrayOutputStream()
+    val unparseOut = java.nio.channels.Channels.newChannel(unparseBos)
+    val unparseRes = dp.unparse(unparseIn, unparseOut)
+    val unparseDiags = unparseRes.getDiagnostics.map(_.toString)
+    val unparseStrOpt = if (unparseRes.isProcessingError) None else Some(unparseBos.toString)
+    (unparseDiags, unparseStrOpt)
+  }
+
+  @Test def test_stringAsXml_01(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val parseData = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat").toURL.openStream
+    val (parseDiags, Some(parseInfosetActual)) = doParse(dp, parseData)
+    val parseInfosetExpected = {
+      val is = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml").toURL.openStream
+      IOUtils.toString(is, StandardCharsets.UTF_8)
+    }
+    // diagnostic from full validation
+    assertTrue(parseDiags.find(_.contains("Element 'xmlStr' is a simple type")).isDefined)
+    // diagnostic from limited validation
+    assertTrue(parseDiags.find(_.contains("xmlStr failed facet checks due to: facet maxLength")).isDefined)
+    // we still get the expected infoset, replace CRLF with LF because of git windows autocrlf
+    assertEquals(parseInfosetExpected.replace("\r\n", "\n"), parseInfosetActual.replace("\r\n", "\n"))
+
+    // validate the infoset using the handwritten WithPayload schema
+    val xsdFile = new File(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessageWithXmlPayload.xsd"))
+    val xsdFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
+    val xsd = xsdFactory.newSchema(xsdFile)
+    val source = new StreamSource(new ByteArrayInputStream(parseInfosetActual.getBytes(StandardCharsets.UTF_8)))
+    val validator = xsd.newValidator()
+    val e = intercept[SAXParseException] {
+      validator.validate(source)
+    }
+    assertTrue(e.toString.contains("Value '=invalid field' is not facet-valid"))
+  }
+
+  @Test def test_stringAsXml_02(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val unparseInfoset = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml").toURL.openStream
+    val (_, Some(unparseDataActual)) = doUnparse(dp, unparseInfoset)
+    val unparseDataExpected = {
+      val is = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml.dat").toURL.openStream
+      IOUtils.toString(is, StandardCharsets.UTF_8)
+    }
+    assertEquals(unparseDataExpected, unparseDataActual)
+  }
+
+  @Test def test_stringAsXml_03(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val infoset1 = {
+      val is = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_02.xml").toURL.openStream
+      IOUtils.toString(is, StandardCharsets.UTF_8)
+    }
+    val (_, Some(data1)) = doUnparse(dp, new ByteArrayInputStream(infoset1.getBytes(StandardCharsets.UTF_8)))
+    val (_, Some(infoset2)) = doParse(dp, new ByteArrayInputStream(data1.getBytes(StandardCharsets.UTF_8)))
+    val (_, Some(data2)) = doUnparse(dp, new ByteArrayInputStream(infoset2.getBytes(StandardCharsets.UTF_8)))
+    // unparsing canonicalizes the XML infoset payload. The original infoset is
+    // not canoncialized, so the parsed infoset should not match the original.
+    // But both unprsed data should match because the first unparse
+    // canonicailzed the XML, and the second unparse should have made not
+    // additional changes since it's already canonicalized
+    assertNotEquals(infoset1, infoset2)
+    assertEquals(data1, data2)
+  }
+
+  @Test def test_stringAsXml_04(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val parseData = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_03.dat").toURL.openStream
+    val (parseDiags, _) = doParse(dp, parseData)
+    assertTrue(parseDiags.find(_.contains("Unexpected character")).isDefined)
+  }
+
+  @Test def test_stringAsXml_05(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val unparseInfoset = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_04.xml").toURL.openStream
+    val (unparseDiags, _) = doUnparse(dp, unparseInfoset)
+    assertTrue(unparseDiags.find(_.contains("Unexpected character")).isDefined)
+  }
+
+  @Test def test_stringAsXml_06(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val unparseInfoset = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_05.xml").toURL.openStream
+    val (unparseDiags, _) = doUnparse(dp, unparseInfoset)
+    assertTrue(unparseDiags.find(_.contains("Expected start of stringAsXml")).isDefined)
+  }
+
+  @Test def test_stringAsXml_07(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val unparseInfoset = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_06.xml").toURL.openStream
+    val (unparseDiags, _) = doUnparse(dp, unparseInfoset)
+    assertTrue(unparseDiags.find(_.contains("Expected end of element following end of stringAsXml")).isDefined)
+  }
+
+  @Test def test_stringAsXml_08(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val unparseInfoset = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_07.xml").toURL.openStream
+    val (unparseDiags, _) = doUnparse(dp, unparseInfoset)
+    assertTrue(unparseDiags.find(_.contains("Illegal content for simple element")).isDefined)
+  }
+
+  @Test def test_stringAsXml_09(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/xsd/binMessage.dfdl.xsd"))
+    val unparseInfoset = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_08.dat").toURL.openStream
+    val (unparseDiags, _) = doParse(dp, unparseInfoset)
+    unparseDiags.foreach(System.err.println)
+    assertTrue(unparseDiags.find(_.contains("Undeclared general entity \"name\"")).isDefined)
+  }
+
+  @Test def test_stringAsXml_10(): Unit = {
+    val dp = compileSchema(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/binMessage.dfdl.xsd"))
+    val parseData = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat").toURL.openStream
+    val (parseDiags, Some(parseInfosetActual)) = doParse(dp, parseData)
+    val parseInfosetExpected = {
+      val is = Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat.xml").toURL.openStream
+      IOUtils.toString(is, StandardCharsets.UTF_8)
+    }
+    // diagnostic from full validation
+    assertTrue(parseDiags.find(_.contains("Element 'xmlStr' is a simple type")).isDefined)
+    // diagnostic from limited validation
+    assertTrue(parseDiags.find(_.contains("xmlStr failed facet checks due to: facet maxLength")).isDefined)
+    // we still get the expected infoset, replace CRLF with LF because of git windows autocrlf
+    assertEquals(parseInfosetExpected.replace("\r\n", "\n"), parseInfosetActual.replace("\r\n", "\n"))
+
+    // validate the infoset using the handwritten WithPayload schema
+    val xsdFile = new File(Misc.getRequiredResource("/org/apache/daffodil/infoset/stringAsXml/nonamespace/xsd/binMessageWithXmlPayload.xsd"))
+    val xsdFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
+    val xsd = xsdFactory.newSchema(xsdFile)
+    val source = new StreamSource(new ByteArrayInputStream(parseInfosetActual.getBytes(StandardCharsets.UTF_8)))
+    val validator = xsd.newValidator()
+    val e = intercept[SAXParseException] {
+      validator.validate(source)
+    }
+    assertTrue(e.toString.contains("Value '=invalid field' is not facet-valid"))
+  }
+
+}
diff --git a/project/Rat.scala b/project/Rat.scala
index c7eafbc2f..9eb7524f2 100644
--- a/project/Rat.scala
+++ b/project/Rat.scala
@@ -170,6 +170,19 @@ object Rat {
     file("daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13c.bin"),
     file("daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13d.bin"),
     file("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/02nine_headers.txt"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_01.dat.xml.dat"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_02.dat"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_02.xml"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_03.dat"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_04.xml"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_05.xml"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_06.xml"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_07.xml"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/namespaced/binMessage_08.dat"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat"),
+    file("daffodil-test/src/test/resources/org/apache/daffodil/infoset/stringAsXml/nonamespace/binMessage_01.dat.xml"),
     file("daffodil-test/src/test/resources/org/apache/daffodil/usertests/Book2.csv"),
     file("daffodil-test/src/test/resources/org/apache/daffodil/usertests/test_prefix_separator_as_variable"),
     file("daffodil-test/src/test/resources/test space/A BTinyData.tdml.dat"),