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 | ||||
---|---|---|---|---|
| ||||
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.