Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Describes the feature as-is-built 2021-09-09

Motivation

The Daffodil Java and Scala API's require the use of user allocated classes that are passed into the Daffodil parse/unparse functions. Examples of such objects include implementations of the InputSourceDataInputStream , InfosetInputter , and InfosetOutputter interfaces. These interfaces are designed so that users can implement custom inputs and outputs that require no knowledge from Daffodil aside from the interface functions. For example, a user could implement a custom InfosetInputter  and InfosetOutputter  to support EXI or YAML infoset representations, which Daffodil does not natively support, without Daffodil needing to know the implementation details.

...

Code Block
scala
scala
class MyInfosetInputter extends InfosetInputter {
  override def getSimpleText(primNode: NodeInfo.Kind, runtimeProperties: Map[String,String)): String = {
    val simpleText = ... // get the simple text for this current event
    val key1Value = runtimeProperties.getOrDefault("key1", "defaultValue")
    val key2Value = runtimeProperties.getOrDefault("key2", "defaultValue")
    // redact or transform simpleText base on key1/key2 values, and return
    val transformedSimpleText = ...
    transformedSimpleText
  }
}

Example Implementation: stringAsXml

The A primary use case for this feature is the ability to parse a simple element with an xs:string type that expected to contain XML content. Rather than escaping the XML string and treating it like simple content, we instead want to output it as if it were part of the XML infoset. Similarly, when unparsing, we want to treat all the children of a particular infoset element as if it raw text so that it unparses as a normal string.To accomplish this with the new dfdlx:runtimeProperties  feature, we can annotate specific elements that should have text string treated as if it where XML, like so:

...

<xs:schema ...>

  <xs:element name="root>
    <xs:complexType>
      <xs:sequence>
        ...
        <xs:element name="payload" type="xs:string" dfdlx:runtimeProperties="stringAsXML=true" ... />
        ...
      </xs:sequence>
    </xs:complexType>
  </xs:element>

</xs:schema>

This annotates the payload element as one where the string value should be treated as XML.

Different InfosetInputter  and InfosetOutputters  could handle this property differently (or not at all if they chose), but one possible implementation based on Scala XML Nodes might look like the following (note that error checking or XML validity is excluded for brevity).

...

class ScalaXMLWithStringAsXMLInfosetOutputter extends InfosetOutputter {
  private val elemStack = ...
  ...
  override def startSimple(simple: DISimple): Boolean = {
    val text = simple.dataValueAsString
    val children =
      if (simple.erd.runtimeProperties.getOrDefault("stringAsXML", "false") == "true") {
        scala.xml.XML.load(text)
      } else {
        new scala.xml.Text(text)
      }
    val elem = scala.xml.Elem(
      simple.erd.prefix,
      simple.erd.name,
      ...
      Seq(children)
    )
    elemStack.push(elem)
    true
  }
  ...
}

The above InfosetOutputter converts the string to Scala XML Nodes if the runtimeProperty value is set, otherwise it treats the text as normal scala.xml.Text. A new Element is created with this as it's children, and added to a stack, which is ultimately used to build the final infoset.

The unparse side looks like this:

...

class ScalaXMLWithStringAsXMLInfosetInputter extends InfosetInputter {
  private val elemStack = ...
  ...
  override def getSimpleText(primType: NodeInfo.Kind, runtimeProperties: java.util.Map[String,String]): String = {
    val sb = new StringBuilder()
    val curElem = elemStack.top
    val childrenIter = curElem.child.iterator()
    val stringAsXML = runtimeProperties.getOrDefault("stringAsXML", "false") == "true"
	while (childrenIter.hasNext) {
      childrenIter.next() match {
        case txt: scala.xml.Text => txt.addString(sb)
        case elem: scala.xml.Elem if stringAsXML => sb.append(elem.toString)
        case _ => throw new NonTextFoundInSimpleContentException(...)
      }
    }
    sb.toString()
  }
  ...
}

The above InfosetInputter returns the text of the current simple element, but if the runtimeProperty is set, then it consumes children elements, converts them to a string, and appends that string to a builder to be returned as the simple textThis was implemented in Daffodil 3.4.0 in as part as commit 3b213ce30b.