blob: 1f7fb908d3f2666ee926df914072c5a0127b6a28 [file] [log] [blame]
/*
* 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.processors.unparsers
import org.apache.daffodil.processors._
import org.apache.daffodil.infoset._
import org.apache.daffodil.processors.RuntimeData
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.MaybeInt
import org.apache.daffodil.util.Maybe._
case class ChoiceBranchMap(
lookupTable: Map[ChoiceBranchEvent, Unparser],
unmappedDefault: Option[Unparser])
extends Serializable {
def get(cbe: ChoiceBranchEvent): Maybe[Unparser] = {
val fromTable = lookupTable.get(cbe)
val res =
if (fromTable.isDefined) One(fromTable.get)
else {
//
// There must be an unmapped default in this case
// because otherwise the map is incomplete.
//
if (unmappedDefault.isDefined)
One(unmappedDefault.get)
else
Nope
}
res
}
def defaultUnparser = unmappedDefault
def childProcessors = lookupTable.values.toSeq ++ unmappedDefault
def keys = lookupTable.keys
}
/*
* Sometimes choices have an empty branch (e.g. a sequence that just has an
* assert in it) that optimizes to a NadaUnparser. NadaUnparsers should all
* optimize out, but the ChoiceCombinatorUnparser still expects to have
* something in these cases. So we have a special empty branch unparser that
* does nothing, but gives the ChoiceCombinatorUnparser something that it can
* use.
*/
class ChoiceBranchEmptyUnparser(val context: RuntimeData)
extends PrimUnparserNoData {
override lazy val runtimeDependencies = Vector()
def unparse(state: UState): Unit = {
//do nothing
}
}
class ChoiceCombinatorUnparser(
mgrd: ModelGroupRuntimeData,
choiceBranchMap: ChoiceBranchMap,
choiceLengthInBits: MaybeInt)
extends CombinatorUnparser(mgrd)
with ToBriefXMLImpl {
override def nom = "Choice"
override val runtimeDependencies = Vector()
override val childProcessors = choiceBranchMap.childProcessors.toVector
def unparse(state: UState): Unit = {
if (state.withinHiddenNest) {
val branchForUnparseIfHidden = choiceBranchMap.defaultUnparser
branchForUnparseIfHidden.get.unparse1(state)
} else {
state.pushTRD(mgrd)
val event: InfosetAccessor = state.inspectOrError
val key: ChoiceBranchEvent = event match {
//
// The ChoiceBranchStartEvent(...) is not a case class constructor. It is a
// hash-table lookup for a cached value. This avoids constructing these
// objects over and over again.
//
case e if e.isStart && e.isElement => ChoiceBranchStartEvent(e.erd.namedQName)
case e if e.isEnd && e.isElement => ChoiceBranchEndEvent(e.erd.namedQName)
case e if e.isStart && e.isArray => ChoiceBranchStartEvent(e.erd.namedQName)
case e if e.isEnd && e.isArray => ChoiceBranchEndEvent(e.erd.namedQName)
}
val maybeChildUnparser = choiceBranchMap.get(key)
if (maybeChildUnparser.isEmpty) {
UnparseError(One(mgrd.schemaFileLocation), One(state.currentLocation),
"Found next element %s, but expected one of %s.",
key.qname.toExtendedSyntax,
choiceBranchMap.keys.map {
_.qname.toExtendedSyntax
}.mkString(", "))
}
val childUnparser = maybeChildUnparser.get
state.popTRD(mgrd)
state.pushTRD(childUnparser.context.asInstanceOf[TermRuntimeData])
if (choiceLengthInBits.isDefined) {
val suspendableOp = new ChoiceUnusedUnparserSuspendableOperation(mgrd, choiceLengthInBits.get)
val choiceUnusedUnparser = new ChoiceUnusedUnparser(mgrd, choiceLengthInBits.get, suspendableOp)
suspendableOp.captureDOSStartForChoiceUnused(state)
childUnparser.unparse1(state)
suspendableOp.captureDOSEndForChoiceUnused(state)
choiceUnusedUnparser.unparse(state)
} else {
childUnparser.unparse1(state)
}
state.popTRD(childUnparser.context.asInstanceOf[TermRuntimeData])
}
}
}
class DelimiterStackUnparser(
initiatorOpt: Maybe[InitiatorUnparseEv],
separatorOpt: Maybe[SeparatorUnparseEv],
terminatorOpt: Maybe[TerminatorUnparseEv],
ctxt: TermRuntimeData,
bodyUnparser: Unparser)
extends CombinatorUnparser(ctxt) {
override def nom = "DelimiterStack"
override def toBriefXML(depthLimit: Int = -1): String = {
if (depthLimit == 0) "..." else
"<DelimiterStack initiator='" + initiatorOpt +
"' separator='" + separatorOpt +
"' terminator='" + terminatorOpt + "'>" +
bodyUnparser.toBriefXML(depthLimit - 1) +
"</DelimiterStack>"
}
override lazy val childProcessors = Vector(bodyUnparser)
override lazy val runtimeDependencies = (initiatorOpt.toList ++ separatorOpt.toList ++ terminatorOpt.toList).toVector
def unparse(state: UState): Unit = {
// Evaluate Delimiters
val init = if (initiatorOpt.isDefined) initiatorOpt.get.evaluate(state) else EmptyDelimiterStackUnparseNode.empty
val sep = if (separatorOpt.isDefined) separatorOpt.get.evaluate(state) else EmptyDelimiterStackUnparseNode.empty
val term = if (terminatorOpt.isDefined) terminatorOpt.get.evaluate(state) else EmptyDelimiterStackUnparseNode.empty
val node = DelimiterStackUnparseNode(init, sep, term)
state.pushDelimiters(node)
bodyUnparser.unparse1(state)
state.popDelimiters
}
}
class DynamicEscapeSchemeUnparser(escapeScheme: EscapeSchemeUnparseEv, ctxt: TermRuntimeData, bodyUnparser: Unparser)
extends CombinatorUnparser(ctxt) {
override def nom = "EscapeSchemeStack"
override lazy val childProcessors = Vector(bodyUnparser)
override lazy val runtimeDependencies = Vector(escapeScheme)
def unparse(state: UState): Unit = {
// evaluate the dynamic escape scheme in the correct scope. the resulting
// value is cached in the Evaluatable (since it is manually cached) and
// future parsers/evaluatables that use this escape scheme will use that
// cached value.
escapeScheme.newCache(state)
escapeScheme.evaluate(state)
// Unparse
bodyUnparser.unparse1(state)
// invalidate the escape scheme cache
escapeScheme.invalidateCache(state)
}
}