| /* |
| * 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) |
| } |
| } |