| /* Copyright (c) 2012-2014 Tresys Technology, LLC. All rights reserved. |
| * |
| * Developed by: Tresys Technology, LLC |
| * http://www.tresys.com |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy of |
| * this software and associated documentation files (the "Software"), to deal with |
| * the Software without restriction, including without limitation the rights to |
| * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
| * of the Software, and to permit persons to whom the Software is furnished to do |
| * so, subject to the following conditions: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimers. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimers in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * 3. Neither the names of Tresys Technology, nor the names of its contributors |
| * may be used to endorse or promote products derived from this Software |
| * without specific prior written permission. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE |
| * SOFTWARE. |
| */ |
| |
| package edu.illinois.ncsa.daffodil.grammar |
| |
| import edu.illinois.ncsa.daffodil.exceptions.Assert |
| import edu.illinois.ncsa.daffodil.processors._ |
| import edu.illinois.ncsa.daffodil.schema.annotation.props.gen._ |
| import edu.illinois.ncsa.daffodil.dpath.NodeInfo.PrimType |
| import edu.illinois.ncsa.daffodil.dsom.Found |
| import edu.illinois.ncsa.daffodil.dsom.InitiatedTerminatedMixin |
| import edu.illinois.ncsa.daffodil.dsom.SimpleTypeBase |
| import edu.illinois.ncsa.daffodil.dsom.ElementBase |
| import edu.illinois.ncsa.daffodil.api.DaffodilTunableParameters |
| import java.lang.{ Long => JLong } |
| import edu.illinois.ncsa.daffodil.dpath.NodeInfo |
| import edu.illinois.ncsa.daffodil.dsom.ExpressionCompilers |
| import edu.illinois.ncsa.daffodil.xml.GlobalQName |
| import edu.illinois.ncsa.daffodil.xml.XMLUtils |
| import edu.illinois.ncsa.daffodil.dsom.NotFound |
| |
| ///////////////////////////////////////////////////////////////// |
| // Elements System |
| ///////////////////////////////////////////////////////////////// |
| |
| trait ElementBaseGrammarMixin |
| extends InitiatedTerminatedMixin |
| with AlignedMixin |
| with ByteOrderMixin |
| with HasStatementsGrammarMixin |
| with PaddingInfoMixin { self: ElementBase => |
| |
| private val context = this |
| |
| private lazy val (leftPadding, rightPadding) = { |
| if (unparsingPadChar.isEmpty) |
| (EmptyGram, EmptyGram) |
| else { |
| import TextJustificationType._ |
| this.justificationPad match { |
| case None => (EmptyGram, EmptyGram) |
| case Left => (EmptyGram, OnlyPadding(context)) |
| case Right => (OnlyPadding(context), EmptyGram) |
| case Center => (LeftCenteredPadding(context), RightCenteredPadding(context)) |
| } |
| } |
| } |
| |
| protected lazy val isDelimitedPrefixedPattern = { |
| 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 _ => false |
| } |
| } |
| |
| /** |
| * true if padding will be inserted for this delimited element when unparsing. |
| */ |
| protected lazy val isDelimitedPrefixedPatternWithPadding = { |
| (isDelimitedPrefixedPattern && |
| (impliedRepresentation eq Representation.Text) && |
| (justificationPad ne TextJustificationType.None) && |
| minLen > 0) |
| } |
| |
| /** |
| * Quite tricky when we add padding or fill |
| * |
| * For complex types, there has to be a length defined. That's it. |
| * |
| * For simple types, we need the fill region processors |
| * to detect excess length |
| */ |
| final lazy val shouldAddPadding = { |
| val res = |
| if (isComplexType && |
| (this.maybeLengthEv.isDefined) && |
| (lengthUnits eq LengthUnits.Characters)) |
| maybeUnparseTargetLengthInBitsEv.isDefined // "pad" complex types. |
| else if (isDelimitedPrefixedPatternWithPadding) |
| true // simple type for unparse that needs to be padded. |
| else |
| // simple type, specified length. |
| (maybeUnparseTargetLengthInBitsEv.isDefined && |
| (justificationPad ne TextJustificationType.None)) // None if not text. |
| // if (res) |
| // println("%s should add padding.".format(this.prettyName)) |
| // else |
| // println("%s NOT adding padding.".format(this.prettyName)) |
| res |
| } |
| |
| /** |
| * We add fill to complex types of specified length so long as length units |
| * are bytes/bits. If characters then "pad" puts the characters on. |
| * |
| * We also add it to text elements of variable specified length again, unless |
| * length units are in characters. |
| */ |
| final lazy val shouldAddFill = { |
| val res = |
| (isComplexType && |
| isSpecifiedLengthForUnparsing && |
| (lengthUnits ne LengthUnits.Characters)) || |
| (isSimpleType && |
| isSpecifiedLengthForUnparsing && |
| couldBeVariableLengthInfoset && |
| (lengthUnits ne LengthUnits.Characters)) |
| // if (res) |
| // println("%s should add fill.".format(this.prettyName)) |
| // else |
| // println("%s NOT adding fill.".format(this.prettyName)) |
| res |
| } |
| |
| // /** |
| // * Means the representation could have varying sizes. |
| // * |
| // * Does not mean the infoset element might be bigger or smaller and |
| // * might get truncated or padded to fit a fixed size representation. |
| // * |
| // * This is about whether the box we're fitting it into is fixed or varying size. |
| // */ |
| // final protected lazy val isVariableLengthRep: Boolean = { |
| // isDelimitedPrefixedPattern || |
| // ((lengthKind eq LengthKind.Explicit) && |
| // !lengthEv.optConstant.isDefined) |
| // // |
| // // TODO: do we have to consider utf-8 situation for strings when |
| // // even though the length is constant, because it's utf-8 |
| // // the length units are characters but we don't know |
| // // how many bytes without knowing what characters are? |
| // // |
| // // This requires an Evaluatable, since it depends on charset. |
| // // |
| // // Also, whether we want this to be true/false depends on |
| // // context. If the context is a complex type with lengthUnits characters |
| // // then we're ok with utf-8 fixed number of characters. We're not |
| // // ok if the complex type has length its bytes, but contains an |
| // // element with length units bytes and charset utf-8. |
| // // |
| // } |
| |
| /** |
| * Means the specified length must, necessarily, be big enough to hold the representation |
| * so long as the value in the infoset is legal for the type. |
| * |
| * This does not include numeric range checking. So for example if you |
| * have an xs:unsignedInt but length is 3 bits, this will be true even though an |
| * integer value of greater than 7 cannot fit. |
| * |
| * Another way to think of this is that legal infoset values will have fixed |
| * length representations. |
| * |
| * This is a conservative analysis, meaning if true the property definitely |
| * holds, but if false it may mean we just couldn't tell if it holds or not. |
| * |
| * If this is true, then we never need to check how many bits were written when |
| * unparsing, because we know a legal value has to fit. If the value is illegal |
| * then we'll get an unparse error anyway. |
| * |
| * If this is false, then it's possible that the value, even a legal value, |
| * might not fit if the length is specified. We're unable to prove that all |
| * legal values WILL fit. |
| * |
| * A critical case is that fixed length binary integers should always |
| * return true here so that we're not doing excess length checks on them |
| * Or computing their value length unnecessarily. |
| */ |
| final protected lazy val mustBeFixedLengthInfoset = { |
| (isSimpleType && |
| (impliedRepresentation eq Representation.Binary) && |
| (primType ne PrimType.HexBinary) && |
| { |
| import NodeInfo._ |
| primType match { |
| case Float => true |
| case Double => true; |
| case n: Numeric.Kind => |
| binaryNumberRep eq BinaryNumberRep.Binary |
| case _ => true |
| } |
| } && |
| ((lengthKind eq LengthKind.Implicit) || |
| ((lengthKind eq LengthKind.Explicit) && |
| lengthEv.optConstant.isDefined)) // TODO: consider if delimiters matter, alignment, nil values,.... Are we excluding |
| // that stuff here? For example, if the element is nillable, but |
| // if isNilled, the representation will still be exactly the same |
| // length, then we'd like this to be true. |
| ) || |
| ( |
| // a fixed-length textual number, where a legal value |
| // (or nil value) must by definition be smaller than the |
| // fixed length. E.g., since the largest unsignedByte value |
| // is 256, and with textNumberPattern="###" the max width is 3 |
| // then so long as the fixed length is 3 or greater we know |
| // it's going to fit. |
| // |
| // Furthermore, if it is nillable, and the nilValue is "nil", which |
| // is also length 3, then it's going to fit. |
| // |
| // Also matters that if the textNumberPattern can output fewer |
| // characters than the fixed width, then padding must be enabled and a |
| // pad char specified, and that the padChar is the same width as |
| // a digit. (similarly if the nilValue was "-" we need to pad that too) |
| // |
| // This also assumes that in every charset encoding the digit |
| // characters are always the same width so the whole utf-8 |
| // variable-width encoding crud doesn't apply. |
| // |
| // However, textNumberPattern |
| // matters, because sometimes the lengths of positive and negative |
| // numbers can differ. So the fixed length has to be big enough |
| // for the largest possible textNumber matching the pattern. |
| // |
| false // TODO: implement this |
| ) || |
| (isComplexType && !isNillable && |
| ( |
| // |
| // recursively, the complex type can contain only isFixedLengthInfoset items |
| // but in addition, no delimiter can be varying width - so there can only be one |
| // delimiter value for each of these, nor can there be any variable-width |
| // alignment regions. |
| // |
| false // TODO: implement this |
| )) |
| } |
| |
| /** |
| * Means the infoset element could have varying length. |
| * |
| * And that means if there is a specified length box to fit it into |
| * that we have to check if it is too big/small for the box. |
| * |
| * So that means hexBinary, or representation text (for simple types) |
| * or any complex type unless everything in it is fixedLengthInfoset. |
| * |
| * So for example, a complex type containing only fixed length binary |
| * integers is itself fixed length. |
| */ |
| final protected lazy val couldBeVariableLengthInfoset: Boolean = { |
| !mustBeFixedLengthInfoset |
| } |
| |
| /** |
| * Only strings can be truncated, only if they are specified length, |
| * and only if truncateSpecifiedLengthString is 'yes'. |
| * |
| * Note that specified length might mean fixed length or variable (but specified) |
| * length. |
| */ |
| final protected lazy val isTruncatable = { |
| isSimpleType && |
| (primType eq PrimType.String) && |
| (truncateSpecifiedLengthString eq YesNo.Yes) && |
| isSpecifiedLengthForUnparsing |
| } |
| |
| /** |
| * Fixed length, or variable length with explicit length expression. |
| */ |
| final protected lazy val isSpecifiedLengthForUnparsing: Boolean = { |
| (isSimpleType && ( |
| (lengthKind eq LengthKind.Explicit) || |
| (lengthKind eq LengthKind.Implicit))) || |
| (isComplexType && |
| (lengthKind eq LengthKind.Explicit)) |
| } |
| |
| /** |
| * Check for excess length if it's variable length and we cannot truncate it. |
| */ |
| final lazy val shouldCheckExcessLength = { |
| val res = |
| couldBeVariableLengthInfoset && |
| !isTruncatable && |
| isSpecifiedLengthForUnparsing |
| // if (res) |
| // println("%s should check excess length.".format(this.prettyName)) |
| // else |
| // println("%s NOT checking excess length.".format(this.prettyName)) |
| res |
| } |
| |
| private lazy val rightFill = new RightFill(context) |
| |
| // maybe can be private - if it is used still |
| protected lazy val elementUnused = new ElementUnused(context) |
| |
| /** |
| * provided by LocalElementBase for array considerations, and GlobalElementDecl - scalar only |
| */ |
| protected def allowedValue: Gram |
| // |
| // This silly redundancy where the variable name has to also be passed as a string, |
| // is, by the way, a good reason Scala needs real Lisp-style macros, that can take an argument and |
| // turn it into a type/class, object, def, or val/var name, as well as a string, etc. |
| // |
| |
| private lazy val parsedNil = prod("parsedNil", NYI && isNillable && nilKind == NilKind.LogicalValue) { |
| nilElementInitiator ~ |
| captureLengthRegions(leftPadding, LogicalNilValue(this), rightPadding ~ rightFill) ~ |
| nilElementTerminator |
| } |
| |
| private def captureLengthRegions(leftPaddingArg: => Gram, bodyArg: => Gram, rightPadFillArg: => Gram) = { |
| lazy val leftPadding = leftPaddingArg |
| lazy val rightPadFill = rightPadFillArg |
| lazy val body = bodyArg |
| CaptureContentLengthStart(this) ~ |
| leftPadding ~ |
| CaptureValueLengthStart(this) ~ |
| body ~ |
| CaptureValueLengthEnd(this) ~ |
| rightPadFill ~ |
| CaptureContentLengthEnd(this) |
| } |
| |
| private lazy val parsedValue = prod("parsedValue", isSimpleType) { |
| initiatorRegion ~ |
| valueMTA ~ |
| captureLengthRegions(leftPadding, ovcRetry(allowedValue), rightPadding ~ rightFill) ~ |
| terminatorRegion |
| } |
| |
| /** |
| * Wrapped around the simple value unparsers used for dfdl:outputValueCalc (OVC) |
| * with a combinator. Will retry them until the value is available. |
| * |
| * Evaporates for parsing, and when the element is not dfdl:outputValueCalc. |
| */ |
| private def ovcRetry(allowedValueArg: => Gram) = { |
| lazy val allowedValue = allowedValueArg |
| if (this.isOutputValueCalc) |
| OVCRetry(this, allowedValue) |
| else |
| allowedValue |
| } |
| |
| // Length is in bits, (size would be in bytes) (from DFDL Spec 12.3.3) |
| final protected lazy val implicitBinaryLengthInBits: Long = primType match { |
| case PrimType.Byte | PrimType.UnsignedByte => 8 |
| case PrimType.Short | PrimType.UnsignedShort => 16 |
| case PrimType.Float | PrimType.Int | PrimType.UnsignedInt | PrimType.Boolean => 32 |
| case PrimType.Double | PrimType.Long | PrimType.UnsignedLong => 64 |
| case _ => schemaDefinitionError("Size of binary data '" + primType.name + "' cannot be determined implicitly.") |
| } |
| |
| private lazy val binaryNumberKnownLengthInBits: Long = lengthKind match { |
| case LengthKind.Implicit => implicitBinaryLengthInBits |
| case LengthKind.Explicit if (lengthEv.isConstant) => { |
| val lengthFromProp: JLong = lengthEv.optConstant.get |
| val nbits = lengthUnits match { |
| case LengthUnits.Bits => lengthFromProp.longValue() |
| case LengthUnits.Bytes => lengthFromProp.longValue() * 8 |
| case LengthUnits.Characters => SDE("The lengthUnits for binary numbers must be either 'bits' or 'bytes'. Not 'characters'.") |
| } |
| nbits |
| } |
| case LengthKind.Explicit => -1 // means must be computed at runtime. |
| case LengthKind.Delimited => schemaDefinitionError("Binary data elements cannot have lengthKind='delimited'.") |
| case LengthKind.Pattern => schemaDefinitionError("Binary data elements cannot have lengthKind='pattern'.") |
| case LengthKind.Prefixed => subsetError("lengthKind='prefixed' not yet supported.") |
| case LengthKind.EndOfParent => schemaDefinitionError("Binary data elements cannot have lengthKind='endOfParent'.") |
| } |
| |
| private lazy val fixedLengthHexBinary = prod("fixedLengthHexBinary", isFixedLength) { |
| lengthUnits match { |
| case LengthUnits.Bytes => HexBinaryFixedLengthInBytes(this, fixedLength) |
| case LengthUnits.Bits => SDE("lengthUnits='bits' is not valid for hexBinary.") |
| case LengthUnits.Characters => SDE("lengthUnits='characters' is not valid for hexBinary.") |
| } |
| } |
| |
| private lazy val implicitLengthHexBinary = prod("implicitLengthHexBinary", lengthKind eq LengthKind.Implicit) { |
| val maxLengthLong = maxLength.longValueExact |
| lengthUnits match { |
| case LengthUnits.Bytes => HexBinaryFixedLengthInBytes(this, maxLengthLong) |
| case LengthUnits.Bits => SDE("lengthUnits='bits' is not valid for hexBinary.") |
| case LengthUnits.Characters => SDE("lengthUnits='characters' is not valid for hexBinary.") |
| } |
| } |
| |
| private lazy val variableLengthHexBinary = prod("variableLengthHexBinary", !isFixedLength) { |
| lengthUnits match { |
| case LengthUnits.Bytes => new SpecifiedLengthExplicit(this, HexBinaryVariableLengthInBytes(this), 8) |
| case LengthUnits.Bits => SDE("lengthUnits='bits' is not valid for hexBinary.") |
| case LengthUnits.Characters => SDE("lengthUnits='characters' is not valid for hexBinary.") |
| } |
| } |
| |
| private lazy val stringDelimitedEndOfData = prod("stringDelimitedEndOfData") { StringDelimitedEndOfData(this) } |
| // private lazy val stringPatternMatched = prod("stringPatternMatched") { StringPatternMatched(this) } |
| |
| private lazy val stringValue = prod("stringValue") { stringPrim } |
| |
| private lazy val stringPrim = { |
| lengthKind match { |
| case LengthKind.Explicit => specifiedLength(StringOfSpecifiedLength(this)) |
| case LengthKind.Delimited => stringDelimitedEndOfData |
| case LengthKind.Pattern => specifiedLength(StringOfSpecifiedLength(this)) |
| case LengthKind.Implicit => { |
| val pt = this.simpleType.primitiveType |
| Assert.invariant(pt == PrimType.String) |
| specifiedLength(StringOfSpecifiedLength(this)) |
| } |
| case _ => SDE("Unimplemented lengthKind %s", lengthKind) |
| } |
| } |
| |
| private lazy val hexBinaryDelimitedEndOfData = prod("hexBinaryDelimitedEndOfData") { HexBinaryDelimitedEndOfData(this) } |
| |
| private lazy val hexBinaryValue = prod("hexBinaryValue") { |
| lengthKind match { |
| case LengthKind.Explicit if isFixedLength => fixedLengthHexBinary |
| case LengthKind.Explicit => variableLengthHexBinary |
| case LengthKind.Delimited => hexBinaryDelimitedEndOfData |
| case LengthKind.Pattern => SDE("lengthKind Pattern is not allowed for hexBinary.") |
| case LengthKind.Implicit => implicitLengthHexBinary |
| case _ => SDE("Unimplemented lengthKind %s", lengthKind) |
| } |
| } |
| |
| private lazy val textInt = prod("textInt", impliedRepresentation == Representation.Text) { |
| standardTextInt || zonedTextInt |
| } |
| |
| private lazy val textByte = prod("textByte", impliedRepresentation == Representation.Text) { |
| standardTextByte || zonedTextInt |
| } |
| |
| private lazy val textShort = prod("textShort", impliedRepresentation == Representation.Text) { |
| standardTextShort || zonedTextInt |
| } |
| |
| private lazy val textLong = prod("textLong", impliedRepresentation == Representation.Text) { |
| standardTextLong || zonedTextInt |
| } |
| |
| private lazy val textInteger = prod("textInteger", impliedRepresentation == Representation.Text) { |
| standardTextInteger || zonedTextInt |
| } |
| |
| private lazy val textDecimal = prod("textDecimal", impliedRepresentation == Representation.Text) { |
| standardTextDecimal || zonedTextInt |
| } |
| |
| private lazy val textNonNegativeInteger = prod("textNonNegativeInteger", impliedRepresentation == Representation.Text) { |
| standardTextNonNegativeInteger || zonedTextInt |
| } |
| |
| private lazy val textUnsignedInt = prod("textUnsignedInt", impliedRepresentation == Representation.Text) { |
| standardTextUnsignedInt || zonedTextInt |
| } |
| |
| private lazy val textUnsignedByte = prod("textUnsignedByte", impliedRepresentation == Representation.Text) { |
| standardTextUnsignedByte || zonedTextInt |
| } |
| |
| private lazy val textUnsignedShort = prod("textUnsignedShort", impliedRepresentation == Representation.Text) { |
| standardTextUnsignedShort || zonedTextInt |
| } |
| |
| private lazy val textUnsignedLong = prod("textUnsignedLong", impliedRepresentation == Representation.Text) { |
| standardTextUnsignedLong || zonedTextInt |
| } |
| |
| // |
| // We could now break it down by lengthKind, and have specialized primitives |
| // depending on the length kind. |
| // |
| private lazy val standardTextInteger = prod("standardTextInteger", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextIntegerPrim(this)) } |
| private lazy val standardTextDecimal = prod("standardTextDecimal", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextDecimalPrim(this)) } |
| private lazy val standardTextNonNegativeInteger = prod("standardTextNonNegativeInteger", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextNonNegativeIntegerPrim(this)) } |
| private lazy val standardTextLong = prod("standardTextLong", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextLongPrim(this)) } |
| private lazy val standardTextInt = prod("standardTextInt", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextIntPrim(this)) } |
| private lazy val standardTextShort = prod("standardTextShort", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextShortPrim(this)) } |
| private lazy val standardTextByte = prod("standardTextByte", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextBytePrim(this)) } |
| private lazy val standardTextUnsignedLong = prod("standardTextUnsignedLong", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextUnsignedLongPrim(this)) } |
| private lazy val standardTextUnsignedInt = prod("standardTextUnsignedInt", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextUnsignedIntPrim(this)) } |
| private lazy val standardTextUnsignedShort = prod("standardTextUnsignedShort", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextUnsignedShortPrim(this)) } |
| private lazy val standardTextUnsignedByte = prod("standardTextUnsignedByte", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextUnsignedBytePrim(this)) } |
| private lazy val zonedTextInt = prod("zonedTextInt", |
| textNumberRep == TextNumberRep.Zoned) { ZonedTextIntPrim(this) } |
| |
| private lazy val textDouble = prod("textDouble", impliedRepresentation == Representation.Text) { |
| standardTextDouble || zonedTextDouble |
| } |
| |
| // private lazy val ibm390HexBinaryRepDouble = prod("ibm390HexBinaryRepDouble", |
| // binaryFloatRep.isConstant && |
| // binaryFloatRep.constant == BinaryFloatRep.Ibm390Hex.toString) { |
| // subsetError("ibm390Hex not supported") |
| // } |
| |
| private lazy val standardTextDouble = prod("standardTextDouble", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextDoublePrim(this)) } |
| |
| private lazy val zonedTextDouble = prod("zonedTextDouble", |
| textNumberRep == TextNumberRep.Zoned) { SDE("Zoned not supported for float and double") } |
| |
| private lazy val textFloat = prod("textFloat", impliedRepresentation == Representation.Text) { |
| standardTextFloat || zonedTextFloat |
| } |
| |
| private lazy val standardTextFloat = prod("standardTextFloat", |
| textNumberRep == TextNumberRep.Standard) { ConvertTextCombinator(this, stringValue, ConvertTextFloatPrim(this)) } |
| |
| private lazy val zonedTextFloat = prod("zonedTextFloat", |
| textNumberRep == TextNumberRep.Zoned) { SDE("Zoned not supported for float and double") } |
| |
| private lazy val textDate = prod("textDate", impliedRepresentation == Representation.Text) { |
| ConvertTextCombinator(this, stringValue, ConvertTextDatePrim(this)) |
| } |
| |
| private lazy val textTime = prod("textTime", impliedRepresentation == Representation.Text) { |
| ConvertTextCombinator(this, stringValue, ConvertTextTimePrim(this)) |
| } |
| |
| private lazy val textDateTime = prod("textDateTime", impliedRepresentation == Representation.Text) { |
| ConvertTextCombinator(this, stringValue, ConvertTextDateTimePrim(this)) |
| } |
| |
| // shorthand |
| final lazy val primType = { |
| val res = typeDef.asInstanceOf[SimpleTypeBase].primitiveType |
| res |
| } |
| |
| protected final lazy val value = prod("value", isSimpleType) { |
| // TODO: Consider issues with matching a stopValue. Can't say isScalar here because |
| // this gets used for array contents also. |
| { |
| primType match { |
| case PrimType.String => stringValue |
| case PrimType.HexBinary => hexBinaryValue |
| case _ => { |
| val res = impliedRepresentation match { |
| case Representation.Binary => binaryValue |
| case Representation.Text => textValue |
| } |
| res |
| } |
| } |
| } |
| } |
| |
| // This is the right name that the DFDL property should have had! |
| private lazy val binaryIntRep = { |
| subset(binaryNumberRep == BinaryNumberRep.Binary, "binaryNumberRep='%s' is unsupported. Only 'binary' is supported.", binaryNumberRep.toString) |
| binaryNumberRep |
| } |
| |
| private lazy val staticBinaryFloatRep = { |
| subset(binaryFloatRepEv.isConstant, "Dynamic binaryFloatRep is not supported.") |
| binaryFloatRepEv.optConstant.get |
| } |
| |
| val bin = BinaryNumberRep.Binary // shorthands for table dispatch |
| val ieee = BinaryFloatRep.Ieee |
| type BO = java.nio.ByteOrder |
| |
| private def binaryIntegerValue(isSigned: Boolean) = { |
| // |
| // Is it a single byte or smaller |
| // |
| if ((primType != PrimType.Byte) && |
| (binaryNumberKnownLengthInBits == -1 || |
| binaryNumberKnownLengthInBits > 8)) { |
| byteOrderRaw // must be defined or SDE |
| } |
| Assert.invariant(binaryIntRep == bin) |
| binaryNumberKnownLengthInBits match { |
| case -1 => new BinaryIntegerRuntimeLength(this, isSigned) |
| case _ => new BinaryIntegerKnownLength(this, isSigned, binaryNumberKnownLengthInBits) |
| } |
| } |
| |
| private lazy val binaryValue: Gram = { |
| Assert.invariant(primType != PrimType.String) |
| |
| // We have to dispatch carefully here. We cannot force evaluation of properties |
| // that may not be necessary. E.g., float does not need property binaryNumberRep, so |
| // if our dispatch table uses that, it will create a false dependency on the property |
| // being defined. |
| // The DFDL spec has a section where it gives the precedence order of properties. |
| // This is in the spirit of that section. |
| val res: Gram = primType match { |
| |
| case PrimType.Byte | PrimType.Short | PrimType.Int | PrimType.Long | PrimType.Integer => { |
| binaryIntegerValue(true) |
| } |
| |
| case PrimType.UnsignedByte | PrimType.UnsignedShort | PrimType.UnsignedInt | PrimType.UnsignedLong | PrimType.NonNegativeInteger => { |
| binaryIntegerValue(false) |
| } |
| |
| case PrimType.Double | PrimType.Float => { |
| byteOrderRaw // is required. SDE if not defined |
| (primType, binaryNumberKnownLengthInBits, staticBinaryFloatRep) match { |
| case (_, -1, BinaryFloatRep.Ieee) => SDE("Floating point binary numbers may not have runtime-specified lengths.") |
| case (PrimType.Float, 32, BinaryFloatRep.Ieee) => new BinaryFloat(this) |
| case (PrimType.Float, n, BinaryFloatRep.Ieee) => SDE("binary xs:float must be 32 bits. Length in bits was %s.", n) |
| case (PrimType.Double, 64, BinaryFloatRep.Ieee) => new BinaryDouble(this) |
| case (PrimType.Double, n, BinaryFloatRep.Ieee) => SDE("binary xs:double must be 64 bits. Length in bits was %s.", n) |
| case (_, _, floatRep) => subsetError("binaryFloatRep='%s' not supported. Only binaryFloatRep='ieee'", floatRep.toString) |
| } |
| } |
| |
| case PrimType.Decimal => { |
| Assert.invariant(binaryIntRep == bin) |
| if (binaryDecimalVirtualPoint > DaffodilTunableParameters.maxBinaryDecimalVirtualPoint) |
| SDE("Property binaryDecimalVirtualPoint %s is greater than limit %s", binaryDecimalVirtualPoint, DaffodilTunableParameters.maxBinaryDecimalVirtualPoint) |
| if (binaryDecimalVirtualPoint < DaffodilTunableParameters.minBinaryDecimalVirtualPoint) |
| SDE("Property binaryDecimalVirtualPoint %s is less than limit %s", binaryDecimalVirtualPoint, DaffodilTunableParameters.minBinaryDecimalVirtualPoint) |
| if (binaryNumberKnownLengthInBits == -1 || |
| binaryNumberKnownLengthInBits > 8) byteOrderRaw // must have or SDE |
| binaryNumberKnownLengthInBits match { |
| case -1 => new BinaryDecimalRuntimeLength(this) |
| case _ => new BinaryDecimalKnownLength(this, binaryNumberKnownLengthInBits) |
| } |
| } |
| case _ => notYetImplemented("Type %s when representation='binary'", primType.name) |
| } |
| res |
| } |
| |
| private lazy val textValue: Gram = { |
| val pt = primType |
| Assert.invariant(pt != PrimType.String) |
| Assert.invariant(pt != PrimType.HexBinary) |
| Assert.invariant(impliedRepresentation == Representation.Text) |
| schemaDefinitionWhen(lengthKind == LengthKind.Implicit, |
| "Type %s cannot have lengthKind='implicit' when representation='text'", |
| pt.name) |
| val res = primType match { |
| case PrimType.Int => textInt |
| case PrimType.Byte => textByte |
| case PrimType.Short => textShort |
| case PrimType.Long => textLong |
| case PrimType.Integer => textInteger |
| case PrimType.Decimal => textDecimal |
| case PrimType.UnsignedInt => textUnsignedInt |
| case PrimType.UnsignedByte => textUnsignedByte |
| case PrimType.UnsignedShort => textUnsignedShort |
| case PrimType.UnsignedLong => textUnsignedLong |
| case PrimType.NonNegativeInteger => textNonNegativeInteger // Should be treated as unsigned xs:integer |
| case PrimType.Double => textDouble |
| case PrimType.Float => textFloat |
| case PrimType.HexBinary => Assert.invariantFailed("Primitive hexBinary must be representation='binary'.") |
| case PrimType.Boolean => notYetImplemented("textValue: boolean") |
| case PrimType.Date => textDate |
| case PrimType.Time => textTime |
| case PrimType.DateTime => textDateTime |
| case _ => schemaDefinitionError("Unrecognized primitive type: " + primType.name) |
| } |
| res |
| } |
| |
| protected final lazy val empty = prod("empty", NYI && emptyIsAnObservableConcept) { EmptyGram } |
| |
| // private lazy val emptyRepresentation = prod("emptyRepresentation") { |
| // simpleOrNonImplicitComplexEmpty | complexImplicitEmpty |
| // } |
| |
| // private lazy val simpleOrNonImplicitComplexEmpty = prod("simpleOrNonImplicitComplexEmpty", |
| // isSimpleType | isComplexType && lengthKind != LengthKind.Implicit) { |
| // emptyElementInitiator ~ |
| // valueMTA ~ |
| // captureLengthRegions(EmptyGram, EmptyGram, EmptyGram) ~ |
| // emptyElementTerminator |
| // } |
| // |
| // /** |
| // * This is about the case where we take an empty, parse a complex type recursively from it |
| // * and potentially succeed. |
| // */ |
| // private lazy val complexImplicitEmpty = prod("complexImplicitEmpty", |
| // isComplexType && lengthKind == LengthKind.Implicit) { |
| // SaveInputStream(this) ~ SetEmptyInputStream(this) ~ elementComplexType.mainGrammar ~ |
| // RestoreInputStream(this) ~ emptyElementTerminator |
| // } |
| |
| // private lazy val emptyDefaulted = prod("emptyDefaulted", |
| // isDefaultable && emptyIsAnObservableConcept) { |
| // empty ~ TheDefaultValue(this) |
| // } |
| |
| private lazy val nilElementInitiator = prod("nilElementInitiator", hasInitiator) { delimMTA ~ Initiator(this) } |
| private lazy val nilElementTerminator = prod("nilElementTerminator", hasTerminator) { delimMTA ~ Terminator(this) } |
| |
| // private lazy val emptyElementInitiator = prod("emptyElementInitiator", NYI && hasEmptyValueInitiator) { delimMTA ~ Initiator(this) } |
| // private lazy val emptyElementTerminator = prod("emptyElementTerminator", NYI && hasEmptyValueTerminator) { delimMTA ~ Terminator(this) } |
| |
| private lazy val complexContent = prod("complexContent", isComplexType) { |
| elementComplexType.mainGrammar |
| } |
| |
| private lazy val isNilLit = isNillable && ((nilKind == NilKind.LiteralValue) || (nilKind == NilKind.LiteralCharacter)) |
| |
| /** |
| * In the below, we must have nilLitMTA because, in the case where it's textual, |
| * then to distinguish a lit nil from a value, we have to start at the same place. |
| */ |
| private lazy val nilLit = prod("nilLit", isNilLit) { |
| nilElementInitiator ~ |
| nilLitMTA ~ |
| nilLitSimpleOrComplex ~ |
| nilElementTerminator |
| } |
| |
| private lazy val nilLitSimpleOrComplex = prod("nilLitSimpleOrComplex") { nilLitSimple || nilLitComplex } |
| |
| private lazy val nilLitSimple = prod("nilLitSimple", isSimpleType) { |
| captureLengthRegions(leftPadding, |
| specifiedLength(nilLitContent) ~ // for parser |
| NilLiteralCharacter(context), // for unparser |
| rightPadding ~ rightFill) |
| } |
| |
| private lazy val nilLitComplex = prod("nilLitComplex", isComplexType) { |
| // Note: the only allowed nil value for a complex type is ES. It's length will be zero always. (as of DFDL v1.0 - 2015-07-15) |
| schemaDefinitionUnless(this.hasESNilValue && cookedNilValuesForParse.length == 1, "Nillable complex type elements can only have '%ES;' as their dfdl:nilValue property.") |
| captureLengthRegions(EmptyGram, |
| nilLitContent, |
| // |
| // Because nil complex can only be ES (e.g., length 0), there's no possible |
| // ElementUnused region after a nil. |
| EmptyGram) |
| |
| } |
| |
| private lazy val nilLitMTA = prod("nilLitMTA", isNilLit) { mtaBase } |
| |
| private lazy val nilLitContent = prod("nilLitContent", |
| isNillable && (nilKind == NilKind.LiteralValue || nilKind == NilKind.LiteralCharacter)) { |
| |
| nilKind match { |
| case NilKind.LiteralValue => { |
| // if (impliedRepresentation != Representation.Text) this.SDE("LiteralValue Nils require representation='text'.") |
| lengthKind match { |
| case LengthKind.Delimited => LiteralNilDelimitedEndOfData(this) |
| case LengthKind.Pattern => LiteralValueNilOfSpecifiedLength(this) |
| case LengthKind.Explicit => LiteralValueNilOfSpecifiedLength(this) |
| case LengthKind.Implicit if isSimpleType => { |
| schemaDefinitionUnless(impliedRepresentation != Representation.Text, "LiteralValue Nils with lengthKind='implicit' cannot have representation='text'.") |
| LiteralValueNilOfSpecifiedLength(this) |
| } |
| case LengthKind.Implicit if isComplexType => Assert.invariantFailed("literal nil complex types aren't handled here.") |
| case LengthKind.Prefixed => notYetImplemented("lengthKind='prefixed'") |
| case LengthKind.EndOfParent => notYetImplemented("lengthKind='endOfParent'") |
| } |
| } |
| case NilKind.LiteralCharacter => { |
| if (!isFixedLength) { SDE("dfdl:length must be fixed when nilKind='literalCharacter'.") } |
| |
| lengthKind match { |
| case LengthKind.Explicit => LiteralCharacterNilOfSpecifiedLength(this) |
| case LengthKind.Implicit if isSimpleType => LiteralCharacterNilOfSpecifiedLength(this) |
| case LengthKind.Implicit if isComplexType => Assert.invariantFailed("literal nil complex types aren't handled here.") |
| case LengthKind.Prefixed => SDE("nilKind='literalCharacter' is not valid for lengthKind='prefixed'") |
| case LengthKind.EndOfParent => SDE("nilKind='literalCharacter' is not valid for lengthKind='endOfParent'") |
| case LengthKind.Delimited => SDE("nilKind='literalCharacter' is not valid for lengthKind='delimited'") |
| case LengthKind.Pattern => SDE("nilKind='literalCharacter' is not valid for lengthKind='pattern'") |
| } |
| } |
| case NilKind.LogicalValue => notYetImplemented("nilLitContent nilKind='logicalValue'") |
| } |
| |
| } |
| |
| private def withDelimiterStack(body: => Gram) = { |
| if (hasDelimiters || enclosingTerm.map(_.hasDelimiters).getOrElse(false)) DelimiterStackCombinatorElement(this, body) |
| else body |
| } |
| |
| private lazy val nilOrEmptyOrValue = prod("nilOrEmptyOrValue") { |
| anyOfNilOrEmptyOrValue || |
| nilOrValue || |
| emptyOrValue || |
| nonNilNonEmptyParsedValue |
| } |
| |
| private lazy val anyOfNilOrEmptyOrValue = prod("anyOfNilOrEmptyOrValue", isNillable && NYI && emptyIsAnObservableConcept) { |
| ovcRetry(SimpleNilOrEmptyOrValue(this, nilLit || parsedNil, empty, parsedValue)) |
| } |
| |
| private lazy val nilOrValue = prod("nilOrValue", isNillable) { // TODO: make it exclude emptyness once emptyness is implemented |
| ovcRetry(SimpleNilOrValue(this, nilLit || parsedNil, parsedValue)) |
| } |
| |
| private lazy val emptyOrValue = prod("emptyOrValue", NYI && emptyIsAnObservableConcept && !isNillable) { |
| ovcRetry(SimpleEmptyOrValue(this, empty, parsedValue)) |
| } |
| |
| private lazy val nonNilNonEmptyParsedValue = prod("nonNilnonEmptyParsedValue", !isNillable) { // TODO: make it exclude emptyness once emptyness is implemented |
| parsedValue |
| } |
| |
| private lazy val scalarDefaultableSimpleContent = prod("scalarDefaultableSimpleContent", isSimpleType) { |
| nilOrEmptyOrValue |
| } |
| |
| /** |
| * Note: This must handle unspecified lengths, like lengthKind delimited, |
| * as well, by not enclosing the body in a specified length enforcer. |
| */ |
| private def specifiedLength(bodyArg: => Gram) = { |
| lazy val body = bodyArg |
| lazy val bitsMultiplier = lengthUnits match { |
| case LengthUnits.Bits => 1 |
| case LengthUnits.Bytes => 8 |
| case LengthUnits.Characters if knownEncodingIsFixedWidth => this.knownEncodingWidthInBits |
| case _ => 0 // zero means can't multiply to get width in bits. |
| } |
| val lk = lengthKind |
| lk match { |
| case LengthKind.Delimited => body |
| case LengthKind.Pattern => new SpecifiedLengthPattern(this, body) |
| case LengthKind.Explicit if bitsMultiplier != 0 => |
| new SpecifiedLengthExplicit(this, body, bitsMultiplier) |
| case LengthKind.Explicit => { |
| Assert.invariant(!knownEncodingIsFixedWidth) |
| Assert.invariant(lengthUnits eq LengthUnits.Characters) |
| new SpecifiedLengthExplicitCharacters(this, body) |
| } |
| case LengthKind.Implicit if isSimpleType && primType == PrimType.String && |
| encodingInfo.knownEncodingIsFixedWidth => { |
| // |
| // Important case to optimize |
| // If we can convert to a number of bits, then we should do so |
| // |
| val nBits = encodingInfo.knownFixedWidthEncodingInCharsToBits(this.maxLength.longValue) |
| new SpecifiedLengthImplicit(this, body, nBits) |
| } |
| case LengthKind.Implicit if isSimpleType && primType == PrimType.String => |
| new SpecifiedLengthImplicitCharacters(this, body, this.maxLength.longValue) |
| |
| case LengthKind.Implicit if isSimpleType && primType == PrimType.HexBinary => |
| new SpecifiedLengthImplicit(this, body, this.maxLength.longValue * bitsMultiplier) |
| case LengthKind.Implicit if isSimpleType && impliedRepresentation == Representation.Binary => |
| new SpecifiedLengthImplicit(this, body, implicitBinaryLengthInBits) |
| case LengthKind.Implicit if isComplexType => body // for complex types, implicit means "roll up from the bottom" |
| case _ => { |
| // TODO: implement other specified length like prefixed and end of parent |
| // for now, no restriction |
| body |
| } |
| } |
| } |
| |
| private lazy val complexContentSpecifiedLength = prod("complexContentSpecifiedLength", isComplexType) { |
| initiatorRegion ~ |
| captureLengthRegions(EmptyGram, |
| specifiedLength(complexContent), |
| elementUnused) ~ |
| terminatorRegion |
| } |
| |
| private lazy val scalarComplexContent = prod("scalarComplexContent", isComplexType) { |
| if (!nilLit.isEmpty) { |
| ComplexNilOrContent(this, nilLit, complexContentSpecifiedLength) |
| } else { |
| complexContentSpecifiedLength |
| } |
| } |
| |
| private lazy val hasDynamicEscapeScheme = this.optionEscapeScheme.isDefined && !this.optionEscapeScheme.get.escapeSchemeParseEv.isConstant |
| |
| private def withEscapeScheme(body: Gram) = { |
| if (hasDynamicEscapeScheme) DynamicEscapeSchemeCombinatorElement(this, body) |
| else body |
| } |
| |
| /** |
| * the element left framing does not include the initiator nor the element right framing the terminator |
| */ |
| private lazy val alignAndSkipFraming = prod("alignAndSkipFraming") { |
| LeadingSkipRegion(this) ~ AlignmentFill(this) ~ PrefixLength(this) |
| } |
| |
| private lazy val elementIOPropertiesChange = byteOrderChange ~ termIOPropertiesChange |
| private lazy val elementLeftFraming = elementIOPropertiesChange ~ alignAndSkipFraming |
| |
| private lazy val elementRightFraming = prod("elementRightFraming") { TrailingSkipRegion(this) } |
| |
| protected final lazy val enclosedElement = prod("enclosedElement") { |
| // |
| // not isScalar, because this is reused inside arrays |
| // that is, we're counting on reusuing this production for array elements |
| // which are enclosed by the enclosing array and model group. |
| // |
| // if we didn't reuse this way we'd have to reproduce much of the grammar |
| // for the array case and scalar case that is the same for both. |
| // |
| checkVariousPropertyconstraints |
| (inputValueCalcOption, outputValueCalcOption) match { |
| case (_: NotFound, _: NotFound) => scalarDefaultablePhysical |
| case (_: Found, _: NotFound) => inputValueCalcElement |
| case (_: NotFound, _: Found) => outputValueCalcElement |
| case _ => SDE("Element with both dfdl:inputValueCalc and dfdl:outputValueCalc is not allowed.") |
| } |
| } |
| |
| // |
| // Until empty detection is implemented, there really is no distinction between |
| // defaultable and non-defaulting elements. |
| // |
| protected final def enclosedElementNonDefault = enclosedElement |
| |
| private lazy val inputValueCalcPrim = InputValueCalc(self, inputValueCalcOption) |
| |
| protected final def ivcCompiledExpression = inputValueCalcPrim.expr |
| |
| private lazy val inputValueCalcElement = prod("inputValueCalcElement", |
| isSimpleType && inputValueCalcOption.isInstanceOf[Found]) { |
| // No framing surrounding inputValueCalc elements. |
| // Note that we need these elements even when unparsing, because they appear in the infoset |
| // as regular elements (most times), and so we have to have an unparser that consumes the corresponding events. |
| new ElementParseAndUnspecifiedLength(this, dfdlScopeBegin, |
| inputValueCalcPrim, dfdlScopeEnd) |
| } |
| |
| protected final lazy val ovcCompiledExpression = { // ovcValueCalcObject.expr |
| val exprProp = outputValueCalcOption.asInstanceOf[Found] |
| val exprText = exprProp.value |
| val exprNamespaces = exprProp.location.namespaces |
| val qn = GlobalQName(Some("daf"), "outputValueCalc", XMLUtils.dafintURI) |
| val expr = ExpressionCompilers.AnyRef.compile(qn, |
| primType, exprText, exprNamespaces, dpathCompileInfo, false) |
| expr |
| } |
| |
| private lazy val outputValueCalcElement = |
| |
| prod("outputValueCalcElement", |
| isSimpleType && outputValueCalcOption.isInstanceOf[Found]) { |
| scalarDefaultablePhysical |
| } |
| |
| // Note: there is no such thing as defaultable complex content because you can't have a |
| // default value for a complex type element.... |
| // NOT TRUE: a defaultable complex type is one where everything within it is |
| // recursively defaultable and has no syntax. So you could recursively "parse" |
| // it, get default values for simple type elements in the complex type structure, |
| // yet consume zero bits. |
| // |
| // When parsing that's exactly what should happen. It should parse, come back with |
| // a complex type element, and have consumed no bits. |
| // |
| // When unparsing, we have to recognize that the complex type element matches |
| // this default value, and then choose to unparse nothing. |
| // |
| // TBD: is this actually required by DFDL? Perhaps it is legal to simply |
| // unparse the overall structure. When we unparse a simple type, and the |
| // representation happens to be empty (such as empty string) the question is |
| // are we to recognize this, and put down the emptyValueInitiator/Terminator based |
| // on the emptyValueDelimiterPolicy? That has to be right. In order for a complex |
| // type to be defaultable, and for us to output nothing (empty) corresponding |
| // to a complex type, then all the contained simple type elements must be empty |
| // and must have effectively no initiator nor terminator for empty values. |
| // |
| // But there are additional things like when empty string causes a Nil value to be |
| // created, or a zero to be created for a text number. |
| |
| private lazy val scalarDefaultablePhysical = prod("scalarDefaultablePhysical") { |
| |
| val elem = new ElementCombinator(this, elementLeftFraming ~ dfdlScopeBegin, |
| withDelimiterStack { |
| withEscapeScheme { |
| scalarDefaultableSimpleContent || scalarComplexContent |
| } |
| }, |
| elementRightFraming ~ dfdlScopeEnd) |
| elem |
| } |
| |
| private def checkVariousPropertyconstraints { |
| // |
| // check for consistency. If length units is bytes, and we're going to use the length facets |
| // of xs:string for implicit length, the encoding must be SBCS. Otherwise validation could fail when the |
| // number of characters in that many bytes doesn't satisfy the facet. |
| // |
| if (isSimpleType && |
| primType == PrimType.String && |
| lengthKind == LengthKind.Implicit && |
| lengthUnits == LengthUnits.Bytes) { |
| if (!isKnownEncoding) { |
| // |
| // TODO: this check is insisting on this being clear at compile time. But DFDL doesn't strictly speaking, require that. |
| // If encoding is runtime-valued, this check could be done at runtime. |
| // |
| SDE("dfdl:encoding is a runtime expression, but dfdl:lengthKind 'implicit' for type xs:string and dfdl:lengthUnits 'bytes' requires an explicit known single byte character set encoding (SBCS).") |
| } else if (knownEncodingWidthInBits != 8) { |
| SDE("dfdl:encoding '%s' is not a single-byte encoding, but dfdl:lengthKind 'implicit' for type xs:string and dfdl:lengthUnits 'bytes' a single byte character set encoding (SBCS) is required.", |
| knownEncodingName) |
| } |
| } |
| if (lengthKind != LengthKind.Explicit |
| && optionLengthRaw.isDefined) |
| SDW("dfdl:lengthKind '%s' is not consistent with dfdl:length specified (as %s). The dfdl:length will be ignored.", |
| lengthKind, |
| lengthExpr.prettyExpr) |
| if ((lengthKind == LengthKind.Explicit || lengthKind == LengthKind.Implicit) && |
| impliedRepresentation == Representation.Binary && |
| lengthUnits == LengthUnits.Characters) |
| SDE("Elements of dfdl:lengthKind '%s' cannot have dfdl:lengthUnits '%s' with binary representation.", lengthKind, lengthUnits) |
| (inputValueCalcOption, outputValueCalcOption) match { |
| case (_: Found, _: Found) => SDE("Cannot have both dfdl:inputValueCalc and dfdl:outputValueCalc on the same element.") |
| case _ => // ok |
| } |
| /* |
| * When lengthKind is explicit and length is a constant, it is a warning if |
| * the type is a type that respects minLength and maxLength, and the constant length |
| * is not in range. |
| */ |
| val isTypeUsingMinMaxLengthFacets = typeDef.kind match { |
| case s: NodeInfo.String.Kind => true |
| case s: NodeInfo.HexBinary.Kind => true |
| case _ => false |
| } |
| if ((lengthKind eq LengthKind.Explicit) && |
| isTypeUsingMinMaxLengthFacets && |
| optLengthConstant.isDefined) { |
| val len = optLengthConstant.get |
| val maxLengthLong = maxLength.longValueExact |
| val minLengthLong = minLength.longValueExact |
| def warn(m: String, value: Long) = SDW("Explicit dfdl:length of %s is out of range for facet %sLength='%s'.", len, "max", value) |
| if (maxLengthLong != -1 && len > maxLengthLong) warn("max", maxLengthLong) |
| Assert.invariant(minLengthLong >= 0) |
| if (minLengthLong > 0 && len < minLengthLong) warn("min", minLengthLong) |
| } |
| |
| /* |
| * When length kind is explicit, and length is a constant, it is an SDE if |
| * the type is a type that uses dfdl:textOutputMinLength, and the length constant |
| * is not greater than or equal to that value. |
| */ |
| |
| val isTypeUsingTextOutputMinLength = typeDef.kind match { |
| case s: NodeInfo.String.Kind => false |
| case s: NodeInfo.HexBinary.Kind => false |
| case s: NodeInfo.AnySimpleType.Kind if (impliedRepresentation eq Representation.Binary) && |
| this.textOutputMinLength > 0 => true |
| case _ => false |
| } |
| |
| if ((lengthKind eq LengthKind.Explicit) && |
| isTypeUsingTextOutputMinLength && |
| optLengthConstant.isDefined) { |
| val len = optLengthConstant.get |
| if (len < textOutputMinLength) SDE("Explicit dfdl:length of %s is out of range for dfdl:textOutputMinLength='%s'.", len, textOutputMinLength) |
| |
| } |
| } |
| |
| /** |
| * Mandatory text alignment or mta |
| * |
| * mta can only apply to things with encodings. No encoding, no MTA. |
| * |
| * In addition, it has to be textual data. Just because there's an encoding |
| * in the property environment shouldn't get you an MTA region. It has |
| * to be textual. |
| */ |
| protected final lazy val valueMTA = prod("mandatoryTextAlignment", |
| impliedRepresentation eq Representation.Text) { |
| mtaBase |
| } |
| |
| } |