diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ChoiceGroup.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ChoiceGroup.scala
index 927bb38987..e662e1ce6b 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ChoiceGroup.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ChoiceGroup.scala
@@ -159,7 +159,7 @@ abstract class ChoiceTermBase(
* Open issues:
* 1) Is alignment or leading/trailing skip to be considered syntax. Alignment might not be there.
* 2) What about an empty sequence that only carries statement annotations such as dfdl:assert or
- * dfdl:setVariable
+ * dfdl:setVariable
*
* This latter need to be allowed, because while they do not have known required syntax they do
* have to be executed for side-effect.
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/LocalElementMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/LocalElementMixin.scala
index 0e9aaf57e9..d00cbc2b3f 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/LocalElementMixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/LocalElementMixin.scala
@@ -61,10 +61,8 @@ trait LocalElementMixin extends ParticleMixin with LocalElementGrammarMixin {
else if (representation =:= Representation.Binary) true
else false
}
- case LengthKind.EndOfParent if isComplexType =>
- notYetImplemented("lengthKind='endOfParent' for complex type")
- case LengthKind.EndOfParent =>
- notYetImplemented("lengthKind='endOfParent' for simple type")
+ // we can rarely statically know if an endOfParent element must have non-zero length
+ case LengthKind.EndOfParent => false
}
res
}.value
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/RuntimePropertyMixins.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/RuntimePropertyMixins.scala
index 4145d8fed5..f409ec5b01 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/RuntimePropertyMixins.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/RuntimePropertyMixins.scala
@@ -449,10 +449,12 @@ trait ElementRuntimeValuedPropertiesMixin
case (Explicit, Text, AnySimpleType) => (lengthUnits, 0L)
case (Prefixed, _, String) => (lengthUnits, minLengthLong)
case (Prefixed, Text, _) => (lengthUnits, textOutputMinLength)
- case (Pattern, _, String) => (lengthUnits, minLengthLong)
- case (Pattern, Text, _) => (lengthUnits, textOutputMinLength)
- case (Delimited, _, String) => (lengthUnits, minLengthLong)
- case (Delimited, Text, _) => (lengthUnits, textOutputMinLength)
+ case (Pattern, _, String) => (LengthUnits.Characters, minLengthLong)
+ case (Pattern, Text, _) => (LengthUnits.Characters, textOutputMinLength)
+ case (Delimited, _, String) => (LengthUnits.Characters, minLengthLong)
+ case (Delimited, Text, _) => (LengthUnits.Characters, textOutputMinLength)
+ case (EndOfParent, _, String) => (LengthUnits.Characters, minLengthLong)
+ case (EndOfParent, Text, _) => (LengthUnits.Characters, textOutputMinLength)
case _ => (LengthUnits.Bits, 0L) // anything else. This shuts off checking a min.
}
res
@@ -521,10 +523,18 @@ trait ElementRuntimeValuedPropertiesMixin
((repElement.lengthKind._eq_(LengthKind.Implicit)) && repElement.isSimpleType)
) {
repElement.maybeUnparseTargetLengthInBitsEv
- } else if (this.isDelimitedPrefixedPatternWithPadding) {
+ } else if (this.lengthKind == LengthKind.EndOfParent) {
+ // Per spec 12.3.6/13.2: EOP elements pad to the full "available length" of the
+ // enclosing box (constant-length element or explicit-length choice) when one is known.
+ // Fall back to minLength only when no constant box is found.
+ schemaSet.root.eopElementInfoMap.get(this).flatMap(_._2) match {
+ case Some(boxEv) => One(boxEv)
+ case None => if (isMinLengthKindWithPadding) One(minLengthInBitsEv) else Nope
+ }
+ } else if (this.isMinLengthKindWithPadding) {
//
- // if delimited but there is a min length, we just need the min
- // length. There is no target length other than it.
+ // if delimited/pattern/prefixed but there is a min length,
+ // we just need the min length. There is no target length other than it.
//
val ev = minLengthInBitsEv
One(ev)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/SequenceGroup.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/SequenceGroup.scala
index a6a9bc0037..210797ed01 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/SequenceGroup.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/SequenceGroup.scala
@@ -269,7 +269,6 @@ abstract class SequenceGroupTermBase(xml: Node, lexicalParent: SchemaComponent,
case SequenceKind.Ordered => true
case SequenceKind.Unordered => false
}
-
}
/**
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Term.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Term.scala
index 7558483d5b..626caf9d8f 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Term.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Term.scala
@@ -27,9 +27,7 @@ import org.apache.daffodil.lib.iapi.WarnID
import org.apache.daffodil.lib.schema.annotation.props.Found
import org.apache.daffodil.lib.schema.annotation.props.NotFound
import org.apache.daffodil.lib.schema.annotation.props.SeparatorSuppressionPolicy
-import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind
-import org.apache.daffodil.lib.schema.annotation.props.gen.OccursCountKind
-import org.apache.daffodil.lib.schema.annotation.props.gen.YesNo
+import org.apache.daffodil.lib.schema.annotation.props.gen.*
/**
* Mixin for objects that are shared, but have consistency checks to be run
@@ -562,4 +560,11 @@ trait Term
}
}
+ final lazy val realEOPElementChildren: Seq[ElementBase] = {
+ termChildren.flatMap {
+ case eb: ElementBase => Seq(eb)
+ case c: Choice => Nil
+ case mg: ModelGroup => mg.realEOPElementChildren
+ }
+ }
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala
index 00ebdee130..721211a71b 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala
@@ -456,7 +456,15 @@ trait AlignedMixin extends GrammarMixin { self: Term =>
}
case LengthKind.Delimited => encodingLengthApprox
case LengthKind.Pattern => encodingLengthApprox
- case LengthKind.EndOfParent => LengthMultipleOf(1) // NYI
+ case LengthKind.EndOfParent =>
+ // Technically, the approximate length of an EndOfParent element is the length
+ // of its parent minus the length of prior siblings. However, this value is only
+ // used to compute the ending alignment available to subsequent represented terms.
+ // Per spec (12.3.6), no represented element or model group may follow an
+ // EndOfParent element (only unrepresented elements such as IVC elements are
+ // permitted), so the ending alignment is never consumed. LengthMultipleOf(1)
+ // is a safe conservative value to return.
+ LengthMultipleOf(1)
// If an element is lengthKind="prefixed", the element's length is the length
// of the value of the prefix element, which can't be known till runtime
case LengthKind.Prefixed => LengthMultipleOf(1) // NYI (see DAFFODIL-3066)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala
index 3a33fdfb07..e677918611 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala
@@ -71,13 +71,19 @@ trait ElementBaseGrammarMixin
lazy val isPrefixed: Boolean = lengthKind == LengthKind.Prefixed
- protected lazy val isDelimitedPrefixedPattern: Boolean = {
+ /**
+ * True for the length kinds where padding uses a minimum target length
+ * (textOutputMinLength or xs:minLength) rather than a fixed target length.
+ * These are: Delimited, Pattern, Prefixed, and EndOfParent.
+ */
+ protected lazy val isMinLengthKind: Boolean = {
import LengthKind.*
lengthKind match {
case Delimited =>
true // don't test for hasDelimiters because it might not be our delimiter, but a surrounding group's separator, or it's terminator, etc.
case Pattern => true
case Prefixed => true
+ case EndOfParent => true
case _ => false
}
}
@@ -96,13 +102,37 @@ trait ElementBaseGrammarMixin
}
/**
- * true if padding will be inserted for this delimited element when unparsing.
+ * True when this element is explicit-length with a runtime-evaluated dfdl:length
+ * expression. Per DFDL spec 12.3.6, EOP children of such a parent must not receive
+ * padding grammar pieces on unparse; padding only applies when the parent has a
+ * constant dfdl:length.
*/
- protected lazy val isDelimitedPrefixedPatternWithPadding = {
- (isDelimitedPrefixedPattern &&
+ lazy val suppressesEOPChildPadding: Boolean =
+ lengthKind == LengthKind.Explicit && !lengthEv.isConstant
+
+ /**
+ * True when this EOP element's nearest enclosing explicit-length parent uses a
+ * runtime expression for dfdl:length. Queries the root-level eopElementInfoMap
+ * so the rule is established top-down from the root.
+ * Per spec 12.3.6, padding is only valid when the parent has constant dfdl:length.
+ */
+ private lazy val isEOPInsideExpressionLengthParent: Boolean =
+ (lengthKind eq LengthKind.EndOfParent) &&
+ schemaSet.root.eopElementInfoMap.get(this).exists(_._1)
+
+ /**
+ * True when this element requires min length padding to be inserted on
+ * unparse i.e the length kind is in the min length group, representation is
+ * text, justification is not none, and the min length is positive.
+ * Excludes EOP elements whose parent uses a runtime-expression dfdl:length
+ * (spec 12.3.6: padding is only valid when parent has constant dfdl:length).
+ */
+ protected lazy val isMinLengthKindWithPadding = {
+ (isMinLengthKind &&
(impliedRepresentation eq Representation.Text) &&
(justificationPad ne TextJustificationType.None) &&
- minLen > 0)
+ minLen > 0 &&
+ !isEOPInsideExpressionLengthParent)
}
private lazy val prefixLengthTypeGSTD = LV(Symbol("prefixLengthTypeGSTD")) {
@@ -252,6 +282,60 @@ trait ElementBaseGrammarMixin
}
final lazy val prefixedLengthBody = prefixedLengthElementDecl.parsedValue
+ def checkEndOfParentRestrictionsOnCurrentElement(optParentELU: Option[LengthUnits]): Unit = {
+ Assert.invariant(optParentELU.isDefined)
+ val parentELU = optParentELU.get
+ val currentElement: ElementBase = this
+ Assert.invariant(currentElement.lengthKind == LengthKind.EndOfParent)
+ schemaDefinitionWhen(
+ currentElement.hasTerminator,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but specifies a dfdl:terminator."
+ )
+ schemaDefinitionWhen(
+ currentElement.trailingSkip != 0,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but specifies a non-zero dfdl:trailingSkip."
+ )
+ schemaDefinitionWhen(
+ currentElement.maxOccurs > 1,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but specifies a maxOccurs greater than 1."
+ )
+ schemaDefinitionWhen(
+ currentElement.impliedRepresentation == Representation.Text
+ && (!currentElement.isKnownEncoding || !currentElement.knownEncodingIsFixedWidth || currentElement.knownEncodingWidthInBits != 8)
+ && (parentELU != LengthUnits.Characters),
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but the element has text representation, and does not have a single-byte character set encoding, and the effective length units of the parent is not 'characters'."
+ )
+ if (currentElement.isSimpleType) {
+ val meetsSimpleTypeRestrictions = (currentElement.primType eq PrimType.String)
+ || (currentElement.representation == Representation.Text)
+ || (currentElement.primType eq PrimType.HexBinary)
+ || (currentElement.representation == Representation.Binary
+ && ((
+ currentElement.binaryNumberRep,
+ currentElement.optionBinaryCalendarRep
+ ) match {
+ case (
+ BinaryNumberRep.Packed | BinaryNumberRep.Bcd | BinaryNumberRep.Ibm4690Packed,
+ _
+ ) =>
+ true
+ case (
+ _,
+ Some(
+ BinaryCalendarRep.Packed | BinaryCalendarRep.Bcd |
+ BinaryCalendarRep.Ibm4690Packed
+ )
+ ) =>
+ true
+ case _ => false
+ }))
+ schemaDefinitionWhen(
+ !meetsSimpleTypeRestrictions,
+ "element is a simple type specified as dfdl:lengthKind=\"endOfParent\", but isn't a string type, doesn't have text representation, isn't a hexbinary type, or doesn't have binary representation with packed decimal representation."
+ )
+ }
+ }
+
/**
* Quite tricky when we add padding or fill
*
@@ -268,7 +352,7 @@ trait ElementBaseGrammarMixin
(lengthUnits eq LengthUnits.Characters)
)
maybeUnparseTargetLengthInBitsEv.isDefined // "pad" complex types.
- else if (isDelimitedPrefixedPatternWithPadding)
+ else if (isMinLengthKindWithPadding)
true // simple type for unparse that needs to be padded.
else
// simple type, specified length.
@@ -309,7 +393,7 @@ trait ElementBaseGrammarMixin
// * This is about whether the box we're fitting it into is fixed or varying size.
// */
// final protected lazy val isVariableLengthRep: Boolean = {
- // isDelimitedPrefixedPattern ||
+ // isMinLengthKind ||
// ((lengthKind eq LengthKind.Explicit) &&
// !lengthEv.optConstant.isDefined)
// //
@@ -575,6 +659,9 @@ trait ElementBaseGrammarMixin
)
}
+ private lazy val unsupportedLengthKindEOPMessage =
+ "lengthKind='endOfParent' only supported for packed binary formats."
+
/**
* Property consistency check for called for all binary numbers
*
@@ -604,8 +691,20 @@ trait ElementBaseGrammarMixin
}
case LengthKind.Pattern =>
schemaDefinitionError("Binary data elements cannot have lengthKind='pattern'.")
+ case LengthKind.EndOfParent if optionBinaryCalendarRep.isDefined =>
+ binaryCalendarRep match {
+ case BinaryCalendarRep.BinaryMilliseconds | BinaryCalendarRep.BinarySeconds =>
+ SDE(unsupportedLengthKindEOPMessage)
+ case _ => -1
+ }
+ case LengthKind.EndOfParent if optionBinaryNumberRep.isDefined =>
+ binaryNumberRep match {
+ case BinaryNumberRep.Binary =>
+ SDE(unsupportedLengthKindEOPMessage)
+ case _ => -1
+ }
case LengthKind.EndOfParent =>
- schemaDefinitionError("Binary data elements cannot have lengthKind='endOfParent'.")
+ SDE(unsupportedLengthKindEOPMessage)
}
private def explicitBinaryLengthInBits() = {
@@ -648,10 +747,7 @@ trait ElementBaseGrammarMixin
Assert.invariant(pt == PrimType.String)
StringOfSpecifiedLength(this)
}
- case LengthKind.EndOfParent if isComplexType =>
- notYetImplemented("lengthKind='endOfParent' for complex type")
- case LengthKind.EndOfParent =>
- notYetImplemented("lengthKind='endOfParent' for simple type")
+ case LengthKind.EndOfParent => StringOfSpecifiedLength(this)
}
}
@@ -667,6 +763,10 @@ trait ElementBaseGrammarMixin
new HexBinaryLengthPrefixed(this)
}
+ private lazy val hexBinaryLengthEndOfParent = prod("hexBinaryLengthEndOfParent") {
+ new HexBinaryLengthEndOfParent(this)
+ }
+
private lazy val hexBinaryValue = prod("hexBinaryValue") {
schemaDefinitionWhen(
lengthUnits == LengthUnits.Characters,
@@ -678,10 +778,7 @@ trait ElementBaseGrammarMixin
case LengthKind.Delimited => hexBinaryDelimitedEndOfData
case LengthKind.Pattern => hexBinaryLengthPattern
case LengthKind.Prefixed => hexBinaryLengthPrefixed
- case LengthKind.EndOfParent if isComplexType =>
- notYetImplemented("lengthKind='endOfParent' for complex type")
- case LengthKind.EndOfParent =>
- notYetImplemented("lengthKind='endOfParent' for simple type")
+ case LengthKind.EndOfParent => hexBinaryLengthEndOfParent
}
}
@@ -793,6 +890,10 @@ trait ElementBaseGrammarMixin
prod("bcdPrefixedLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Bcd) {
ConvertZonedCombinator(this, new BCDIntegerPrefixedLength(this), textConverter)
}
+ private lazy val bcdEndOfParentLengthCalendar =
+ prod("bcdEndOfParentLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Bcd) {
+ ConvertZonedCombinator(this, new BCDIntegerEndOfParentLength(this), textConverter)
+ }
private lazy val ibm4690PackedKnownLengthCalendar = prod(
"ibm4690PackedKnownLengthCalendar",
@@ -834,6 +935,16 @@ trait ElementBaseGrammarMixin
textConverter
)
}
+ private lazy val ibm4690PackedEndOfParentLengthCalendar = prod(
+ "ibm4690PackedEndOfParentLengthCalendar",
+ binaryCalendarRep == BinaryCalendarRep.Ibm4690Packed
+ ) {
+ ConvertZonedCombinator(
+ this,
+ new IBM4690PackedIntegerEndOfParentLength(this),
+ textConverter
+ )
+ }
private lazy val packedKnownLengthCalendar =
prod("packedKnownLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Packed) {
@@ -871,6 +982,14 @@ trait ElementBaseGrammarMixin
textConverter
)
}
+ private lazy val packedEndOfParentLengthCalendar =
+ prod("packedPrefixedLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Packed) {
+ ConvertZonedCombinator(
+ this,
+ new PackedIntegerEndOfParentLength(this, packedSignCodes),
+ textConverter
+ )
+ }
def primType: PrimType
@@ -939,6 +1058,8 @@ trait ElementBaseGrammarMixin
new PackedIntegerDelimitedEndOfData(this, packedSignCodes)
case (BinaryNumberRep.Packed, LengthKind.Prefixed, -1) =>
new PackedIntegerPrefixedLength(this, packedSignCodes)
+ case (BinaryNumberRep.Packed, LengthKind.EndOfParent, -1) =>
+ new PackedIntegerEndOfParentLength(this, packedSignCodes)
case (BinaryNumberRep.Packed, _, -1) =>
new PackedIntegerRuntimeLength(this, packedSignCodes)
case (BinaryNumberRep.Packed, _, _) =>
@@ -951,6 +1072,8 @@ trait ElementBaseGrammarMixin
new IBM4690PackedIntegerDelimitedEndOfData(this)
case (BinaryNumberRep.Ibm4690Packed, LengthKind.Prefixed, -1) =>
new IBM4690PackedIntegerPrefixedLength(this)
+ case (BinaryNumberRep.Ibm4690Packed, LengthKind.EndOfParent, -1) =>
+ new IBM4690PackedIntegerEndOfParentLength(this)
case (BinaryNumberRep.Ibm4690Packed, _, -1) =>
new IBM4690PackedIntegerRuntimeLength(this)
case (BinaryNumberRep.Ibm4690Packed, _, _) =>
@@ -963,6 +1086,7 @@ trait ElementBaseGrammarMixin
(lengthKind, binaryNumberKnownLengthInBits) match {
case (LengthKind.Delimited, -1) => new BCDIntegerDelimitedEndOfData(this)
case (LengthKind.Prefixed, -1) => new BCDIntegerPrefixedLength(this)
+ case (LengthKind.EndOfParent, -1) => new BCDIntegerEndOfParentLength(this)
case (_, -1) => new BCDIntegerRuntimeLength(this)
case (_, _) => new BCDIntegerKnownLength(this, binaryNumberKnownLengthInBits)
}
@@ -1030,6 +1154,8 @@ trait ElementBaseGrammarMixin
) byteOrderRaw // must have or SDE
(binaryNumberRep, lengthKind, binaryNumberKnownLengthInBits) match {
+ case (BinaryNumberRep.Binary, LengthKind.EndOfParent, _) =>
+ SDE("lengthKind='endOfParent' is not allowed with binary number representation")
case (BinaryNumberRep.Binary, LengthKind.Prefixed, _) =>
new BinaryDecimalPrefixedLength(this)
case (BinaryNumberRep.Binary, _, -1) => new BinaryDecimalRuntimeLength(this)
@@ -1047,6 +1173,8 @@ trait ElementBaseGrammarMixin
new PackedDecimalDelimitedEndOfData(this, packedSignCodes)
case (BinaryNumberRep.Packed, LengthKind.Prefixed, _) =>
new PackedDecimalPrefixedLength(this, packedSignCodes)
+ case (BinaryNumberRep.Packed, LengthKind.EndOfParent, _) =>
+ new PackedDecimalEndOfParentLength(this, packedSignCodes)
case (BinaryNumberRep.Packed, _, -1) =>
new PackedDecimalRuntimeLength(this, packedSignCodes)
case (BinaryNumberRep.Packed, _, _) =>
@@ -1055,6 +1183,8 @@ trait ElementBaseGrammarMixin
new BCDDecimalDelimitedEndOfData(this)
case (BinaryNumberRep.Bcd, LengthKind.Prefixed, _) =>
new BCDDecimalPrefixedLength(this)
+ case (BinaryNumberRep.Bcd, LengthKind.EndOfParent, _) =>
+ new BCDDecimalEndOfParentLength(this)
case (BinaryNumberRep.Bcd, _, -1) => new BCDDecimalRuntimeLength(this)
case (BinaryNumberRep.Bcd, _, _) =>
new BCDDecimalKnownLength(this, binaryNumberKnownLengthInBits)
@@ -1062,6 +1192,8 @@ trait ElementBaseGrammarMixin
new IBM4690PackedDecimalDelimitedEndOfData(this)
case (BinaryNumberRep.Ibm4690Packed, LengthKind.Prefixed, _) =>
new IBM4690PackedDecimalPrefixedLength(this)
+ case (BinaryNumberRep.Ibm4690Packed, LengthKind.EndOfParent, _) =>
+ new IBM4690PackedDecimalEndOfParentLength(this)
case (BinaryNumberRep.Ibm4690Packed, _, -1) =>
new IBM4690PackedDecimalRuntimeLength(this)
case (BinaryNumberRep.Ibm4690Packed, _, _) =>
@@ -1072,6 +1204,13 @@ trait ElementBaseGrammarMixin
case PrimType.Boolean => {
lengthKind match {
case LengthKind.Prefixed => new BinaryBooleanPrefixedLength(this)
+ case LengthKind.EndOfParent =>
+ // xs:boolean is not one of the allowed simple types for lengthKind='endOfParent'
+ // (spec 12.3.6: only xs:string, text representation, xs:hexBinary, or binary
+ // with packed decimal representation are permitted).
+ SDE(
+ "lengthKind='endOfParent' is not supported for xs:boolean with binary representation."
+ )
case _ => new BinaryBoolean(this)
}
}
@@ -1125,6 +1264,7 @@ trait ElementBaseGrammarMixin
(lengthKind, binaryNumberKnownLengthInBits) match {
case (LengthKind.Delimited, -1) => bcdDelimitedLengthCalendar
case (LengthKind.Prefixed, -1) => bcdPrefixedLengthCalendar
+ case (LengthKind.EndOfParent, -1) => bcdEndOfParentLengthCalendar
case (_, -1) => bcdRuntimeLengthCalendar
case (_, _) => bcdKnownLengthCalendar
}
@@ -1133,6 +1273,7 @@ trait ElementBaseGrammarMixin
(lengthKind, binaryNumberKnownLengthInBits) match {
case (LengthKind.Delimited, -1) => ibm4690PackedDelimitedLengthCalendar
case (LengthKind.Prefixed, -1) => ibm4690PackedPrefixedLengthCalendar
+ case (LengthKind.EndOfParent, -1) => ibm4690PackedEndOfParentLengthCalendar
case (_, -1) => ibm4690PackedRuntimeLengthCalendar
case (_, _) => ibm4690PackedKnownLengthCalendar
}
@@ -1141,6 +1282,7 @@ trait ElementBaseGrammarMixin
(lengthKind, binaryNumberKnownLengthInBits) match {
case (LengthKind.Delimited, -1) => packedDelimitedLengthCalendar
case (LengthKind.Prefixed, -1) => packedPrefixedLengthCalendar
+ case (LengthKind.EndOfParent, -1) => packedEndOfParentLengthCalendar
case (_, -1) => packedRuntimeLengthCalendar
case (_, _) => packedKnownLengthCalendar
}
@@ -1280,10 +1422,7 @@ trait ElementBaseGrammarMixin
case LengthKind.Implicit =>
LiteralValueNilOfSpecifiedLength(this)
case LengthKind.Prefixed => LiteralValueNilOfSpecifiedLength(this)
- case LengthKind.EndOfParent if isComplexType =>
- notYetImplemented("lengthKind='endOfParent' for complex type")
- case LengthKind.EndOfParent =>
- notYetImplemented("lengthKind='endOfParent' for simple type")
+ case LengthKind.EndOfParent => LiteralValueNilOfSpecifiedLength(this)
}
}
case NilKind.LiteralCharacter => {
@@ -1340,19 +1479,24 @@ trait ElementBaseGrammarMixin
// so it can do any internal checks for example blobValue's check for
// non-explicit lengthKind
val body = bodyArg
-
// there are essentially two categories of processors that read/write data input/output
- // stream: those that calculate lengths themselves and those that expect another
- // processor to calculate the length and set the bit limit which this processor will use as
- // the length. The following determines if this element requires another processor to
- // calculate and set the bit limit, and if so adds the appropriate grammar to do that
- val bodyRequiresSpecifiedLengthBitLimit = lengthKind != LengthKind.Delimited && (
- isSimpleType && impliedRepresentation == Representation.Text ||
- isSimpleType && isNillable ||
- isComplexType && lengthKind != LengthKind.Implicit ||
- lengthKind == LengthKind.Prefixed ||
- isSimpleType && primType == PrimType.HexBinary && lengthKind == LengthKind.Pattern
- )
+ // stream: those that calculate lengths themselves (ex: binary numeric parsers) and those
+ // that expect another processor to calculate the length and set the bit limit which
+ // this processor will use as the length (such as text parsers). The following determines
+ // if this element requires another processor to calculate and set the bit limit, and if so
+ // adds the appropriate grammar to do that
+ val bodyRequiresSpecifiedLengthBitLimit = {
+ lengthKind match {
+ case LengthKind.Delimited => false
+ case LengthKind.Implicit if isComplexType => false
+ case LengthKind.Prefixed => true
+ case LengthKind.Pattern if isSimpleType && primType == PrimType.HexBinary => true
+ case _ if isComplexType => true
+ case _ if isSimpleType =>
+ impliedRepresentation == Representation.Text || isNillable
+ case _ => false
+ }
+ }
if (!bodyRequiresSpecifiedLengthBitLimit) {
body
} else {
@@ -1396,10 +1540,8 @@ trait ElementBaseGrammarMixin
case LengthKind.Implicit
if isSimpleType && impliedRepresentation == Representation.Binary =>
new SpecifiedLengthImplicit(this, body, implicitBinaryLengthInBits)
- case LengthKind.EndOfParent if isComplexType =>
- notYetImplemented("lengthKind='endOfParent' for complex type")
case LengthKind.EndOfParent =>
- notYetImplemented("lengthKind='endOfParent' for simple type")
+ new SpecifiedLengthEndOfParent(this, body)
case LengthKind.Delimited | LengthKind.Implicit =>
Assert.impossibleCase(
"Delimited and ComplexType Implicit cases should not be reached"
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementDeclGrammarMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementDeclGrammarMixin.scala
index c1c3febadb..0d2733f441 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementDeclGrammarMixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementDeclGrammarMixin.scala
@@ -19,11 +19,14 @@ package org.apache.daffodil.core.grammar
import org.apache.daffodil.core.dsom.Root
import org.apache.daffodil.core.grammar.primitives.UnicodeByteOrderMark
+import org.apache.daffodil.lib.schema.annotation.props.gen.LengthUnits
trait RootGrammarMixin
extends LocalElementGrammarMixin // can be repeating if not root
{ self: Root =>
+ requiredEvaluationsAlways(checkEndOfParentRestrictions(Some(LengthUnits.Characters)))
+
final lazy val document = prod("document") {
schemaDefinitionUnless(isScalar, "The document element cannot be an array.")
UnicodeByteOrderMark(this) ~ documentElement
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala
index 995a97cd87..06f120a4b5 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala
@@ -17,9 +17,27 @@
package org.apache.daffodil.core.grammar
+import scala.collection.mutable
+
+import org.apache.daffodil.core.dsom.ChoiceTermBase
+import org.apache.daffodil.core.dsom.ElementBase
+import org.apache.daffodil.core.dsom.ModelGroup
+import org.apache.daffodil.core.dsom.Root
+import org.apache.daffodil.core.dsom.SequenceTermBase
import org.apache.daffodil.core.dsom.Term
import org.apache.daffodil.core.grammar.primitives.MandatoryTextAlignment
import org.apache.daffodil.core.runtime1.TermRuntime1Mixin
+import org.apache.daffodil.lib.schema.annotation.props.gen.ChoiceLengthKind
+import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind
+import org.apache.daffodil.lib.schema.annotation.props.gen.LengthUnits
+import org.apache.daffodil.lib.schema.annotation.props.gen.SeparatorPosition
+import org.apache.daffodil.lib.schema.annotation.props.gen.SequenceKind
+import org.apache.daffodil.lib.schema.annotation.props.gen.YesNo
+import org.apache.daffodil.lib.util.Maybe.Nope
+import org.apache.daffodil.runtime1.processors.ImplicitLengthEv
+import org.apache.daffodil.runtime1.processors.LengthInBitsEv
+import org.apache.daffodil.runtime1.processors.MinLengthInBitsEv
+import org.apache.daffodil.runtime1.processors.UnparseTargetLengthInBitsEv
/////////////////////////////////////////////////////////////////
// Common to all Terms (Elements and ModelGroups)
@@ -84,4 +102,227 @@ trait TermGrammarMixin extends AlignedMixin with BitOrderMixin with TermRuntime1
MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
}
+ def optEffectiveLengthUnits(optLastNonEOPELU: Option[LengthUnits]): Option[LengthUnits] = {
+ this match {
+ case e: ElementBase =>
+ e.lengthKind match {
+ case LengthKind.EndOfParent => optLastNonEOPELU
+ case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+ case LengthKind.Pattern => Some(LengthUnits.Characters)
+ case LengthKind.Implicit | LengthKind.Delimited =>
+ None // invalid parent; SDE fires separately
+ }
+ case c: ChoiceTermBase if c.choiceLengthKind == ChoiceLengthKind.Explicit =>
+ Some(LengthUnits.Bytes)
+ // Sequences are transparent — the ELU is inherited from the nearest enclosing box.
+ case _: SequenceTermBase => optLastNonEOPELU
+ case _: ChoiceTermBase => None // implicit-length choice; SDE fires separately
+ }
+ }
+
+ /**
+ * Top-down map from each EOP element to a tuple of:
+ * _1: Boolean - whether padding is suppressed (parent has expression-based dfdl:length)
+ * _2: Option[UnparseTargetLengthInBitsEv] - the box target ev when the EOP is inside a
+ * constant-length element or explicit-length choice; None otherwise.
+ *
+ * Built in one pass from this term's descendant tree. Call on the schema root
+ * to get the complete picture for all EOP elements in the schema.
+ */
+ final lazy val eopElementInfoMap
+ : Map[ElementBase, (Boolean, Option[UnparseTargetLengthInBitsEv])] = {
+ type Info = (Boolean, Option[UnparseTargetLengthInBitsEv])
+
+ def elementInfo(e: ElementBase): (Boolean, Option[UnparseTargetLengthInBitsEv]) = {
+ val suppresses = e.suppressesEOPChildPadding
+ val optBoxEv: Option[UnparseTargetLengthInBitsEv] =
+ if (e.lengthKind == LengthKind.Explicit && e.lengthEv.isConstant)
+ Some(e.unparseTargetLengthInBitsEv)
+ else
+ None
+ (suppresses, optBoxEv)
+ }
+
+ def choiceBoxEv(c: ChoiceTermBase, eop: ElementBase): UnparseTargetLengthInBitsEv = {
+ // we use ImplicitLengthEv because it exists for lengths that are
+ // schema-compile-time constants with no expression to evaluate.
+ val constLenEv = new ImplicitLengthEv(c.choiceLength.toLong, eop.eci)
+ constLenEv.compile(eop.tunable)
+ // dfdl:choiceLength is specified by the DFDL spec to always be in bytes
+ val lenInBitsEv =
+ new LengthInBitsEv(LengthUnits.Bytes, LengthKind.Explicit, Nope, constLenEv, eop.eci)
+ lenInBitsEv.compile(eop.tunable)
+ val zeroMinEv =
+ new MinLengthInBitsEv(LengthUnits.Bytes, LengthKind.Explicit, Nope, 0L, eop.eci)
+ zeroMinEv.compile(eop.tunable)
+ val boxEv = new UnparseTargetLengthInBitsEv(lenInBitsEv, zeroMinEv, eop.eci)
+ boxEv.compile(eop.tunable)
+ boxEv
+ }
+
+ // Iterative DFS over the schema term tree so that deeply-nested schemas
+ // (hundreds of levels) do not overflow the JVM stack the way a recursive
+ // implementation would.
+ def collectFromTerms(initialTerms: Seq[Term]): Seq[(ElementBase, Info)] = {
+ val result = new mutable.ArrayBuffer[(ElementBase, Info)]()
+ val work = new mutable.ArrayDeque[Term]()
+ work.appendAll(initialTerms)
+
+ while (work.nonEmpty) {
+ val term = work.removeHead()
+ term match {
+ case e: ElementBase if e.lengthKind == LengthKind.EndOfParent =>
+ // EOP child; its enclosing parent/choice already added the entry.
+ case e: ElementBase =>
+ val info = elementInfo(e)
+ e.childrenEndOfParent.foreach(eop => result += (eop -> info))
+ // Prepend in reverse so leftmost child is processed next (DFS order).
+ e.termChildren.reverseIterator.foreach(work.prepend)
+ case c: ChoiceTermBase if c.choiceLengthKind == ChoiceLengthKind.Explicit =>
+ val eops = c.childrenEndOfParent
+ eops.foreach(eop => result += (eop -> (false, Some(choiceBoxEv(c, eop)))))
+ c.termChildren.reverseIterator.foreach(work.prepend)
+ case other =>
+ other.termChildren.reverseIterator.foreach(work.prepend)
+ }
+ }
+ result.toSeq
+ }
+
+ this match {
+ case e: ElementBase =>
+ val info = elementInfo(e)
+ (e.childrenEndOfParent.map(_ -> info) ++ collectFromTerms(e.termChildren)).toMap
+ case other =>
+ collectFromTerms(other.termChildren).toMap
+ }
+ }
+
+ final lazy val childrenEndOfParent: Seq[ElementBase] = LV(Symbol("childrenEndOfParent")) {
+ val gms = termChildren
+ val chls = gms.flatMap {
+ case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => Seq(eb)
+ case eb: ElementBase => Nil
+ case c: ChoiceTermBase => Nil
+ case mg: ModelGroup => mg.childrenEndOfParent
+ }
+ chls
+ }.value
+
+ def checkEndOfParentRestrictions(lastNonEOPELU: Option[LengthUnits]): Boolean = {
+ val term = this
+ lazy val eopChildren = this.childrenEndOfParent
+ lazy val optParentELU = term.optEffectiveLengthUnits(lastNonEOPELU)
+ // checks
+ term match {
+ case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+ rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+ eopChildren.foreach { child =>
+ child.checkEndOfParentRestrictionsOnCurrentElement(lastNonEOPELU)
+ }
+ }
+ case e: ElementBase if eopChildren.nonEmpty => {
+ eopChildren.foreach { child =>
+ child.schemaDefinitionWhen(
+ e.lengthKind == LengthKind.Implicit || e.lengthKind == LengthKind.Delimited,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but its parent is an element with dfdl:lengthKind 'implicit' or 'delimited'."
+ )
+ child.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+ }
+ }
+ case s: SequenceTermBase if eopChildren.nonEmpty => {
+ eopChildren.foreach { child =>
+ child.schemaDefinitionWhen(
+ s.separatorPosition == SeparatorPosition.Postfix,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but is in a sequence with dfdl:separatorPosition defined as 'postfix'."
+ )
+ child.schemaDefinitionWhen(
+ s.sequenceKind != SequenceKind.Ordered,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but is in a sequence with dfdl:sequenceKind defined as 'unordered'."
+ )
+ child.schemaDefinitionWhen(
+ s.hasTerminator,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but is in a sequence with a dfdl:terminator."
+ )
+ child.schemaDefinitionWhen(
+ s.realEOPElementChildren.exists(e => e.floating == YesNo.Yes),
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but is in a sequence with elements defining dfdl:floating='yes'."
+ )
+ child.schemaDefinitionWhen(
+ s.trailingSkip != 0,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but is in a sequence with a non-zero dfdl:trailingSkip."
+ )
+ }
+
+ }
+ case c: ChoiceTermBase if eopChildren.nonEmpty => {
+ // TODO: The DFDL spec (12.3.6) explicitly mentions that an EndOfParent element
+ // can be terminated by a choice with choiceLengthKind='explicit', with no reference
+ // to implicit length choices. Later on in 12.3.6, it specified what the parent Effective
+ // Length Units would be for an explicit length choice, again with no reference to
+ // implicit length choices. The only way to get an EndOfParent element within an
+ // implicit length choice would be to have it actually be terminated by the choice's
+ // parent, similar to how we treat elements within an element that is also EndOfParent.
+ // The only mention of implicit length choices in the DFDL Spec is to mention SDEs when
+ // an EndOfParent element is enclosed by a choice with choiceLengthKind='implicit'. We
+ // think that may be a typo, so for now we disallow ChoiceLengthKind.Implicit
+ // enclosing an EndOfParent element.
+ // See Daffodil-3080
+ eopChildren.foreach { child =>
+ child.schemaDefinitionWhen(
+ c.choiceLengthKind == ChoiceLengthKind.Implicit,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but its parent is a choice with dfdl:choiceLengthKind 'implicit'."
+ )
+ child.schemaDefinitionWhen(
+ c.hasTerminator,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but is in a choice with a dfdl:terminator."
+ )
+ child.schemaDefinitionWhen(
+ c.trailingSkip != 0,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but is in a choice with a non-zero dfdl:trailingSkip."
+ )
+ child.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+ }
+ }
+ case _ => // do nothing
+ }
+ // end checks
+ val sawEOP = term.termChildren.foldLeft(false) { case (sawEOP, child) =>
+ if (sawEOP) {
+ // Choice branches are alternatives, not sequential data — the after-EOP SDE must
+ // not fire across branches. Only sequences and elements have sequential ordering.
+ term match {
+ case _: ChoiceTermBase => // has alternatives which can all be EOP; skip
+ case _ =>
+ child.schemaDefinitionWhen(
+ child.isInstanceOf[ModelGroup],
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but a model group is defined between this element and the end of the enclosing component"
+ )
+ child.schemaDefinitionWhen(
+ child.isRepresented,
+ "element is specified as dfdl:lengthKind=\"endOfParent\", but a represented element is defined between this element and the end of the enclosing component"
+ )
+ }
+ }
+ val res = child.checkEndOfParentRestrictions(optParentELU)
+ term match {
+ // Branches of a choice are alternatives, so should never carry sawEOP from one branch to the next.
+ case _: ChoiceTermBase => false
+ case _ =>
+ child match {
+ case e: ElementBase if e.lengthKind == LengthKind.EndOfParent => true
+ // A non-EOP element forms a hard length boundary. EOP elements nested inside it are
+ // scoped to that element's length, so they must not affect sibling scanning here.
+ case _: ElementBase => false
+ // An explicit-length choice owns a fixed byte span, so EOP elements inside it are
+ // scoped to that span. After the choice ends the parent continues normally.
+ case c: ChoiceTermBase if c.choiceLengthKind == ChoiceLengthKind.Explicit => false
+ // Sequences are transparent (no own length boundary),
+ // so EOP state propagates through them.
+ case _ => res
+ }
+ }
+ }
+ sawEOP
+ }
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesBCD.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesBCD.scala
index e9e0996b00..3052f25e4d 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesBCD.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesBCD.scala
@@ -20,9 +20,11 @@ package org.apache.daffodil.core.grammar.primitives
import org.apache.daffodil.core.dsom.ElementBase
import org.apache.daffodil.core.grammar.Terminal
import org.apache.daffodil.runtime1.processors.parsers.BCDDecimalBitLimitLengthParser
+import org.apache.daffodil.runtime1.processors.parsers.BCDDecimalEndOfParentLengthParser
import org.apache.daffodil.runtime1.processors.parsers.BCDDecimalKnownLengthParser
import org.apache.daffodil.runtime1.processors.parsers.BCDDecimalRuntimeLengthParser
import org.apache.daffodil.runtime1.processors.parsers.BCDIntegerBitLimitLengthParser
+import org.apache.daffodil.runtime1.processors.parsers.BCDIntegerEndOfParentLengthParser
import org.apache.daffodil.runtime1.processors.parsers.BCDIntegerKnownLengthParser
import org.apache.daffodil.runtime1.processors.parsers.BCDIntegerRuntimeLengthParser
import org.apache.daffodil.runtime1.processors.unparsers.Unparser
@@ -52,7 +54,18 @@ class BCDIntegerKnownLength(val e: ElementBase, lengthInBits: Long) extends Term
class BCDIntegerPrefixedLength(val e: ElementBase) extends Terminal(e, true) {
- override lazy val parser = new BCDIntegerBitLimitLengthParser(e.elementRuntimeData)
+ override lazy val parser =
+ new BCDIntegerBitLimitLengthParser(e.elementRuntimeData)
+
+ override lazy val unparser: Unparser = new BCDIntegerMinimumLengthUnparser(
+ e.elementRuntimeData
+ )
+}
+
+class BCDIntegerEndOfParentLength(val e: ElementBase) extends Terminal(e, true) {
+
+ override lazy val parser =
+ new BCDIntegerEndOfParentLengthParser(e.elementRuntimeData)
override lazy val unparser: Unparser = new BCDIntegerMinimumLengthUnparser(
e.elementRuntimeData
@@ -93,10 +106,19 @@ class BCDDecimalKnownLength(val e: ElementBase, lengthInBits: Long) extends Term
class BCDDecimalPrefixedLength(val e: ElementBase) extends Terminal(e, true) {
override lazy val parser =
- new BCDDecimalBitLimitLengthParser(
+ new BCDDecimalBitLimitLengthParser(e.elementRuntimeData, e.binaryDecimalVirtualPoint)
+
+ override lazy val unparser: Unparser =
+ new BCDDecimalMinimumLengthUnparser(
e.elementRuntimeData,
e.binaryDecimalVirtualPoint
)
+}
+
+class BCDDecimalEndOfParentLength(val e: ElementBase) extends Terminal(e, true) {
+
+ override lazy val parser =
+ new BCDDecimalEndOfParentLengthParser(e.elementRuntimeData, e.binaryDecimalVirtualPoint)
override lazy val unparser: Unparser =
new BCDDecimalMinimumLengthUnparser(
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesIBM4690Packed.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesIBM4690Packed.scala
index 91d0d25efe..78332ef333 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesIBM4690Packed.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesIBM4690Packed.scala
@@ -20,9 +20,11 @@ package org.apache.daffodil.core.grammar.primitives
import org.apache.daffodil.core.dsom.ElementBase
import org.apache.daffodil.core.grammar.Terminal
import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedDecimalBitLimitLengthParser
+import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedDecimalEndOfParentLengthParser
import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedDecimalKnownLengthParser
import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedDecimalRuntimeLengthParser
import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedIntegerBitLimitLengthParser
+import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedIntegerEndOfParentLengthParser
import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedIntegerKnownLengthParser
import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedIntegerRuntimeLengthParser
import org.apache.daffodil.runtime1.processors.unparsers.Unparser
@@ -66,6 +68,15 @@ class IBM4690PackedIntegerPrefixedLength(val e: ElementBase) extends Terminal(e,
)
}
+class IBM4690PackedIntegerEndOfParentLength(val e: ElementBase) extends Terminal(e, true) {
+ override lazy val parser =
+ new IBM4690PackedIntegerEndOfParentLengthParser(e.elementRuntimeData)
+
+ override lazy val unparser: Unparser = new IBM4690PackedIntegerMinimumLengthUnparser(
+ e.elementRuntimeData
+ )
+}
+
class IBM4690PackedDecimalRuntimeLength(val e: ElementBase) extends Terminal(e, true) {
override lazy val parser = new IBM4690PackedDecimalRuntimeLengthParser(
e.elementRuntimeData,
@@ -115,3 +126,17 @@ class IBM4690PackedDecimalPrefixedLength(val e: ElementBase) extends Terminal(e,
e.decimalSigned
)
}
+
+class IBM4690PackedDecimalEndOfParentLength(val e: ElementBase) extends Terminal(e, true) {
+ override lazy val parser = new IBM4690PackedDecimalEndOfParentLengthParser(
+ e.elementRuntimeData,
+ e.binaryDecimalVirtualPoint,
+ e.decimalSigned
+ )
+
+ override lazy val unparser: Unparser = new IBM4690PackedDecimalMinimumLengthUnparser(
+ e.elementRuntimeData,
+ e.binaryDecimalVirtualPoint,
+ e.decimalSigned
+ )
+}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesLengthKind.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesLengthKind.scala
index e069353bd5..3c9ae08786 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesLengthKind.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesLengthKind.scala
@@ -34,6 +34,7 @@ import org.apache.daffodil.runtime1.processors.parsers.BCDIntegerDelimitedParser
import org.apache.daffodil.runtime1.processors.parsers.BlobSpecifiedLengthParser
import org.apache.daffodil.runtime1.processors.parsers.HexBinaryDelimitedParser
import org.apache.daffodil.runtime1.processors.parsers.HexBinaryEndOfBitLimitParser
+import org.apache.daffodil.runtime1.processors.parsers.HexBinaryEndOfParentParser
import org.apache.daffodil.runtime1.processors.parsers.HexBinarySpecifiedLengthParser
import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedDecimalDelimitedParser
import org.apache.daffodil.runtime1.processors.parsers.IBM4690PackedIntegerDelimitedParser
@@ -191,6 +192,16 @@ case class HexBinaryLengthPrefixed(e: ElementBase) extends Terminal(e, true) {
new HexBinaryMinLengthInBytesUnparser(e.minLength.longValue, e.elementRuntimeData)
}
+case class HexBinaryLengthEndOfParent(e: ElementBase) extends Terminal(e, true) {
+
+ override lazy val parser: DaffodilParser = new HexBinaryEndOfParentParser(
+ e.elementRuntimeData
+ )
+
+ override lazy val unparser: DaffodilUnparser =
+ new HexBinaryMinLengthInBytesUnparser(e.minLength.longValue, e.elementRuntimeData)
+}
+
abstract class PackedIntegerDelimited(
e: ElementBase,
packedSignCodes: PackedSignCodes
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesPacked.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesPacked.scala
index f5c81be1f4..80d1280f17 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesPacked.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesPacked.scala
@@ -21,9 +21,11 @@ import org.apache.daffodil.core.dsom.ElementBase
import org.apache.daffodil.core.grammar.Terminal
import org.apache.daffodil.lib.util.PackedSignCodes
import org.apache.daffodil.runtime1.processors.parsers.PackedDecimalBitLimitLengthParser
+import org.apache.daffodil.runtime1.processors.parsers.PackedDecimalEndOfParentLengthParser
import org.apache.daffodil.runtime1.processors.parsers.PackedDecimalKnownLengthParser
import org.apache.daffodil.runtime1.processors.parsers.PackedDecimalRuntimeLengthParser
import org.apache.daffodil.runtime1.processors.parsers.PackedIntegerBitLimitLengthParser
+import org.apache.daffodil.runtime1.processors.parsers.PackedIntegerEndOfParentLengthParser
import org.apache.daffodil.runtime1.processors.parsers.PackedIntegerKnownLengthParser
import org.apache.daffodil.runtime1.processors.parsers.PackedIntegerRuntimeLengthParser
import org.apache.daffodil.runtime1.processors.unparsers.Unparser
@@ -84,6 +86,18 @@ class PackedIntegerPrefixedLength(
new PackedIntegerMinimumLengthUnparser(e.elementRuntimeData, packedSignCodes)
}
+class PackedIntegerEndOfParentLength(
+ val e: ElementBase,
+ packedSignCodes: PackedSignCodes
+) extends Terminal(e, true) {
+
+ override lazy val parser =
+ new PackedIntegerEndOfParentLengthParser(e.elementRuntimeData, packedSignCodes)
+
+ override lazy val unparser: Unparser =
+ new PackedIntegerMinimumLengthUnparser(e.elementRuntimeData, packedSignCodes)
+}
+
class PackedDecimalRuntimeLength(val e: ElementBase, packedSignCodes: PackedSignCodes)
extends Terminal(e, true) {
override lazy val parser = new PackedDecimalRuntimeLengthParser(
@@ -145,3 +159,21 @@ class PackedDecimalPrefixedLength(val e: ElementBase, packedSignCodes: PackedSig
e.decimalSigned
)
}
+
+class PackedDecimalEndOfParentLength(val e: ElementBase, packedSignCodes: PackedSignCodes)
+ extends Terminal(e, true) {
+
+ override lazy val parser = new PackedDecimalEndOfParentLengthParser(
+ e.elementRuntimeData,
+ e.binaryDecimalVirtualPoint,
+ packedSignCodes,
+ e.decimalSigned
+ )
+
+ override lazy val unparser: Unparser = new PackedDecimalMinimumLengthUnparser(
+ e.elementRuntimeData,
+ e.binaryDecimalVirtualPoint,
+ packedSignCodes,
+ e.decimalSigned
+ )
+}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/SpecifiedLength.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/SpecifiedLength.scala
index 0ae3e6a4d9..22edb1bccb 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/SpecifiedLength.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/SpecifiedLength.scala
@@ -132,6 +132,27 @@ class SpecifiedLengthExplicit(e: ElementBase, eGram: => Gram, bitsMultiplier: In
}
+class SpecifiedLengthEndOfParent(e: ElementBase, eGram: => Gram)
+ extends SpecifiedLengthCombinatorBase(e, eGram) {
+
+ lazy val kind = "EndOfParent_" + e.lengthUnits.toString
+
+ lazy val parser: Parser = {
+ if (eParser.isEmpty) eParser
+ else
+ new SpecifiedLengthEndOfParentParser(
+ eParser,
+ e.elementRuntimeData
+ )
+ }
+
+ // No length-management wrapper needed: the enclosing element's
+ // unparser handles RightFill/ElementUnused for any space
+ // the EOP child doesn't fill
+ lazy val unparser: Unparser = eUnparser
+
+}
+
class SpecifiedLengthImplicit(e: ElementBase, eGram: => Gram, nBits: Long)
extends SpecifiedLengthCombinatorBase(e, eGram)
with SpecifiedLengthExplicitImplicitUnparserMixin {
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala b/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala
index c951fb2c7f..ec6f6ea198 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala
@@ -19,7 +19,7 @@ package org.apache.daffodil.io
import java.io.InputStream
import java.nio.ByteBuffer
-import scala.collection.mutable.ArrayBuffer
+import scala.collection.mutable.ArrayDeque
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.exceptions.ThinException
@@ -181,6 +181,12 @@ abstract class InputSource {
*/
def releasePosition(bytePos0b: Long): Unit
+ /**
+ * Returns the 0-based exclusive byte position of the end of the data stream,
+ * or None if the end of data is not reachable within the implementation's cache size limit.
+ */
+ def optEndOfDataPosition: Option[Long]
+
/**
* Alerts the implementation to attempt to free data that is no longer used,
* if possible. If possible, this should free any unlocked bytes.
@@ -214,11 +220,19 @@ abstract class InputSource {
* @param inputStream the java.io.Inputstream to read data from
* @param bucketSizeExponent the exponent used to calculate the size of a single bucket, defined as 2^buckeSizeExponent bytes
* @param maxCacheSizeInBytes the max memory allowed to be used for bucket storage (num buckets * bucketSize)
+ * @param optSizeHint if the total number of bytes available from this stream is known in advance,
+ * supply it here so that [[optEndOfDataPosition]] can short-circuit the
+ * 256 MiB scan. Must reflect the bytes remaining from the stream's current
+ * position at the time this BucketingInputSource is constructed. Use the
+ * factory methods on [[InputSourceDataInputStream]] rather than constructing
+ * this class directly — they populate this hint automatically for file-backed
+ * streams.
*/
class BucketingInputSource(
inputStream: java.io.InputStream,
bucketSizeExponent: Int = 13,
- maxCacheSizeInBytes: Int = 256 * (1 << 20)
+ maxCacheSizeInBytes: Int = 256 * (1 << 20),
+ optSizeHint: Option[Long] = None
) extends InputSource {
private final val bucketSize: Int = 1 << bucketSizeExponent
@@ -245,11 +259,17 @@ class BucketingInputSource(
*/
private val maxNumberOfNonNullBuckets = Math.max(maxCacheSizeInBytes / bucketSize, 2)
+ // Compact the null-entry spine of the buckets ArrayBuffer after this many
+ // buckets have been freed, so that the ArrayBuffer length stays proportional
+ // to live buckets rather than total bytes ever read. 256 × 8 KiB = 2 MiB
+ // freed between compactions; the ArrayBuffer.remove call is O(live buckets).
+ private val compactThresholdBuckets = 256
+
/**
* Array of buckets
*/
private val buckets = {
- val b = new ArrayBuffer[Bucket]()
+ val b = new ArrayDeque[Bucket]()
// initialize with an empty bucket
b += new Bucket()
b
@@ -350,9 +370,19 @@ class BucketingInputSource(
bytesFilledInLastBucket = 0
lastBucketIndex += 1
if ((lastBucketIndex - oldestBucketIndex) >= maxNumberOfNonNullBuckets) {
- // This frees the oldest bucket, allowing it to be garbage collected.
- buckets(oldestBucketIndex) = null
- oldestBucketIndex += 1
+ if (buckets(oldestBucketIndex).refCount == 0) {
+ // This frees the oldest bucket, allowing it to be garbage collected.
+ buckets(oldestBucketIndex) = null
+ oldestBucketIndex += 1
+ } else {
+ // The oldest bucket has an active backtracking mark. We cannot evict
+ // it, and we cannot advance oldestBucketIndex past it (that would
+ // break the invariant checked in releasePosition). Stop filling here
+ // to prevent the window from growing without bound. Callers that need
+ // more data (e.g. optEndOfDataPosition) will receive a "goal not
+ // reached" result and handle the limitation appropriately.
+ needsMoreData = false
+ }
}
}
@@ -529,6 +559,15 @@ class BucketingInputSource(
// buckets as possible. Note that this might still not release anything
// if the oldest bucket contains the current byte position.
releaseBuckets()
+ // Periodically collapse the null-reference spine so that the ArrayBuffer
+ // length stays bounded by the live bucket count, not the total number of
+ // bytes ever read. Only reachable when oldestBucketIndex has actually
+ // advanced (i.e. refCount was 0), so no live mark covers any removed entry.
+ if (oldestBucketIndex >= compactThresholdBuckets) {
+ buckets.remove(0, oldestBucketIndex)
+ headBucketBytePosition0b += oldestBucketIndex.toLong * bucketSize
+ oldestBucketIndex = 0
+ }
}
}
@@ -560,10 +599,33 @@ class BucketingInputSource(
def compact(): Unit = {
releaseBuckets()
buckets.remove(0, oldestBucketIndex)
- val bytesRemoved = oldestBucketIndex * bucketSize
+ val bytesRemoved = oldestBucketIndex.toLong * bucketSize
headBucketBytePosition0b += bytesRemoved
oldestBucketIndex = 0
}
+
+ /**
+ * Returns the 0-based exclusive byte position of the end of the data stream.
+ *
+ * When the total stream size was known at construction time (supplied via
+ * [[optSizeHint]]) this returns immediately without reading any additional
+ * data from the stream.
+ *
+ * Otherwise it falls back to the scan-based approach: reads and buckets data
+ * until EOF is found or the [[maxCacheSizeInBytes]] scan window is exhausted.
+ * The scan window extends [[maxCacheSizeInBytes]] bytes beyond the last
+ * already-filled bucket (not from the current read position), maximising
+ * coverage without invalidating backtracking state.
+ *
+ * Returns None if EOF is not reachable within the scan window.
+ */
+ override lazy val optEndOfDataPosition: Option[Long] =
+ optSizeHint.orElse {
+ val lastFilledBucketIndex = buckets.length - 1
+ val goalBucketIndex = lastFilledBucketIndex + maxNumberOfNonNullBuckets - 1
+ val endOfDataNotReached = fillBucketsToIndex(goalBucketIndex, bucketSize)
+ if (endOfDataNotReached) None else Some(totalBytesBucketed)
+ }
}
/**
@@ -645,4 +707,12 @@ class ByteBufferInputSource(byteBuffer: ByteBuffer) extends InputSource {
override def close(): Unit = {
// do nothing. No resources to release.
}
+
+ /**
+ * Returns the 0-based exclusive byte position of the end of the buffer data.
+ */
+ override def optEndOfDataPosition: Option[Long] = {
+ val finalPosition = position() + bb.remaining
+ Some(finalPosition)
+ }
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala b/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala
index 8e39d7db43..b7edfa6e22 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala
@@ -17,11 +17,16 @@
package org.apache.daffodil.io
+import java.io.FileInputStream
import java.io.InputStream
import java.math.BigInteger as JBigInt
import java.nio.ByteBuffer
import java.nio.CharBuffer
import java.nio.LongBuffer
+import java.nio.channels.Channels
+import java.nio.channels.FileChannel
+import java.nio.channels.ReadableByteChannel
+import scala.util.Try
import org.apache.daffodil.api
import org.apache.daffodil.lib.exceptions.Assert
@@ -54,7 +59,42 @@ object InputSourceDataInputStream {
}
def apply(in: InputStream): InputSourceDataInputStream = {
- new InputSourceDataInputStream(new BucketingInputSource(in))
+ new InputSourceDataInputStream(
+ new BucketingInputSource(in, optSizeHint = sizeHintForStream(in))
+ )
+ }
+
+ /**
+ * Create an InputSourceDataInputStream from a ReadableByteChannel.
+ * When the channel is a FileChannel its size is captured up front so that
+ * dfdl:lengthKind='endOfParent' parsers can skip the 256 MiB scan
+ * normally required to locate end-of-data.
+ */
+ def apply(channel: ReadableByteChannel): InputSourceDataInputStream = {
+ new InputSourceDataInputStream(
+ new BucketingInputSource(
+ Channels.newInputStream(channel),
+ optSizeHint = sizeHintForChannel(channel)
+ )
+ )
+ }
+
+ /**
+ * Returns the number of bytes remaining in a FileInputStream at the moment
+ * of the call, or None for any other stream type or on I/O error.
+ */
+ private def sizeHintForStream(in: InputStream): Option[Long] = in match {
+ case fis: FileInputStream => sizeHintForChannel(fis.getChannel)
+ case _ => None
+ }
+
+ /**
+ * Returns the number of bytes remaining in a FileChannel at the moment of
+ * the call, or None for any other channel type or on I/O error.
+ */
+ private def sizeHintForChannel(channel: ReadableByteChannel): Option[Long] = channel match {
+ case fc: FileChannel => Try(fc.size() - fc.position()).toOption
+ case _ => None
}
}
@@ -136,6 +176,8 @@ final class InputSourceDataInputStream private (val inputSource: InputSource)
*/
def hasReachedEndOfData: Boolean = inputSource.hasReachedEndOfData
+ def optEndOfDataPosition: Option[Long] = inputSource.optEndOfDataPosition
+
/**
* Return the number of currently available bytes.
*
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/BCDParsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/BCDParsers.scala
index 36fc5ddeda..d832354c4c 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/BCDParsers.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/BCDParsers.scala
@@ -50,16 +50,22 @@ class BCDDecimalRuntimeLengthParser(
}
-class BCDDecimalBitLimitLengthParser(
- e: ElementRuntimeData,
- binaryDecimalVirtualPoint: Int
-) extends PackedBinaryDecimalBaseParser(e, binaryDecimalVirtualPoint, None)
+class BCDDecimalBitLimitLengthParser(e: ElementRuntimeData, binaryDecimalVirtualPoint: Int)
+ extends PackedBinaryDecimalBaseParser(e, binaryDecimalVirtualPoint, None)
with BitLengthFromBitLimitMixin {
override def toNumber(num: Array[Byte]): JBigDecimal =
DecimalUtils.bcdToBigDecimal(num, binaryDecimalVirtualPoint)
}
+class BCDDecimalEndOfParentLengthParser(e: ElementRuntimeData, binaryDecimalVirtualPoint: Int)
+ extends PackedBinaryDecimalBaseParser(e, binaryDecimalVirtualPoint, None)
+ with EndOfParentBitLengthMixin {
+
+ override def toNumber(num: Array[Byte]): JBigDecimal =
+ DecimalUtils.bcdToBigDecimal(num, binaryDecimalVirtualPoint)
+}
+
class BCDIntegerRuntimeLengthParser(
val e: ElementRuntimeData,
val lengthEv: Evaluatable[JLong],
@@ -85,3 +91,10 @@ class BCDIntegerBitLimitLengthParser(e: ElementRuntimeData)
override def toNumber(num: Array[Byte]): JBigInteger = DecimalUtils.bcdToBigInteger(num)
}
+
+class BCDIntegerEndOfParentLengthParser(e: ElementRuntimeData)
+ extends PackedBinaryIntegerBaseParser(e)
+ with EndOfParentBitLengthMixin {
+
+ override def toNumber(num: Array[Byte]): JBigInteger = DecimalUtils.bcdToBigInteger(num)
+}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/IBM4690PackedDecimalParsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/IBM4690PackedDecimalParsers.scala
index 6ea612a57d..4c4d58c06e 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/IBM4690PackedDecimalParsers.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/IBM4690PackedDecimalParsers.scala
@@ -65,6 +65,18 @@ class IBM4690PackedDecimalBitLimitLengthParser(
}
+class IBM4690PackedDecimalEndOfParentLengthParser(
+ e: ElementRuntimeData,
+ binaryDecimalVirtualPoint: Int,
+ decimalSigned: YesNo
+) extends PackedBinaryDecimalBaseParser(e, binaryDecimalVirtualPoint, Some(decimalSigned))
+ with EndOfParentBitLengthMixin {
+
+ override def toNumber(num: Array[Byte]): JBigDecimal =
+ DecimalUtils.ibm4690ToBigDecimal(num, binaryDecimalVirtualPoint)
+
+}
+
class IBM4690PackedIntegerRuntimeLengthParser(
val e: ElementRuntimeData,
val lengthEv: Evaluatable[JLong],
@@ -96,3 +108,12 @@ class IBM4690PackedIntegerBitLimitLengthParser(e: ElementRuntimeData)
DecimalUtils.ibm4690ToBigInteger(num)
}
+
+class IBM4690PackedIntegerEndOfParentLengthParser(e: ElementRuntimeData)
+ extends PackedBinaryIntegerBaseParser(e)
+ with EndOfParentBitLengthMixin {
+
+ override def toNumber(num: Array[Byte]): JBigInteger =
+ DecimalUtils.ibm4690ToBigInteger(num)
+
+}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/PackedDecimalParsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/PackedDecimalParsers.scala
index 46fc6c091d..823db43d99 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/PackedDecimalParsers.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/PackedDecimalParsers.scala
@@ -66,6 +66,18 @@ class PackedDecimalBitLimitLengthParser(
DecimalUtils.packedToBigDecimal(num, binaryDecimalVirtualPoint, packedSignCodes)
}
+class PackedDecimalEndOfParentLengthParser(
+ e: ElementRuntimeData,
+ binaryDecimalVirtualPoint: Int,
+ packedSignCodes: PackedSignCodes,
+ decimalSigned: YesNo
+) extends PackedBinaryDecimalBaseParser(e, binaryDecimalVirtualPoint, Some(decimalSigned))
+ with EndOfParentBitLengthMixin {
+
+ override def toNumber(num: Array[Byte]): JBigDecimal =
+ DecimalUtils.packedToBigDecimal(num, binaryDecimalVirtualPoint, packedSignCodes)
+}
+
class PackedIntegerRuntimeLengthParser(
val e: ElementRuntimeData,
packedSignCodes: PackedSignCodes,
@@ -101,3 +113,14 @@ class PackedIntegerBitLimitLengthParser(
DecimalUtils.packedToBigInteger(num, packedSignCodes)
}
+
+class PackedIntegerEndOfParentLengthParser(
+ e: ElementRuntimeData,
+ packedSignCodes: PackedSignCodes
+) extends PackedBinaryIntegerBaseParser(e)
+ with EndOfParentBitLengthMixin {
+
+ override def toNumber(num: Array[Byte]): JBigInteger =
+ DecimalUtils.packedToBigInteger(num, packedSignCodes)
+
+}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/BinaryBooleanParsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/BinaryBooleanParsers.scala
index a4a30de0bd..2df8114f7e 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/BinaryBooleanParsers.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/BinaryBooleanParsers.scala
@@ -115,7 +115,5 @@ class BinaryBooleanBitLimitLengthParser(
override def runtimeDependencies = Vector()
- override def getBitLength(state: PState): Int = {
- getLengthInBits(state).toInt
- }
+ override def getBitLength(state: PState): Int = super.getBitLengthAsInt(state)
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/HexBinaryLengthParsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/HexBinaryLengthParsers.scala
index 7c9f50efca..9d07194c0d 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/HexBinaryLengthParsers.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/HexBinaryLengthParsers.scala
@@ -84,11 +84,15 @@ final class HexBinarySpecifiedLengthParser(erd: ElementRuntimeData, lengthEv: Le
}
final class HexBinaryEndOfBitLimitParser(erd: ElementRuntimeData)
- extends HexBinaryLengthParser(erd) {
+ extends HexBinaryLengthParser(erd),
+ BitLengthFromBitLimitMixin {
override def runtimeDependencies = Vector()
+}
- override def getLengthInBits(pstate: PState): Long = {
- pstate.bitLimit0b.get - pstate.bitPos0b
- }
+final class HexBinaryEndOfParentParser(erd: ElementRuntimeData)
+ extends HexBinaryLengthParser(erd),
+ EndOfParentBitLengthMixin {
+
+ override def runtimeDependencies = Vector()
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala
index fa64d628bb..25c744b622 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala
@@ -17,7 +17,6 @@
package org.apache.daffodil.runtime1.processors.parsers
-import java.nio.channels.Channels
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
@@ -801,7 +800,7 @@ object PState {
dataProc: DFDL.DataProcessor,
areDebugging: Boolean
): PState = {
- val dis = InputSourceDataInputStream(Channels.newInputStream(input))
+ val dis = InputSourceDataInputStream(input)
createInitialPState(root, dis, output, dataProc, areDebugging)
}
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/BinaryNumberTraits.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ParserTraits.scala
similarity index 74%
rename from daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/BinaryNumberTraits.scala
rename to daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ParserTraits.scala
index 814a9ee31b..62a95c081b 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/BinaryNumberTraits.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ParserTraits.scala
@@ -170,18 +170,73 @@ trait PrefixedLengthParserMixin {
* Some parsers do not calculate their own length, but instead expect another parser
* to set the bit limit, and then they use that bit limit as the length.
* An example of this is prefix length parsers. This trait can be used by those
- * parsers to do determine the length based on the bitLimit and position.
+ * parsers to determine the length based on the bitLimit and position.
+ *
+ * For dfdl:lengthKind='endOfParent' parsers that need to scan to end-of-stream
+ * when no bit limit is set, mix in [[EndOfParentBitLengthMixin]] instead.
*/
trait BitLengthFromBitLimitMixin {
- def getBitLength(s: ParseOrUnparseState): Int = {
- val pState = s.asInstanceOf[PState]
- val len = getLengthInBits(pState)
- len.toInt
+ def getBitLength(s: ParseOrUnparseState): Int = getBitLengthAsInt(s.asInstanceOf[PState])
+
+ /**
+ * getLengthInBits converted to Int with a parse error on overflow.
+ *
+ * A bare .toInt without this guard can overflow
+ * for a bit length over 4GB.
+ */
+ def getBitLengthAsInt(pstate: PState): Int = {
+ val len = getLengthInBits(pstate)
+ if (pstate.processorStatus ne Success) return 0
+ if (len > Int.MaxValue) {
+ pstate.SDE(
+ "Length exceeds maximum of %s bits: %s",
+ Int.MaxValue,
+ len
+ )
+ 0
+ } else {
+ len.toInt
+ }
}
def getLengthInBits(pstate: PState): Long = {
- val len = pstate.bitLimit0b.get - pstate.bitPos0b
- len
+ if (pstate.bitLimit0b.isDefined) {
+ pstate.bitLimit0b.get - pstate.bitPos0b
+ } else {
+ Assert.invariantFailed("BitLimit not set for parser.")
+ }
+ }
+}
+
+/**
+ * Extends [[BitLengthFromBitLimitMixin]] with dfdl:lengthKind='endOfParent' support.
+ * When no bit limit is set (i.e., the parser is at the root or outermost EOP element),
+ * falls back to [[org.apache.daffodil.io.InputSource#optEndOfDataPosition]] to locate
+ * the end of the data stream.
+ *
+ * Only mix this in for parsers that are instantiated specifically for EOP elements.
+ * Parsers for prefixed or explicitly-bounded elements should use the plain
+ * [[BitLengthFromBitLimitMixin]]; mixing in this trait by mistake would silently
+ * consume the entire remaining stream instead of failing at the missing bit limit.
+ */
+trait EndOfParentBitLengthMixin extends BitLengthFromBitLimitMixin {
+
+ override def getLengthInBits(pstate: PState): Long = {
+ if (pstate.bitLimit0b.isDefined) {
+ pstate.bitLimit0b.get - pstate.bitPos0b
+ } else {
+ val dis = pstate.dataInputStream
+ dis.optEndOfDataPosition match {
+ case Some(endOfDataPosition) =>
+ (endOfDataPosition * 8) - dis.bitPos0b
+ case None =>
+ pstate.SDE(
+ "Cannot determine end-of-data position for dfdl:lengthKind='endOfParent': %s bytes read",
+ dis.knownBytesAvailable
+ )
+ 0L // callers check pstate.processorStatus before using this value
+ }
+ }
}
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SpecifiedLengthParsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SpecifiedLengthParsers.scala
index 2f3ee1f5dd..f77ca32849 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SpecifiedLengthParsers.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SpecifiedLengthParsers.scala
@@ -20,7 +20,7 @@ package org.apache.daffodil.runtime1.processors.parsers
import java.lang.Long as JLong
import java.util.regex.Matcher
-import org.apache.daffodil.lib.equality.*
+import org.apache.daffodil.io.InputSourceDataInputStream
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.schema.annotation.props.gen.LengthUnits
import org.apache.daffodil.lib.util.MaybeULong
@@ -52,11 +52,10 @@ sealed abstract class SpecifiedLengthParserBase(eParser: Parser, erd: RuntimeDat
*/
protected def getBitLength(s: PState): MaybeULong
- final def parse(pState: PState): Unit = {
-
+ def parse(pState: PState): Unit = {
val maybeNBits = getBitLength(pState)
- if (pState.processorStatus._ne_(Success)) return
+ if (pState.processorStatus ne Success) return
val nBits = maybeNBits.get
val dis = pState.dataInputStream
@@ -87,16 +86,22 @@ sealed abstract class SpecifiedLengthParserBase(eParser: Parser, erd: RuntimeDat
// we want to capture the length before we do any skipping
// value length of simple types is captured by the eParser if needed
- // the SpecifiedLengthParserBase is extended by SpecifiedLengthChoiceParser which should not have its valueLength captured here
+ // the SpecifiedLengthParserBase is extended by SpecifiedLengthChoiceParser
+ // which should not have its valueLength captured here
if (pState.infoset.isComplex && !erd.isInstanceOf[ChoiceRuntimeData])
captureValueLength(pState, ULong(startingBitPos0b), ULong(dis.bitPos0b))
Assert.invariant(dis eq pState.dataInputStream)
val bitsToSkip = finalEndPos0b - dis.bitPos0b
+ skipBits(pState, bitsToSkip)
+ }
+
+ def skipBits(pState: PState, bitsToSkip: Long): Unit = {
Assert.invariant(
bitsToSkip >= 0
) // if this is < 0, then the parsing of children went past the limit, which it isn't supposed to.
if (bitsToSkip > 0) {
+ val dis = pState.dataInputStream
// skip left over bits
val skipSuccess = dis.skip(bitsToSkip, pState)
if (!skipSuccess) {
@@ -104,6 +109,7 @@ sealed abstract class SpecifiedLengthParserBase(eParser: Parser, erd: RuntimeDat
}
}
}
+
}
class SpecifiedLengthPatternParser(
@@ -132,6 +138,56 @@ class SpecifiedLengthPatternParser(
}
}
+class SpecifiedLengthEndOfParentParser(
+ eParser: Parser,
+ erd: ElementRuntimeData
+) extends SpecifiedLengthParserBase(eParser, erd),
+ EndOfParentBitLengthMixin {
+
+ override protected def getBitLength(s: PState): MaybeULong = {
+ MaybeULong(getLengthInBits(s))
+ }
+
+ override def parse(pState: PState): Unit = {
+ val dis = pState.dataInputStream
+
+ if (erd.isComplexType && dis.bitLimit0b.isEmpty) {
+ // when bitLimit is not defined, getBitLength will read to EOD,
+ // which we don't want to do for complex types because EOD could
+ // be very far away. So we just let the children parse as much as
+ // they want, and after that we figure out how much is left over to
+ // EOD and skip it
+ val startingBitPos0b = dis.bitPos0b
+
+ eParser.parse1(pState)
+
+ // at this point the recursive parse of the children is finished
+ // so if we're still successful we need to advance the position
+ // to skip past any bits that the recursive child parse did not
+ // consume at the end. That is, the specified length can be an
+ // outer constraint, but the children may not use it all up, leaving
+ // a section at the end.
+ if (pState.processorStatus ne Success) return
+
+ captureValueLength(pState, ULong(startingBitPos0b), ULong(dis.bitPos0b))
+
+ // this will find remaining bits to EndOfData
+ // Since this is called after the children parse, this value indicates
+ // the number of bits until EndOfData, rather than the length of
+ // the complex type
+ val maybeNBits = getBitLength(pState)
+
+ if (pState.processorStatus ne Success) return
+ val nBits = maybeNBits.get
+
+ skipBits(pState, nBits)
+ } else {
+ super.parse(pState)
+ }
+ }
+
+}
+
class SpecifiedLengthExplicitParser(
eParser: Parser,
erd: ElementRuntimeData,
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/SpecifiedLength2.scala b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/SpecifiedLength2.scala
index 6974e3f3f7..bec7480478 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/SpecifiedLength2.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/SpecifiedLength2.scala
@@ -556,14 +556,23 @@ class ChoiceUnusedUnparserSuspendableOperation(
}
/**
- * determine delta between value length and target length
+ * Fills the unused tail of the choice box with skip bits.
*
- * and skip that many bits.
+ * [[dosToCheck_]] is the DOS chain bracketing the branch content, from the
+ * RegionSplitUnparser capture at entry to the one at exit. Each DOS in the
+ * chain either died (was collapsed into its successor via setFinished /
+ * deliverBufferContent), freezing its relBitPos0b at the bits it had absorbed
+ * at death, or is still live (dosForEnd), accumulating any remaining writes.
+ *
+ * Because deliverBufferContent only ever adds to the absorbing DOS, the DOS
+ * that was made direct most recently always holds the largest relBitPos0b,
+ * which equals the true total branch length in bits. Taking the max across
+ * the chain is therefore correct regardless of how many intermediate DOS
+ * splits were introduced by suspensions inside the branch (e.g.
+ * AlignmentFillUnparserSuspendableOperation, OnlyPaddingUnparserSuspendableOperation).
*/
override def continuation(ustate: UState): Unit = {
- val startPos0b = dosToCheck_(0).relBitPos0b
- val endPos0b = dosToCheck_.last.relBitPos0b + startPos0b
- val vl = (endPos0b - startPos0b).toLong
+ val vl = dosToCheck_.map(_.relBitPos0b.toLong).max
val skipInBits = targetLengthInBits - vl
if (skipInBits < 0)
UE(
diff --git a/daffodil-core/src/test/scala/org/apache/daffodil/io/TestDaffodilDataInputSource.scala b/daffodil-core/src/test/scala/org/apache/daffodil/io/TestDaffodilDataInputSource.scala
index b6220d65af..196df04bc0 100644
--- a/daffodil-core/src/test/scala/org/apache/daffodil/io/TestDaffodilDataInputSource.scala
+++ b/daffodil-core/src/test/scala/org/apache/daffodil/io/TestDaffodilDataInputSource.scala
@@ -20,6 +20,7 @@ package org.apache.daffodil.io
import java.nio.ByteBuffer
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
import org.junit.Test
/**
@@ -256,6 +257,41 @@ class TestBucketingInputSource {
assertEquals(6, bis.get())
assertEquals(-1, bis.get())
}
+
+ /**
+ * Regression for: optEndOfDataPosition must not evict a bucket that has an
+ * active backtracking mark (refCount > 0).
+ *
+ * Configuration: bucketSize=2 bytes, maxNumberOfNonNullBuckets=2 (4 bytes total
+ * cache). Locking position 0 pins bucket 0. When optEndOfDataPosition tries to
+ * scan beyond the 2-bucket window it would need to evict bucket 0 — which is
+ * forbidden. The fix stops the scan and returns None rather than nulling the
+ * locked bucket.
+ *
+ * Before fix: fillBucketsToIndex evicted bucket 0 unconditionally; the
+ * subsequent bis.position(0)/bis.get() threw BacktrackingException.
+ * After fix: optEndOfDataPosition returns None; the locked position is still
+ * readable after the scan.
+ */
+ @Test def testOptEndOfDataPositionDoesNotEvictLockedBucket(): Unit = {
+ // bucketSizeExponent=1 -> bucketSize=2; maxCacheSizeInBytes=4 -> maxNumberOfNonNullBuckets=2
+ val tis = new TestInputStream()
+ tis.setEOF(12)
+ val bis = new BucketingInputSource(tis, bucketSizeExponent = 1, maxCacheSizeInBytes = 4)
+
+ bis.lockPosition(0) // simulates a choice-parser mark at the start of the stream
+
+ // The oldest bucket (index 0) is locked. fillBucketsToIndex would exceed
+ // maxNumberOfNonNullBuckets after filling 2 buckets and be unable to evict
+ // bucket 0, so it stops the scan and returns None.
+ assertEquals(None, bis.optEndOfDataPosition)
+
+ // Bucket 0 must still be live: backtracking to position 0 must succeed.
+ bis.position(0)
+ assertEquals(0, bis.get())
+
+ bis.releasePosition(0)
+ }
}
class TestByteBufferInputSource {
@@ -386,3 +422,92 @@ class TestByteBufferInputSource {
assertEquals(-1, bis.get())
}
}
+
+class TestBucketingInputSourceFileChannel {
+
+ /**
+ * Regression for fix 1: optEndOfDataPosition must short-circuit via the
+ * file size supplied by InputSourceDataInputStream.apply(InputStream) for a
+ * FileInputStream, rather than reading up to maxCacheSizeInBytes (256 MiB)
+ * of lookahead data to scan for EOF.
+ *
+ * Verified by asserting knownBytesAvailable() == 0 both before and after the
+ * call: if the scan path had run it would have loaded data into buckets,
+ * making knownBytesAvailable() > 0.
+ */
+ @Test def testOptEndOfDataPositionUsesFileSizeForFileInputStream(): Unit = {
+ val content = Array[Byte](1, 2, 3, 4, 5)
+ val tmpFile = java.nio.file.Files.createTempFile("daffodil-eop-test", ".bin")
+ try {
+ java.nio.file.Files.write(tmpFile, content)
+ val fis = new java.io.FileInputStream(tmpFile.toFile)
+ try {
+ // Size hint is supplied by the factory, not by BucketingInputSource directly.
+ val src = InputSourceDataInputStream(fis).inputSource
+ assertEquals(0L, src.knownBytesAvailable())
+ assertEquals(Some(5L), src.optEndOfDataPosition)
+ // Scan path would have populated buckets; verify it was not taken.
+ assertEquals(0L, src.knownBytesAvailable())
+ } finally {
+ fis.close()
+ }
+ } finally {
+ java.nio.file.Files.deleteIfExists(tmpFile)
+ }
+ }
+
+ /**
+ * Verify that BucketingInputSource constructed directly (no optSizeHint) falls
+ * back to the scan path: optEndOfDataPosition reads the entire stream into
+ * buckets, so knownBytesAvailable() is positive after the call.
+ */
+ @Test def testBucketingInputSourceWithoutHintUsesScanner(): Unit = {
+ val content = Array[Byte](1, 2, 3, 4, 5)
+ val tmpFile = java.nio.file.Files.createTempFile("daffodil-eop-test", ".bin")
+ try {
+ java.nio.file.Files.write(tmpFile, content)
+ val fis = new java.io.FileInputStream(tmpFile.toFile)
+ try {
+ val bis = new BucketingInputSource(fis) // no optSizeHint
+ assertEquals(0L, bis.knownBytesAvailable())
+ assertEquals(Some(5L), bis.optEndOfDataPosition)
+ // Scan path was taken - data has been buffered.
+ assertTrue(bis.knownBytesAvailable() > 0)
+ } finally {
+ fis.close()
+ }
+ } finally {
+ java.nio.file.Files.deleteIfExists(tmpFile)
+ }
+ }
+
+ /**
+ * Regression for compact() Int overflow: compact() must correctly update
+ * headBucketBytePosition0b when oldestBucketIndex is large. Uses tiny 2-byte
+ * buckets so the scenario exercises multiple compaction cycles without
+ * needing gigabytes of test data. Verifies that position() and subsequent
+ * reads are correct after compact() runs with a non-trivial oldestBucketIndex.
+ */
+ @Test def testCompactCorrectlyUpdatesPositionOffset(): Unit = {
+ // 2-byte buckets, 4-byte max cache (2 live buckets). Buckets are evicted
+ // quickly, driving oldestBucketIndex to a non-zero value before compact().
+ val tis = new TestInputStream
+ tis.setEOF(30)
+ val bis = new BucketingInputSource(tis, bucketSizeExponent = 1, maxCacheSizeInBytes = 4)
+
+ // Read 20 bytes; with 2-byte buckets the eviction loop inside
+ // fillBucketsToIndex sets oldestBucketIndex > 0.
+ var i = 0
+ while (i < 20) { assertEquals(i % 10, bis.get()); i += 1 }
+
+ // compact() advances headBucketBytePosition0b. Before the Int-overflow fix
+ // the multiplication oldestBucketIndex * bucketSize was Int×Int; with large
+ // oldestBucketIndex that would produce a wrong (potentially negative) offset.
+ bis.compact()
+ assertEquals(20L, bis.position())
+
+ // Reads after compact() must still produce the correct sequence.
+ while (i < 30) { assertEquals(i % 10, bis.get()); i += 1 }
+ assertEquals(30L, bis.position())
+ }
+}
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml
index c2be97ef3f..e97a545c6b 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml
@@ -639,6 +639,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -664,6 +686,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4357,6 +4403,21 @@
+
+ IaaBbb
+
+
+
+
+
+ aa
+ bb
+
+
+
+
+
+
IAaabbee
@@ -4373,6 +4434,22 @@
+
+ IAaabbee
+
+
+
+
+
+ aa
+ bb
+
+ ee
+
+
+
+
+
aaTBbb
@@ -4613,4 +4690,6 @@
+
+
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/DelimitedTests.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/DelimitedTests.tdml
index c2f55a0d04..77e998562a 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/DelimitedTests.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/DelimitedTests.tdml
@@ -1135,4 +1135,222 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hi
+ after
+
+
+
+
+
+
+
+
+
+
+
+ abc
+ rest
+
+
+
+
+
+
+
+
+
+
+
+ abcde
+ rest
+
+
+
+
+
+
+
+
+
+
+
+ 42
+ rest
+
+
+
+
+
+
+
+
+ 00 5F 00 5F 00 5F 00 68 00 69 00 7C
+
+
+
+
+ hi
+
+
+
+
+
+
+
+
+ 00 5F 00 5F 00 5F 00 68 00 69 00 7C
+
+
+
+
+ hi
+
+
+
+
+
+
+
+
+ 00 61 00 62 00 63 00 64 00 65 00 7C
+
+
+
+
+ abcde
+
+
+
+
+
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/EndOfParentTests.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/EndOfParentTests.tdml
index fdbcc2e32d..39e1a99b17 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/EndOfParentTests.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/EndOfParentTests.tdml
@@ -20,9 +20,10 @@
description="Section 12 - lengthKind=endOfParent" xmlns:tdml="http://www.ibm.com/xmlns/dfdl/testData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions"
- xmlns:ex="http://example.com" defaultRoundTrip="true">
+ xmlns:ex="http://example.com" defaultRoundTrip="onePass"
+ xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Schema Definition Error
+ endOfParent
+ but its parent
+ lengthKind
+ delimited
+
+
+
+
+
+
+ Schema Definition Error
+ endOfParent
+ but its parent
+ lengthKind
+ delimited
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
Schema Definition Error
- not
- implemented
endOfParent
- complex
- type
+ but its parent
+ lengthKind
+ implicit
-
-
+
+
Schema Definition Error
- not
- implemented
endOfParent
- simple
- type
+ but its parent
+ lengthKind
+ implicit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+
+
+
+ 2
+ 2
+
+
+ 3
+ 3
+
+
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+ X
+ YZ
+
+ A
+
+
+
+
+
+
+
+
+ endOfParent
+ does not have
+ single-byte character set encoding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+
+
+
+ 2
+ 2
+
+
+ 3
+ 3
+
+
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+ X
+ YZ
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+
+
+
+ 2
+ 2
+
+
+ 3
+ 3
+
+
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+ X
+ YZ
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+
+
+
+ 2
+ 2
+
+
+ 3
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ X
+ YZ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ represented element
+ between this element and
+ end of the enclosing component
+
+
+
+
+
+
+ endOfParent
+ represented element
+ between this element and
+ end of the enclosing component
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ model group
+ between this element and
+ end of the enclosing component
+
+
+
+ endOfParent
+ model group
+ between this element and
+ end of the enclosing component
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+
+
+
+ 2
+ 2
+
+
+ 3
+ 3
+
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+ X
+ YZ
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ specifies
+ terminator
+
+
+
+
+
+
+ endOfParent
+ specifies
+ terminator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ specifies
+ non-zero
+ trailingSkip
+
+
+
+
+
+
+ endOfParent
+ specifies
+ non-zero
+ trailingSkip
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ specifies
+ maxOccurs
+ greater than 1
+
+
+
+
+
+
+ endOfParent
+ specifies
+ maxOccurs
+ greater than 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ does not have
+ single-byte character set encoding
+
+
+
+
+
+
+ endOfParent
+ does not have
+ single-byte character set encoding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ in a sequence with
+ separatorPosition
+ postfix
+
+
+
+
+
+
+ endOfParent
+ in a sequence with
+ separatorPosition
+ postfix
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ in a sequence with
+ sequenceKind
+ unordered
+
+
+
+
+
+
+ endOfParent
+ in a sequence with
+ sequenceKind
+ unordered
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ in a sequence with
+ elements
+ floating='yes
+
+
+
+
+
+
+ endOfParent
+ in a sequence with
+ elements
+ floating='yes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ in a sequence with
+ non-zero
+ trailingSkip
+
+
+
+
+
+
+ endOfParent
+ in a sequence with
+ non-zero
+ trailingSkip
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ in a choice with
+ non-zero
+ trailingSkip
+
+
+
+
+
+
+ endOfParent
+ in a choice with
+ non-zero
+ trailingSkip
+
+
+
+
+
+
+ endOfParent
+ a choice with
+ choiceLengthKind
+ implicit
+
+
+
+
+
+
+ endOfParent
+ a choice with
+ choiceLengthKind
+ implicit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ in a choice with
+ terminator
+
+
+
+
+
+
+ endOfParent
+ in a choice with
+ terminator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xyzw
+ A
+
+
+
+
+
+
+
+
+
+
+
+ xyzw
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+ abcdef
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ X
+ YZ
+ COMPUTED
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+
+
+ 2
+ 2
+
+ COMPUTED
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endOfParent
+ supported for
+ packed binary
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ X
+ 3132
+
+
+
+
+
+
+
+
+
+
+
+
+ X
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+
+
+
+ 2
+ 2
+
+
+ 3
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+ X
+ YZ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ abcde
+
+
+
+ abcde
+
+
+
+
+
+
+ ábcde
+
+
+ endOfParent
+ represented element
+ between this element and
+ end of the enclosing component
+
+
+
+
+
+ abcde
+
+
+
+
+ abcde
+
+
+
+
+
+
+
+ abcde
+
+
+
+
+
+ abcde
+
+
+
+
+
+
+
+
+ abcde
+
+
+
+ abcde
+
+
+
+
+
+
+ abcde
+
+
+
+ abcde
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 12
+
+
+
+ 12
+
+
+
+
+
+
+ 123
+
+
+ endOfParent
+ represented element
+ between this element and
+ end of the enclosing component
+
+
+
+
+
+ 12
+
+
+
+ 12
+
+
+
+
+
+
+ 12
+
+
+
+ 12
+
+
+
+
+
+
+ 123.45
+
+
+
+ 123.45
+
+
+
+
+
+
+ 123.45
+
+
+
+ 123.45
+
+
+
+
+
+
+ 123.45
+
+
+
+ 123.45
+
+
+
+
+
+
+ 2017-08-28T18:29:00
+
+
+
+ 2017-08-28T18:29:00
+
+
+
+
+
+
+ 2017-08-28T18:29:00
+
+
+
+ 2017-08-28T18:29:00
+
+
+
+
+
+
+ 2017-08-28T18:29:00
+
+
+
+ 2017-08-28T18:29:00
+
+
+
+
+
+
+ TRUE
+
+
+
+ true
+
+
+
+
+
+
+ TRUE
+
+
+
+ true
+
+
+
+
+
+
+ TRUE
+
+
+
+ true
+
+
+
+
+
+
+
+ abcdefg
+
+
+
+
+
+ abcd
+
+ e
+
+
+
+
+
+
+
+
+ abcd
+
+
+
+
+
+ abcd
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 01234C
+
+
+
+ 1234
+
+
+
+
+
+
+
+ 1234
+
+
+
+ 1234
+
+
+
+
+
+
+ 1234
+
+
+
+ 1234
+
+
+
+
+
+
+ 30 39
+
+
+ endOfParent
+ only supported
+ packed binary formats
+
+
+
+
+
+ 01234C
+
+
+
+ 12.34
+
+
+
+
+
+
+ 1234
+
+
+
+ 12.34
+
+
+
+
+
+
+ 1234
+
+
+
+ 12.34
+
+
+
+
+
+
+
+ DE AD BE EF
+
+
+
+ DEADBEEF
+
+
+
+
+
+
+
+ 01
+
+
+ endOfParent
+ not supported
+ boolean with binary representation
+
+
+
+
+
+
+ 01 22 51 64 51 93 65 0C
+
+
+
+ 1645-12-25T19:36:50
+
+
+
+
+
+
+ 06 14 20 04 18 56 03
+
+
+
+ 2004-06-14T18:56:03
+
+
+
+
+
+
+ 11 30 20 07 04 15 08
+
+
+
+ 2007-11-30T04:15:08
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hello
+
+
+
+ hello
+
+
+
+
+
+
+ hello
+
+
+
+ hello
+
+
+
+
+
+
+ hello}
+
+
+ endOfParent
+ sequence with
+ terminator
+
+
+
+
+
+ hello
+
+
+
+ hello
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ helff
+
+
+
+ hel
+
+
+
+
+
+
+ helff
+
+
+
+ hel
+
+
+
+
+
+
+ helloA
+
+
+
+ helloA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0111
+ 10101011
+
+
+
+
+
+ 7
+ AB
+
+
+
+
+
+
+
+
+
+ 0111
+ 10101011 1010
+
+
+
+
+
+ 7
+ 101010111010
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AA BB CC DD EE
+
+
+
+
+
+ AABBCCDDEE
+
+
+
+
+
+
+
+
+
+ AA BB 00 00 00
+
+
+
+
+
+ AABB
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ___hi
+
+
+
+
+
+ hi
+
+
+
+
+
+
+
+
+
+ __abc
+
+
+
+
+
+ abc
+
+
+
+
+
+
+
+
+
+ abc
+
+
+
+
+
+ abc
+
+
+
+
+
+
+
+
+
+ 42
+
+
+
+
+
+ 42
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 02 34 32
+
+
+
+
+ 2
+
+ 42
+
+
+
+
+
+
+
+
+
+ 02 34 32
+
+
+
+
+ 2
+
+ 42
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ab
+
+
+
+
+ ab
+
+
+
+
+
+
+
+
+ ab
+
+
+
+
+ ab
+
+
+
+
+
+
+
+
+ ab
+
+
+
+
+ ab
+
+
+
+
+
+
+
+
+ ab
+
+
+
+
+ ab
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ abcde|fg|
+
+
+
+
+ abc
+ de|fg
+
+
+
+
+
+
+
+
+ abcde|fg|
+
+
+
+
+ abc
+ de|fg
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hello
+
+
+
+
+ hello
+
+
+
+
+
+
+
+
+
+ DE AD BE EF
+
+
+
+
+ DEADBEEF
+
+
+
+
+
+
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/PatternTests.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/PatternTests.tdml
index d79649ac30..654e3beedc 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/PatternTests.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/PatternTests.tdml
@@ -999,6 +999,181 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hi
+ after
+
+
+
+
+
+
+
+
+
+
+ abc
+ rest
+
+
+
+
+
+
+
+
+
+
+
+ abcde
+ rest
+
+
+
+
+
+
+
+
+ 00 5F 00 5F 00 5F 00 68 00 69 00 7C
+
+
+
+
+ hi
+
+
+
+
+
+
+
+
+ 00 5F 00 5F 00 5F 00 68 00 69 00 7C
+
+
+
+
+ hi
+
+
+
+
+
+
+
+
+ 00 61 00 62 00 63 00 64 00 65 00 7C
+
+
+
+
+ abcde
+
+
+
+
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section13/packed/packed.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section13/packed/packed.tdml
index 6714a288c5..ce71d4d347 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section13/packed/packed.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section13/packed/packed.tdml
@@ -259,6 +259,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1050,4 +1112,187 @@
+
+
+
+ 003C 123D
+
+
+
+
+ 3
+ -123
+
+
+
+
+
+
+
+
+ 003C 123D
+
+
+
+
+ 0.03
+ -1.23
+
+
+
+
+
+
+
+
+
+ 3
+ -123
+
+
+
+
+
+ 003C 123D
+
+
+
+
+
+
+
+
+ 0.03
+ -1.23
+
+
+
+
+
+ 003C 123D
+
+
+
+
+
+
+ 0003 0123
+
+
+
+
+ 3
+ 123
+
+
+
+
+
+
+
+
+ 0003 0123
+
+
+
+
+ 0.03
+ 1.23
+
+
+
+
+
+
+
+
+
+ 3
+ 123
+
+
+
+
+
+ 0003 0123
+
+
+
+
+
+
+
+ 0.03
+ 1.23
+
+
+
+
+
+ 0003 0123
+
+
+
+
+
+
+
+ FFF3 D123
+
+
+
+
+ 3
+ -123
+
+
+
+
+
+
+
+
+ FFF3 D123
+
+
+
+
+ 0.03
+ -1.23
+
+
+
+
+
+
+
+
+
+
+ 3
+ -123
+
+
+
+
+
+ FFF3 D123
+
+
+
+
+
+
+
+ 0.03
+ -1.23
+
+
+
+
+
+ FFF3 D123
+
+
+
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceLengthExplicit.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceLengthExplicit.tdml
index 099c6c5a1b..ff00047748 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceLengthExplicit.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceLengthExplicit.tdml
@@ -84,6 +84,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A
+ BC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A
+ BC
+
+
+
+
+
+
+
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala
index 3c4de0add4..ad587d2a09 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala
@@ -209,6 +209,10 @@ class TestAlignedData extends TdmlTests {
@Test def prior_siblings_3 = test
@Test def prior_siblings_4 = test
@Test def prior_siblings_5 = test
+
+ // DAFFODIL-238
+ @Test def test_init_alignment_1_eop = test
+ @Test def test_init_alignment_2_eop = test
}
class TestBinaryInput extends TdmlTests {
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindDelimited.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindDelimited.scala
index 7f1b4ef80e..bc695344ec 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindDelimited.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindDelimited.scala
@@ -78,6 +78,15 @@ class TestLengthKindDelimited extends TdmlTests {
@Test def Lesson1_lengthKind_delimited = test
@Test def Lesson4_delimited_fixed_length = test
@Test def delimited_construct = test
+
+ @Test def delim_pad_str_right_unparse = test
+ @Test def delim_pad_str_right_roundtrip = test
+ @Test def delim_pad_str_no_padding_needed = test
+ @Test def delim_pad_num_right_roundtrip = test
+
+ @Test def delim_pad_utf16_str_right_unparse = test
+ @Test def delim_pad_utf16_str_right_roundtrip = test
+ @Test def delim_pad_utf16_str_no_padding_needed = test
}
class TestLengthKindDelimitedAB extends TdmlTests {
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindEndOfParent.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindEndOfParent.scala
new file mode 100644
index 0000000000..4cf4d9c712
--- /dev/null
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindEndOfParent.scala
@@ -0,0 +1,154 @@
+/*
+ * 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.section12.lengthKind
+
+import org.apache.daffodil.junit.tdml.TdmlSuite
+import org.apache.daffodil.junit.tdml.TdmlTests
+
+import org.junit.Test
+
+object TestLengthKindEndOfParent extends TdmlSuite {
+ val tdmlResource = "/org/apache/daffodil/section12/lengthKind/EndOfParentTests.tdml"
+}
+
+class TestLengthKindEndOfParent extends TdmlTests {
+ val tdmlSuite = TestLengthKindEndOfParent
+
+ @Test def TestEndOfParentComplexTypesDelimited = test
+ @Test def TestEndOfParentSimpleTypesDelimited = test
+ @Test def TestEndOfParentComplexTypesImplicit = test
+ @Test def TestEndOfParentSimpleTypesImplicit = test
+ @Test def TestEndOfParentComplexTypesExplicit = test
+ @Test def TestEndOfParentSimpleTypesExplicit = test
+ @Test def TestEndOfParentCSVExplicit = test
+ @Test def TestEndOfParentComplexTypesPrefixed = test
+ @Test def TestEndOfParentSimpleTypesPrefixed = test
+ @Test def TestEndOfParentComplexTypesPattern = test
+ @Test def TestEndOfParentSimpleTypesPattern = test
+ @Test def TestEndOfParentComplexTypesEOP = test
+ @Test def TestEndOfParentSimpleTypesEOP = test
+ @Test def TestEndOfParentComplexTypes1 = test
+ @Test def TestEndOfParentSimpleTypes1 = test
+ @Test def TestEndOfParentComplexTypes2 = test
+ @Test def TestEndOfParentSimpleTypes2 = test
+ @Test def TestEndOfParentComplexTypes3 = test
+ @Test def TestEndOfParentSimpleTypes3 = test
+ @Test def TestEndOfParentComplexTypes4 = test
+ @Test def TestEndOfParentSimpleTypes4 = test
+ @Test def TestEndOfParentComplexTypes5 = test
+ @Test def TestEndOfParentSimpleTypes5 = test
+ @Test def TestEndOfParentComplexTypes6 = test
+ @Test def TestEndOfParentSimpleTypes6 = test
+ @Test def TestEndOfParentComplexTypes7 = test
+ @Test def TestEndOfParentSimpleTypes7 = test
+ @Test def TestEndOfParentComplexTypes8 = test
+ @Test def TestEndOfParentSimpleTypes8 = test
+ @Test def TestEndOfParentComplexTypes9 = test
+ @Test def TestEndOfParentSimpleTypes9 = test
+ @Test def TestEndOfParentComplexTypes10 = test
+ @Test def TestEndOfParentSimpleTypes10 = test
+ @Test def TestEndOfParentComplexTypes11 = test
+ @Test def TestEndOfParentSimpleTypes11 = test
+ @Test def TestEndOfParentComplexTypes12 = test
+ @Test def TestEndOfParentSimpleTypes12 = test
+ @Test def TestEndOfParentComplexTypes12Implicit = test
+ @Test def TestEndOfParentSimpleTypes12Implicit = test
+ @Test def TestEndOfParentComplexTypes13 = test
+ @Test def TestEndOfParentSimpleTypes13 = test
+ @Test def eopBranchFirst_ST_eopBranch = test
+ @Test def eopBranchFirst_ST_fixedBranch = test
+ @Test def eopBranchFirst_CT_eopBranch = test
+ @Test def eopBranchFirst_CT_fixedBranch = test
+ @Test def eopFollowedByIVC_ST = test
+ @Test def eopFollowedByIVC_CT = test
+ @Test def TestEndOfParentSimpleTypes14 = test
+ @Test def TestEndOfParentSimpleTypes16 = test
+ @Test def TestEndOfParentSimpleTypes17 = test
+ @Test def TestEndOfParentComplexTypesRootEOP = test
+ @Test def TestEndOfParentSimpleTypesRootEOP = test
+
+ @Test def text_string_txt_bytes = test
+ @Test def text_string_txt_bits = test
+ @Test def text_string_txt_chars = test
+ @Test def text_string_txt_ref1 = test
+ @Test def text_string_txt_ref2 = test
+ @Test def text_string_txt_ref3 = test
+ @Test def text_string_txt_bytes_nil = test
+ @Test def text_string_txt_bits_nil = test
+ @Test def text_string_txt_chars_nil = test
+ @Test def text_int_txt_bytes = test
+ @Test def text_int_txt_bytes_group_ref = test
+ @Test def text_int_txt_bits = test
+ @Test def text_int_txt_chars = test
+ @Test def text_dec_txt_bytes = test
+ @Test def text_dec_txt_bits = test
+ @Test def text_dec_txt_chars = test
+ @Test def text_date_txt_bytes = test
+ @Test def text_date_txt_bits = test
+ @Test def text_date_txt_chars = test
+ @Test def text_bool_txt_bytes = test
+ @Test def text_bool_txt_bits = test
+ @Test def text_bool_txt_chars = test
+ @Test def check_eop_simple_elem_01 = test
+ @Test def check_eop_simple_elem_02 = test
+ @Test def bin_int_bin_bytes_packed = test
+ @Test def bin_int_bin_bytes_bcd = test
+ @Test def bin_int_bin_bytes_ibm4690 = test
+ @Test def bin_dec_bin_bytes = test
+ @Test def bin_dec_bin_bytes_packed = test
+ @Test def bin_dec_bin_bytes_bcd = test
+ @Test def bin_dec_bin_bytes_ibm4690 = test
+ @Test def bin_hex_bytes = test
+ @Test def bin_bool_bin_bytes = test
+ @Test def bin_date_bin_bytes_packed = test
+ @Test def bin_date_bin_bytes_bcd = test
+ @Test def bin_date_bin_bytes_ibm4690 = test
+
+ @Test def nested_01 = test
+ @Test def nested_02 = test
+ @Test def nested_03 = test
+ @Test def nested_04 = test
+ @Test def checks_01 = test
+ @Test def checks_02 = test
+ @Test def checks_03 = test
+
+ @Test def eop_not_byte_aligned_01 = test
+ @Test def eop_not_byte_aligned_02 = test
+
+ @Test def eop_unparse_fill_exact = test
+ @Test def eop_unparse_fill_short = test
+
+ @Test def eop_text_pad_str_right_unparse = test
+ @Test def eop_text_pad_str_right_roundtrip = test
+ @Test def eop_text_pad_str_left_roundtrip = test
+ @Test def eop_text_pad_num_right_roundtrip = test
+
+ @Test def eop_no_pad_when_expr_parent_unparse = test
+ @Test def eop_no_pad_when_expr_parent_roundtrip = test
+
+ @Test def eop_pad_to_parent_box_01 = test
+ @Test def eop_pad_to_parent_box_02 = test
+ @Test def eop_pad_to_choice_box_01 = test
+ @Test def eop_pad_to_choice_box_02 = test
+
+ @Test def eop_delim_scan_suspended_01 = test
+ @Test def eop_delim_scan_suspended_02 = test
+
+ @Test def eop_zero_length_st_01 = test
+ @Test def eop_zero_length_bin_01 = test
+}
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindEndOfParent2.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindEndOfParent2.scala
deleted file mode 100644
index 5928ed5f71..0000000000
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindEndOfParent2.scala
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.section12.lengthKind
-
-import org.apache.daffodil.junit.tdml.TdmlSuite
-import org.apache.daffodil.junit.tdml.TdmlTests
-
-import org.junit.Test
-
-object TestLengthKindEndOfParent2 extends TdmlSuite {
- val tdmlResource = "/org/apache/daffodil/section12/lengthKind/EndOfParentTests.tdml"
-}
-
-class TestLengthKindEndOfParent2 extends TdmlTests {
- val tdmlSuite = TestLengthKindEndOfParent2
-
- @Test def TestEndOfParentNYIComplexTypes = test
- @Test def TestEndOfParentNYISimpleTypes = test
-}
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindPattern.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindPattern.scala
index abd9de9d7e..c0e08a7ddc 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindPattern.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section12/lengthKind/TestLengthKindPattern.scala
@@ -77,6 +77,14 @@ class TestLengthKindPattern extends TdmlTests {
@Test def lengthPatternEncodingErrorReplace = test
@Test def lengthPatternBinaryPatternLimit = test
+
+ @Test def pattern_pad_str_right_unparse = test
+ @Test def pattern_pad_str_right_roundtrip = test
+ @Test def pattern_pad_str_no_padding_needed = test
+
+ @Test def pattern_pad_utf16_str_right_unparse = test
+ @Test def pattern_pad_utf16_str_right_roundtrip = test
+ @Test def pattern_pad_utf16_str_no_padding_needed = test
}
class TestLengthKindPatternAI extends TdmlTests {
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section13/packed/TestPacked.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section13/packed/TestPacked.scala
index 48fbcf95cf..4c7b516881 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section13/packed/TestPacked.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section13/packed/TestPacked.scala
@@ -95,4 +95,18 @@ class TestPacked extends TdmlTests {
// Daffodil-2961
@Test def bcdBigIntToLongExpr = test
+
+ // Daffodil-238
+ @Test def EOPPackedIntSeq = test
+ @Test def EOPPackedDecSeq = test
+ @Test def EOPPackedIntSeqUnparser = test
+ @Test def EOPPackedDecSeqUnparser = test
+ @Test def EOPBCDIntSeq = test
+ @Test def EOPBCDDecSeq = test
+ @Test def EOPBCDIntSeqUnparser = test
+ @Test def EOPBCDDecSeqUnparser = test
+ @Test def EOPIBM4690IntSeq = test
+ @Test def EOPIBM4690DecSeq = test
+ @Test def EOPIBM4690IntSeqUnparser = test
+ @Test def EOPIBM4690DecSeqUnparser = test
}
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section15/choice_groups/TestChoice.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section15/choice_groups/TestChoice.scala
index cfbb16a117..075f3a5183 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section15/choice_groups/TestChoice.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section15/choice_groups/TestChoice.scala
@@ -133,6 +133,8 @@ class TestChoiceLengthExplicit extends TdmlTests {
@Test def explicit_07 = test
@Test def explicit_08 = test
@Test def explicit_09 = test
+ @Test def explicit_10 = test
+ @Test def explicit_11 = test
@Test def explicit_multiple_choices = test