You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ol...@apache.org on 2020/09/15 18:06:53 UTC
[incubator-daffodil] branch master updated: Support for SAX Parsing
This is an automated email from the ASF dual-hosted git repository.
olabusayo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-daffodil.git
The following commit(s) were added to refs/heads/master by this push:
new 8628820 Support for SAX Parsing
8628820 is described below
commit 86288209122994fa7d1de4a39574e286a3f354d0
Author: olabusayoT <50...@users.noreply.github.com>
AuthorDate: Thu Aug 6 12:40:40 2020 -0400
Support for SAX Parsing
- add sax option to command line
- add DaffodilXMLReader instance to DataProcessor
- implement DaffodilXMLReader, DaffodilOutputContentHandler and
SAXInfosetOutputter
- updates cached validator to re-add features, handlers and resolvers
- adds SAX Parsing/Testing to TDMLRunner
- adds SAX TDML Parser implementation to DaffofilTDMLProcessor
- update getInfosetOutputter to return SAXHandler in the case of sax,
and other outputters as appropriate
- Added setBlobAttribute for SAXInfosetOutputters using Blob properties
- Added parseresult as Property on xmlReader
- Updated optPrefix to have same implementation as getPrefix
- Updated TDML to compare SAX ouput and data processor output
- Added checkPrefixes and checkNamespaces flag to computeDiff and
compareReport
- Added DaffodilXMLReader to DFDL
- implement DaffodilOutputContentHandler and replaced SAXHandler
(DAFFODIL-2389)
- removed thisElementNamepace/Prefix, infavor of namedQName
- Added SAX Parsing implementation to Scala/Java APIs with tests
- created Error Handler for TDML
- Add scala/java API documentation
- Fixed bug in JSONInfosetOutputter that was causing additional
diagnostic for legacy parsing
DAFFODIL-2382
---
.../src/main/scala/org/apache/daffodil/Main.scala | 215 +++++++++------
.../org/apache/daffodil/dsom/ElementBase.scala | 5 +-
.../runtime1/ElementBaseRuntime1Mixin.scala | 2 -
.../daffodil/dsom/TestSimpleTypeUnions.scala | 44 +--
.../scala/org/apache/daffodil/util/TestUtils.scala | 1 +
.../org/apache/daffodil/japi/package-info.java | 139 +++++++++-
.../scala/org/apache/daffodil/japi/Daffodil.scala | 126 +++++++++
.../org/apache/daffodil/example/TestJavaAPI.java | 92 ++++++-
.../daffodil/japi/SAXErrorHandlerForJAPITest.java | 60 ++++
.../scala/org/apache/daffodil/util/Validator.scala | 49 ++--
.../scala/org/apache/daffodil/xml/QNameBase.scala | 25 +-
.../scala/org/apache/daffodil/xml/XMLUtils.scala | 78 +++++-
.../apache/daffodil/api/DFDLParserUnparser.scala | 12 +-
.../daffodil/infoset/JDOMInfosetOutputter.scala | 6 +-
.../daffodil/infoset/JsonInfosetOutputter.scala | 2 +-
.../daffodil/infoset/SAXInfosetOutputter.scala | 303 +++++++++++++++++++++
.../infoset/ScalaXMLInfosetOutputter.scala | 20 +-
.../daffodil/infoset/W3CDOMInfosetOutputter.scala | 4 +-
.../daffodil/infoset/XMLTextInfosetOutputter.scala | 2 +-
.../apache/daffodil/processors/DataProcessor.scala | 193 ++++++++++++-
.../apache/daffodil/processors/RuntimeData.scala | 14 +-
.../scala/org/apache/daffodil/sapi/Daffodil.scala | 125 ++++++++-
.../scala/org/apache/daffodil/sapi/package.scala | 119 +++++++-
.../org/apache/daffodil/example/TestScalaAPI.scala | 92 ++++++-
.../daffodil/sapi/SAXErrorHandlerForSAPITest.scala | 47 ++++
.../org/apache/daffodil/tdml/TDMLRunner.scala | 79 +++---
.../tdml/processor/DaffodilTDMLDFDLProcessor.scala | 135 ++++++++-
.../daffodil/section07/variables/variables.tdml | 2 +-
28 files changed, 1728 insertions(+), 263 deletions(-)
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
index 4e148d8..2aa2b1b 100644
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
@@ -17,84 +17,89 @@
package org.apache.daffodil
+import java.io.ByteArrayInputStream
+import java.io.File
+import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStream
-import java.io.FileInputStream
-import java.io.ByteArrayInputStream
+import java.net.URI
+import java.nio.ByteBuffer
import java.nio.channels.Channels
import java.nio.file.Paths
import java.util.Scanner
-
-import scala.xml.{SAXParseException, Node}
-import org.rogach.scallop
-import org.apache.daffodil.debugger.{InteractiveDebugger, CLIDebuggerRunner, TraceDebuggerRunner}
-import org.apache.daffodil.util.Misc
-import org.apache.daffodil.util.Timer
-import org.apache.daffodil.xml._
-import org.apache.daffodil.exceptions.Assert
-import org.apache.daffodil.compiler.Compiler
-import org.apache.daffodil.api.{WithDiagnostics, URISchemaSource, DFDL, DaffodilTunables}
-import org.apache.daffodil.util.Logging
-import org.apache.daffodil.util.LogLevel
-import org.apache.daffodil.util.LogWriter
-import org.apache.daffodil.util.LoggingDefaults
-import org.apache.daffodil.exceptions.NotYetImplementedException
-import java.io.File
-
-import org.apache.daffodil.tdml.DFDLTestSuite
-import org.apache.daffodil.externalvars.{Binding, BindingException}
-import org.apache.daffodil.externalvars.ExternalVariablesLoader
-import org.apache.daffodil.configuration.ConfigurationLoader
-import org.apache.daffodil.api.ValidationMode
-
-import scala.language.reflectiveCalls
-import scala.concurrent.Future
import java.util.concurrent.Executors
+import scala.concurrent.Await
import scala.concurrent.ExecutionContext
-import org.rogach.scallop.ScallopOption
-
+import scala.concurrent.Future
import scala.concurrent.duration.Duration
-import scala.concurrent.Await
-import org.apache.daffodil.xml.QName
-import org.apache.daffodil.dsom.ExpressionCompilers
-import org.apache.daffodil.compiler.InvalidParserException
-import java.net.URI
+import scala.language.reflectiveCalls
+import scala.xml.Node
+import scala.xml.SAXParseException
-import org.apache.daffodil.tdml.TDMLException
-import org.apache.daffodil.xml.RefQName
-import org.rogach.scallop.ArgType
-import org.rogach.scallop.ValueConverter
-import org.apache.daffodil.processors.DataProcessor
-import org.apache.daffodil.processors.DataLoc
-import org.apache.daffodil.processors.HasSetDebugger
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+import org.apache.commons.io.IOUtils
+import org.apache.daffodil.api.DFDL
+import org.apache.daffodil.api.DFDL.ParseResult
+import org.apache.daffodil.api.DaffodilTunables
+import org.apache.daffodil.api.URISchemaSource
+import org.apache.daffodil.api.ValidationMode
+import org.apache.daffodil.api.WithDiagnostics
+import org.apache.daffodil.compiler.Compiler
+import org.apache.daffodil.compiler.InvalidParserException
+import org.apache.daffodil.configuration.ConfigurationLoader
+import org.apache.daffodil.debugger.CLIDebuggerRunner
+import org.apache.daffodil.debugger.InteractiveDebugger
+import org.apache.daffodil.debugger.TraceDebuggerRunner
+import org.apache.daffodil.dsom.ExpressionCompilers
+import org.apache.daffodil.exceptions.Assert
+import org.apache.daffodil.exceptions.NotYetImplementedException
import org.apache.daffodil.exceptions.UnsuppressableException
-import org.apache.daffodil.infoset.XMLTextInfosetOutputter
-import org.apache.daffodil.infoset.NullInfosetOutputter
-import org.apache.daffodil.infoset.ScalaXMLInfosetOutputter
-import org.apache.daffodil.infoset.JsonInfosetOutputter
+import org.apache.daffodil.externalvars.Binding
+import org.apache.daffodil.externalvars.BindingException
+import org.apache.daffodil.externalvars.ExternalVariablesLoader
+import org.apache.daffodil.infoset.DaffodilOutputContentHandler
+import org.apache.daffodil.infoset.InfosetInputter
import org.apache.daffodil.infoset.InfosetOutputter
+import org.apache.daffodil.infoset.JDOMInfosetInputter
import org.apache.daffodil.infoset.JDOMInfosetOutputter
-import org.apache.daffodil.infoset.W3CDOMInfosetOutputter
-import org.apache.daffodil.infoset.XMLTextInfosetInputter
import org.apache.daffodil.infoset.JsonInfosetInputter
+import org.apache.daffodil.infoset.JsonInfosetOutputter
+import org.apache.daffodil.infoset.NullInfosetOutputter
import org.apache.daffodil.infoset.ScalaXMLInfosetInputter
-import org.apache.daffodil.infoset.JDOMInfosetInputter
+import org.apache.daffodil.infoset.ScalaXMLInfosetOutputter
import org.apache.daffodil.infoset.W3CDOMInfosetInputter
-import org.apache.daffodil.infoset.InfosetInputter
-import javax.xml.transform.TransformerFactory
-import javax.xml.transform.dom.DOMSource
-import javax.xml.transform.stream.StreamResult
-import javax.xml.parsers.DocumentBuilderFactory
-import org.apache.commons.io.IOUtils
-import org.apache.daffodil.io.InputSourceDataInputStream
-import org.apache.daffodil.tdml.TDMLTestNotCompatibleException
+import org.apache.daffodil.infoset.W3CDOMInfosetOutputter
+import org.apache.daffodil.infoset.XMLTextInfosetInputter
+import org.apache.daffodil.infoset.XMLTextInfosetOutputter
import org.apache.daffodil.io.DataDumper
-import java.nio.ByteBuffer
-
import org.apache.daffodil.io.FormatInfo
+import org.apache.daffodil.io.InputSourceDataInputStream
+import org.apache.daffodil.processors.DataLoc
+import org.apache.daffodil.processors.DataProcessor
+import org.apache.daffodil.processors.HasSetDebugger
import org.apache.daffodil.schema.annotation.props.gen.BitOrder
+import org.apache.daffodil.tdml.DFDLTestSuite
+import org.apache.daffodil.tdml.TDMLException
+import org.apache.daffodil.tdml.TDMLTestNotCompatibleException
import org.apache.daffodil.udf.UserDefinedFunctionFatalErrorException
+import org.apache.daffodil.util.LogLevel
+import org.apache.daffodil.util.LogWriter
+import org.apache.daffodil.util.Logging
+import org.apache.daffodil.util.LoggingDefaults
+import org.apache.daffodil.util.Misc
+import org.apache.daffodil.util.Timer
+import org.apache.daffodil.xml.QName
+import org.apache.daffodil.xml.RefQName
+import org.apache.daffodil.xml.DaffodilXMLLoader
+import org.apache.daffodil.xml.XMLUtils
+import org.rogach.scallop
+import org.rogach.scallop.ArgType
+import org.rogach.scallop.ScallopOption
+import org.rogach.scallop.ValueConverter
class NullOutputStream extends OutputStream {
override def close(): Unit = {
@@ -114,14 +119,14 @@ class NullOutputStream extends OutputStream {
}
}
-class CommandLineXMLLoaderErrorHandler() extends org.xml.sax.ErrorHandler with Logging {
+class CommandLineSAXErrorHandler() extends org.xml.sax.ErrorHandler with Logging {
def warning(exception: SAXParseException) = {
- log(LogLevel.Warning, "loading schema: " + exception.getMessage())
+ log(LogLevel.Warning, exception.getMessage())
}
def error(exception: SAXParseException) = {
- log(LogLevel.Error, "loading schema: " + exception.getMessage())
+ log(LogLevel.Error, exception.getMessage())
System.exit(1)
}
@@ -337,7 +342,7 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used when parsing. An optional namespace may be provided.")
val tunables = props[String]('T', keyName = "tunable", valueName = "value", descr = "daffodil tunable to be used when parsing.")
val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
- val infosetType = opt[String](short = 'I', argName = "infoset_type", descr = "infoset type to output. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom', or 'null'.", default = Some("xml")).map { _.toLowerCase }
+ val infosetType = opt[String](short = 'I', argName = "infoset_type", descr = "infoset type to output. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom', 'sax', or 'null'.", default = Some("xml")).map { _.toLowerCase }
val stream = toggle(noshort = true, default = Some(false), descrYes = "when left over data exists, parse again with remaining data, separating infosets by a NUL character", descrNo = "stop after the first parse, even if left over data exists")
val infile = trailArg[String](required = false, descr = "input file to parse. If not specified, or a value of -, reads from stdin.")
@@ -364,6 +369,7 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
case (Some("json")) => Right(Unit)
case (Some("jdom")) => Right(Unit)
case (Some("w3cdom")) => Right(Unit)
+ case (Some("sax")) => Right(Unit)
case (Some("null")) => Right(Unit)
case (Some(t)) => Left("Unknown infoset type: " + t)
case _ => Assert.impossible() // not possible due to default value
@@ -401,7 +407,7 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used when processing. An optional namespace may be provided.")
val tunables = props[String]('T', keyName = "tunable", valueName = "value", descr = "daffodil tunable to be used when processing.")
val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
- val infosetType = opt[String](short = 'I', argName = "infoset_type", descr = "infoset type to parse/unparse. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom', or 'null'.", default = Some("xml")).map { _.toLowerCase }
+ val infosetType = opt[String](short = 'I', argName = "infoset_type", descr = "infoset type to parse/unparse. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom', 'sax', or 'null'.", default = Some("xml")).map { _.toLowerCase }
val infile = trailArg[String](required = true, descr = "input file or directory containing files to process.")
validateOpt(schema, parser, rootNS) {
@@ -417,6 +423,8 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
case (Some("json"), _) => Right(Unit)
case (Some("jdom"), _) => Right(Unit)
case (Some("w3cdom"), _) => Right(Unit)
+ case (Some("sax"), Some(true)) => Left("SAX unparse is not yet implemented")
+ case (Some("sax"), _) => Right(Unit)
case (Some("null"), Some(true)) => Left("infoset type null not valid with performance --unparse")
case (Some("null"), _) => Right(Unit)
case (Some(t), _) => Left("Unknown infoset type: " + t)
@@ -482,6 +490,7 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
case (Some("json")) => Right(Unit)
case (Some("jdom")) => Right(Unit)
case (Some("w3cdom")) => Right(Unit)
+ case (Some("sax")) => Right(Unit)
//case (Some("null")) => Right(Unit) // null is not valid for unparsing
case (Some(t)) => Left("Unknown infoset type: " + t)
case _ => Assert.impossible() // not possible due to default value
@@ -723,16 +732,23 @@ object Main extends Logging {
val blobDir = Paths.get(System.getProperty("user.dir"), "daffodil-blobs")
val blobSuffix = ".bin"
- def getInfosetOutputter(infosetType: String, os: java.io.OutputStream): InfosetOutputter = {
+ def getInfosetOutputter(infosetType: String, os: java.io.OutputStream)
+ : Either[InfosetOutputter, DaffodilOutputContentHandler] = {
val outputter = infosetType match {
- case "xml" => new XMLTextInfosetOutputter(os, true)
- case "scala-xml" => new ScalaXMLInfosetOutputter()
- case "json" => new JsonInfosetOutputter(os, true)
- case "jdom" => new JDOMInfosetOutputter()
- case "w3cdom" => new W3CDOMInfosetOutputter()
- case "null" => new NullInfosetOutputter()
+ case "xml" => Left(new XMLTextInfosetOutputter(os, pretty = true))
+ case "scala-xml" => Left(new ScalaXMLInfosetOutputter())
+ case "json" => Left(new JsonInfosetOutputter(os, pretty = true))
+ case "jdom" => Left(new JDOMInfosetOutputter())
+ case "w3cdom" => Left(new W3CDOMInfosetOutputter())
+ case "sax" => Right(new DaffodilOutputContentHandler(os, pretty = true))
+ case "null" => Left(new NullInfosetOutputter())
+ }
+ if (outputter.isLeft) {
+ outputter.left.map(_.setBlobAttributes(blobDir, null, blobSuffix))
+ } else {
+ // do nothing here, we set the blobAttributes using the Blob* properties
+ // within the sax parse calls
}
- outputter.setBlobAttributes(blobDir, null, blobSuffix)
outputter
}
@@ -840,7 +856,8 @@ object Main extends Logging {
case Some("-") | None => System.out
case Some(file) => new FileOutputStream(file)
}
- val outputter = getInfosetOutputter(parseOpts.infosetType.toOption.get, output)
+ val infosetType = parseOpts.infosetType.toOption.get
+ val eitherOutputterOrHandler = getInfosetOutputter(infosetType, output)
var lastParseBitPosition = 0L
var keepParsing = true
@@ -848,9 +865,17 @@ object Main extends Logging {
while (keepParsing) {
- outputter.reset() // reset in case we are streaming
-
- val parseResult = Timer.getResult("parsing", processor.parse(inStream, outputter))
+ val parseResult = eitherOutputterOrHandler match {
+ case Right(saxContentHandler) =>
+ // reset in case we are streaming
+ saxContentHandler.reset()
+ Timer.getResult("parsing",
+ parseWithSAX(processor, inStream, saxContentHandler,
+ new CommandLineSAXErrorHandler()))
+ case Left(outputter) =>
+ outputter.reset() // reset in case we are streaming
+ Timer.getResult("parsing", processor.parse(inStream, outputter))
+ }
val finfo = parseResult.resultState.asInstanceOf[FormatInfo]
val loc = parseResult.resultState.currentLocation.asInstanceOf[DataLoc]
displayDiagnostics(parseResult)
@@ -859,19 +884,19 @@ object Main extends Logging {
keepParsing = false
error = true
} else {
- // only XMLTextInfosetOutputter and JsonInfosetOutputter write
- // directly to the output stream. Other InfosetOutputters must manually
- // get the result and write it to the stream
- outputter match {
- case sxml: ScalaXMLInfosetOutputter => {
+ // only XMLTextInfosetOutputter, JsonInfosetOutputter and
+ // DaffodilOutputContentHandler write directly to the output stream. Other
+ // InfosetOutputters must manually get the result and write it to the stream below
+ eitherOutputterOrHandler match {
+ case Left(sxml: ScalaXMLInfosetOutputter) => {
val writer = new java.io.OutputStreamWriter(output, "UTF-8")
scala.xml.XML.write(writer, sxml.getResult, "UTF-8", true, null)
writer.flush()
}
- case jdom: JDOMInfosetOutputter => {
+ case Left(jdom: JDOMInfosetOutputter) => {
new org.jdom2.output.XMLOutputter().output(jdom.getResult, output)
}
- case w3cdom: W3CDOMInfosetOutputter => {
+ case Left(w3cdom: W3CDOMInfosetOutputter) => {
val tf = TransformerFactory.newInstance()
val transformer = tf.newTransformer()
val result = new StreamResult(output)
@@ -1037,8 +1062,15 @@ object Main extends Logging {
})
case Right(data) => Timer.getTimeResult({
val input = InputSourceDataInputStream(data)
- val outputterForParse = getInfosetOutputter(infosetType, nullOutputStreamForParse)
- processor.parse(input, outputterForParse)
+ val eitherOutputterOrHandlerForParse = getInfosetOutputter(infosetType, nullOutputStreamForParse)
+ eitherOutputterOrHandlerForParse match {
+ case Left(outputter) => processor.parse(input, outputter)
+ case Right(saxContentHandler) =>
+ val errorHandler = new CommandLineSAXErrorHandler()
+ val parseResult = parseWithSAX(processor, input, saxContentHandler,
+ errorHandler)
+ parseResult
+ }
})
}
@@ -1315,6 +1347,21 @@ object Main extends Logging {
ret
}
+ private def parseWithSAX(
+ processor: DFDL.DataProcessor,
+ data: InputSourceDataInputStream,
+ saxContentHandler: DaffodilOutputContentHandler,
+ errorHandler: CommandLineSAXErrorHandler): ParseResult = {
+ val saxXmlRdr = processor.newXMLReaderInstance
+ saxXmlRdr.setContentHandler(saxContentHandler)
+ saxXmlRdr.setErrorHandler(errorHandler)
+ saxXmlRdr.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY, blobDir)
+ saxXmlRdr.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX, blobSuffix)
+ saxXmlRdr.parse(data)
+ val pr = saxXmlRdr.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT).asInstanceOf[ParseResult]
+ pr
+ }
+
def bugFound(e: Exception): Int = {
System.err.println("""|
|!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
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 a2dc4d9..0e10d9f 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
@@ -191,9 +191,6 @@ trait ElementBase
*/
def isArrayWithAtLeastOneRequiredArrayElement: Boolean
- final protected lazy val thisElementsNamespace: NS = this.namedQName.namespace
- final protected lazy val thisElementsNamespacePrefix: String = this.namespaces.getPrefix(thisElementsNamespace.toString)
-
private def nsBindingsToSet(nsb: NamespaceBinding): Set[(String, NS)] = {
if (nsb == scala.xml.TopScope) Set()
else {
@@ -207,7 +204,7 @@ trait ElementBase
val childrenRequiredNSBindings =
this.elementChildren.flatMap { _.thisElementsRequiredNamespaceBindings }.toSet
- val myRequiredNSBinding = Set((thisElementsNamespacePrefix, thisElementsNamespace))
+ val myRequiredNSBinding = Set((namedQName.prefixOrNull, namedQName.namespace))
val nilNSBinding = {
if (!isNillable) Set()
else {
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/ElementBaseRuntime1Mixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/ElementBaseRuntime1Mixin.scala
index d96379b..dc726d6 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/ElementBaseRuntime1Mixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/ElementBaseRuntime1Mixin.scala
@@ -173,7 +173,6 @@ trait ElementBaseRuntime1Mixin { self: ElementBase =>
defaultBitOrder,
optPrimType,
targetNamespace,
- thisElementsNamespace,
optSimpleTypeRuntimeData,
optComplexTypeModelGroupRuntimeData,
minOccurs,
@@ -181,7 +180,6 @@ trait ElementBaseRuntime1Mixin { self: ElementBase =>
Maybe.toMaybe(optionOccursCountKind),
name,
targetNamespacePrefix,
- thisElementsNamespacePrefix,
isNillable,
isArray, // can have more than 1 occurrence
isOptional, // can have exactly 0 or 1 occurrence
diff --git a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestSimpleTypeUnions.scala b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestSimpleTypeUnions.scala
index 98ae9bf..35c8c14 100644
--- a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestSimpleTypeUnions.scala
+++ b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestSimpleTypeUnions.scala
@@ -80,9 +80,9 @@ class TestSimpleTypeUnions {
val Seq(st1, st2) = u.unionMemberTypes
st1.asInstanceOf[GlobalSimpleTypeDef].globalQName.toQNameString
val st1n = st1.diagnosticDebugName
- assertEquals("ex:int1Type", st1n)
+ assertEquals("int1Type", st1n)
val st2n = st2.diagnosticDebugName
- assertEquals("ex:int2Type", st2n)
+ assertEquals("int2Type", st2n)
val st1rd = st1.simpleTypeRuntimeData
val st2rd = st2.simpleTypeRuntimeData
val st1mini = st1rd.minInclusive
@@ -91,15 +91,15 @@ class TestSimpleTypeUnions {
assertTrue(st2rd.unionMemberTypes.isEmpty)
val st2mini = st2rd.minInclusive
assertEquals(2, st2mini.get.intValue())
- assertEquals("ex:int1Type", st1rd.diagnosticDebugName)
- assertEquals("ex:int2Type", st2rd.diagnosticDebugName)
+ assertEquals("int1Type", st1rd.diagnosticDebugName)
+ assertEquals("int2Type", st2rd.diagnosticDebugName)
}
@Test def testUnionFirstUnionMemberOk: Unit = {
val (result, actual) = TestUtils.testString(testSchema1, "1")
val i = result.resultState.asInstanceOf[PState].infoset.asInstanceOf[DIDocument].contents(0).asInstanceOf[DISimple]
val umstrd = i.unionMemberRuntimeData.get
- assertEquals("ex:int1Type", umstrd.diagnosticDebugName)
+ assertEquals("int1Type", umstrd.diagnosticDebugName)
assertTrue(i.valid.get)
val expected = <e1>1</e1>
TestUtils.assertEqualsXMLElements(expected, actual)
@@ -109,7 +109,7 @@ class TestSimpleTypeUnions {
val (result, actual) = TestUtils.testString(testSchema1, "2")
val i = result.resultState.asInstanceOf[PState].infoset.asInstanceOf[DIDocument].contents(0).asInstanceOf[DISimple]
val umstrd = i.unionMemberRuntimeData.get
- assertEquals("ex:int2Type", umstrd.diagnosticDebugName)
+ assertEquals("int2Type", umstrd.diagnosticDebugName)
assertTrue(i.valid.get)
val expected = <e1>2</e1>
TestUtils.assertEqualsXMLElements(expected, actual)
@@ -128,13 +128,13 @@ class TestSimpleTypeUnions {
assertTrue(d.isInstanceOf[ValidationError])
val msg = d.getMessage()
def die = { println(msg); fail() }
- if (!msg.contains("ex:e1"))
+ if (!msg.contains("e1"))
die
if (!msg.contains("union members"))
die
- if (!msg.contains("ex:int1Type"))
+ if (!msg.contains("int1Type"))
die
- if (!msg.contains("ex:int2Type"))
+ if (!msg.contains("int2Type"))
die
if (!msg.contains("failed facet checks"))
die
@@ -216,13 +216,13 @@ class TestSimpleTypeUnions {
assertTrue(d.isInstanceOf[ValidationError])
val msg = d.getMessage()
def die = { println(msg); fail() }
- if (!msg.contains("ex:e1"))
+ if (!msg.contains("e1"))
die
if (!msg.contains("union members"))
die
- if (!msg.contains("ex:int12or47Type"))
+ if (!msg.contains("int12or47Type"))
die
- if (!msg.contains("ex:negIntType"))
+ if (!msg.contains("negIntType"))
die
if (!msg.contains("failed facet checks"))
die
@@ -232,7 +232,7 @@ class TestSimpleTypeUnions {
val (result, actual) = TestUtils.testString(testSchema2, "1")
val i = result.resultState.asInstanceOf[PState].infoset.asInstanceOf[DIDocument].contents(0).asInstanceOf[DISimple]
val umstrd = i.unionMemberRuntimeData.get
- assertEquals("ex:int12Type", umstrd.diagnosticDebugName)
+ assertEquals("int12Type", umstrd.diagnosticDebugName)
assertTrue(i.valid.get)
val expected = <e1>1</e1>
TestUtils.assertEqualsXMLElements(expected, actual)
@@ -242,7 +242,7 @@ class TestSimpleTypeUnions {
val (result, actual) = TestUtils.testString(testSchema2, "2")
val i = result.resultState.asInstanceOf[PState].infoset.asInstanceOf[DIDocument].contents(0).asInstanceOf[DISimple]
val umstrd = i.unionMemberRuntimeData.get
- assertEquals("ex:int12Type", umstrd.diagnosticDebugName)
+ assertEquals("int12Type", umstrd.diagnosticDebugName)
assertTrue(i.valid.get)
val expected = <e1>2</e1>
TestUtils.assertEqualsXMLElements(expected, actual)
@@ -252,7 +252,7 @@ class TestSimpleTypeUnions {
val (result, actual) = TestUtils.testString(testSchema2, "-1")
val i = result.resultState.asInstanceOf[PState].infoset.asInstanceOf[DIDocument].contents(0).asInstanceOf[DISimple]
val umstrd = i.unionMemberRuntimeData.get
- assertEquals("ex:negIntType", umstrd.diagnosticDebugName) // anonymous simple type gets this name from base.
+ assertEquals("negIntType", umstrd.diagnosticDebugName) // anonymous simple type gets this name from base.
assertTrue(i.valid.get)
val expected = <e1>-1</e1>
TestUtils.assertEqualsXMLElements(expected, actual)
@@ -309,7 +309,7 @@ class TestSimpleTypeUnions {
val (result, actual) = TestUtils.testString(testSchema3, "foo3bar")
val i = result.resultState.asInstanceOf[PState].infoset.asInstanceOf[DIDocument].contents(0).asInstanceOf[DISimple]
val umstrd = i.unionMemberRuntimeData.get
- assertEquals("ex:foo3or4bar", umstrd.diagnosticDebugName)
+ assertEquals("foo3or4bar", umstrd.diagnosticDebugName)
assertTrue(i.valid.get)
val expected = <e1>foo3bar</e1>
TestUtils.assertEqualsXMLElements(expected, actual)
@@ -319,7 +319,7 @@ class TestSimpleTypeUnions {
val (result, actual) = TestUtils.testString(testSchema3, "foo1bar")
val i = result.resultState.asInstanceOf[PState].infoset.asInstanceOf[DIDocument].contents(0).asInstanceOf[DISimple]
val umstrd = i.unionMemberRuntimeData.get
- assertEquals("ex:foo1or2bar", umstrd.diagnosticDebugName)
+ assertEquals("foo1or2bar", umstrd.diagnosticDebugName)
assertTrue(i.valid.get)
val expected = <e1>foo1bar</e1>
TestUtils.assertEqualsXMLElements(expected, actual)
@@ -329,7 +329,7 @@ class TestSimpleTypeUnions {
val (result, actual) = TestUtils.testString(testSchema3, "foo2bar")
val i = result.resultState.asInstanceOf[PState].infoset.asInstanceOf[DIDocument].contents(0).asInstanceOf[DISimple]
val umstrd = i.unionMemberRuntimeData.get
- assertEquals("ex:foo1or2bar", umstrd.diagnosticDebugName)
+ assertEquals("foo1or2bar", umstrd.diagnosticDebugName)
assertTrue(i.valid.get)
val expected = <e1>foo2bar</e1>
TestUtils.assertEqualsXMLElements(expected, actual)
@@ -348,13 +348,13 @@ class TestSimpleTypeUnions {
assertTrue(d.isInstanceOf[ValidationError])
val msg = d.getMessage()
def die = { println(msg); fail() }
- if (!msg.contains("ex:e1"))
+ if (!msg.contains("e1"))
die
if (!msg.contains("union members"))
die
- if (!msg.contains("ex:foo1or2bar"))
+ if (!msg.contains("foo1or2bar"))
die
- if (!msg.contains("ex:foo3or4bar"))
+ if (!msg.contains("foo3or4bar"))
die
if (!msg.contains("failed facet checks"))
die
@@ -377,7 +377,7 @@ class TestSimpleTypeUnions {
assertTrue(d.isInstanceOf[ValidationError])
val msg = d.getMessage()
def die = { println(msg); fail() }
- if (!msg.contains("ex:e1"))
+ if (!msg.contains("e1"))
die
if (!msg.contains("facet pattern"))
die
diff --git a/daffodil-core/src/test/scala/org/apache/daffodil/util/TestUtils.scala b/daffodil-core/src/test/scala/org/apache/daffodil/util/TestUtils.scala
index c1d23c2..df9ade1 100644
--- a/daffodil-core/src/test/scala/org/apache/daffodil/util/TestUtils.scala
+++ b/daffodil-core/src/test/scala/org/apache/daffodil/util/TestUtils.scala
@@ -348,6 +348,7 @@ class Fakes private () {
override def withTunables(tunables: Map[String,String]): DFDL.DataProcessor = this
override def withValidationMode(mode: ValidationMode.Type): DFDL.DataProcessor = this
+ override def newXMLReaderInstance: DFDL.DaffodilXMLReader = null
}
lazy val fakeDP = new FakeDataProcessor
diff --git a/daffodil-japi/src/main/java/org/apache/daffodil/japi/package-info.java b/daffodil-japi/src/main/java/org/apache/daffodil/japi/package-info.java
index dea0152..efc540a 100644
--- a/daffodil-japi/src/main/java/org/apache/daffodil/japi/package-info.java
+++ b/daffodil-japi/src/main/java/org/apache/daffodil/japi/package-info.java
@@ -23,7 +23,7 @@
* <h3>Overview</h3>
*
* The {@link org.apache.daffodil.japi.Daffodil} object is a factory object to create a {@link org.apache.daffodil.japi.Compiler}. The
- * {@link org.apache.daffodil.japi.Compiler} provides a method to compils a provided DFDL schema into a
+ * {@link org.apache.daffodil.japi.Compiler} provides a method to compile a provided DFDL schema into a
* {@link org.apache.daffodil.japi.ProcessorFactory}, which creates a {@link org.apache.daffodil.japi.DataProcessor}:
*
* <pre>
@@ -36,10 +36,69 @@
* The {@link org.apache.daffodil.japi.DataProcessor} provides the necessary functions to parse and unparse
* data, returning a {@link org.apache.daffodil.japi.ParseResult} or {@link org.apache.daffodil.japi.UnparseResult}, respectively. These
* contain information about the parse/unparse, such as whether or not the
- * processing succeeded any diagnostic information.
+ * processing succeeded with any diagnostic information.
+ *
+ * The {@link org.apache.daffodil.japi.DataProcessor} also provides a function to create a
+ * {@link org.apache.daffodil.japi.DaffodilXMLReader} that can be used to perform parsing via the
+ * SAX API.
+ *
+ * <pre>
+ * {@code
+ * DaffodilXMLReader xmlRdr = dp.newXMLReaderInstance();
+ * }</pre>
+ *
+ * The {@link org.apache.daffodil.japi.DaffodilXMLReader} has several methods that allow one to
+ * set properties and handlers (such as ContentHandlers or ErrorHandlers) for the reader. One can
+ * use any contentHandler/errorHandler as long as they extend the
+ * {@link org.xml.sax.ContentHandler} and {@link org.xml.sax.ErrorHandler} interfaces
+ * respectively. One can also set properties for the {@link org.apache.daffodil.japi.DaffodilXMLReader}
+ * using {@link org.apache.daffodil.japi.DaffodilXMLReader#setProperty(java.lang.String, java.lang.Object)}.
+ *
+ * The following properties can be set as follows:
+ * <pre>
+ * {@code
+ * xmlRdr.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY(), "/tmp/");
+ * xmlRdr.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX(), "daffodil-sax-");
+ * xmlRdr.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX(), ".bin");
+ * }
+ * </pre>
+ *
+ * The variables above start with "urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:sax:" and end
+ * with BlobDirectory, BlobPrefix and BlobSuffix respectively.
+ *
+ * The properites can be retrieved using the same variables with
+ * {@link org.apache.daffodil.japi.DaffodilXMLReader#getProperty(java.lang.String)}
+ *
+ * The following handlers can be set as follows:
+ * <pre>
+ * {@code
+ * xmlRdr.setContentHandler(contentHandler);
+ * xmlRdr.setErrorHandler(errorHandler);
+ * xmlRdr.setDTDHandler(dtdHandler);
+ * xmlRdr.setEntityResolver(entityResolver);
+ * }
+ * </pre>
+ *
+ * The handlers above must implement the following interfaces respectively:
+ * <pre>
+ * {@code
+ * org.xml.sax.ContentHandler
+ * org.xml.sax.ErrorHandler
+ * org.xml.sax.DTDHandler
+ * org.xml.sax.EntityResolver
+ * }
+ * </pre>
+ *
+ * The {@link org.apache.daffodil.japi.ParseResult} can be found as a property within the
+ * {@link org.apache.daffodil.japi.DaffodilXMLReader} using
+ * {@link org.apache.daffodil.japi.DaffodilXMLReader#getProperty(java.lang.String)} together
+ * with the following uri: "urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:sax:ParseResult" or
+ * XMLUtils.DAFFODIL_SAX_URN_PARSERESULT().
*
* <h4>Parse</h4>
*
+ * <h5>Dataprocessor Parse</h5>
+ *
* The {@link org.apache.daffodil.japi.DataProcessor#parse(org.apache.daffodil.japi.io.InputSourceDataInputStream, org.apache.daffodil.japi.infoset.InfosetOutputter)} method accepts input data to parse in the form
* of a {@link org.apache.daffodil.japi.io.InputSourceDataInputStream} and an {@link org.apache.daffodil.japi.infoset.InfosetOutputter}
* to determine the output representation of the infoset (e.g. Scala XML Nodes,
@@ -85,6 +144,66 @@
* }
* }</pre>
*
+ * <h5>SAX Parse</h5>
+ * The {@link org.apache.daffodil.japi.DaffodilXMLReader#parse(
+ * org.apache.daffodil.japi.io.InputSourceDataInputStream)} method accepts input data to parse in
+ * the form of a {@link org.apache.daffodil.japi.io.InputSourceDataInputStream}. The output
+ * representation of the infoset, as well as how parse errors are handled, are dependent on the
+ * content handler and the error handler provided to the {@link org.apache.daffodil.japi.DaffodilXMLReader}. For example the
+ * {@link org.jdom2.input.sax.SAXHandler} provides a JDOM representation, whereas other Content
+ * Handlers may output directly to an {@link java.io.OutputStream} or {@link java.io.Writer}.
+ *
+ * <pre>
+ * {@code
+ * SAXHandler contentHandler = new SAXHandler();
+ * xmlRdr.setContentHandler(contentHandler);
+ * InputSourceDataInputStream is = new InputSourceDataInputStream(data);
+ * xmlReader.parse(is);
+ * ParseResult pr = (ParseResult) xmlRdr.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT());
+ * Document doc = saxHandler.getDocument();
+ * }</pre>
+ *
+ * The The {@link org.apache.daffodil.japi.DaffodilXMLReader#parse(
+ * org.apache.daffodil.japi.io.InputSourceDataInputStream)} method is not thread-safe and may
+ * only be called again/reused once a parse operation is completed. This can be done multiple
+ * times without the need to create new DaffodilXMLReaders, ContentHandlers or ErrorHandlers. It
+ * might be necessary to reset whatever ContentHandler is used (or allocate a new one). A
+ * thread-safe implementation would require unique instances of the DaffodilXMLReader and its
+ * components. For example:
+ *
+ * <pre>
+ * {@code
+ * SAXHandler contentHandler = new SAXHandler();
+ * xmlRdr.setContentHandler(contentHandler);
+ * for (File f : inputFiles) {
+ * contentHandler.reset();
+ * InputSourceDataInputStream is = new InputSourceDataInputStream(new FileInputStream(f));
+ * xmlReader.parse(is);
+ * ParseResult pr = (ParseResult) xmlRdr.getProperty("urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:sax:ParseResult");
+ * Document doc = saxHandler.getDocument();
+ * }
+ * }
+ * </pre>
+ *
+ * One can repeat calls to parse() using the same InputSourceDataInputStream to continue parsing
+ * where the previous parse ended. For example:
+ *
+ * <pre>
+ * {@code
+ * InputSourceDataInputStream is = new InputSourceDataInputStream(dataStream);
+ * SAXHandler contentHandler = new SAXHandler();
+ * xmlRdr.setContentHandler(contentHandler);
+ * Boolean keepParsing = true;
+ * while (keepParsing) {
+ * contentHandler.reset();
+ * xmlRdr.parse(is);
+ * val pr = xmlRdr.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT());
+ * ...
+ * keepParsing = !pr.location().isAtEnd() && !pr.isError();
+ * }
+ * }
+ * </pre>
+ *
* <h4>Unparse</h4>
*
* The same {@link org.apache.daffodil.japi.DataProcessor} used for parse can be used to unparse an infoset
@@ -141,6 +260,22 @@
* ParseResult pr = dp.parse(data);
* }</pre>
*
+ * And use like below:
+ * <pre>
+ * {@code
+ * ParseResult pr = dp.parse(data);
+ * }</pre>
+ *
+ * or
+ *
+ * <pre>
+ * {@code
+ * DaffodilXMLReader xmlRdr = dp.newXMLReaderInstance();
+ * ... // setting appropriate handlers
+ * xmlReader.parse(data);
+ * ParseResult pr = xmlRdr.getProperty("...ParseResult");
+ * }</pre>
+ *
*/
package org.apache.daffodil.japi;
diff --git a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala
index 3feca34..68a1815 100644
--- a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala
+++ b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala
@@ -38,6 +38,7 @@ import org.apache.daffodil.api.{ LocationInSchemaFile => SLocationInSchemaFile }
import org.apache.daffodil.api.{ WithDiagnostics => SWithDiagnostics }
import org.apache.daffodil.compiler.{ ProcessorFactory => SProcessorFactory }
import org.apache.daffodil.processors.{ DataProcessor => SDataProcessor }
+import org.apache.daffodil.processors.{ DaffodilXMLReader => SDaffodilXMLReader }
import org.apache.daffodil.processors.{ ParseResult => SParseResult }
import org.apache.daffodil.processors.{ UnparseResult => SUnparseResult }
import org.apache.daffodil.util.{ ConsoleWriter => SConsoleWriter }
@@ -657,6 +658,12 @@ class DataProcessor private[japi] (private var dp: SDataProcessor)
def save(output: WritableByteChannel): Unit = dp.save(output)
/**
+ * Obtain a new [[DaffodilXMLReader]] from the current [[DataProcessor]].
+ */
+ def newXMLReaderInstance: DaffodilXMLReader =
+ new DaffodilXMLReader(xmlrdr = dp.newXMLReaderInstance.asInstanceOf[SDaffodilXMLReader])
+
+ /**
* Parse input data with a specified length
*
* @param input data to be parsed
@@ -827,3 +834,122 @@ class InvalidParserException private[japi] (cause: org.apache.daffodil.compiler.
* This exception will be thrown as a result of an invalid usage of the Daffodil API
*/
class InvalidUsageException private[japi] (cause: org.apache.daffodil.processors.InvalidUsageException) extends Exception(cause.getMessage(), cause.getCause())
+
+/**
+ * SAX method of parsing schema and getting the DFDL Infoset via some
+ * org.xml.sax.ContentHandler, based on the org.xml.sax.XMLReader interface
+ */
+class DaffodilXMLReader private[japi] (xmlrdr: SDaffodilXMLReader) extends org.xml.sax.XMLReader {
+ /**
+ * Get the value of the feature flag
+ * @param name feature flag whose value is to be retrieved
+ * @return value of the feature flag
+ */
+ override def getFeature(name: String): Boolean = xmlrdr.getFeature(name)
+
+ /**
+ * Set the value of the feature flag
+ * @param name feature flag to be set
+ * @param value value to be assigned to feature flag
+ */
+ override def setFeature(name: String, value: Boolean): Unit = xmlrdr.setFeature(name, value)
+
+ /**
+ * Get the value of the property
+ * @param name property whose value is to be retrieved
+ * @return value of the property
+ */
+ override def getProperty(name: String): AnyRef = xmlrdr.getProperty(name)
+
+ /**
+ * Set the value of the property
+ * @param name property whose value is to be set
+ * @param value value to be assigned to the property
+ */
+ override def setProperty(name: String, value: AnyRef): Unit = xmlrdr.setProperty(name, value)
+
+ /**
+ * Register an entity resolver
+ * @param resolver entity resolver to be registered
+ */
+ override def setEntityResolver(resolver: org.xml.sax.EntityResolver): Unit =
+ xmlrdr.setEntityResolver(resolver)
+
+ /**
+ * Return the registered entity resolver
+ * @return registered entity resolver or null
+ */
+ override def getEntityResolver: org.xml.sax.EntityResolver = xmlrdr.getEntityResolver
+
+ /**
+ * Register a DTD Handler
+ * @param handler DTD Handler to be registered
+ */
+ override def setDTDHandler(handler: org.xml.sax.DTDHandler): Unit = xmlrdr.setDTDHandler(handler)
+
+ /**
+ * Retrieve registered DTD Handler
+ * @return registered DTD Handler or null
+ */
+ override def getDTDHandler: org.xml.sax.DTDHandler = xmlrdr.getDTDHandler
+
+ /**
+ * Register a content handler
+ * @param handler content handler to be registered
+ */
+ override def setContentHandler(handler: org.xml.sax.ContentHandler): Unit =
+ xmlrdr.setContentHandler(handler)
+
+ /**
+ * Retrieve registered content handler
+ * @return registered content handler or null
+ */
+ override def getContentHandler: org.xml.sax.ContentHandler = xmlrdr.getContentHandler
+
+ /**
+ * Register an error handler
+ * @param handler error handler to be registered
+ */
+ override def setErrorHandler(handler: org.xml.sax.ErrorHandler): Unit =
+ xmlrdr.setErrorHandler(handler)
+
+ /**
+ * Retrieve registered error handler
+ * @return registered error handler or null
+ */
+ override def getErrorHandler: org.xml.sax.ErrorHandler = xmlrdr.getErrorHandler
+
+ /**
+ * Parse input data from an InputSource. Infoset can be retrieved via the registered
+ * contentHandler and diagnostics via the registered errorHandler
+ * @param input data to be parsed
+ */
+ override def parse(input: org.xml.sax.InputSource): Unit = xmlrdr.parse(input)
+
+ /**
+ * Parse data from a system identifier/URI. This method is not supported by the API.
+ * @param systemId URI for data to be parsed
+ */
+ override def parse(systemId: String): Unit = xmlrdr.parse(systemId)
+
+ /**
+ * Parse input data from an InputSourceDataInputStream. Infoset can retrieved via the registered
+ * contentHandler and diagnostics via the registered errorHandler
+ * @param isdis data to be parsed
+ */
+ def parse(isdis: InputSourceDataInputStream): Unit = xmlrdr.parse(isdis.dis)
+
+ /**
+ * Parse input data from an InputStream. Infoset can retrieved via the registered contentHandler
+ * and diagnostics via the registered errorHandler
+ * @param stream data to be parsed
+ */
+ def parse(stream: java.io.InputStream): Unit = xmlrdr.parse(stream)
+
+ /**
+ * Parse input data from an array of bytes. Infoset can retrieved via the registered
+ * contentHandler and diagnostics via the registered errorHandler
+ * @param arr data to be parsed
+ */
+ def parse(arr: Array[Byte]): Unit = xmlrdr.parse(arr)
+}
diff --git a/daffodil-japi/src/test/java/org/apache/daffodil/example/TestJavaAPI.java b/daffodil-japi/src/test/java/org/apache/daffodil/example/TestJavaAPI.java
index 284c1e9..c0116b0 100644
--- a/daffodil-japi/src/test/java/org/apache/daffodil/example/TestJavaAPI.java
+++ b/daffodil-japi/src/test/java/org/apache/daffodil/example/TestJavaAPI.java
@@ -24,17 +24,19 @@ import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
import java.io.File;
import java.io.IOException;
-import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
+import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.List;
+import org.apache.daffodil.infoset.DaffodilOutputContentHandler;
import org.apache.daffodil.japi.*;
+import org.apache.daffodil.japi.infoset.XMLTextInfosetOutputter;
+import org.apache.daffodil.xml.XMLUtils;
import org.jdom2.output.Format;
import org.junit.Test;
@@ -955,4 +957,88 @@ public class TestJavaAPI {
}
}
+ @Test
+ public void testJavaAPI20() throws IOException, ClassNotFoundException {
+ // Test SAX parsing
+ org.apache.daffodil.japi.Compiler c = Daffodil.compiler();
+ java.io.File schemaFile = getResource("/test/japi/mySchema1.dfdl.xsd");
+ ProcessorFactory pf = c.compileFile(schemaFile);
+ DataProcessor dp = pf.onPath("/");
+ dp = reserializeDataProcessor(dp);
+ DaffodilXMLReader xri = dp.newXMLReaderInstance();
+
+ java.io.File file = getResource("/test/japi/myData.dat");
+ java.io.FileInputStream fisDP = new java.io.FileInputStream(file);
+ java.io.FileInputStream fisSAX = new java.io.FileInputStream(file);
+ InputSourceDataInputStream disDP = new InputSourceDataInputStream(fisDP);
+ InputSourceDataInputStream disSAX = new InputSourceDataInputStream(fisSAX);
+ ByteArrayOutputStream xmlBos = new ByteArrayOutputStream();
+ XMLTextInfosetOutputter outputter = new XMLTextInfosetOutputter(xmlBos, true);
+ ParseResult res = dp.parse(disDP, outputter);
+ String infosetDPString = xmlBos.toString();
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DaffodilOutputContentHandler contentHandler = new DaffodilOutputContentHandler(bos, true);
+ SAXErrorHandlerForJAPITest errorHandler = new SAXErrorHandlerForJAPITest();
+ xri.setContentHandler(contentHandler);
+ xri.setErrorHandler(errorHandler);
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY(),
+ Paths.get(System.getProperty("java.io.tmpdir")));
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX(),
+ "daffodil-sapi-");
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX(),
+ ".sax.blob");
+ xri.parse(disSAX);
+ org.apache.daffodil.processors.ParseResult resSAX =
+ (org.apache.daffodil.processors.ParseResult) xri.getProperty(
+ XMLUtils.DAFFODIL_SAX_URN_PARSERESULT());
+ boolean err = errorHandler.isError();
+ ArrayList<Diagnostic> diags = errorHandler.getDiagnostics();
+ String infosetSAXString = bos.toString();
+
+ assertFalse(err);
+ assertTrue(resSAX.resultState().currentLocation().isAtEnd());
+ assertTrue(diags.isEmpty());
+ assertEquals(infosetDPString, infosetSAXString);
+ }
+
+
+ @Test
+ public void testJavaAPI21() throws IOException, ClassNotFoundException {
+ // Test SAX parsing with errors
+ org.apache.daffodil.japi.Compiler c = Daffodil.compiler();
+ java.io.File schemaFile = getResource("/test/japi/mySchema1.dfdl.xsd");
+ ProcessorFactory pf = c.compileFile(schemaFile);
+ DataProcessor dp = pf.onPath("/");
+ dp = reserializeDataProcessor(dp);
+ DaffodilXMLReader xri = dp.newXMLReaderInstance();
+
+ java.io.File file = getResource("/test/japi/myDataBroken.dat");
+ java.io.FileInputStream fis = new java.io.FileInputStream(file);
+ InputSourceDataInputStream dis = new InputSourceDataInputStream(fis);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DaffodilOutputContentHandler contentHandler = new DaffodilOutputContentHandler(bos, false);
+ SAXErrorHandlerForJAPITest errorHandler = new SAXErrorHandlerForJAPITest();
+ xri.setContentHandler(contentHandler);
+ xri.setErrorHandler(errorHandler);
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY(),
+ Paths.get(System.getProperty("java.io.tmpdir")));
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX(), "daffodil-sapi-");
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX(), ".sax.blob");
+ xri.parse(dis);
+ boolean err = errorHandler.isError();
+ ArrayList<Diagnostic> diags = errorHandler.getDiagnostics();
+
+ assertTrue(err);
+ assertEquals(1, diags.size());
+ Diagnostic d = diags.get(0);
+ assertTrue(d.getMessage().contains("int"));
+ assertTrue(d.getMessage().contains("Not an int"));
+ assertTrue(d.getDataLocations().toString().contains("10"));
+ java.util.List<LocationInSchemaFile> locs = d.getLocationsInSchemaFiles();
+ assertEquals(1, locs.size());
+ LocationInSchemaFile loc = locs.get(0);
+ assertTrue(loc.toString().contains("mySchema1.dfdl.xsd"));
+ }
}
diff --git a/daffodil-japi/src/test/java/org/apache/daffodil/japi/SAXErrorHandlerForJAPITest.java b/daffodil-japi/src/test/java/org/apache/daffodil/japi/SAXErrorHandlerForJAPITest.java
new file mode 100644
index 0000000..f646fdb
--- /dev/null
+++ b/daffodil-japi/src/test/java/org/apache/daffodil/japi/SAXErrorHandlerForJAPITest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.japi;
+
+import org.apache.daffodil.api.Diagnostic;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import scala.collection.Seq;
+
+import java.util.ArrayList;
+
+public class SAXErrorHandlerForJAPITest implements ErrorHandler {
+ private ArrayList<org.apache.daffodil.japi.Diagnostic> _diagnostics = new ArrayList<>();
+ private Boolean _isError = false;
+
+ @Override
+ public void warning(SAXParseException exception) throws SAXException {
+ _isError = false;
+ org.apache.daffodil.japi.Diagnostic embeddedDiagnostic =
+ new org.apache.daffodil.japi.Diagnostic((Diagnostic) exception.getCause());
+ _diagnostics.add(embeddedDiagnostic);
+ }
+
+ @Override
+ public void error(SAXParseException exception) throws SAXException {
+ _isError = true;
+ org.apache.daffodil.japi.Diagnostic embeddedDiagnostic =
+ new org.apache.daffodil.japi.Diagnostic((Diagnostic) exception.getCause());
+ _diagnostics.add(embeddedDiagnostic);
+ }
+
+ @Override
+ public void fatalError(SAXParseException exception) throws SAXException {
+ error(exception);
+ }
+
+ public ArrayList<org.apache.daffodil.japi.Diagnostic> getDiagnostics() {
+ return _diagnostics;
+ }
+
+ public boolean isError() {
+ return _isError;
+ }
+}
diff --git a/daffodil-lib/src/main/scala/org/apache/daffodil/util/Validator.scala b/daffodil-lib/src/main/scala/org/apache/daffodil/util/Validator.scala
index f0cb8b9..1e5684a 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/util/Validator.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/util/Validator.scala
@@ -17,12 +17,14 @@
package org.apache.daffodil.util
-import javax.xml.transform.stream.StreamSource
-import javax.xml.XMLConstants
-import scala.xml.parsing.NoBindingFactoryAdapter
import java.net.URI
-import org.apache.daffodil.xml.DFDLCatalogResolver
+
import scala.collection.mutable
+import scala.xml.parsing.NoBindingFactoryAdapter
+
+import javax.xml.XMLConstants
+import javax.xml.transform.stream.StreamSource
+import org.apache.daffodil.xml.DFDLCatalogResolver
import org.xml.sax.ErrorHandler
/**
@@ -45,8 +47,14 @@ object Validator extends NoBindingFactoryAdapter {
val validator = {
val optCachedValidator = cache.get(schemaFileNames)
optCachedValidator match {
- case Some(validator) => {
- validator.reset()
+ case Some(cachedValidator) => {
+ cachedValidator.reset()
+ // reset takes it back to the original state at the point we call
+ // newValidator. So we need to re-set the features, resolvers,
+ // and handlers
+ val resolver = DFDLCatalogResolver.get
+ val validator : javax.xml.validation.Validator =
+ initializeValidator(cachedValidator, errHandler, resolver)
validator
}
case None => {
@@ -65,18 +73,7 @@ object Validator extends NoBindingFactoryAdapter {
val resolver = DFDLCatalogResolver.get
factory.setResourceResolver(resolver)
val schema = factory.newSchema(schemaSources.toArray)
- val validator = schema.newValidator()
- validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
- //
- validator.setFeature("http://xml.org/sax/features/validation", true)
-
- // If you enable the feature below, it seems to do no validation at all. Just passes.
- // validator.setFeature("http://apache.org/xml/features/validation/dynamic", true)
-
- validator.setFeature("http://apache.org/xml/features/validation/schema", true)
- validator.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true)
- validator.setErrorHandler(errHandler)
- validator.setResourceResolver(resolver)
+ val validator = initializeValidator(schema.newValidator(), errHandler, resolver)
cache.put(schemaFileNames, validator)
validator
}
@@ -85,5 +82,21 @@ object Validator extends NoBindingFactoryAdapter {
val documentSource = new StreamSource(document)
validator.validate(documentSource)
}
+
+ def initializeValidator(validator: javax.xml.validation.Validator, errHandler: ErrorHandler, resolver: DFDLCatalogResolver):
+ javax.xml.validation.Validator = {
+ validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
+ //
+ validator.setFeature("http://xml.org/sax/features/validation", true)
+
+ // If you enable the feature below, it seems to do no validation at all. Just passes.
+ // validator.setFeature("http://apache.org/xml/features/validation/dynamic", true)
+
+ validator.setFeature("http://apache.org/xml/features/validation/schema", true)
+ validator.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true)
+ validator.setErrorHandler(errHandler)
+ validator.setResourceResolver(resolver)
+ validator
+ }
}
diff --git a/daffodil-lib/src/main/scala/org/apache/daffodil/xml/QNameBase.scala b/daffodil-lib/src/main/scala/org/apache/daffodil/xml/QNameBase.scala
index b7d4f09..4d35706 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/xml/QNameBase.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/xml/QNameBase.scala
@@ -183,27 +183,9 @@ object QName {
else if (targetNamespace == NoNamespace) None
else if (targetNamespace == UnspecifiedNamespace) None
else {
- val prefixes = NS.allPrefixes(targetNamespace, scope)
- prefixes.length match {
- case 0 => None
- case 1 => Option(prefixes.head)
- case _ => {
- //
- // suppose we have xmlns="..." xmlns:ex="..." xmlns:tns="..."
- //
- // We want to prefer ex as the prefix in this case.
- // So we take the shortest prefix that isn't empty string.
- //
- val notNullPrefixes = prefixes.filter(_ ne null) // remove null prefix. There must be a non-empty one.
- notNullPrefixes.foreach { p => Assert.invariant(p.length > 0) }
- val pairs = notNullPrefixes.map { _.length } zip notNullPrefixes
- val first = pairs.head // there has to be 1 at least. But there might be only 1
- val minLengthPair = pairs.foldLeft(first) { case (x @ (xlen, _), y @ (ylen, _)) => if (xlen <= ylen) x else y }
- val (n, shortest) = minLengthPair
- Assert.invariant(n > 0)
- Some(shortest)
- }
- }
+ val prefix = scope.getPrefix(targetNamespace)
+ if (prefix eq null) None
+ else Some(prefix)
}
def createGlobal(name: String, targetNamespace: NS, scope: scala.xml.NamespaceBinding) = {
@@ -243,6 +225,7 @@ trait QNameBase extends Serializable {
* or by omitting one, are very common.
*/
def prefix: Option[String]
+ def prefixOrNull: String = prefix.orNull
def local: String
def namespace: NS // No namespace is represented by the NoNamespace object.
diff --git a/daffodil-lib/src/main/scala/org/apache/daffodil/xml/XMLUtils.scala b/daffodil-lib/src/main/scala/org/apache/daffodil/xml/XMLUtils.scala
index 698d1cb..ac51669 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/xml/XMLUtils.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/xml/XMLUtils.scala
@@ -28,13 +28,12 @@ import scala.xml.NamespaceBinding
import scala.xml._
import org.apache.commons.io.IOUtils
-
-import org.apache.daffodil.exceptions._
-import org.apache.daffodil.util.Misc
import org.apache.daffodil.calendar.DFDLDateConversion
import org.apache.daffodil.calendar.DFDLDateTimeConversion
import org.apache.daffodil.calendar.DFDLTimeConversion
+import org.apache.daffodil.exceptions._
import org.apache.daffodil.schema.annotation.props.LookupLocation
+import org.apache.daffodil.util.Misc
/**
* Utilities for handling XML
@@ -410,15 +409,22 @@ object XMLUtils {
val EXT_PREFIX_NCSA = "daf"
val EXT_NS_NCSA = NS(DAFFODIL_EXTENSION_NAMESPACE_NCSA.uri)
- private val DAFFODIL_EXTENSIONS_NAMESPACE_ROOT_APACHE = "urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018"
- private val DAFFODIL_EXTENSION_NAMESPACE_APACHE = NS(DAFFODIL_EXTENSIONS_NAMESPACE_ROOT_APACHE + ":ext")
+ private val DAFFODIL_NAMESPACE_ROOT_APACHE = "urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018"
+ private val DAFFODIL_EXTENSION_NAMESPACE_APACHE = NS(DAFFODIL_NAMESPACE_ROOT_APACHE + ":ext")
val EXT_PREFIX_APACHE = "daf"
val EXT_NS_APACHE = NS(DAFFODIL_EXTENSION_NAMESPACE_APACHE.uri)
- private val DAFFODIL_INTERNAL_NAMESPACE = NS(DAFFODIL_EXTENSIONS_NAMESPACE_ROOT_APACHE + ":int")
+ private val DAFFODIL_INTERNAL_NAMESPACE = NS(DAFFODIL_NAMESPACE_ROOT_APACHE + ":int")
val INT_PREFIX = "dafint"
val INT_NS = NS(DAFFODIL_INTERNAL_NAMESPACE.uri)
+ val DAFFODIL_SAX_URN_ROOT: String = DAFFODIL_NAMESPACE_ROOT_APACHE + ":sax"
+ val DAFFODIL_SAX_URN_PARSERESULT: String = DAFFODIL_SAX_URN_ROOT + ":ParseResult"
+ val DAFFODIL_SAX_URN_BLOBDIRECTORY: String = DAFFODIL_SAX_URN_ROOT + ":BlobDirectory"
+ val DAFFODIL_SAX_URN_BLOBPREFIX: String = DAFFODIL_SAX_URN_ROOT + ":BlobPrefix"
+ val DAFFODIL_SAX_URN_BLOBSUFFIX: String = DAFFODIL_SAX_URN_ROOT + ":BlobSuffix"
+
+
val FILE_ATTRIBUTE_NAME = "file"
val LINE_ATTRIBUTE_NAME = "line"
val COLUMN_ATTRIBUTE_NAME = "col"
@@ -798,10 +804,20 @@ object XMLUtils {
class XMLDifferenceException(message: String) extends Exception(message)
- def compareAndReport(expected: Node, actual: Node, ignoreProcInstr: Boolean = true) = {
+ def compareAndReport(
+ expected: Node,
+ actual: Node,
+ ignoreProcInstr: Boolean = true,
+ checkPrefixes: Boolean = false,
+ checkNamespaces: Boolean = false): Unit = {
val expectedMinimized = prepareForDiffComparison(expected)
val actualMinimized = prepareForDiffComparison(actual)
- val diffs = XMLUtils.computeDiff(expectedMinimized, actualMinimized, ignoreProcInstr)
+ val diffs = XMLUtils.computeDiff(
+ expectedMinimized,
+ actualMinimized,
+ ignoreProcInstr,
+ checkPrefixes,
+ checkNamespaces)
if (diffs.length > 0) {
throw new XMLDifferenceException("""
Comparison failed.
@@ -821,8 +837,21 @@ Differences were (path, expected, actual):
* computes a precise difference list which is a sequence of triples.
* Each triple is the path (an x-path-like string), followed by expected, and actual values.
*/
- def computeDiff(a: Node, b: Node, ignoreProcInstr: Boolean = true) = {
- computeDiffOne(a, b, None, Nil, ignoreProcInstr, None)
+ def computeDiff(
+ a: Node,
+ b: Node,
+ ignoreProcInstr: Boolean = true,
+ checkPrefixes: Boolean = false,
+ checkNamespaces: Boolean = false) = {
+ computeDiffOne(
+ a,
+ b,
+ None,
+ Nil,
+ ignoreProcInstr,
+ checkPrefixes,
+ checkNamespaces,
+ None)
}
def childArrayCounters(e: Elem) = {
@@ -841,21 +870,32 @@ Differences were (path, expected, actual):
maybeIndex: Option[Int],
parentPathSteps: Seq[String],
ignoreProcInstr: Boolean,
+ checkPrefixes: Boolean,
+ checkNamespaces: Boolean,
maybeType: Option[String]): Seq[(String, String, String)] = {
lazy val zPath = parentPathSteps.reverse.mkString("/")
(an, bn) match {
case (a: Elem, b: Elem) => {
- val Elem(_, labelA, attribsA, _, childrenA @ _*) = a
- val Elem(_, labelB, attribsB, _, childrenB @ _*) = b
+ val Elem(prefixA, labelA, attribsA, nsbA, childrenA @ _*) = a
+ val Elem(prefixB, labelB, attribsB, nsbB, childrenB @ _*) = b
val typeA: Option[String] = a.attribute(XSI_NAMESPACE.toString, "type").map(_.head.text)
val typeB: Option[String] = b.attribute(XSI_NAMESPACE.toString, "type").map(_.head.text)
val maybeType: Option[String] = Option(typeA.getOrElse(typeB.getOrElse(null)))
val nilledA = a.attribute(XSI_NAMESPACE.toString, "nil")
val nilledB = b.attribute(XSI_NAMESPACE.toString, "nil")
+ // we sort here, since sameElements is not order independent
+ val nsbACompare = nsbA.toString().trim.split(" ").sorted
+ val nsbBCompare = nsbB.toString().trim.split(" ").sorted
if (labelA != labelB) {
// different label
List((zPath, labelA, labelB))
+ } else if (checkPrefixes && prefixA != prefixB) {
+ // different prefix
+ List((zPath, prefixA, prefixB))
+ } else if (checkNamespaces && !nsbACompare.sameElements(nsbBCompare)) {
+ // different namespace bindings
+ List((zPath, nsbA.toString(), nsbB.toString()))
} else if (nilledA != nilledB) {
// different xsi:nil
List((zPath + "/" + labelA + "@xsi:nil",
@@ -893,7 +933,15 @@ Differences were (path, expected, actual):
countMap(ca.label) += 1
count + 1
}
- computeDiffOne(ca, cb, maybeChildIndex, thisPathStep, ignoreProcInstr, maybeType)
+ computeDiffOne(
+ ca,
+ cb,
+ maybeChildIndex,
+ thisPathStep,
+ ignoreProcInstr,
+ checkPrefixes,
+ checkNamespaces,
+ maybeType)
}
// if childrenA and childrenB have different length, zip will drop an
@@ -1009,7 +1057,9 @@ Differences were (path, expected, actual):
dataB: String,
maybeType: Option[String]): Seq[(String, String, String)] = {
- if (maybeType.isDefined && maybeType.get == "xs:anyURI") computeBlobDiff(zPath, dataA, dataB)
+ val hasBlobType = maybeType.isDefined && maybeType.get == "xs:anyURI"
+ val dataLooksLikeBlobURI = Seq(dataA, dataB).forall(_.startsWith("file://"))
+ if (hasBlobType || dataLooksLikeBlobURI) computeBlobDiff(zPath, dataA, dataB)
else if (textIsSame(dataA, dataB, maybeType)) Nil
else {
// There must be some difference, so let's find just the first index of
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/api/DFDLParserUnparser.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/api/DFDLParserUnparser.scala
index 39f1371..d63e6a6 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/api/DFDLParserUnparser.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/api/DFDLParserUnparser.scala
@@ -20,6 +20,7 @@ package org.apache.daffodil.api
import org.apache.daffodil.processors.ProcessorResult
import org.apache.daffodil.processors.Success
import java.io.File
+
import org.apache.daffodil.processors.VariableMap
import org.apache.daffodil.externalvars.Binding
import org.apache.daffodil.infoset.InfosetInputter
@@ -179,7 +180,10 @@ object DFDL {
@deprecated("Use withTunables.", "2.6.0")
def setTunables(tunables: Map[String,String]): Unit
-
+ /**
+ * Creates a new instance of XMLReader for SAX Parsing/Unparsing
+ */
+ def newXMLReaderInstance: DaffodilXMLReader
/**
* Unparses (that is, serializes) data to the output, returns an object which contains any diagnostics.
@@ -192,6 +196,12 @@ object DFDL {
def parse(input: InputSourceDataInputStream, output: InfosetOutputter): ParseResult
}
+ trait DaffodilXMLReader extends org.xml.sax.XMLReader {
+ def parse(is: java.io.InputStream): Unit
+ def parse(in: InputSourceDataInputStream): Unit
+ def parse(ab: Array[Byte]): Unit
+ }
+
trait ParseResult extends Result with WithDiagnostics {
def resultState: State
}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/JDOMInfosetOutputter.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/JDOMInfosetOutputter.scala
index 0fdfc34..18c03de 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/JDOMInfosetOutputter.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/JDOMInfosetOutputter.scala
@@ -99,11 +99,11 @@ class JDOMInfosetOutputter extends InfosetOutputter
private def createElement(diElement: DIElement): org.jdom2.Element = {
val elem: org.jdom2.Element =
- if(diElement.erd.thisElementsNamespace.isNoNamespace)
+ if(diElement.erd.namedQName.namespace.isNoNamespace)
new org.jdom2.Element(diElement.erd.name)
else
- new org.jdom2.Element(diElement.erd.name, diElement.erd.thisElementsNamespacePrefix,
- diElement.erd.thisElementsNamespace)
+ new org.jdom2.Element(diElement.erd.name, diElement.erd.namedQName.prefixOrNull,
+ diElement.erd.namedQName.namespace)
if (isNilled(diElement)) {
elem.setAttribute("nil", "true", xsiNS)
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/JsonInfosetOutputter.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/JsonInfosetOutputter.scala
index 8e39225..c521e37 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/JsonInfosetOutputter.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/JsonInfosetOutputter.scala
@@ -100,7 +100,7 @@ class JsonInfosetOutputter private (writer: java.io.Writer, pretty: Boolean, dum
override def startSimple(simple: DISimple): Boolean = {
startNode()
startElement(simple)
- if (!isNilled(simple)) {
+ if (!isNilled(simple) && simple.hasValue) {
val text =
if (simple.erd.optPrimType.get.isInstanceOf[NodeInfo.String.Kind]) {
new String(stringEncoder.quoteAsString(simple.dataValueAsString)) // escapes according to Json spec
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
new file mode 100644
index 0000000..d786db7
--- /dev/null
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/SAXInfosetOutputter.scala
@@ -0,0 +1,303 @@
+/*
+ * 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.OutputStream
+import java.io.OutputStreamWriter
+
+import scala.xml.NamespaceBinding
+
+import org.apache.daffodil.api.DFDL
+import org.apache.daffodil.dpath.NodeInfo
+import org.apache.daffodil.util.Indentable
+import org.apache.daffodil.util.MStackOfBoolean
+import org.apache.daffodil.xml.XMLUtils
+import org.xml.sax.Attributes
+import org.xml.sax.ContentHandler
+import org.xml.sax.Locator
+import org.xml.sax.SAXException
+import org.xml.sax.helpers.AttributesImpl
+
+class SAXInfosetOutputter(xmlReader: DFDL.DaffodilXMLReader)
+ extends InfosetOutputter
+ with XMLInfosetOutputter {
+ /**
+ * Reset the internal state of this InfosetOutputter. This should be called
+ * inbetween calls to the parse method.
+ */
+ override def reset(): Unit = {
+ // this doesn't do anything as the ContentHandler API does not support
+ // resetting, but some implemented ContentHandlers, such as the JDOM SaxHandler,
+ // do support resetting so it's up to the creator of the contentHandler, to call
+ // their contentHandler's reset if applicable and if necessary
+ }
+
+ override def startDocument(): Boolean = {
+ val contentHandler = xmlReader.getContentHandler
+ try {
+ contentHandler.startDocument()
+ true
+ } catch {
+ case _: SAXException => false
+ }
+ }
+
+ override def endDocument(): Boolean = {
+ val contentHandler = xmlReader.getContentHandler
+ try {
+ contentHandler.endDocument()
+ true
+ } catch {
+ case _: SAXException => false
+ }
+ }
+
+ override def startSimple(diSimple: DISimple): Boolean = {
+ val contentHandler = xmlReader.getContentHandler
+ try {
+ doStartElement(diSimple, contentHandler)
+ if (diSimple.hasValue) {
+ val text =
+ if (diSimple.erd.optPrimType.get.isInstanceOf[NodeInfo.String.Kind]) {
+ val s = remapped(diSimple.dataValueAsString)
+ scala.xml.Utility.escape(s)
+ } else {
+ diSimple.dataValueAsString
+ }
+ val arr = text.toCharArray
+ contentHandler.characters(arr, 0, arr.length)
+ }
+ true
+ } catch {
+ case _: SAXException => false
+ }
+ }
+
+ override def endSimple(diSimple: DISimple): Boolean = {
+ val contentHandler = xmlReader.getContentHandler
+ try {
+ doEndElement(diSimple, contentHandler)
+ true
+ } catch {
+ case _: SAXException => false
+ }
+ }
+
+ override def startComplex(diComplex: DIComplex): Boolean = {
+ val contentHandler = xmlReader.getContentHandler
+ try {
+ doStartElement(diComplex, contentHandler)
+ true
+ } catch {
+ case _: SAXException => false
+ }
+ }
+
+ override def endComplex(diComplex: DIComplex): Boolean = {
+ val contentHandler = xmlReader.getContentHandler
+ try {
+ doEndElement(diComplex, contentHandler)
+ true
+ } catch {
+ case _: SAXException => false
+ }
+ }
+
+ override def startArray(diArray: DIArray): Boolean = true // not applicable
+
+ override def endArray(diArray: DIArray): Boolean = true // not applicable
+
+ private def createNilAttribute(): AttributesImpl = {
+ val attrs = new AttributesImpl()
+ attrs.addAttribute(XMLUtils.XSI_NAMESPACE, "nil", "xsi:nil", "", "true")
+ attrs
+ }
+
+ private def doStartPrefixMapping(diElem: DIElement, contentHandler: ContentHandler): Unit = {
+ val nsbStart = diElem.erd.minimizedScope
+ val nsbEnd = if (diElem.isRoot) {
+ scala.xml.TopScope
+ } else {
+ diElem.diParent.erd.minimizedScope
+ }
+ var n = nsbStart
+ while (n != nsbEnd) {
+ val prefix = if (n.prefix == null) "" else n.prefix
+ val uri = if (n.uri == null) "" else n.uri
+ contentHandler.startPrefixMapping(prefix, uri)
+ n = n.parent
+ }
+ }
+
+ private def doEndPrefixMapping(diElem: DIElement, contentHandler: ContentHandler): Unit = {
+ val nsbStart = diElem.erd.minimizedScope
+ val nsbEnd = if (diElem.isRoot) {
+ scala.xml.TopScope
+ } else {
+ diElem.diParent.erd
+ .minimizedScope
+ }
+ var n = nsbStart
+ while (n != nsbEnd) {
+ val prefix = if (n.prefix == null) "" else n.prefix
+ contentHandler.endPrefixMapping(prefix)
+ n = n.parent
+ }
+ }
+
+ private def doStartElement(diElem: DIElement, contentHandler: ContentHandler): Unit = {
+ val (ns: String, elemName: String, qName: String) = getNameSpaceElemNameAndQName(diElem)
+ doStartPrefixMapping(diElem, contentHandler)
+
+ val attrs = if (isNilled(diElem)) {
+ createNilAttribute()
+ } else {
+ new AttributesImpl()
+ }
+
+ contentHandler.startElement(ns, elemName, qName, attrs)
+ }
+
+ private def doEndElement (diElem: DIElement, contentHandler: ContentHandler): Unit = {
+ val (ns: String, elemName: String, qName: String) = getNameSpaceElemNameAndQName(diElem)
+ contentHandler.endElement(ns, elemName, qName)
+ doEndPrefixMapping(diElem, contentHandler)
+ }
+
+ private def getNameSpaceElemNameAndQName(
+ diElem: DIElement): (String, String, String) = {
+ val ns: String =
+ if (diElem.erd.namedQName.namespace.isNoNamespace) {
+ ""
+ } else {
+ diElem.erd.namedQName.namespace.toString
+ }
+ val elemName = diElem.erd.namedQName.local
+ val qName = diElem.erd.namedQName.toQNameString
+ (ns, elemName, qName)
+ }
+
+}
+
+class DaffodilOutputContentHandler(out: OutputStream, pretty: Boolean = false)
+ extends ContentHandler with Indentable with XMLInfosetOutputter {
+ private val writer = new OutputStreamWriter(out)
+ private var prefixMapping: NamespaceBinding = null
+ private val outputNewlineStack: MStackOfBoolean = {
+ val s = MStackOfBoolean()
+ s.push(false)
+ s
+ }
+
+ // if the top of the stack is true, we have guessed we should output a newline
+ private def outputNewline: Boolean = outputNewlineStack.top
+
+ def reset(): Unit = {
+ resetIndentation()
+ writer.flush()
+ prefixMapping = null
+ outputNewlineStack.clear()
+ outputNewlineStack.push(false) //to match initialization state
+ out.flush()
+ }
+
+ override def setDocumentLocator(locator: Locator): Unit = {
+ // do nothing
+ }
+
+ override def startDocument(): Unit = {
+ writer.write("""<?xml version="1.0" encoding="UTF-8"?>""")
+ }
+
+ override def endDocument(): Unit = {
+ writer.write(System.lineSeparator())
+ writer.flush()
+ }
+
+ override def startPrefixMapping(prefix: String, uri: String): Unit = {
+ val _prefix = if (prefix == "") null else prefix
+ prefixMapping = NamespaceBinding(_prefix, uri, prefixMapping)
+ }
+
+ override def endPrefixMapping(prefix: String): Unit = {
+ // do nothing
+ }
+
+ override def startElement(uri: String, localName: String, qName: String, atts: Attributes): Unit = {
+ // the pop/true removes whatever is on the stack which is our previous guess for whether we
+ // would need a newline after the previous end tag. As we are currently at the start of a new
+ // tag, we want to correct that assumption (in case it was false)
+ outputNewlineStack.pop()
+ outputNewlineStack.push(true)
+ if (pretty) {
+ writer.write(System.lineSeparator())
+ outputIndentation(writer)
+ }
+ // handle start of tag
+ writer.write("<")
+ writer.write(qName)
+ // handle attributes
+ for (i <- 0 until atts.getLength) {
+ val attsValue = atts.getValue(i)
+ val attsQName = atts.getQName(i)
+ writer.write(s""" $attsQName="$attsValue"""")
+ }
+ // handle namespaces
+ if (prefixMapping != null) {
+ val pm = prefixMapping.toString()
+ writer.write(pm)
+ prefixMapping = null
+ }
+ // handle end of tag
+ writer.write(">")
+ incrementIndentation()
+ // this push makes the assumption that we would not need to output a newline after this end
+ // tag is complete
+ outputNewlineStack.push(false)
+ }
+
+ override def endElement(uri: String, localName: String, qName: String): Unit = {
+ decrementIndentation()
+ if (outputNewline) {
+ if (pretty) {
+ writer.write(System.lineSeparator())
+ outputIndentation(writer)
+ }
+ }
+ writer.write("</")
+ writer.write(qName)
+ writer.write(">")
+ outputNewlineStack.pop()
+ }
+
+ override def characters(ch: Array[Char], start: Int, length: Int): Unit = {
+ writer.write(ch, start, length)
+ }
+
+ override def ignorableWhitespace(ch: Array[Char], start: Int, length: Int): Unit = {
+ // do nothing
+ }
+
+ override def processingInstruction(target: String, data: String): Unit = {
+ // do nothing
+ }
+
+ override def skippedEntity(name: String): Unit = {
+ // do nothing
+ }
+}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/ScalaXMLInfosetOutputter.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/ScalaXMLInfosetOutputter.scala
index ab46fd6..b75a9a1 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/ScalaXMLInfosetOutputter.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/ScalaXMLInfosetOutputter.scala
@@ -52,8 +52,8 @@ class ScalaXMLInfosetOutputter(showFormatInfo: Boolean = false) extends InfosetO
val e =
if (isNilled(diSimple)) {
- scala.xml.Elem(diSimple.erd.thisElementsNamespacePrefix, diSimple.erd.name,
- XMLUtils.xmlNilAttribute, diSimple.erd.minimizedScope, true)
+ scala.xml.Elem(diSimple.erd.namedQName.prefixOrNull, diSimple.erd.name,
+ XMLUtils.xmlNilAttribute, diSimple.erd.minimizedScope, minimizeEmpty = true)
} else if (diSimple.hasValue) {
val text =
if (diSimple.erd.optPrimType.get.isInstanceOf[NodeInfo.String.Kind]) {
@@ -62,12 +62,12 @@ class ScalaXMLInfosetOutputter(showFormatInfo: Boolean = false) extends InfosetO
diSimple.dataValueAsString
}
val textNode = new scala.xml.Text(text)
- scala.xml.Elem(diSimple.erd.thisElementsNamespacePrefix, diSimple.erd.name, Null,
- diSimple.erd.minimizedScope, true, textNode)
+ scala.xml.Elem(diSimple.erd.namedQName.prefixOrNull, diSimple.erd.name, Null,
+ diSimple.erd.minimizedScope, minimizeEmpty = true, textNode)
} else {
// element has been created but has no value yet, display an empty element tag
- scala.xml.Elem(diSimple.erd.thisElementsNamespacePrefix, diSimple.erd.name, Null,
- diSimple.erd.minimizedScope, true)
+ scala.xml.Elem(diSimple.erd.namedQName.prefixOrNull, diSimple.erd.name, Null,
+ diSimple.erd.minimizedScope, minimizeEmpty = true)
}
val elem = addFmtInfo(diSimple, e, showFormatInfo)
@@ -92,11 +92,11 @@ class ScalaXMLInfosetOutputter(showFormatInfo: Boolean = false) extends InfosetO
val e =
if (isNilled(diComplex)) {
- scala.xml.Elem(diComplex.erd.thisElementsNamespacePrefix, diComplex.erd.name,
- XMLUtils.xmlNilAttribute, diComplex.erd.minimizedScope, true)
+ scala.xml.Elem(diComplex.erd.namedQName.prefixOrNull, diComplex.erd.name,
+ XMLUtils.xmlNilAttribute, diComplex.erd.minimizedScope, minimizeEmpty = true)
} else {
- scala.xml.Elem(diComplex.erd.thisElementsNamespacePrefix, diComplex.erd.name,
- scala.xml.Null, diComplex.erd.minimizedScope, true, children: _*)
+ scala.xml.Elem(diComplex.erd.namedQName.prefixOrNull, diComplex.erd.name,
+ scala.xml.Null, diComplex.erd.minimizedScope, minimizeEmpty = true, children: _*)
}
val elem = addFmtInfo(diComplex, e, showFormatInfo)
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/W3CDOMInfosetOutputter.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/W3CDOMInfosetOutputter.scala
index faf0cf2..2bb809e 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/W3CDOMInfosetOutputter.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/W3CDOMInfosetOutputter.scala
@@ -109,10 +109,10 @@ class W3CDOMInfosetOutputter extends InfosetOutputter
assert(document != null)
val elem: Element =
- if(diElement.erd.thisElementsNamespace.isNoNamespace) {
+ if(diElement.erd.namedQName.namespace.isNoNamespace) {
document.createElementNS(null, diElement.erd.name)
} else {
- document.createElementNS(diElement.erd.thisElementsNamespace, diElement.erd.prefixedName)
+ document.createElementNS(diElement.erd.namedQName.namespace, diElement.erd.prefixedName)
}
if (isNilled(diElement)) {
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 6dbd1a0..a2487dc 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
@@ -48,7 +48,7 @@ class XMLTextInfosetOutputter private (writer: java.io.Writer, pretty: Boolean,
}
private def outputTagName(elem: DIElement): Unit = {
- val prefix = elem.erd.thisElementsNamespacePrefix
+ val prefix = elem.erd.namedQName.prefixOrNull
if (prefix != null && prefix != "") {
writer.write(prefix)
writer.write(":")
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 0522f2d..80fe696 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
@@ -19,19 +19,21 @@ package org.apache.daffodil.processors
import java.io.File
import java.io.IOException
+import java.io.InputStream
import java.io.ObjectOutputStream
import java.nio.CharBuffer
import java.nio.LongBuffer
import java.nio.channels.Channels
import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
import java.util.zip.GZIPOutputStream
import scala.collection.immutable.Queue
+import scala.collection.mutable
-import org.xml.sax.ErrorHandler
-import org.xml.sax.SAXException
-import org.xml.sax.SAXParseException
-
+import org.apache.daffodil.Implicits._; object INoWarn4 {
+ ImplicitsSuppressUnusedImportWarning() }
import org.apache.daffodil.api.DFDL
import org.apache.daffodil.api.DaffodilTunables
import org.apache.daffodil.api.ValidationMode
@@ -39,9 +41,11 @@ import org.apache.daffodil.api.WithDiagnostics
import org.apache.daffodil.debugger.Debugger
import org.apache.daffodil.dsom.TunableLimitExceededError
import org.apache.daffodil.dsom._
-import org.apache.daffodil.equality._; object EqualityNoWarn3 { EqualitySuppressUnusedImportWarning() }
+import org.apache.daffodil.equality._; object EqualityNoWarn3 {
+ EqualitySuppressUnusedImportWarning() }
import org.apache.daffodil.events.MultipleEventHandler
import org.apache.daffodil.exceptions.Assert
+import org.apache.daffodil.exceptions.SchemaFileLocation
import org.apache.daffodil.exceptions.UnsuppressableException
import org.apache.daffodil.externalvars.Binding
import org.apache.daffodil.externalvars.ExternalVariablesLoader
@@ -50,6 +54,7 @@ import org.apache.daffodil.infoset.InfosetElement
import org.apache.daffodil.infoset.InfosetException
import org.apache.daffodil.infoset.InfosetInputter
import org.apache.daffodil.infoset.InfosetOutputter
+import org.apache.daffodil.infoset.SAXInfosetOutputter
import org.apache.daffodil.infoset.TeeInfosetOutputter
import org.apache.daffodil.infoset.XMLTextInfosetOutputter
import org.apache.daffodil.io.BitOrderChangeException
@@ -68,6 +73,17 @@ import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.Maybe._
import org.apache.daffodil.util.Misc
import org.apache.daffodil.util.Validator
+import org.apache.daffodil.xml.XMLUtils
+import org.xml.sax.ContentHandler
+import org.xml.sax.DTDHandler
+import org.xml.sax.EntityResolver
+import org.xml.sax.ErrorHandler
+import org.xml.sax.InputSource
+import org.xml.sax.SAXException
+import org.xml.sax.SAXNotRecognizedException
+import org.xml.sax.SAXNotSupportedException
+import org.xml.sax.SAXParseException
+
/**
* Implementation mixin - provides simple helper methods
@@ -337,6 +353,8 @@ class DataProcessor private (
override def getDiagnostics = ssrd.diagnostics
+ override def newXMLReaderInstance: DFDL.DaffodilXMLReader = new DaffodilXMLReader(this)
+
def save(output: DFDL.Output): Unit = {
externalVars.foreach{ ev =>
@@ -735,3 +753,168 @@ class UnparseResult(dp: DataProcessor, ustate: UState)
encodingInfo.knownEncodingName
}
}
+
+class DaffodilXMLReader(dp: DataProcessor) extends DFDL.DaffodilXMLReader {
+ private var contentHandler: ContentHandler = _
+ private var errorHandler: ErrorHandler = _
+ private var dtdHandler: DTDHandler = _
+ private var entityResolver: EntityResolver = _
+ private val saxNamespaceFeature = "http://xml.org/sax/features/namespaces"
+ private val saxNamespacePrefixFeature = "http://xml.org/sax/features/namespace-prefixes"
+ var saxParseResultProperty: ParseResult = _
+ var saxBlobDirectoryProperty: Path = Paths.get(System.getProperty("java.io.tmpdir"))
+ var saxBlobPrefixProperty: String = "daffodil-sax-"
+ var saxBlobSuffixProperty: String = ".blob"
+
+ private val featureMap = mutable.Map[String, Boolean](saxNamespaceFeature -> false,
+ saxNamespacePrefixFeature -> false)
+
+ override def getFeature(name: String): Boolean = {
+ if (name == saxNamespaceFeature || name == saxNamespacePrefixFeature) {
+ featureMap(name)
+ } else {
+ throw new SAXNotRecognizedException("Feature unsupported: " + name + ".\n" +
+ "Supported features are: " + featureMap.keys.mkString(", "))
+ false
+ }
+ }
+
+ override def setFeature(name: String, value: Boolean): Unit = {
+ if (name == saxNamespaceFeature || name == saxNamespacePrefixFeature) {
+ featureMap(name) = value
+ } else {
+ throw new SAXNotRecognizedException("Feature unsupported: " + name + ".\n" +
+ "Supported features are: " + featureMap.keys.mkString(", "))
+ }
+ }
+
+ override def getProperty(name: String): AnyRef = {
+ val prop = name match {
+ case XMLUtils.DAFFODIL_SAX_URN_PARSERESULT => saxParseResultProperty
+ case XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY => saxBlobDirectoryProperty
+ case XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX => saxBlobPrefixProperty
+ case XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX => saxBlobSuffixProperty
+ case _ =>
+ throw new SAXNotRecognizedException("Property unsupported: " + name + ".")
+ }
+ prop
+ }
+
+ override def setProperty(name: String, value: AnyRef): Unit = {
+ try {
+ name match {
+ case XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY => saxBlobDirectoryProperty =
+ value.asInstanceOf[Path]
+ case XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX => saxBlobPrefixProperty = value
+ .asInstanceOf[String]
+ case XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX => saxBlobSuffixProperty = value
+ .asInstanceOf[String]
+ case _ =>
+ throw new SAXNotRecognizedException("Property unsupported: " + name + ".")
+ }
+ } catch {
+ case _: ClassCastException =>
+ throw new SAXNotSupportedException("Unsupported value for property: " + name + "." )
+ }
+ }
+
+ override def setEntityResolver(resolver: EntityResolver): Unit = {
+ entityResolver = resolver
+ }
+
+ override def getEntityResolver: EntityResolver = entityResolver
+
+ override def setDTDHandler(handler: DTDHandler): Unit = {
+ dtdHandler = handler
+ }
+
+ override def getDTDHandler: DTDHandler = dtdHandler
+
+ override def setContentHandler(handler: ContentHandler): Unit = {
+ contentHandler = handler;
+ }
+
+ override def getContentHandler: ContentHandler = contentHandler
+
+ override def setErrorHandler(handler: ErrorHandler): Unit = {
+ errorHandler = handler;
+ }
+
+ override def getErrorHandler: ErrorHandler = errorHandler
+
+ override def parse(input: InputSource): Unit = {
+ val is = input.getByteStream
+ if (is != null) {
+ val isdis = InputSourceDataInputStream(is)
+ parse(isdis)
+ } else {
+ throw new IOException("Inputsource must be backed by Inputstream")
+ }
+ }
+
+ override def parse(systemId: String): Unit = {
+ throw new IOException("SAX parsing of systemId is unsupported")
+ }
+
+ def parse(isdis: InputSourceDataInputStream): Unit = {
+ val sio = createSAXInfosetOutputter(this)
+ val pr = dp.parse(isdis, sio)
+ handleDiagnostics(pr)
+ saxParseResultProperty = pr .asInstanceOf[ParseResult]
+ }
+
+ def parse(stream: InputStream): Unit = {
+ val isdis = InputSourceDataInputStream(stream)
+ parse(isdis)
+ }
+
+ def parse(arr: Array[Byte]): Unit = {
+ val isdis = InputSourceDataInputStream(arr)
+ parse(isdis)
+ }
+
+ private def handleDiagnostics(pr: DFDL.ParseResult): Unit = {
+ val diagnostics = pr.getDiagnostics
+ val eh = this.getErrorHandler
+ if (diagnostics.nonEmpty && eh != null) {
+ diagnostics.foreach { d =>
+ val spe = {
+ val msg = d.getMessage()
+ val (lineNo, colNo, systemId) = d.getLocationsInSchemaFiles.headOption.map { s =>
+ val sl = s.asInstanceOf[SchemaFileLocation]
+ val ln = sl.lineNumber.getOrElse("0").toInt
+ val cn = sl.columnNumber.getOrElse("0").toInt
+ val sId = sl.uriString
+ (ln, cn, sId)
+ }.getOrElse((0,0, null))
+
+ val spe = new SAXParseException(msg, null, systemId, lineNo, colNo, d)
+ spe
+ }
+
+ if (d.isError) {
+ eh.error(spe)
+ } else {
+ eh.warning(spe)
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates SAXInfosetOutputter object and attempts to setBlobAttributes on it if
+ * it has at least the blobDirectory property set
+ *
+ * @return SAXInfosetOutputter object with or without blob Attributes set
+ */
+ private def createSAXInfosetOutputter(xmlReader: DaffodilXMLReader): SAXInfosetOutputter = {
+ val sioo = new SAXInfosetOutputter(xmlReader)
+ val siof = try {
+ sioo.setBlobAttributes(saxBlobDirectoryProperty, saxBlobPrefixProperty, saxBlobSuffixProperty)
+ sioo
+ } catch {
+ case e: SAXNotSupportedException => sioo
+ }
+ siof
+ }
+}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
index 3afca03..aa6138c 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
@@ -561,7 +561,6 @@ sealed class ElementRuntimeData(
defaultBitOrderArg: BitOrder,
@TransientParam optPrimTypeArg: => Option[PrimType],
@TransientParam targetNamespaceArg: => NS,
- @TransientParam thisElementsNamespaceArg: => NS,
@TransientParam optSimpleTypeRuntimeDataArg: => Option[SimpleTypeRuntimeData],
@TransientParam optComplexTypeModelGroupRuntimeDataArg: => Option[ModelGroupRuntimeData],
@TransientParam minOccursArg: => Long,
@@ -569,7 +568,6 @@ sealed class ElementRuntimeData(
@TransientParam maybeOccursCountKindArg: => Maybe[OccursCountKind],
@TransientParam nameArg: => String,
@TransientParam targetNamespacePrefixArg: => String,
- @TransientParam thisElementsNamespacePrefixArg: => String,
@TransientParam isNillableArg: => Boolean,
@TransientParam isArrayArg: => Boolean, // can have more than 1 occurrence
@TransientParam isOptionalArg: => Boolean, // can have only 0 or 1 occurrence
@@ -617,7 +615,6 @@ sealed class ElementRuntimeData(
lazy val minimizedScope = minimizedScopeArg
lazy val optPrimType = optPrimTypeArg
lazy val targetNamespace = targetNamespaceArg
- lazy val thisElementsNamespace = thisElementsNamespaceArg
lazy val optSimpleTypeRuntimeData = optSimpleTypeRuntimeDataArg
lazy val optComplexTypeModelGroupRuntimeData = optComplexTypeModelGroupRuntimeDataArg
lazy val minOccurs = minOccursArg
@@ -625,7 +622,6 @@ sealed class ElementRuntimeData(
lazy val maybeOccursCountKind = maybeOccursCountKindArg
lazy val name = nameArg
lazy val targetNamespacePrefix = targetNamespacePrefixArg
- lazy val thisElementsNamespacePrefix = thisElementsNamespacePrefixArg
lazy val isNillable = isNillableArg
override lazy val isArray = isArrayArg
lazy val isOptional = isOptionalArg
@@ -648,7 +644,6 @@ sealed class ElementRuntimeData(
minimizedScope
optPrimType
targetNamespace
- thisElementsNamespace
optSimpleTypeRuntimeData
optComplexTypeModelGroupRuntimeData
minOccurs
@@ -656,7 +651,6 @@ sealed class ElementRuntimeData(
maybeOccursCountKind
name
targetNamespacePrefix
- thisElementsNamespacePrefix
isNillable
isArray
isOptional
@@ -685,8 +679,8 @@ sealed class ElementRuntimeData(
def isComplexType = !isSimpleType
def prefixedName = {
- if (thisElementsNamespacePrefix != null) {
- thisElementsNamespacePrefix + ":" + name
+ if (namedQName.prefixOrNull != null) {
+ namedQName.prefixOrNull + ":" + name
} else {
name
}
@@ -744,7 +738,6 @@ sealed abstract class ErrorERD(local: String, namespaceURI: String)
null, //defaultBitOrderArg: => BitOrder,
None, // optPrimTypeArg: => Option[PrimType],
null, // targetNamespaceArg: => NS,
- NS(namespaceURI), // thisElementsNamespaceArg: => NS,
null, // optSimpleTypeRuntimeDataArg: => Option[SimpleTypeRuntimeData],
null, // optComplexTypeModelGroupRuntimeDataArg: => Option[ModelGroupRuntimeData],
0L, // minOccursArg: => Long,
@@ -752,7 +745,6 @@ sealed abstract class ErrorERD(local: String, namespaceURI: String)
Nope, // maybeOccursCountKindArg: => Maybe[OccursCountKind],
local, // nameArg: => String,
null, // targetNamespacePrefixArg: => String,
- null, // thisElementsNamespacePrefixArg: => String,
false, // isNillableArg: => Boolean,
false, // isArrayArg: => Boolean, // can have more than 1 occurrence
false, // isOptionalArg: => Boolean, // can have only 0 or 1 occurrence
@@ -816,7 +808,7 @@ final class NamespaceAmbiguousElementErrorERD(
* is what was expected.
*/
def toUnparseError(nothingWasExpected: Boolean = false) = {
- val sqn = StepQName(None, name, thisElementsNamespace)
+ val sqn = StepQName(None, name, namedQName.namespace)
val sqnx = sqn.toExtendedSyntax
val allPossiblesString =
allPossibleNQNs.map { _.toExtendedSyntax }.mkString(", ")
diff --git a/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/Daffodil.scala b/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/Daffodil.scala
index 35b0016..c9946ba 100644
--- a/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/Daffodil.scala
+++ b/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/Daffodil.scala
@@ -25,7 +25,6 @@ import org.apache.daffodil.sapi.infoset._
import org.apache.daffodil.sapi.io.InputSourceDataInputStream
import org.apache.daffodil.debugger.{ InteractiveDebugger => SInteractiveDebugger }
import org.apache.daffodil.debugger.{ TraceDebuggerRunner => STraceDebuggerRunner }
-import org.apache.daffodil.api.{ Diagnostic => SDiagnostic }
import java.io.File
import java.nio.channels.Channels
import java.nio.channels.ReadableByteChannel
@@ -37,6 +36,7 @@ import org.apache.daffodil.api.{ LocationInSchemaFile => SLocationInSchemaFile }
import org.apache.daffodil.api.{ WithDiagnostics => SWithDiagnostics }
import org.apache.daffodil.compiler.{ ProcessorFactory => SProcessorFactory }
import org.apache.daffodil.processors.{ DataProcessor => SDataProcessor }
+import org.apache.daffodil.processors.{ DaffodilXMLReader => SDaffodilXMLReader }
import org.apache.daffodil.processors.{ ParseResult => SParseResult }
import org.apache.daffodil.processors.{ UnparseResult => SUnparseResult }
import org.apache.daffodil.util.{ ConsoleWriter => SConsoleWriter }
@@ -619,6 +619,12 @@ class DataProcessor private[sapi] (private var dp: SDataProcessor)
def save(output: WritableByteChannel): Unit = dp.save(output)
/**
+ * Obtain a new [[DaffodilXMLReader]] from the current [[DataProcessor]].
+ */
+ def newXMLReaderInstance: DaffodilXMLReader =
+ new DaffodilXMLReader(xmlrdr = dp.newXMLReaderInstance.asInstanceOf[SDaffodilXMLReader])
+
+ /**
* Parse input data with a specified length
*
* @param input data to be parsed
@@ -790,3 +796,120 @@ class InvalidParserException(cause: org.apache.daffodil.compiler.InvalidParserEx
* This exception will be thrown as a result of an invalid usage of the Daffodil API
*/
class InvalidUsageException(cause: org.apache.daffodil.processors.InvalidUsageException) extends Exception(cause.getMessage(), cause.getCause())
+
+/**
+ * SAX Method of parsing schema and getting the DFDL Infoset via designated
+ * org.xml.sax.ContentHandler, based on the org.xml.sax.XMLReader interface
+ */
+class DaffodilXMLReader private[sapi] (xmlrdr: SDaffodilXMLReader) extends org.xml.sax.XMLReader {
+ /**
+ * Get the value of the feature flag
+ * @param name feature flag whose value is to be retrieved
+ * @return value of the feature flag
+ */
+ override def getFeature(name: String): Boolean = xmlrdr.getFeature(name)
+
+ /**
+ * Set the value of the feature flag
+ * @param name feature flag to be set
+ * @param value value to be assigned to feature flag
+ */
+ override def setFeature(name: String, value: Boolean): Unit = xmlrdr.setFeature(name, value)
+
+ /**
+ * Get the value of the property
+ * @param name property whose value is to be retrieved
+ * @return value of the property
+ */
+ override def getProperty(name: String): AnyRef = xmlrdr.getProperty(name)
+
+ /**
+ * Set the value of the property
+ * @param name property whose value is to be set
+ * @param value value to be assigned to the property
+ */
+ override def setProperty(name: String, value: AnyRef): Unit = xmlrdr.setProperty(name, value)
+
+ /**
+ * Register an entity resolver
+ * @param resolver entity resolver to be registered
+ */
+ override def setEntityResolver(resolver: org.xml.sax.EntityResolver): Unit =
+ xmlrdr.setEntityResolver(resolver)
+
+ /**
+ * Return the registered entity resolver
+ * @return registered entity resolver or null
+ */
+ override def getEntityResolver: org.xml.sax.EntityResolver = xmlrdr.getEntityResolver
+
+ /**
+ * Register a DTD Handler
+ * @param handler DTD Handler to be registered
+ */
+ override def setDTDHandler(handler: org.xml.sax.DTDHandler): Unit = xmlrdr.setDTDHandler(handler)
+
+ /**
+ * Retrieve registered DTD Handler
+ * @return registered DTD Handler or null
+ */
+ override def getDTDHandler: org.xml.sax.DTDHandler = xmlrdr.getDTDHandler
+
+ /**
+ * Register a content handler
+ * @param handler content handler to be registered
+ */
+ override def setContentHandler(handler: org.xml.sax.ContentHandler): Unit = xmlrdr.setContentHandler(handler)
+
+ /**
+ * Retrieve registered content handler
+ * @return registered content handler or null
+ */
+ override def getContentHandler: org.xml.sax.ContentHandler = xmlrdr.getContentHandler
+
+ /**
+ * Register an error handler
+ * @param handler error handler to be registered
+ */
+ override def setErrorHandler(handler: org.xml.sax.ErrorHandler): Unit = xmlrdr.setErrorHandler(handler)
+
+ /**
+ * Retrieve registered error handler
+ * @return registered error handler or null
+ */
+ override def getErrorHandler: org.xml.sax.ErrorHandler = xmlrdr.getErrorHandler
+
+ /**
+ * Parse input data from an InputSource. Infoset can be retrieved via the registered
+ * contentHandler and diagnostics via the registered errorHandler
+ * @param input data to be parsed
+ */
+ override def parse(input: org.xml.sax.InputSource): Unit = xmlrdr.parse(input)
+
+ /**
+ * Parse data from a system identifier/URI. This method is not supported by the API.
+ * @param systemId URI for data to be parsed
+ */
+ override def parse(systemId: String): Unit = xmlrdr.parse(systemId)
+
+ /**
+ * Parse input data from an InputSourceDataInputStream. Infoset can retrieved via the registered
+ * contentHandler and diagnostics via the registered errorHandler
+ * @param isdis data to be parsed
+ */
+ def parse(isdis: InputSourceDataInputStream): Unit = xmlrdr.parse(isdis.dis)
+
+ /**
+ * Parse input data from an InputStream. Infoset can retrieved via the registered contentHandler
+ * and diagnostics via the registered errorHandler
+ * @param stream data to be parsed
+ */
+ def parse(stream: java.io.InputStream): Unit = xmlrdr.parse(stream)
+
+ /**
+ * Parse input data from an array of bytes. Infoset can retrieved via the registered
+ * contentHandler and diagnostics via the registered errorHandler
+ * @param arr data to be parsed
+ */
+ def parse(arr: Array[Byte]): Unit = xmlrdr.parse(arr)
+}
diff --git a/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/package.scala b/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/package.scala
index 47e1b20..2d20e64 100644
--- a/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/package.scala
+++ b/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/package.scala
@@ -25,7 +25,7 @@ package org.apache.daffodil
* <h3>Overview</h3>
*
* The [[Daffodil]] object is a factory object to create a [[Compiler]]. The
- * [[Compiler]] provides a method to compils a provided DFDL schema into a
+ * [[Compiler]] provides a method to compile a provided DFDL schema into a
* [[ProcessorFactory]], which creates a [[DataProcessor]]:
*
* {{{
@@ -37,10 +37,59 @@ package org.apache.daffodil
* The [[DataProcessor]] provides the necessary functions to parse and unparse
* data, returning a [[ParseResult]] or [[UnparseResult]], respectively. These
* contain information about the parse/unparse, such as whether or not the
- * processing succeeded any diagnostic information.
+ * processing succeeded with any diagnostic information.
+ *
+ * The [[DataProcessor]] also provides a function to create a [[DaffodilXMLReader]] that can be
+ * used to perform parsing via the SAX API.
+ *
+ * {{{
+ * val xmlRdr = dp.newXMLReaderInstance
+ * }}}
+ *
+ * The [[DaffodilXMLReader]] has several methods that allow one to set properties and handlers
+ * (such as ContentHandlers or ErrorHandlers) for the reader. One can use any
+ * contentHandler/errorHandler as long as they extend the org.xml.sax.ContentHandler and
+ * org.xml.sax.ErrorHandler interfaces respectively. One can use any contentHandler/errorHandler
+ * as long as they extend the org.xml.sax.ContentHandler and org.xml.sax.ErrorHandler interfaces
+ * respectively. One can also set properties for the [[DaffodilXMLReader]] using
+ * [[DaffodilXMLReader.setProperty(name:String* DaffodilXMLReader.setProperty]].
+ *
+ * The following properties can be set as follows:
+ * {{{
+ * xmlRdr.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY, "/tmp/")
+ * xmlRdr.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX, "daffodil-sax-")
+ * xmlRdr.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX, ".bin")
+ * }}}
+ * The variables above start with "urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:sax:" and end
+ * with BlobDirectory, BlobPrefix and BlobSuffix respectively.
+ *
+ * The properties can be retrieved using the same variables with
+ * [[DaffodilXMLReader.getProperty(name:String* DaffodilXMLReader.getProperty]]
+ *
+ * The following handlers can be set as follows:
+ * {{{
+ * xmlRdr.setContentHandler(contentHandler)
+ * xmlRdr.setErrorHandler(errorHandler)
+ * xmlRdr.setDTDHandler(dtdHandler)
+ * xmlRdr.setEntityResolver(entityResolver)
+ * }}}
+ *
+ * The handlers above must implement the following interfaces respectively:
+ * {{{
+ * org.xml.sax.ContentHandler
+ * org.xml.sax.ErrorHandler
+ * org.xml.sax.DTDHandler
+ * org.xml.sax.EntityResolver
+ * }}}
+ *
+ * The [[ParseResult]] can be found as a property within the [[DaffodilXMLReader]] using this
+ * uri: "urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:sax:ParseResult" or
+ * XMLUtils.DAFFODIL_SAX_URN_PARSERESULT
*
* <h4>Parse</h4>
*
+ * <h5>Dataprocessor Parse</h5>
+ *
* The [[DataProcessor.parse(input:org\.apache\.daffodil\.sapi\.io\.InputSourceDataInputStream* DataProcessor.parse]] method accepts input data to parse in the form
* of a [[io.InputSourceDataInputStream InputSourceDataInputStream]] and an [[infoset.InfosetOutputter InfosetOutputter]]
* to determine the output representation of the infoset (e.g. Scala XML Nodes,
@@ -83,6 +132,59 @@ package org.apache.daffodil
* }
* }}}
*
+ * <h5>SAX Parse</h5>
+ *
+ * The [[DaffodilXMLReader.parse(isdis:org\.apache\.daffodil\.sapi\.io\.InputSourceDataInputStream* DaffodilXMLReader.parse]] method accepts input data to parse in the form of a
+ * [[io.InputSourceDataInputStream InputSourceDataInputStream]]. The output representation of the
+ * infoset, as well as how parse errors are handled, are dependent on the content handler and the
+ * error handler provided to the [[DaffodilXMLReader]]. For example the
+ * org.jdom2.input.sax.SAXHandler provides a JDOM representation, whereas other Content
+ * Handlers may output directly to an java.io.OutputStream or java.io.Writer.
+ *
+ * {{{
+ * val contentHandler = new SAXHandler()
+ * xmlRdr.setContentHandler(contentHandler)
+ * val is = new InputSourceDataInputStream(data)
+ * xmlReader.parse(is)
+ * val pr = xmlRdr.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT)
+ * val doc = saxHandler.getDocument
+ * }}}
+ *
+ * The [[DaffodilXMLReader.parse(isdis:org\.apache\.daffodil\.sapi\.io\.InputSourceDataInputStream* DaffodilXMLReader.parse]] method is not thread-safe and may only be called again/reused once a
+ * parse operation is completed. This can be done multiple times without the need to create new
+ * DaffodilXMLReaders, ContentHandlers or ErrorHandlers. It might be necessary to reset whatever
+ * ContentHandler is used (or allocate a new one). A thread-safe implementation would require
+ * unique instances of the DaffodilXMLReader and its components. For example:
+ *
+ * {{{
+ * val contentHandler = new SAXHandler()
+ * xmlRdr.setContentHandler(contentHandler)
+ * files.foreach { f => {
+ * contentHandler.reset
+ * val is = new InputSourceDataInputStream(new FileInputStream(f))
+ * xmlReader.parse(is)
+ * val pr = xmlRdr.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT)
+ * val doc = saxHandler.getDocument
+ * }
+ * }}}
+ *
+ * One can repeat calls to parse() using the same InputSourceDataInputStream to continue parsing
+ * where the previous parse ended. For example:
+ *
+ * {{{
+ * val is = new InputSourceDataInputStream(dataStream)
+ * val contentHandler = new SAXHandler()
+ * xmlRdr.setContentHandler(contentHandler)
+ * val keepParsing = true
+ * while (keepParsing) {
+ * contentHandler.reset()
+ * xmlRdr.parse(is)
+ * val pr = xmlRdr.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT)
+ * ...
+ * keepParsing = !pr.location().isAtEnd() && !pr.isError()
+ * }
+ * }}}
+ *
* <h4>Unparse</h4>
*
* The same [[DataProcessor]] used for parse can be used to unparse an infoset
@@ -132,9 +234,22 @@ package org.apache.daffodil
*
* {{{
* val dp = Daffodil.reload(saveFile);
+ * }}}
+ *
+ * And use like below:
+ * {{{
* val pr = dp.parse(data, inputter);
* }}}
*
+ * or
+ *
+ * {{{
+ * val xmlRdr = dp.newXMLReaderInstance
+ * ... // setting appropriate handlers
+ * xmlReader.parse(data)
+ * val pr = xmlRdr.getProperty("...ParseResult")
+ * }}}
+ *
*/
package object sapi
diff --git a/daffodil-sapi/src/test/scala/org/apache/daffodil/example/TestScalaAPI.scala b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/TestScalaAPI.scala
index 0e49291..2e84525 100644
--- a/daffodil-sapi/src/test/scala/org/apache/daffodil/example/TestScalaAPI.scala
+++ b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/TestScalaAPI.scala
@@ -27,6 +27,8 @@ import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.File
import java.nio.channels.Channels
+import java.nio.file.Paths
+
import org.junit.Test
import org.apache.daffodil.sapi.Daffodil
import org.apache.daffodil.sapi.DataProcessor
@@ -39,6 +41,11 @@ import org.apache.daffodil.sapi.infoset.ScalaXMLInfosetOutputter
import org.apache.daffodil.sapi.infoset.ScalaXMLInfosetInputter
import org.apache.daffodil.sapi.io.InputSourceDataInputStream
import org.apache.daffodil.exceptions.Abort
+import org.apache.daffodil.infoset.DaffodilOutputContentHandler
+import org.apache.daffodil.processors.{ ParseResult => SParseResult }
+import org.apache.daffodil.sapi.SAXErrorHandlerForSAPITest
+import org.apache.daffodil.sapi.infoset.XMLTextInfosetOutputter
+import org.apache.daffodil.xml.XMLUtils
class TestScalaAPI {
@@ -144,7 +151,6 @@ class TestScalaAPI {
// reset the global logging and debugger state
Daffodil.setLogWriter(new ConsoleLogWriter())
Daffodil.setLoggingLevel(LogLevel.Info)
-
}
// This is a duplicate of test testScalaAPI1 that serializes the parser
@@ -971,4 +977,88 @@ class TestScalaAPI {
}
}
+ @Test
+ def testScalaAPI20(): Unit = {
+ /** Test SAX parsing */
+ val output = new ByteArrayOutputStream
+
+ val c = Daffodil.compiler()
+
+ val schemaFile = getResource("/test/sapi/mySchema1.dfdl.xsd")
+ val pf = c.compileFile(schemaFile)
+ val dp1 = pf.onPath("/")
+ val dp = reserializeDataProcessor(dp1)
+ val xri = dp.newXMLReaderInstance
+
+ // val file = getResource("/test/sapi/myDataBroken.dat")
+ val file = getResource("/test/sapi/myData.dat")
+ val fisDP = new java.io.FileInputStream(file)
+ val fisSAX = new java.io.FileInputStream(file)
+ val inputSAX = new InputSourceDataInputStream(fisSAX)
+ val inputDP = new InputSourceDataInputStream(fisDP)
+ val bosDP = new ByteArrayOutputStream()
+ val outputter = new XMLTextInfosetOutputter(bosDP, pretty = true)
+ dp.parse(inputDP, outputter)
+ val infosetDPString = bosDP.toString()
+
+ val bos = new ByteArrayOutputStream()
+ val contentHandler = new DaffodilOutputContentHandler(bos, pretty = true)
+ val errorHandler = new SAXErrorHandlerForSAPITest()
+ xri.setContentHandler(contentHandler)
+ xri.setErrorHandler(errorHandler)
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY,
+ Paths.get(System.getProperty("java.io.tmpdir")))
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX, "daffodil-sapi-")
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX, ".sax.blob")
+ xri.parse(inputSAX)
+ val resSAX = xri.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT)
+ .asInstanceOf[SParseResult]
+ val err = errorHandler.isError
+ val diags = errorHandler.getDiagnostics
+ val infosetSAXString = bos.toString
+
+ assertFalse(err)
+ assertTrue(resSAX.resultState.currentLocation.isAtEnd)
+ assertTrue(diags.isEmpty)
+ assertEquals(infosetDPString, infosetSAXString)
+ }
+
+ @Test
+ def testScalaAPI21(): Unit = {
+ /** Test parse with errors */
+ val c = Daffodil.compiler()
+
+ val schemaFile = getResource("/test/sapi/mySchema1.dfdl.xsd")
+ val pf = c.compileFile(schemaFile)
+ val dp1 = pf.onPath("/")
+ val dp = reserializeDataProcessor(dp1)
+ val xri = dp.newXMLReaderInstance
+
+ val file = getResource("/test/sapi/myDataBroken.dat")
+ val fis = new java.io.FileInputStream(file)
+ val input = new InputSourceDataInputStream(fis)
+ val bos = new ByteArrayOutputStream()
+ val contentHandler = new DaffodilOutputContentHandler(bos)
+ val errorHandler = new SAXErrorHandlerForSAPITest()
+ xri.setContentHandler(contentHandler)
+ xri.setErrorHandler(errorHandler)
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY,
+ Paths.get(System.getProperty("java.io.tmpdir")))
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX, "daffodil-sapi-")
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX, ".sax.blob")
+ xri.parse(input)
+
+ assertTrue(errorHandler.isError)
+ val diags = errorHandler.getDiagnostics
+ assertEquals(1, diags.size)
+ val d = diags.head
+ assertTrue(d.getMessage().contains("int"))
+ assertTrue(d.getMessage().contains("Not an int"))
+ assertTrue(d.getDataLocations.toString().contains("10"))
+ val locs = d.getLocationsInSchemaFiles
+ assertEquals(1, locs.size)
+ val loc = locs.head
+ assertTrue(loc.toString().contains("mySchema1.dfdl.xsd")) // reports the element ref, not element decl.
+ }
+
}
diff --git a/daffodil-sapi/src/test/scala/org/apache/daffodil/sapi/SAXErrorHandlerForSAPITest.scala b/daffodil-sapi/src/test/scala/org/apache/daffodil/sapi/SAXErrorHandlerForSAPITest.scala
new file mode 100644
index 0000000..774731f
--- /dev/null
+++ b/daffodil-sapi/src/test/scala/org/apache/daffodil/sapi/SAXErrorHandlerForSAPITest.scala
@@ -0,0 +1,47 @@
+/*
+ * 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.sapi
+
+import org.apache.daffodil.api.{Diagnostic => SDiagnostic}
+import org.xml.sax.ErrorHandler
+import org.xml.sax.SAXParseException
+
+class SAXErrorHandlerForSAPITest extends ErrorHandler {
+ private var _diagnostics: Seq[Diagnostic] = Nil
+ private var _isError: Boolean = false
+
+ override def warning(exception: SAXParseException): Unit = {
+ _isError = false
+ val embeddedDiagnostic = new Diagnostic(exception.getCause.asInstanceOf[SDiagnostic])
+ _diagnostics :+= embeddedDiagnostic
+ }
+
+ override def error(exception: SAXParseException): Unit = {
+ _isError = true
+ val embeddedDiagnostic = new Diagnostic(exception.getCause.asInstanceOf[SDiagnostic])
+ _diagnostics :+= embeddedDiagnostic
+ }
+
+ override def fatalError(exception: SAXParseException): Unit = {
+ error(exception)
+ }
+
+ def getDiagnostics: Seq[Diagnostic] = _diagnostics
+
+ def isError: Boolean = _isError
+ }
diff --git a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
index fff99d7..9e2a3c1 100644
--- a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
+++ b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
@@ -17,16 +17,29 @@
package org.apache.daffodil.tdml
+import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileNotFoundException
+import java.io.InputStream
+import java.io.OutputStream
import java.net.URI
+import java.nio.ByteBuffer
+import java.nio.CharBuffer
+import java.nio.LongBuffer
+import java.nio.charset.CoderResult
+import java.nio.charset.{Charset => JavaCharset}
+import java.nio.file.Files
-import scala.xml.Node
+import scala.language.postfixOps
+import scala.util.Try
import scala.xml.Elem
+import scala.xml.Node
import scala.xml.NodeSeq
import scala.xml.NodeSeq.seqToNodeSeq
import scala.xml.SAXParseException
import scala.xml.transform._
+
+import org.apache.commons.io.IOUtils
import org.apache.daffodil.api.DaffodilSchemaSource
import org.apache.daffodil.api.DaffodilTunables
import org.apache.daffodil.api.DataLocation
@@ -35,56 +48,40 @@ import org.apache.daffodil.api.EmbeddedSchemaSource
import org.apache.daffodil.api.URISchemaSource
import org.apache.daffodil.api.UnitTestSchemaSource
import org.apache.daffodil.api.ValidationMode
+import org.apache.daffodil.configuration.ConfigurationLoader
+import org.apache.daffodil.cookers.EntityReplacer
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.exceptions.UnsuppressableException
import org.apache.daffodil.externalvars.Binding
-import org.apache.daffodil.util.LogLevel
-import org.apache.daffodil.util.Logging
-import org.apache.daffodil.util.Misc
-import org.apache.daffodil.util.Misc.bits2Bytes
-import org.apache.daffodil.util.Misc.hex2Bits
-import org.apache.daffodil.util.SchemaUtils
-import org.apache.daffodil.xml.DaffodilXMLLoader
-import org.apache.daffodil.xml._
+import org.apache.daffodil.io.FormatInfo
+import org.apache.daffodil.io.InputSourceDataInputStream
+import org.apache.daffodil.processors.HasSetDebugger
+import org.apache.daffodil.processors.charset.BitsCharsetDecoder
+import org.apache.daffodil.processors.charset.BitsCharsetEncoder
+import org.apache.daffodil.processors.charset.BitsCharsetNonByteSize
+import org.apache.daffodil.processors.charset.BitsCharsetNonByteSizeEncoder
import org.apache.daffodil.processors.charset.CharsetUtils
-import java.nio.ByteBuffer
-import java.nio.CharBuffer
-import java.nio.LongBuffer
-import java.nio.charset.CoderResult
-import java.io.ByteArrayInputStream
-
-import scala.language.postfixOps
-import java.nio.file.Files
-import java.io.InputStream
-
-import org.apache.commons.io.IOUtils
-import org.apache.daffodil.cookers.EntityReplacer
-import org.apache.daffodil.configuration.ConfigurationLoader
+import org.apache.daffodil.schema.annotation.props.gen.BinaryFloatRep
import org.apache.daffodil.schema.annotation.props.gen.BitOrder
import org.apache.daffodil.schema.annotation.props.gen.ByteOrder
-import org.apache.daffodil.schema.annotation.props.gen.BinaryFloatRep
import org.apache.daffodil.schema.annotation.props.gen.EncodingErrorPolicy
import org.apache.daffodil.schema.annotation.props.gen.UTF16Width
-import org.apache.daffodil.util.MaybeInt
-import org.apache.daffodil.util.Maybe
-import org.apache.daffodil.processors.charset.BitsCharsetDecoder
-import org.apache.daffodil.processors.charset.BitsCharsetEncoder
-import org.apache.daffodil.processors.charset.BitsCharsetNonByteSize
-import org.apache.daffodil.processors.charset.BitsCharsetNonByteSizeEncoder
-import org.apache.daffodil.io.FormatInfo
-import org.apache.daffodil.io.InputSourceDataInputStream
-import java.nio.charset.{Charset => JavaCharset}
-import java.io.OutputStream
-
-import org.apache.daffodil.tdml.processor.TDMLParseResult
+import org.apache.daffodil.tdml.processor.AbstractTDMLDFDLProcessorFactory
+import org.apache.daffodil.tdml.processor.TDML
import org.apache.daffodil.tdml.processor.TDMLDFDLProcessor
+import org.apache.daffodil.tdml.processor.TDMLParseResult
import org.apache.daffodil.tdml.processor.TDMLResult
import org.apache.daffodil.tdml.processor.TDMLUnparseResult
-import org.apache.daffodil.tdml.processor.TDML
-import org.apache.daffodil.tdml.processor.AbstractTDMLDFDLProcessorFactory
-
-import scala.util.Try
-import org.apache.daffodil.processors.HasSetDebugger
+import org.apache.daffodil.util.LogLevel
+import org.apache.daffodil.util.Logging
+import org.apache.daffodil.util.Maybe
+import org.apache.daffodil.util.MaybeInt
+import org.apache.daffodil.util.Misc
+import org.apache.daffodil.util.Misc.bits2Bytes
+import org.apache.daffodil.util.Misc.hex2Bits
+import org.apache.daffodil.util.SchemaUtils
+import org.apache.daffodil.xml.DaffodilXMLLoader
+import org.apache.daffodil.xml.XMLUtils
/**
* Parses and runs tests expressed in IBM's contributed tdml "Test Data Markup Language"
@@ -860,7 +857,7 @@ case class ParserTestCase(ptc: NodeSeq, parentArg: DFDLTestSuite)
if (processor.isError || optExtVarDiag.isDefined) {
val noParseResult: TDMLParseResult = null
val allDiags: Seq[Diagnostic] = processor.getDiagnostics ++ optExtVarDiag
- (noParseResult, allDiags , true)
+ (noParseResult, allDiags, true)
} else {
val actual =
try {
diff --git a/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/processor/DaffodilTDMLDFDLProcessor.scala b/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/processor/DaffodilTDMLDFDLProcessor.scala
index e8d50fb..6e941d1 100644
--- a/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/processor/DaffodilTDMLDFDLProcessor.scala
+++ b/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/processor/DaffodilTDMLDFDLProcessor.scala
@@ -17,12 +17,15 @@
package org.apache.daffodil.tdml.processor
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
import java.nio.channels.Channels
import java.nio.file.Path
import java.nio.file.Paths
import scala.xml.Node
+import org.apache.commons.io.IOUtils
import org.apache.daffodil.api._
import org.apache.daffodil.compiler.Compiler
import org.apache.daffodil.debugger.Debugger
@@ -31,15 +34,21 @@ import org.apache.daffodil.debugger.TraceDebuggerRunner
import org.apache.daffodil.dsom.ExpressionCompilers
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.externalvars.Binding
+import org.apache.daffodil.infoset.DaffodilOutputContentHandler
import org.apache.daffodil.infoset.ScalaXMLInfosetInputter
import org.apache.daffodil.io.InputSourceDataInputStream
import org.apache.daffodil.processors.DataProcessor
import org.apache.daffodil.processors.UnparseResult
import org.apache.daffodil.processors.unparsers.UState
import org.apache.daffodil.tdml.SchemaDataProcessorCache
+import org.apache.daffodil.tdml.TDMLException
import org.apache.daffodil.tdml.TDMLInfosetInputter
import org.apache.daffodil.tdml.TDMLInfosetOutputter
import org.apache.daffodil.util.MaybeULong
+import org.apache.daffodil.xml.XMLUtils
+import org.apache.daffodil.xml.XMLUtils.XMLDifferenceException
+import org.xml.sax.ErrorHandler
+import org.xml.sax.SAXParseException
final class TDMLDFDLProcessorFactory private (
private var compiler: Compiler,
@@ -234,21 +243,22 @@ class DaffodilTDMLDFDLProcessor private (private var dp: DataProcessor) extends
override def getDiagnostics: Seq[Diagnostic] = dp.getDiagnostics
- override def parse(is: java.io.InputStream, lengthLimitInBits: Long): TDMLParseResult = {
-
- val outputter = new TDMLInfosetOutputter()
- outputter.setBlobAttributes(blobDir, blobPrefix, blobSuffix)
+ def parse(uri: java.net.URI, lengthLimitInBits: Long): TDMLParseResult = {
+ val url = uri.toURL
+ val dpInputStream = url.openStream()
+ val saxInputStream = url.openStream()
+ doParseWithBothApis(dpInputStream, saxInputStream, lengthLimitInBits)
+ }
- val dis = InputSourceDataInputStream(is)
- if (lengthLimitInBits >= 0 && lengthLimitInBits % 8 != 0) {
- // Only set the bit limit if the length is not a multiple of 8. In that
- // case, we aren't expected to consume all the data and need a bitLimit
- // to prevent messages about left over bits.
- dis.setBitLimit0b(MaybeULong(lengthLimitInBits))
- }
- val actual = dp.parse(dis, outputter)
+ def parse(arr: Array[Byte], lengthLimitInBits: Long): TDMLParseResult = {
+ val dpInputStream = new ByteArrayInputStream(arr)
+ val saxInputStream = new ByteArrayInputStream(arr)
+ doParseWithBothApis(dpInputStream, saxInputStream, lengthLimitInBits)
+ }
- new DaffodilTDMLParseResult(actual, outputter)
+ override def parse(is: java.io.InputStream, lengthLimitInBits: Long): TDMLParseResult = {
+ val arr = IOUtils.toByteArray(is)
+ parse(arr, lengthLimitInBits)
}
override def unparse(infosetXML: scala.xml.Node, outStream: java.io.OutputStream): TDMLUnparseResult = {
@@ -274,6 +284,80 @@ class DaffodilTDMLDFDLProcessor private (private var dp: DataProcessor) extends
new DaffodilTDMLUnparseResult(actual, outStream)
}
+ def doParseWithBothApis(dpInputStream: java.io.InputStream, saxInputStream: java.io.InputStream,
+ lengthLimitInBits: Long): TDMLParseResult = {
+ val outputter = new TDMLInfosetOutputter()
+ outputter.setBlobAttributes(blobDir, blobPrefix, blobSuffix)
+
+ val xri = dp.newXMLReaderInstance
+ val errorHandler = new DaffodilTDMLSAXErrorHandler()
+ val outputStream = new ByteArrayOutputStream()
+ val saxHandler = new DaffodilOutputContentHandler(outputStream, pretty = true)
+ xri.setContentHandler(saxHandler)
+ xri.setErrorHandler(errorHandler)
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY, blobDir)
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBPREFIX, blobPrefix)
+ xri.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX, blobSuffix)
+
+ val dis = InputSourceDataInputStream(dpInputStream)
+ val sis = InputSourceDataInputStream(saxInputStream)
+ if (lengthLimitInBits >= 0 && lengthLimitInBits % 8 != 0) {
+ // Only set the bit limit if the length is not a multiple of 8. In that
+ // case, we aren't expected to consume all the data and need a bitLimit
+ // to prevent messages about left over bits.
+ dis.setBitLimit0b(MaybeULong(lengthLimitInBits))
+ sis.setBitLimit0b(MaybeULong(lengthLimitInBits))
+ }
+ xri.parse(sis)
+ val actual = dp.parse(dis, outputter)
+
+
+ if (!actual.isError && !errorHandler.isError) {
+ verifySameParseOutput(outputter, outputStream)
+ }
+ verifySameDiagnostics(actual, errorHandler)
+
+ new DaffodilTDMLParseResult(actual, outputter)
+ }
+
+ def verifySameParseOutput(dpOutputter: TDMLInfosetOutputter, outputStream: ByteArrayOutputStream): Unit = {
+ val dpParseOutput = dpOutputter.getResult()
+ val saxParseOutputString = outputStream.toString
+ val saxParseOutput = scala.xml.XML.loadString(saxParseOutputString)
+
+ try {
+ XMLUtils.compareAndReport(
+ dpParseOutput,
+ saxParseOutput,
+ checkNamespaces = true,
+ checkPrefixes = true)
+ } catch {
+ case e: XMLDifferenceException => {
+ throw TDMLException(
+ """SAX parse output (actual) does not match DataProcessor Parse output (expected)""" +
+ "\n" + e.getMessage, None)
+ }
+ }
+ }
+
+ private def verifySameDiagnostics(
+ actual: DFDL.ParseResult,
+ errorHandler: DaffodilTDMLSAXErrorHandler): Unit = {
+ val dpParseDiag = actual.getDiagnostics.map(_.getMessage()).sorted
+ val saxParseDiag = errorHandler.getDiagnostics.map(_.getMessage()).sorted
+
+ if (dpParseDiag != saxParseDiag) {
+ throw TDMLException(
+ """SAX parse diagnostics do not match DataProcessor Parse diagnostics""" +
+ "\n" +
+ """DataProcessor Parse diagnostics: """ + dpParseDiag +
+ (if (saxParseDiag.isEmpty) {
+ "\n No SAX diagnostics were generated."
+ } else {
+ "\n SAX Parse diagnostics: " + saxParseDiag
+ }), None)
+ }
+ }
}
final class DaffodilTDMLParseResult(actual: DFDL.ParseResult, outputter: TDMLInfosetOutputter) extends TDMLParseResult {
@@ -313,3 +397,28 @@ final class DaffodilTDMLUnparseResult(actual: DFDL.UnparseResult, outStream: jav
override def bitPos0b: Long = ustate.bitPos0b
}
+
+class DaffodilTDMLSAXErrorHandler extends ErrorHandler with WithDiagnostics {
+ private var diagnostics: Seq[Diagnostic] = Nil
+ private var errorStatus: Boolean = false
+
+ override def warning(exception: SAXParseException): Unit = {
+ errorStatus = false
+ val embeddedDiagnostic = exception.getCause.asInstanceOf[Diagnostic]
+ diagnostics :+= embeddedDiagnostic
+ }
+
+ override def error(exception: SAXParseException): Unit = {
+ errorStatus = true
+ val embeddedDiagnostic = exception.getCause.asInstanceOf[Diagnostic]
+ diagnostics :+= embeddedDiagnostic
+ }
+
+ override def fatalError(exception: SAXParseException): Unit = {
+ error(exception)
+ }
+
+ override def getDiagnostics: Seq[Diagnostic] = diagnostics
+
+ override def isError: Boolean = errorStatus
+}
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
index 6bf91cf..66f5f8f 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
@@ -802,7 +802,7 @@
<tdml:errors>
<tdml:error>Schema Definition Error</tdml:error>
<tdml:error>newVariableInstances must all be distinct within the same scope</tdml:error>
- <tdml:error>ex:myVar1</tdml:error>
+ <tdml:error>myVar1</tdml:error>
</tdml:errors>
</tdml:parserTestCase>