| /* |
| * 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.unparsers.runtime1 |
| |
| import scala.collection.mutable.Buffer |
| |
| import org.apache.daffodil.lib.exceptions.Assert |
| import org.apache.daffodil.lib.schema.annotation.props.SeparatorSuppressionPolicy |
| import org.apache.daffodil.lib.schema.annotation.props.SeparatorSuppressionPolicy._ |
| import org.apache.daffodil.lib.schema.annotation.props.gen.OccursCountKind |
| import org.apache.daffodil.lib.schema.annotation.props.gen.SeparatorPosition |
| import org.apache.daffodil.lib.schema.annotation.props.gen.SeparatorPosition._ |
| import org.apache.daffodil.lib.util.Maybe |
| import org.apache.daffodil.lib.util.MaybeInt |
| import org.apache.daffodil.runtime1.processors.ModelGroupRuntimeData |
| import org.apache.daffodil.runtime1.processors.unparsers._ |
| import org.apache.daffodil.runtime1.processors.{ |
| ElementRuntimeData, |
| SequenceRuntimeData, |
| TermRuntimeData, |
| } |
| |
| trait Separated { self: SequenceChildUnparser => |
| |
| def sep: Unparser |
| def spos: SeparatorPosition |
| def ssp: SeparatorSuppressionPolicy |
| def zeroLengthDetector: ZeroLengthDetector |
| def isPotentiallyTrailing: Boolean |
| |
| def isKnownStaticallyNotToSuppressSeparator: Boolean |
| |
| def isDeclaredLast: Boolean |
| |
| def isPositional: Boolean |
| |
| val childProcessors = Vector(childUnparser, sep) |
| } |
| |
| sealed abstract class ScalarOrderedSeparatedSequenceChildUnparserBase( |
| childUnparser: Unparser, |
| srd: SequenceRuntimeData, |
| trd: TermRuntimeData, |
| override val sep: Unparser, |
| override val spos: SeparatorPosition, |
| override val ssp: SeparatorSuppressionPolicy, |
| override val zeroLengthDetector: ZeroLengthDetector, |
| override val isPotentiallyTrailing: Boolean, |
| override val isKnownStaticallyNotToSuppressSeparator: Boolean, |
| override val isPositional: Boolean, |
| override val isDeclaredLast: Boolean, |
| ) extends SequenceChildUnparser(childUnparser, srd, trd) |
| with Separated { |
| |
| override def unparse(state: UState) = childUnparser.unparse1(state) |
| } |
| |
| class ScalarOrderedSeparatedSequenceChildUnparser( |
| childUnparser: Unparser, |
| srd: SequenceRuntimeData, |
| trd: TermRuntimeData, |
| sep: Unparser, |
| spos: SeparatorPosition, |
| ssp: SeparatorSuppressionPolicy, |
| zlDetector: ZeroLengthDetector, |
| isPotentiallyTrailing: Boolean, |
| isKnownStaticallyNotToSuppressSeparator: Boolean, |
| isPositional: Boolean, |
| isDeclaredLast: Boolean, |
| ) extends ScalarOrderedSeparatedSequenceChildUnparserBase( |
| childUnparser, |
| srd, |
| trd, |
| sep, |
| spos, |
| ssp, |
| zlDetector, |
| isPotentiallyTrailing, |
| isKnownStaticallyNotToSuppressSeparator, |
| isPositional, |
| isDeclaredLast, |
| ) |
| |
| class RepOrderedSeparatedSequenceChildUnparser( |
| childUnparser: Unparser, |
| srd: SequenceRuntimeData, |
| erd: ElementRuntimeData, |
| override val sep: Unparser, |
| override val spos: SeparatorPosition, |
| override val ssp: SeparatorSuppressionPolicy, // need for diagnostics perhaps |
| override val zeroLengthDetector: ZeroLengthDetector, |
| override val isPotentiallyTrailing: Boolean, |
| override val isKnownStaticallyNotToSuppressSeparator: Boolean, |
| override val isPositional: Boolean, |
| override val isDeclaredLast: Boolean, |
| ) extends RepeatingChildUnparser(childUnparser, srd, erd) |
| with Separated { |
| |
| override def checkArrayPosAgainstMaxOccurs(state: UState) = |
| state.arrayIterationPos <= maxRepeats(state) |
| } |
| |
| class OrderedSeparatedSequenceUnparser( |
| rd: SequenceRuntimeData, |
| ssp: SeparatorSuppressionPolicy, |
| spos: SeparatorPosition, |
| sepMtaAlignmentMaybe: MaybeInt, |
| sepMtaUnparserMaybe: Maybe[Unparser], |
| sep: Unparser, |
| childUnparsersArg: Vector[SequenceChildUnparser], |
| ) extends OrderedSequenceUnparserBase( |
| rd, |
| (childUnparsersArg :+ sep) ++ sepMtaUnparserMaybe.toSeq, |
| ) { |
| |
| private val childUnparsers = |
| childUnparsersArg.asInstanceOf[Seq[SequenceChildUnparser with Separated]] |
| |
| /** |
| * Unparses one occurrence with associated separator (non-suppressable). |
| */ |
| protected def unparseOne( |
| unparser: SequenceChildUnparser, |
| trd: TermRuntimeData, |
| state: UState, |
| ): Unit = { |
| |
| if (trd.isRepresented) { |
| spos match { |
| case Prefix => { |
| unparseJustSeparator(state) |
| unparser.unparse1(state) |
| } |
| case Infix => { |
| if (state.groupPos > 1) { |
| unparseJustSeparator(state) |
| } |
| unparser.unparse1(state) |
| } |
| case Postfix => { |
| unparser.unparse1(state) |
| unparseJustSeparator(state) |
| } |
| } |
| } else { |
| Assert.invariant(!trd.isRepresented) |
| unparser.unparse1(state) |
| } |
| } |
| |
| /** |
| * Unparses just the separator, as well as any mandatory text alignment if necessary |
| * |
| * Does not deals with infix boundary condition. |
| */ |
| private def unparseJustSeparator(state: UState): Unit = { |
| if (sepMtaUnparserMaybe.isDefined) { |
| // we know we are unparsing a separator here, so we must also unparse |
| // mandatory text alignment for that separator. If we didn't staticaly |
| // determine MTA isn't necessary, we must unparse the MTA. This might |
| // lead to a suspension, which is okay in this case because this logic is |
| // not in a suspension, so nested suspensions are avoided. |
| sepMtaUnparserMaybe.get.unparse1(state) |
| } |
| sep.unparse1(state) |
| } |
| |
| /** |
| * Unparses the separator only, which might be optional. The suspension |
| * handles determine if the separator should be unparsed as well as if |
| * alignment is needed, and avoids issues with nested suspensions. |
| * |
| * Does not have to deal with infix and first child. |
| * |
| * FIXME: this function is not used anywhere and appears to be dead code. |
| * This is commented out for now so as to not affect code coverage. See |
| * DAFFODIL-2405 and potentially related DAFFODIL-2219 to determine the |
| * future of this code. |
| */ |
| //private def unparseJustSeparatorWithTrailingSuppression( |
| // trd: TermRuntimeData, |
| // state: UState, |
| // trailingSuspendedOps: Buffer[SuppressableSeparatorUnparserSuspendableOperation]): Unit = { |
| // |
| // // We don't know if the unparse will result in zero length or not. We have |
| // // to use a suspendable unparser here for the separator which suspends |
| // // until it is known whether the unparse of the contents were ZL or not. If |
| // // the suspension determines that the field is non-zero length then the |
| // // suspenion must also unparser mandatory text alignment for the separator. |
| // // This cannot be done with a standard MTA alignment unparser since that is |
| // // a suspension and suspensions cannot create suspensions. This this |
| // // suspension is also responsible for unparsing alignment if the separator |
| // // should be unparsed. |
| // |
| // val suspendableOp = new SuppressableSeparatorUnparserSuspendableOperation(sepMtaAlignmentMaybe, sep, trd) |
| // // TODO: merge these two objects. We can allocate just one thing here. |
| // val suppressableSep = SuppressableSeparatorUnparser(sep, trd, suspendableOp) |
| // |
| // suppressableSep.unparse1(state) |
| // trailingSuspendedOps += suspendableOp |
| //} |
| |
| /** |
| * Unparses an entire sequence, including both scalar and array/optional children. |
| */ |
| protected def unparse(state: UState): Unit = { |
| ssp match { |
| case Never => |
| unparseWithNoSuppression(state) |
| case _ => |
| unparseWithSuppression(state) |
| } |
| } |
| |
| private def unparseOneWithSuppression( |
| unparser: SequenceChildUnparser, |
| trd: TermRuntimeData, |
| state: UState, |
| trailingSuspendedOps: Buffer[SuppressableSeparatorUnparserSuspendableOperation], |
| onlySeparatorFlag: Boolean, |
| ): Unit = { |
| val doUnparseChild = !onlySeparatorFlag |
| // We don't know if the unparse will result in zero length or not. We have |
| // to use a suspendable unparser here for the separator which suspends |
| // until it is known whether the unparse of the contents were ZL or not. If |
| // the suspension determines that the field is non-zero length then the |
| // suspension must also unparse mandatory text alignment for the separator. |
| // This cannot be done with a standard MTA alignment unparser since that is |
| // a suspension and suspensions cannot create suspensions. This suspension |
| // is also responsible for unparsing alignment if the separator should be |
| // unparsed. |
| // |
| // infix, prefix, postfix matters here, because the separator comes after |
| // for postfix. |
| |
| if ((spos eq Infix) && state.groupPos == 1) { |
| // no separator possible; hence, no suppression |
| if (doUnparseChild) unparser.unparse1(state) |
| } else { |
| val suspendableOp = |
| new SuppressableSeparatorUnparserSuspendableOperation(sepMtaAlignmentMaybe, sep, trd) |
| // TODO: merge these two objects. We can allocate just one thing here. |
| val suppressableSep = SuppressableSeparatorUnparser(sep, trd, suspendableOp) |
| |
| spos match { |
| case Prefix | Infix => { |
| suppressableSep.unparse1(state) |
| if (doUnparseChild) unparser.unparse1(state) |
| ssp match { |
| case AnyEmpty => { |
| suspendableOp.captureStateAtEndOfPotentiallyZeroLengthRegionFollowingTheSeparator( |
| state, |
| ) |
| } |
| case TrailingEmpty | TrailingEmptyStrict => { |
| trailingSuspendedOps += suspendableOp |
| } |
| case Never => Assert.invariantFailed("Should not be ssp Never") |
| } |
| } |
| case Postfix => { |
| ssp match { |
| case AnyEmpty => { |
| suspendableOp.captureDOSForStartOfSeparatedRegionBeforePostfixSeparator(state) |
| if (doUnparseChild) unparser.unparse1(state) |
| suspendableOp.captureDOSForEndOfSeparatedRegionBeforePostfixSeparator(state) |
| suppressableSep.unparse1(state) |
| suspendableOp.captureStateAtEndOfPotentiallyZeroLengthRegionFollowingTheSeparator( |
| state, |
| ) |
| } |
| case TrailingEmpty | TrailingEmptyStrict => { |
| suspendableOp.captureDOSForStartOfSeparatedRegionBeforePostfixSeparator(state) |
| if (doUnparseChild) unparser.unparse1(state) |
| suspendableOp.captureDOSForEndOfSeparatedRegionBeforePostfixSeparator(state) |
| suppressableSep.unparse1(state) |
| trailingSuspendedOps += suspendableOp |
| } |
| case Never => Assert.invariantFailed("Should not be ssp Never") |
| } |
| } |
| } |
| } |
| } |
| |
| private def unparseWithSuppression(state: UState): Unit = { |
| |
| state.groupIndexStack.push(1L) // one-based indexing |
| |
| var index = 0 |
| var doUnparser = false |
| val limit = childUnparsers.length |
| |
| lazy val trailingSuspendedOps = Buffer[SuppressableSeparatorUnparserSuspendableOperation]() |
| |
| while (index < limit) { |
| val childUnparser = childUnparsers(index) |
| val trd = childUnparser.trd |
| state.pushTRD( |
| trd, |
| ) // because we inspect before we call the unparse1 for the child unparser. |
| val zlDetector = childUnparser.zeroLengthDetector |
| childUnparser match { |
| case unparser: RepOrderedSeparatedSequenceChildUnparser => { |
| state.arrayIterationIndexStack.push(1L) |
| state.occursIndexStack.push(1L) |
| val erd = unparser.erd |
| var numOccurrences = 0 |
| val maxReps = unparser.maxRepeats(state) |
| // |
| // The number of occurrances we unparse is always exactly driven |
| // by the number of infoset events for the repeating/optional element. |
| // |
| // For RepUnparser - array/optional case - in all cases we should get a |
| // startArray event. That is, defaulting of required array elements |
| // (up to minOccurs) happens elsewhere, and we get events for all of those |
| // here. |
| // |
| // If we don't get any array element events, then the element must be |
| // entirely optional, so we get no events for it at all. |
| // |
| if (state.inspect) { |
| val ev = state.inspectAccessor |
| val isArr = erd.isArray |
| if (ev.isStart && (isArr || erd.isOptional)) { |
| if (ev.erd eq erd) { |
| |
| // |
| // Note: leaving in some of these println, since debugger for unparsing is so inadequate currently. |
| // This is the only way to figure out what is going on. |
| // |
| // System.err.println("Starting unparse of array/opt %s. Array Index Stack is: %s".format( |
| // erd.namedQName, state.arrayIndexStack)) |
| // |
| |
| // StartArray for this unparser's array element |
| // |
| unparser.startArrayOrOptional(state) |
| while ({ |
| doUnparser = unparser.shouldDoUnparser(unparser, state) |
| doUnparser |
| }) { |
| // |
| // These are so we can check invariants on these stacks being |
| // pushed and popped reliably, and incremented only once |
| // |
| val arrayIterationIndexBefore = state.arrayIterationPos |
| val arrayIterationIndexStackDepthBefore = |
| state.arrayIterationIndexStack.length |
| val occursIndexBefore = state.occursPos |
| val occursIndexStackDepthBefore = state.occursIndexStack.length |
| val groupIndexBefore = state.groupPos |
| val groupIndexStackDepthBefore = state.groupIndexStack.length |
| |
| Assert.invariant( |
| erd.isRepresented, |
| ) // since this is an array, can't have inputValueCalc |
| |
| if (isArr) |
| if (state.dataProc.isDefined) |
| state.dataProc.get.beforeRepetition(state, this) |
| |
| if ( |
| unparser.isKnownStaticallyNotToSuppressSeparator || { |
| val isKnownNonZeroLength = |
| zlDetector.isKnownNonZeroLength(state.inspectAccessor.info.element) |
| isKnownNonZeroLength |
| } |
| ) { |
| unparseOne(unparser, erd, state) |
| } else { |
| unparseOneWithSuppression( |
| unparser, |
| erd, |
| state, |
| trailingSuspendedOps, |
| onlySeparatorFlag = false, |
| ) |
| } |
| numOccurrences += 1 |
| Assert.invariant( |
| state.arrayIterationIndexStack.length == arrayIterationIndexStackDepthBefore, |
| ) |
| state.moveOverOneArrayIterationIndexOnly() |
| Assert.invariant(state.arrayIterationPos == arrayIterationIndexBefore + 1) |
| |
| Assert.invariant(state.occursIndexStack.length == occursIndexStackDepthBefore) |
| state.moveOverOneOccursIndexOnly() |
| Assert.invariant(state.occursPos == occursIndexBefore + 1) |
| |
| Assert.invariant(state.groupIndexStack.length == groupIndexStackDepthBefore) |
| state.moveOverOneGroupIndexOnly() // array elements are always represented. |
| Assert.invariant(state.groupPos == groupIndexBefore + 1) |
| |
| if (isArr) |
| if (state.dataProc.isDefined) |
| state.dataProc.get.afterRepetition(state, this) |
| |
| } |
| numOccurrences = unparsePositionallyRequiredSeps( |
| unparser, |
| erd, |
| state, |
| numOccurrences, |
| trailingSuspendedOps, |
| ) |
| unparser.checkFinalOccursCountBetweenMinAndMaxOccurs( |
| state, |
| unparser, |
| numOccurrences, |
| maxReps, |
| state.arrayIterationPos - 1, |
| ) |
| unparser.endArrayOrOptional(erd, state) |
| } else { |
| // |
| // start array for some other array. Not this one. |
| // |
| Assert.invariant(erd.minOccurs == 0L) |
| numOccurrences = unparsePositionallyRequiredSeps( |
| unparser, |
| erd, |
| state, |
| numOccurrences, |
| trailingSuspendedOps, |
| ) |
| } |
| |
| } else if (ev.isStart) { |
| Assert.invariant(!ev.erd.isArray && !erd.isOptional) |
| val eventNQN = ev.erd.namedQName |
| Assert.invariant(eventNQN != erd.namedQName) |
| // |
| // start of scalar. |
| // That has to be for a different element later in the sequence |
| // since this one has a RepUnparser (i.e., is NOT scalar) |
| // |
| numOccurrences = unparsePositionallyRequiredSeps( |
| unparser, |
| erd, |
| state, |
| numOccurrences, |
| trailingSuspendedOps, |
| ) |
| } else { |
| Assert.invariant(ev.isEnd && ev.erd.isComplexType) |
| unparser.checkFinalOccursCountBetweenMinAndMaxOccurs( |
| state, |
| unparser, |
| numOccurrences, |
| maxReps, |
| 0, |
| ) |
| numOccurrences = unparsePositionallyRequiredSeps( |
| unparser, |
| erd, |
| state, |
| numOccurrences, |
| trailingSuspendedOps, |
| ) |
| } |
| } else { |
| // no event (state.inspect returned false) |
| Assert.invariantFailed("No event for unparsing.") |
| } |
| state.arrayIterationIndexStack.pop() |
| state.occursIndexStack.pop() |
| } |
| case scalarUnparser => |
| trd match { |
| case erd: ElementRuntimeData => { |
| // scalar element case. These always get associated separator (if represented) |
| unparseOne(scalarUnparser, trd, state) // handles case of non-represented. |
| if (erd.isRepresented) |
| state.moveOverOneGroupIndexOnly() |
| } |
| case mgrd: ModelGroupRuntimeData => { |
| // |
| // There are cases where we suppress the separator associated with a model group child |
| // |
| // The model group must have no required syntax (initiator/terminator nor alignment) |
| // and all optional children (none of which can be present if there are unsuppressed separators) |
| // |
| // The model group must be zero-length |
| // The SSP must be AnyEmpty or |
| // The SSP must be TrailingEmpty | TrailingEmptyStrict, AND the model group must be |
| // potentially trailing AND actually trailing. |
| // |
| // Most of the above is static information. Only whether the length is nonZero or zero, and |
| // whether it is actually trailing are run-time concepts. |
| // |
| if (scalarUnparser.isKnownStaticallyNotToSuppressSeparator) { |
| unparseOne(scalarUnparser, trd, state) |
| } else { |
| unparseOneWithSuppression( |
| scalarUnparser, |
| trd, |
| state, |
| trailingSuspendedOps, |
| onlySeparatorFlag = false, |
| ) |
| } |
| state.moveOverOneGroupIndexOnly() |
| } |
| } |
| } |
| state.popTRD(trd) |
| index += 1 |
| } |
| ssp match { |
| case TrailingEmpty | TrailingEmptyStrict => { |
| // |
| // For trailing empty suppression, things have to be actually trailing in the sequence. |
| // By setting the after-state to the complete end of the sequence, we insure that we suppress |
| // separators only if there is nothing at all after them. |
| // |
| // Note: Quadratic behavior here. |
| // When determining if trailing, each suspended separator will examine a list of length N, where |
| // N is number of children in the list. It will examine them only to determine if the entries are |
| // zero-length or not. But these chains could in principle have shared structure so that there |
| // would, in principle, be only one chain length N, not Sum(for i from 1 to N)of(N - i), whichis O(n^2). |
| // |
| // In practice, these chains are short (separator suppression seldom applies to long arrays, usually to |
| // optional fields near the end of a record. So the above may simply not matter. |
| // |
| for (suspendedOp <- trailingSuspendedOps.toSeq) { |
| suspendedOp.captureStateAtEndOfPotentiallyZeroLengthRegionFollowingTheSeparator(state) |
| } |
| } |
| case _ => // do nothing |
| } |
| state.groupIndexStack.pop() |
| } |
| |
| private def unparsePositionallyRequiredSeps( |
| unparserArg: SequenceChildUnparser, |
| erd: ElementRuntimeData, |
| state: UState, |
| numOccurs: Int, |
| trailingSuspendedOps: Buffer[SuppressableSeparatorUnparserSuspendableOperation], |
| ): Int = { |
| var numOccurrences = numOccurs |
| unparserArg match { |
| case unparser: RepOrderedSeparatedSequenceChildUnparser => { |
| // note that if we are unparsing an array/optional, we only add positionally |
| // required separators for occursCountKind="implicit". All other |
| // occursCountKind's use the number of elements in the infoset to determine |
| // the number of separators, which would have already been unparsed prior to |
| // this function being called. |
| if ( |
| (unparser.ock eq OccursCountKind.Implicit) && |
| unparser.isPositional && unparser.isBoundedMax && |
| (!unparser.isDeclaredLast || !unparser.isPotentiallyTrailing) |
| ) { |
| val maxReps = unparser.maxRepeats(state) |
| while (numOccurrences < maxReps) { |
| unparseOneWithSuppression( |
| unparser, |
| erd, |
| state, |
| trailingSuspendedOps, |
| onlySeparatorFlag = true, |
| ) |
| state.moveOverOneArrayIterationIndexOnly() |
| state.moveOverOneOccursIndexOnly() |
| state.moveOverOneGroupIndexOnly() |
| numOccurrences += 1 |
| } |
| } |
| } |
| case _ => Assert.invariantFailed("Not a repeating element") |
| } |
| numOccurrences |
| } |
| |
| private def unparseWithNoSuppression(state: UState): Unit = { |
| |
| state.groupIndexStack.push(1L) // one-based indexing |
| |
| var index = 0 |
| var doUnparser = false |
| val limit = childUnparsers.length |
| |
| while (index < limit) { |
| val childUnparser = childUnparsers(index) |
| val trd = childUnparser.trd |
| state.pushTRD(trd) // because we inspect before we invoke child unparser |
| // |
| // Unparsing an ordered sequence depends on the incoming |
| // stream of infoset events matching up with the order that |
| // they are expected as the unparser recurses through the |
| // child term unparsers. |
| // |
| childUnparser match { |
| case unparser: RepOrderedSeparatedSequenceChildUnparser => { |
| state.arrayIterationIndexStack.push(1L) |
| state.occursIndexStack.push(1L) |
| val erd = unparser.erd |
| Assert.invariant(erd.isArray || erd.isOptional) |
| Assert.invariant(erd.isRepresented) // arrays/optionals cannot have inputValueCalc |
| |
| var numOccurrences = 0 |
| val maxReps = unparser.maxRepeats(state) |
| |
| Assert.invariant(state.inspect) |
| val ev = state.inspectAccessor |
| val isArr = erd.isArray |
| |
| // If the event is for this Rep unparser, we need to consume the StartArray event |
| if (ev.erd eq erd) { |
| unparser.startArrayOrOptional(state) |
| } |
| |
| // Unparse each occurrence of this array in the infoset. Note that there could be zero |
| // occurrences |
| while ({ |
| doUnparser = unparser.shouldDoUnparser(unparser, state) |
| doUnparser |
| }) { |
| // |
| // These are so we can check invariants on these stacks being |
| // pushed and popped reliably, and incremented only once |
| // |
| val arrayIterationIndexBefore = state.arrayIterationPos |
| val arrayIterationIndexStackDepthBefore = |
| state.arrayIterationIndexStack.length |
| val occursIndexBefore = state.occursPos |
| val occursIndexStackDepthBefore = state.occursIndexStack.length |
| val groupIndexBefore = state.groupPos |
| val groupIndexStackDepthBefore = state.groupIndexStack.length |
| |
| if (isArr && state.dataProc.isDefined) |
| state.dataProc.get.beforeRepetition(state, this) |
| |
| unparseOne(unparser, erd, state) |
| numOccurrences += 1 |
| Assert.invariant( |
| state.arrayIterationIndexStack.length == arrayIterationIndexStackDepthBefore, |
| ) |
| state.moveOverOneArrayIterationIndexOnly() |
| Assert.invariant(state.arrayIterationPos == arrayIterationIndexBefore + 1) |
| |
| Assert.invariant(state.occursIndexStack.length == occursIndexStackDepthBefore) |
| state.moveOverOneOccursIndexOnly() |
| Assert.invariant(state.occursPos == occursIndexBefore + 1) |
| |
| Assert.invariant(state.groupIndexStack.length == groupIndexStackDepthBefore) |
| state.moveOverOneGroupIndexOnly() // array elements are always represented. |
| Assert.invariant(state.groupPos == groupIndexBefore + 1) |
| |
| if (isArr && state.dataProc.isDefined) |
| state.dataProc.get.afterRepetition(state, this) |
| } |
| |
| // If not enough occurrences are in the infoset, we output extra separators because |
| // we are unparsing with no suppression |
| if (maxReps > numOccurrences) { |
| var numExtraSeps = { |
| val sepsNeeded = erd.maxOccurs - numOccurrences |
| if ((spos eq Infix) && state.groupPos == 1) { |
| // If separatorPosition is infix and we haven't output anything for this sequence |
| // yet, then we need one less extra separator, since the separator is skipped |
| // for the first instance of infix separators. |
| sepsNeeded - 1 |
| } else { |
| sepsNeeded |
| } |
| } |
| while (numExtraSeps > 0) { |
| unparseJustSeparator(state) |
| numExtraSeps -= 1 |
| } |
| } |
| |
| unparser.checkFinalOccursCountBetweenMinAndMaxOccurs( |
| state, |
| unparser, |
| numOccurrences, |
| maxReps, |
| state.arrayIterationPos - 1, |
| ) |
| |
| // If the event is for this Rep unparser, we need to consume the EndArray event |
| if (ev.erd eq erd) { |
| unparser.endArrayOrOptional(erd, state) |
| } |
| |
| state.arrayIterationIndexStack.pop() |
| state.occursIndexStack.pop() |
| } |
| case scalarUnparser => { |
| unparseOne(scalarUnparser, trd, state) |
| // only move over in group if the scalar "thing" is an element |
| // that is represented. |
| trd match { |
| case erd: ElementRuntimeData if (!erd.isRepresented) => // ok, skip group advance |
| case _ => state.moveOverOneGroupIndexOnly() |
| } |
| } |
| } |
| state.popTRD(trd) |
| index += 1 |
| } |
| state.groupIndexStack.pop() |
| } |
| } |