| /* |
| * 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.dsom |
| |
| import org.apache.daffodil.equality._ |
| import org.apache.daffodil.processors._ |
| import org.apache.daffodil.schema.annotation.props._ |
| import org.apache.daffodil.xml._ |
| import org.apache.daffodil.grammar.ElementBaseGrammarMixin |
| import org.apache.daffodil.schema.annotation.props.gen._ |
| import org.apache.daffodil.util.Misc |
| |
| import scala.xml.NamespaceBinding |
| import org.apache.daffodil.util.MaybeULong |
| import org.apache.daffodil.dpath.NodeInfo |
| import org.apache.daffodil.dpath.NodeInfo.PrimType |
| import org.apache.daffodil.dpath.InvalidPrimitiveDataException |
| import org.apache.daffodil.exceptions.Assert |
| import org.apache.daffodil.api.WarnID |
| import java.lang.{ Integer => JInt } |
| |
| import org.apache.daffodil.dsom.walker.ElementBaseView |
| import org.apache.daffodil.infoset.DataValue |
| import org.apache.daffodil.infoset.DataValue.DataValuePrimitiveNullable |
| import org.apache.daffodil.infoset.DataValue.DataValuePrimitiveOrUseNilForDefaultOrNull |
| |
| /** |
| * Note about DSOM design versus say XSOM or Apache XSD library. |
| * |
| * Some XSD object models have a single Element class, and distinguish local/global and element references |
| * based on attributes of the instances. |
| * |
| * Our approach is to provide common behaviors on base classes or traits/mixins, and to have distinct |
| * classes for each instance type. |
| * |
| * This base is shared by all forms of elements, local or global or element reference. |
| */ |
| trait ElementBase |
| extends Term |
| with ElementLikeMixin |
| with LocalElementMixin |
| with Element_AnnotationMixin |
| with NillableMixin |
| with ElementBaseGrammarMixin |
| with ElementRuntimeValuedPropertiesMixin |
| with StringTextMixin |
| with NumberTextMixin |
| with CalendarTextMixin |
| with BooleanTextMixin |
| with TextNumberFormatMixin |
| with EmptyElementParsePolicyMixin |
| with TextStandardBaseMixin |
| with OverlapCheckMixin |
| with ElementBaseView { |
| |
| protected override def initialize() = { |
| super.initialize() |
| } |
| |
| override final def eBase = this |
| |
| lazy val init: Unit = { |
| schemaSet.elementBaseInstanceCount += 1 |
| } |
| |
| requiredEvaluationsIfActivated(init) |
| requiredEvaluationsIfActivated(typeDef) |
| requiredEvaluationsIfActivated(isSimpleType) |
| requiredEvaluationsIfActivated(if (hasPattern) patternValues) |
| requiredEvaluationsIfActivated(if (hasEnumeration) enumerationValues) |
| requiredEvaluationsIfActivated(if (hasMinLength) minLength) |
| requiredEvaluationsIfActivated(if (hasMaxLength) maxLength) |
| requiredEvaluationsIfActivated(if (hasMinInclusive) minInclusive) |
| requiredEvaluationsIfActivated(if (hasMaxInclusive) maxInclusive) |
| requiredEvaluationsIfActivated(if (hasMinExclusive) minExclusive) |
| requiredEvaluationsIfActivated(if (hasMaxExclusive) maxExclusive) |
| requiredEvaluationsIfActivated(if (hasTotalDigits) totalDigits) |
| requiredEvaluationsIfActivated(if (hasFractionDigits) fractionDigits) |
| requiredEvaluationsIfActivated(checkForAlignmentAmbiguity) |
| requiredEvaluationsIfActivated(checkFloating) |
| requiredEvaluationsIfActivated(minimizedScope) |
| |
| override def name: String |
| |
| final lazy val inputValueCalcOption = findPropertyOption("inputValueCalc", expressionAllowed = true) |
| |
| final lazy val outputValueCalcOption: PropertyLookupResult = { |
| val optOVC = findPropertyOption("outputValueCalc", expressionAllowed = true) |
| schemaDefinitionWhen(optOVC.isDefined && isOptional, "dfdl:outputValueCalc cannot be defined on optional elements.") |
| schemaDefinitionWhen(optOVC.isDefined && isArray, "dfdl:outputValueCalc cannot be defined on array elements.") |
| schemaDefinitionWhen(optOVC.isDefined && isComplexType, "dfdl:outputValueCalc cannot be defined on complexType elements.") |
| // This should be an SDE, but is very useful for unit tests to be able to specify OVC on a single global element |
| // self.schemaDefinitionWhen(optOVC.isDefined && self.isInstanceOf[GlobalElementDecl], "dfdl:outputValueCalc cannot be defined on global elements.") |
| |
| optOVC |
| } |
| |
| def isNillable: Boolean |
| def isSimpleType: Boolean |
| def isComplexType: Boolean |
| |
| def simpleType: SimpleTypeBase |
| |
| def complexType: ComplexTypeBase |
| |
| def optSimpleType: Option[SimpleTypeBase] |
| def optComplexType: Option[ComplexTypeBase] |
| |
| /** |
| * Irrespective of whether the type of this element is immediate or |
| * primitive, or reached via a type reference, this is the typeDef |
| * of the type. |
| */ |
| def typeDef: TypeBase |
| |
| /** |
| * The DPathElementInfo objects referenced within an IVC |
| * that calls dfdl:contentLength( thingy ) |
| */ |
| override final protected lazy val calcContentParserReferencedElementInfos = |
| if (this.inputValueCalcOption.isDefined) |
| ivcCompiledExpression.contentReferencedElementInfos |
| else |
| ReferencedElementInfos.None |
| |
| /** |
| * The DPathElementInfo objects referenced within an OVC |
| * that calls dfdl:contentLength( thingy ) |
| */ |
| override final protected lazy val calcContentUnparserReferencedElementInfos = |
| if (this.outputValueCalcOption.isDefined) |
| ovcCompiledExpression.contentReferencedElementInfos |
| else |
| ReferencedElementInfos.None |
| |
| /** |
| * The DPathElementInfo objects referenced within an IVC |
| * that calls dfdl:valueLength( thingy ) |
| */ |
| override final protected lazy val calcValueParserReferencedElementInfos = |
| if (this.inputValueCalcOption.isDefined) { |
| val ivcCE = ivcCompiledExpression |
| val setERs = ivcCE.valueReferencedElementInfos |
| setERs |
| } else |
| ReferencedElementInfos.None |
| |
| /** |
| * The DPathElementInfo objects referenced within an IVC |
| * that calls dfdl:valueLength( thingy ) |
| */ |
| override final protected lazy val calcValueUnparserReferencedElementInfos = |
| if (this.outputValueCalcOption.isDefined) |
| ovcCompiledExpression.valueReferencedElementInfos |
| else |
| ReferencedElementInfos.None |
| |
| /** |
| * Tells us if, for this element, we need to capture its content length |
| * at parse runtime, or we can ignore that. |
| */ |
| final lazy val shouldCaptureParseContentLength: Boolean = { |
| val isReferenced = |
| if (this.isInstanceOf[PrefixLengthQuasiElementDecl]) false |
| else { |
| val setElems = schemaSet.root.contentLengthParserReferencedElementInfos |
| setElems.contains(this.dpathElementCompileInfo) |
| } |
| isReferenced |
| } |
| |
| /** |
| * None for complex types, Some(primType) for simple types. |
| */ |
| final lazy val optPrimType: Option[PrimType] = Misc.boolToOpt(isSimpleType, primType) // .typeRuntimeData) |
| |
| /** |
| * An array element is required if its index is less than the minOccurs of the |
| * array. For an array with a fixed number of elements, all elements are required. |
| */ |
| def isArrayWithAtLeastOneRequiredArrayElement: Boolean |
| |
| private def nsBindingsToSet(nsb: NamespaceBinding): Set[(String, NS)] = { |
| if (nsb == scala.xml.TopScope) Set() |
| else { |
| val parentBindings = nsBindingsToSet(nsb.parent) |
| val res = parentBindings.+((nsb.prefix, NS(nsb.uri))) |
| res |
| } |
| } |
| |
| private lazy val thisElementsRequiredNamespaceBindings: Set[(String, NS)] = LV('thisElementsRequiredNamespaceBindings) { |
| val childrenRequiredNSBindings = |
| this.elementChildren.flatMap { _.thisElementsRequiredNamespaceBindings }.toSet |
| |
| val myRequiredNSBinding = Set((this.namespaces.getPrefix(namedQName.namespace), namedQName.namespace)) |
| val nilNSBinding = { |
| if (!isNillable) Set() |
| else { |
| // |
| // Nillable, so we need a binding for xsi:nil='true' case. |
| // |
| val xsiNS = XMLUtils.XSI_NAMESPACE |
| val xsiPrefix = namespaces.getPrefix(xsiNS.toString) |
| if (xsiPrefix != null) { |
| Set((xsiPrefix, xsiNS)) |
| } else { |
| Set(("xsi", xsiNS)) |
| } |
| } |
| } |
| val allBindings = childrenRequiredNSBindings ++ myRequiredNSBinding ++ nilNSBinding |
| |
| // allBindings now contains bindings for this element and its children. The |
| // purpose of this is to kindof bubble up required namespace bindings. For |
| // example, we need the xsi namespace if an element is nillable, but we do |
| // not want to redefine that namespace on every nillable element. By adding |
| // the required namespaces of the children, we can bubble up such bindings |
| // so that they are only declared once at the top of an infoset. However, |
| // there is an issues with bubbling up. That is, children could have |
| // conflicting namespace bindings either with each other or with this |
| // element. So after all namespace bindings are combined, we need to filter |
| // out conflicting namespace bindings. |
| // |
| // Note however, that if myRequiredNSBinding conflicts with a child, then it |
| // (along with anything it conflicts with) would be filtered out. But it is |
| // required for this element, so we need to add it back in. |
| |
| // Creates a Map[prefix, Set[NS]]. Duplicate NS's will be removed from the |
| // Set, since it's a Set |
| val bindingsGroupedByPrefix = allBindings.groupBy { _._1 }.mapValues { _.map { _._2 } } |
| |
| // Any Set with size > 1 has different namespaces for the same prefix, filter them out |
| val bindingsNoConflictsMap = bindingsGroupedByPrefix.filter { case (prefix, bindings) => bindings.size == 1 } |
| |
| // Create a Map[prefix, NS] now that conflicts are removed |
| val bindingsSingleNSMap = bindingsNoConflictsMap.mapValues { _.head } |
| |
| // Convert back to a set |
| val bindings = bindingsSingleNSMap.toSet |
| |
| // Add back in myRequiredNSBinding. This is a Set, so if it already exist |
| // the duplicate will just be ignored |
| val res = bindings ++ myRequiredNSBinding |
| |
| res |
| }.value |
| |
| private lazy val emptyNSPairs: Set[(String, NS)] = nsBindingsToSet(scala.xml.TopScope) |
| |
| private lazy val myOwnNSPairs: Set[(String, NS)] = thisElementsRequiredNamespaceBindings |
| |
| private lazy val myParentNSPairs: Set[(String, NS)] = LV('myParentNSPairs) { |
| val ee: Option[ElementBase] = enclosingElements.headOption // FIXME: DAFFODIL-2282 works only if there is no difference among usages. |
| ee match { |
| case None => emptyNSPairs |
| case Some(ee) => ee.myOwnNSPairs |
| } |
| }.value |
| |
| private lazy val myUniquePairs: Set[(String, NS)] = { |
| val res = myOwnNSPairs -- myParentNSPairs // FIXME: DAFFODIL-2282 works only if there is no difference among usages. |
| res |
| } |
| |
| // FIXME: DAFFODIL-2282 works only if there is no difference among usages. |
| private def pairsToNSBinding(pairs: List[(String, NS)], parentNS: NamespaceBinding): NamespaceBinding = { |
| if (pairs.isEmpty) parentNS |
| else { |
| val (pre, ns) = pairs.head |
| val t = pairs.tail |
| val parentNSBinding = pairsToNSBinding(t, parentNS) |
| val uri = if (ns.optURI.isDefined) ns.optURI.get.toString else null |
| val res = NamespaceBinding(pre, uri, parentNSBinding) |
| res |
| } |
| } |
| |
| private lazy val parentMinimizedScope = LV('parentMinimizedScope) { |
| val ee = enclosingElements.headOption // FIXME: bug DAFFODIL-2282 doesn't work unless all are same. |
| val res = |
| if (ee.isEmpty) |
| scala.xml.TopScope |
| else |
| ee.get.minimizedScope |
| res |
| }.value |
| |
| /** |
| * To be properly constructed, scala's xml Elems must share the scope (namespace bindings) of the enclosing |
| * parent element, except when it adds more of its own bindings, in which case the tail is supposed to be shared. |
| */ |
| protected lazy val minimizedScope: NamespaceBinding = LV('minimizedScope) { |
| val uniquePairs = |
| if (this.isInstanceOf[Root]) { |
| // If this is the root element and it contains xmlns="", then remove |
| // it. xmlns="" is implied at the root. Note that we only do this for |
| // the root because if a child has xmlns="", that implies the parent |
| // had set the default namespace to something else, so we need to keep |
| // the child xmlns="" to override that. |
| myUniquePairs.filterNot { |
| case (prefix, ns) => |
| val res = |
| prefix == null && |
| ns.isNoNamespace |
| res |
| } |
| } else { |
| myUniquePairs |
| } |
| |
| // Sort alphabetically, with null NS going first. This is the order that |
| // NamespaceBinings will be output with the toString method. This ensures |
| // consistent ordering regardless of Set/Map implementation details of |
| // uniquePairs |
| val sortedPairs = uniquePairs.toList.sortWith { case (l, r) => |
| (l._1 == null) || (r._1 == null) || (l._1.compareTo(r._1) < 0) |
| } |
| |
| pairsToNSBinding(sortedPairs, parentMinimizedScope) |
| |
| }.value |
| |
| /** |
| * Is either DataValue[AnyRef], DataValue.NoValue, or DataValue.UseNilForDefault, |
| * which is a singleton object indicating that the item is nillable and |
| * useNilForDefault was true. |
| * |
| * The value will always be of the matching primitive types for the element, and |
| * directly usable as the value of a simple-type element. |
| * |
| * When a value is used, it is created from the XSD default attribute of the |
| * element declaration, and that string cannot contain DFDL entities of any kind, |
| * nor any PUA-remapped characters. This insures the default value can still be |
| * used for ordinary XML-schema validation outside of Daffodil/DFDL. |
| */ |
| final lazy val defaultValue: DataValuePrimitiveOrUseNilForDefaultOrNull = { |
| if (isDefaultable && (isScalar || isArrayWithAtLeastOneRequiredArrayElement)) { |
| val dv = |
| if (isNillable && useNilForDefault =:= YesNo.Yes) { |
| DataValue.UseNilForDefault |
| } else { |
| // |
| // Note: no remapping PUA chars or otherwise messing with the text of the default value |
| // because this must be a regular XSD default value so that Xerces validation |
| // will work. |
| // |
| val str = defaultValueAsString |
| val value = try { |
| primType.fromXMLString(str) |
| } catch { |
| case ipd: InvalidPrimitiveDataException => |
| SDE("Invalid default value: %s", ipd.getMessage) |
| } |
| value |
| } |
| dv |
| } else DataValue.NoValue |
| } |
| |
| /** |
| * Is either DataValue[AnyRef] or DataValue.NoValue. |
| * |
| * The value will always be of the matching primitive types for the element, and |
| * directly usable as the value of a simple-type element. |
| * |
| * When a value is used, it is created from the XSD fixed attribute of the |
| * element declaration, and that string cannot contain DFDL entities of any kind, |
| * nor any PUA-remapped characters. This insures the fixed value can still be |
| * used for ordinary XML-schema validation outside of Daffodil/DFDL. |
| */ |
| final lazy val fixedValue: DataValuePrimitiveNullable = { |
| if (hasFixedValue && isSimpleType) { |
| val fv = { |
| // |
| // Note: no remapping PUA chars or otherwise messing with the text of the fixed value |
| // because this must be a regular XSD fixed value so that Xerces validation |
| // will work. |
| // |
| val str = fixedValueAsString |
| val value = try { |
| primType.fromXMLString(str) |
| } catch { |
| case ipd: InvalidPrimitiveDataException => |
| SDE("Invalid fixed value: %s", ipd.getMessage) |
| } |
| value |
| } |
| fv |
| } else DataValue.NoValue |
| } |
| |
| lazy val unparserInfosetElementDefaultingBehavior: UnparserInfo.InfosetEventBehavior = { |
| import UnparserInfo._ |
| if (!isRepresented) MustExist |
| else if (isOutputValueCalc) Computed |
| else if (isOptional) Optional |
| else if (isArray && !isArrayWithAtLeastOneRequiredArrayElement) Optional |
| else MustExist |
| } |
| |
| lazy val canBeAbsentFromUnparseInfoset: Boolean = { |
| import UnparserInfo._ |
| unparserInfosetElementDefaultingBehavior !=:= MustExist |
| } |
| |
| def isQuasiElement: Boolean = false //overriden by RepTypeQuasiElementDecl |
| |
| final protected lazy val optTruncateSpecifiedLengthString = |
| Option(truncateSpecifiedLengthString =:= YesNo.Yes) |
| // because of the way text numbers are unparsed, we don't know that |
| // the string is for a text number. So we need this property for numbers and other text |
| // simple types also. |
| // if (isSimpleType && simpleType.primType.isInstanceOf[NodeInfo.String.Kind]) { |
| // Option(truncateSpecifiedLengthString =:= YesNo.Yes) |
| // } else None // don't need this property for non-strings |
| |
| /** |
| * This QName should contain the prefix from the element reference |
| */ |
| def namedQName: NamedQName |
| |
| /** |
| * Direct element children of a complex element. |
| * |
| * Include both represented and non-represented elements. |
| */ |
| final lazy val elementChildren: Seq[ElementBase] = LV('elementChildren) { |
| this.typeDef match { |
| case ct: ComplexTypeBase => { |
| ct.group.elementChildren |
| } |
| case _ => Nil |
| } |
| }.value |
| |
| final lazy val elementChildrenCompileInfo = |
| elementChildren.map { |
| _.dpathElementCompileInfo |
| } |
| |
| final lazy val isOutputValueCalc = outputValueCalcOption.isInstanceOf[Found] |
| |
| final override lazy val impliedRepresentation = { |
| val rep = if (isSimpleType) { |
| primType match { |
| case PrimType.HexBinary => Representation.Binary |
| case PrimType.String => Representation.Text |
| case PrimType.AnyURI => objectKind match { |
| case ObjectKindType.Bytes => Representation.Binary |
| case ObjectKindType.Chars => Representation.Text |
| } |
| case _ => representation |
| } |
| } else { |
| representation |
| } |
| rep match { |
| case Representation.Binary => |
| if (isComplexType || (primType != PrimType.HexBinary && primType != PrimType.AnyURI)) byteOrderEv // ensure defined |
| case _ => |
| encodingRaw // ensure defined |
| } |
| rep |
| } |
| |
| final override lazy val couldHaveText: Boolean = { |
| hasDelimiters || |
| (isSimpleType && impliedRepresentation == Representation.Text) || |
| (isComplexType && complexType.group.couldHaveText) |
| } |
| |
| final override lazy val termChildren: Seq[Term] = { |
| if (isSimpleType) Nil |
| else |
| Seq(complexType.group) |
| } |
| |
| final lazy val isParentUnorderedSequence: Boolean = { |
| optLexicalParent.exists { lp => |
| lp match { |
| case s: SequenceTermBase if !s.isOrdered => true |
| case _ => false |
| } |
| } |
| } |
| |
| private def getImplicitAlignmentInBits(thePrimType: PrimType, theRepresentation: Representation): Int = { |
| (theRepresentation, thePrimType) match { |
| case (Representation.Text, PrimType.AnyURI) => this.subsetError("Property value objectKind='chars' is not supported.") |
| case (Representation.Text, PrimType.HexBinary) => Assert.impossible("type xs:hexBinary with representation='text'") |
| case (Representation.Text, _) => knownEncodingAlignmentInBits |
| case (Representation.Binary, PrimType.String) => Assert.impossible("type xs:string with representation='binary'") |
| // Boolean, Float, Double, and HexBinary do not require binaryNumberRep to be defined |
| case (Representation.Binary, PrimType.Float | PrimType.Boolean) => 32 |
| case (Representation.Binary, PrimType.Double) => 64 |
| case (Representation.Binary, PrimType.HexBinary) => 8 |
| case (Representation.Binary, PrimType.AnyURI) => 8 |
| // Handle 64 bit types |
| case (Representation.Binary, PrimType.Long | PrimType.UnsignedLong) => |
| binaryNumberRep match { |
| case BinaryNumberRep.Packed | BinaryNumberRep.Bcd | BinaryNumberRep.Ibm4690Packed => 8 |
| case _ => 64 |
| } |
| // Handle 32 bit types |
| case (Representation.Binary, PrimType.Int | PrimType.UnsignedInt | PrimType.Boolean) => |
| binaryNumberRep match { |
| case BinaryNumberRep.Packed | BinaryNumberRep.Bcd | BinaryNumberRep.Ibm4690Packed => 8 |
| case _ => 32 |
| } |
| // Handle 16 bit types |
| case (Representation.Binary, PrimType.Short | PrimType.UnsignedShort) => |
| binaryNumberRep match { |
| case BinaryNumberRep.Packed | BinaryNumberRep.Bcd | BinaryNumberRep.Ibm4690Packed => 8 |
| case _ => 16 |
| } |
| // Handle 8 bit types |
| case (Representation.Binary, PrimType.Integer | PrimType.Decimal | PrimType.Byte | |
| PrimType.UnsignedByte | PrimType.NonNegativeInteger) => 8 |
| // Handle date types |
| case (Representation.Binary, PrimType.DateTime | PrimType.Date | PrimType.Time) => |
| binaryCalendarRep match { |
| case BinaryCalendarRep.BinaryMilliseconds => 64 |
| case BinaryCalendarRep.BinarySeconds => 32 |
| case _ => schemaDefinitionError("Implicit Alignment: binaryCalendarRep was %s but we expected BinarySeconds or BinaryMilliseconds.", binaryCalendarRep) |
| } |
| } |
| } |
| |
| private lazy val implicitAlignmentInBits: Int = getImplicitAlignmentInBits(primType, impliedRepresentation) |
| |
| final lazy val alignmentValueInBits: JInt = { |
| // |
| // get the alignment, measured in bits based on the alignment property, units, and type (when applicable) |
| // |
| val alignInBits: JInt = |
| alignment match { |
| case AlignmentType.Implicit => { |
| if (this.isComplexType) this.complexType.modelGroup.alignmentValueInBits |
| else implicitAlignmentInBits |
| } |
| case align: JInt => { |
| val alignInBits: JInt = this.alignmentUnits match { |
| case AlignmentUnits.Bits => align |
| case AlignmentUnits.Bytes => 8 * align |
| } |
| alignInBits |
| } |
| } |
| // |
| // Do checking of interactions of alignment with the rest of the representation |
| // |
| if ((alignment ne AlignmentType.Implicit) && this.isSimpleType) { |
| // |
| // For explicitly aligned simple types there are specific checks having to do with |
| // how explicit alignment interacts with text characters, or with binary packed decimal - as text chars |
| // and packed decimal digits come with alignment constraints of their own. |
| // |
| impliedRepresentation match { |
| case Representation.Text => { |
| // |
| // If they have text representation, alignment and the text encoding alignment must be compared. |
| // |
| if (isRepresented && (alignInBits % implicitAlignmentInBits) != 0) |
| SDE( |
| "The given alignment (%s bits) must be a multiple of the encoding specified alignment (%s bits) for %s when representation='text'. Encoding: %s", |
| alignInBits, implicitAlignmentInBits, primType.name, this.knownEncodingName) |
| } |
| case Representation.Binary => { |
| // |
| // if they have binary representation we must worry about packed digits, which require 4-bit alignment. |
| // |
| primType match { |
| case PrimType.Float | PrimType.Double | PrimType.Boolean | PrimType.HexBinary => /* Non textual data, no need to compare alignment to encoding's expected alignment */ |
| case _ => binaryNumberRep match { |
| case BinaryNumberRep.Packed | BinaryNumberRep.Bcd | BinaryNumberRep.Ibm4690Packed => { |
| if ((alignInBits % 4) != 0) |
| SDE( |
| "The given alignment (%s bits) must be a multiple of 4 for %s when using packed binary formats", |
| alignInBits, primType.name) |
| } |
| case _ => /* Since this is non-textual data, no need to compare alignment to encoding's expected alignment */ |
| } |
| } |
| } |
| } |
| } // end if explicit alignment and simple type |
| // |
| // Now regardless of type, check for whether the initiator interacts badly with |
| // the alignment. |
| // |
| if (hasInitiator) { |
| // Check for case where explicit alignment property and |
| // mandatory text alignment of initiator |
| // are not compatible. |
| val textAlign = knownEncodingAlignmentInBits |
| // the explicit alignment must be a multiple of the textAlign |
| if (textAlign < alignInBits || textAlign % alignInBits != 0) |
| SDW( |
| WarnID.AlignmentAndInitiatorTextAlignmentNotCompatible, |
| "Initiator text may leave the element incorrectly aligned. The text encoding of initiator characters is %s bits, " + |
| "but the element alignment requires %s bits. Suggest consider whether both dfdl:initiator and dfdl:alignment should be specified for this element.", |
| textAlign, alignInBits) |
| } |
| // |
| // Having done the checks, just return the answer |
| // |
| alignInBits |
| } |
| |
| /** |
| * Tells us if we have a specific length. |
| * |
| * Keep in mind that 80 characters in length can be anywhere from 80 to 320 bytes |
| * depending on the character encoding. So fixed length doesn't mean in bytes. |
| * it means in dfdl:lengthUnits units, which could be characters, and those can |
| * be fixed or variable width. |
| */ |
| final lazy val isFixedLength = { |
| (lengthKind =:= LengthKind.Explicit && lengthEv.isConstant) || |
| isImplicitLengthString |
| // FIXME: there are lots of other cases where things are fixed length |
| // e.g., implicit length hexBinary uses maxLength for length in bytes |
| // e.g., implicit length fixed-precision binary numbers (byte, short, int, long and unsigned thereof) |
| // In general the things in this file about fixed length seem to miss hexBinary. |
| // Also, things like packed and zoned decimal are usually fixed length, sometimes delimited. |
| } |
| |
| final lazy val isImplicitLengthString = isSimpleType && primType =:= PrimType.String && lengthKind =:= LengthKind.Implicit |
| |
| final lazy val fixedLengthValue: Long = { |
| // FIXME: this calculation only good for string type. |
| // We need this for every type and representation however. |
| // The answer should be in lengthUnits, and -1 should mean |
| // lengthUnits is bytes/bits, but the fixed length is given in characters |
| // of a variable-width charset, so greater than 0, but we don't know |
| // exactly. |
| Assert.usage(isFixedLength) |
| if (lengthKind =:= LengthKind.Explicit) lengthEv.optConstant.get |
| else { |
| Assert.invariant(lengthKind =:= LengthKind.Implicit) |
| // it's a string with implicit length. get from facets |
| schemaDefinitionUnless(this.hasMaxLength, "String with dfdl:lengthKind='implicit' must have an XSD maxLength facet value.") |
| val ml = this.maxLength |
| ml.longValue() |
| } |
| } |
| |
| final def hasFixedLengthOf(n: Int): Boolean = { |
| // FIXME: needs to work in lengthUnits. If length units is bytes/bits |
| // and encoding is variable-width charset, what should this return? |
| // (Perhaps should be usage error?) |
| if (!isFixedLength) false |
| else { |
| val fl = fixedLengthValue |
| n.toLong =#= fl |
| } |
| } |
| |
| final lazy val isLengthAlwaysNonZero = { |
| Assert.usage(isRepresented) |
| if (hasFixedLengthOf(0)) false |
| else if (isFixedLength) true |
| else false |
| } |
| |
| final lazy val fixedLength = { |
| if (isFixedLength) lengthEv.optConstant.get.longValue() else -1L |
| // FIXME: shouldn't even be asking for this if not isFixedLength |
| // try changing to Assert.usage(isFixedLength) |
| // FIXME: needs to return fixed length in lengthUnits. |
| // as for many of the other methods associated with length, |
| // if lengthUnits is bits/bytes and the element is textual, and |
| // the encoding is unknown, or known to be variable-width, what to return? |
| // (perhaps usage error, you must ask about textual data with unknown or |
| // known variable-width charset in lengthUnits characters ??) |
| } |
| |
| // FIXME: bless this method. Deprecate and remove other less reliable things. |
| final lazy val maybeFixedLengthInBits: MaybeULong = { |
| if (optRepTypeElement.isDefined) { |
| optRepTypeElement.get.maybeFixedLengthInBits |
| } else { |
| if (isRepresented && isFixedLength) { |
| val bitsMultiplier = lengthUnits match { |
| case LengthUnits.Bits => 1 |
| case LengthUnits.Bytes => 8 |
| case LengthUnits.Characters => if (knownEncodingIsFixedWidth) knownEncodingWidthInBits else -1 |
| } |
| if (bitsMultiplier > 0) { |
| MaybeULong(fixedLengthValue * bitsMultiplier) |
| } else { |
| MaybeULong.Nope |
| } |
| } else { |
| MaybeULong.Nope |
| } |
| } |
| } |
| |
| /** |
| * Nil Lit = literal nil, as opposed to value nil that uses a reserved value |
| */ |
| private lazy val isDefinedNilLit: Boolean = { |
| val res = isNillable && |
| (nilKind == NilKind.LiteralValue || |
| nilKind == NilKind.LiteralCharacter) |
| res |
| } |
| |
| private lazy val isDefinedNilValue: Boolean = { |
| val res = (isNillable && nilKind == NilKind.LogicalValue) |
| res |
| } |
| |
| /** |
| * These are static properties even though the delimiters can have runtime-computed values. |
| * The existence of an expression to compute a delimiter is assumed to imply a non-zero-length, aka a real delimiter. |
| */ |
| |
| private def NVDP = NilValueDelimiterPolicy |
| private def EVDP = EmptyValueDelimiterPolicy |
| |
| protected final lazy val hasNilValueInitiator = hasNonEmptyDelimiter(initiatorParseEv, nilValueDelimiterPolicy, NVDP.Both, NVDP.Initiator) |
| protected final lazy val hasNilValueTerminator = hasNonEmptyDelimiter(terminatorParseEv, nilValueDelimiterPolicy, NVDP.Both, NVDP.Terminator) |
| |
| /** |
| * We need the nil values in raw form for diagnostic messages. |
| * |
| * We need the nil values in cooked forms of two kinds. For parsing, and for unparsing. |
| * |
| * The difference is due to for unparsing the %NL; is treated specially |
| * because it must be computed based on dfdl:outputNewLine. |
| */ |
| final lazy val cookedNilValuesForParse = cookedNilValue(forUnparse = false) |
| final lazy val rawNilValuesForParse = rawNilValueList(forUnparse = false) |
| |
| final lazy val cookedNilValuesForUnparse = cookedNilValue(forUnparse = true) |
| final lazy val rawNilValuesForUnparse = rawNilValueList(forUnparse = false) |
| |
| final lazy val hasESNilValue = rawNilValuesForParse.contains("%ES;") |
| |
| /** |
| * Determines if the nil representation is one that has non-zero-length |
| * syntax. |
| * |
| * Returnes true if the nil representation *always* has some non-zero length. |
| * |
| * Returns false if a zero-length representation is the nil representation, or one of the |
| * accepted nil representations. This enables one to quickly recognize that |
| * a nilled element should be produced, and that one need not (in the runtime) |
| * entertain considerations of required/optional, separator suppression, etc. |
| * |
| * Since dfdl:nilValue property takes a list of nil representations |
| * (for dfdl:nilKind 'literalValue'), it is possible for a given element to |
| * have both zero-length and non-zero-length representations which cause |
| * a nilled element to be created by the parser. |
| */ |
| final lazy val hasNilValueRequiredSyntax = isNillable && |
| ((isDefinedNilLit && (hasNilValueInitiator || hasNilValueTerminator)) || |
| (isDefinedNilLit && !hasESNilValue) || |
| (isDefinedNilValue && (hasInitiator || hasTerminator)) || |
| // below is the case of string or hexbinary and nilKind logicalValue. A logical value of ES can |
| // cause a nil value to be created. |
| (isDefinedNilValue && (isSimpleType && (simpleType.primType =:= PrimType.String || simpleType.primType =:= PrimType.HexBinary) && !hasESNilValue))) |
| |
| final lazy val hasEmptyValueInitiator = hasNonEmptyDelimiter(initiatorParseEv, emptyValueDelimiterPolicy, EVDP.Both, EVDP.Initiator) |
| final lazy val hasEmptyValueTerminator = hasNonEmptyDelimiter(terminatorParseEv, emptyValueDelimiterPolicy, EVDP.Both, EVDP.Terminator) |
| |
| // See how this function takes the prop: => Any that is pass by name (aka lazy pass). |
| // That allows us to not require the property to exist at all if |
| // expr.isConstantEmptyString turns out to be true. |
| private def hasNonEmptyDelimiter(expr: DelimiterParseEv, prop: => Any, true1: Any, true2: Any): Boolean = { |
| // changed from a match on a 2-tuple to if-then-else logic because we don't even want to ask for |
| // prop's value at all unless the first test is false. |
| if (expr.isConstantEmptyString) |
| false |
| else |
| prop == true1 || prop == true2 |
| } |
| |
| /** |
| * check length and if there are delimiters such that there is a concept of something that we can call 'empty' |
| * |
| * Empty is observable so long as one can |
| * have zero length followed by a separator, or zero length between an |
| * initiator and terminator (as required for empty by emptyValueDelimiterPolicy) |
| * |
| * The concept of Empty here subsumes "empty triggers default values (when defined)" |
| * From DFDL Spec (2014 draft) section 9.4.2, and |
| * zero-length so that separator suppression applies, from DFDL Spec 2014 draft Section 14.2.1) |
| * |
| * It applies in this case to elements only. There is a concept of zero-length for model groups as well |
| * but is defined elsewhere. |
| */ |
| final lazy val isEmptyAnObservableConcept: Boolean = { |
| if (!isRepresented) false |
| else if (this.isLengthAlwaysNonZero) false |
| else if (isSimpleType) { |
| primType match { |
| case NodeInfo.String | NodeInfo.HexBinary => true // can be zero-length so empty, regardless of EVDP and init/term |
| case _ => { |
| // for other simple types, they can be empty only if they are defaultable |
| // because otherwise they always require some representation. |
| // but this calculation is used by the isDefaultable predicate to decide |
| // whether an element can have an empty rep so that it could be defaulted. |
| false |
| } |
| } |
| } else { |
| Assert.invariant(isComplexType) |
| val isZLP = !complexType.modelGroup.hasKnownRequiredSyntax |
| val res = isZLP |
| res |
| } |
| } |
| |
| /** |
| * True if the element can be empty, and there is no syntax to indicate |
| * empty-string or empty-hexBinary values, i.e., they do not require initiator nor terminator) |
| * so they are just zero-length. |
| * |
| * False if the type can't be empty. |
| */ |
| final lazy val hasEmptyValueZLSyntax = |
| !hasEmptyValueInitiator && !hasEmptyValueTerminator && isSimpleType && |
| (simpleType.primType =:= PrimType.String || |
| simpleType.primType =:= PrimType.HexBinary) |
| |
| import org.apache.daffodil.dsom.FacetTypes._ |
| |
| private lazy val hasPattern: Boolean = typeDef.optRestriction.exists(_.hasPattern) |
| private lazy val hasEnumeration: Boolean = typeDef.optRestriction.exists(_.hasEnumeration) |
| protected lazy val hasMinLength = typeDef.optRestriction.exists(_.hasMinLength) |
| protected lazy val hasMaxLength = typeDef.optRestriction.exists(_.hasMaxLength) |
| private lazy val hasMinInclusive = typeDef.optRestriction.exists(_.hasMinInclusive) |
| private lazy val hasMaxInclusive = typeDef.optRestriction.exists(_.hasMaxInclusive) |
| private lazy val hasMinExclusive = typeDef.optRestriction.exists(_.hasMinExclusive) |
| private lazy val hasMaxExclusive = typeDef.optRestriction.exists(_.hasMaxExclusive) |
| private lazy val hasTotalDigits = typeDef.optRestriction.exists(_.hasTotalDigits) |
| private lazy val hasFractionDigits = typeDef.optRestriction.exists(_.hasFractionDigits) |
| |
| final lazy val patternValues: Seq[FacetValueR] = { |
| Assert.invariant(hasPattern) |
| typeDef.optRestriction.map { r => |
| schemaDefinitionUnless( |
| r.primType == PrimType.String, |
| "Pattern is only allowed to be applied to string and types derived from string.") |
| r.patternValues |
| }.getOrElse(Nil) |
| } |
| |
| private lazy val enumerationValues: Option[String] = { |
| Assert.invariant(hasEnumeration) |
| typeDef.optRestriction.flatMap { _.enumerationValues } |
| } |
| |
| /** |
| * Compute minLength and maxLength together to share error-checking |
| * and case dispatch that would otherwise have to be repeated. |
| */ |
| final lazy val (minLength: java.math.BigDecimal, maxLength: java.math.BigDecimal) = computeMinMaxLength |
| // TODO: why are we using java.math.BigDecimal, when scala has a much |
| // nicer decimal class? |
| private val zeroBD = new java.math.BigDecimal(0) |
| private val unbBD = new java.math.BigDecimal(-1) // TODO: should this be a tunable limit? |
| |
| private def computeMinMaxLength: (java.math.BigDecimal, java.math.BigDecimal) = { |
| schemaDefinitionUnless(isSimpleType, "Facets minLength and maxLength are allowed only on types string and hexBinary.") |
| typeDef match { |
| case prim: PrimitiveType => { |
| val pt = prim.primType |
| schemaDefinitionWhen( |
| (pt == PrimType.String || pt == PrimType.HexBinary) && lengthKind == LengthKind.Implicit, |
| "Facets minLength and maxLength must be defined for type %s with lengthKind='implicit'", |
| pt.name) |
| // |
| // We handle text numbers by getting a stringValue first, then |
| // we convert to the number type. |
| // |
| // This means we cannot check and SDE here on incorrect simple type. |
| (zeroBD, unbBD) |
| } |
| case st: SimpleTypeDefBase if st.optRepTypeElement.isDefined => (st.optRepTypeElement.get.minLength, st.optRepTypeElement.get.maxLength) |
| case st: SimpleTypeDefBase if st.optRestriction.isDefined => { |
| val r = st.optRestriction.get |
| val pt = st.primType |
| val typeOK = pt == PrimType.String || pt == PrimType.HexBinary |
| schemaDefinitionWhen( |
| !typeOK && (hasMinLength || hasMaxLength), |
| "Facets minLength and maxLength are not allowed on types derived from type %s.\nThey are allowed only on typed derived from string and hexBinary.", |
| pt.name) |
| val res = (hasMinLength, hasMaxLength, lengthKind) match { |
| case (true, true, LengthKind.Implicit) => { |
| schemaDefinitionUnless( |
| r.minLengthValue.compareTo(r.maxLengthValue) == 0, |
| "The minLength and maxLength must be equal for type %s with lengthKind='implicit'. Values were minLength of %s, maxLength of %s.", |
| pt.name, r.minLengthValue, r.maxLengthValue) |
| (r.minLengthValue, r.maxLengthValue) |
| } |
| case (true, true, _) => { |
| schemaDefinitionWhen( |
| r.minLengthValue.compareTo(r.maxLengthValue) > 0, |
| // always true, so we don't bother to specify the type in the message. |
| "The minLength facet value must be less than or equal to the maxLength facet value. Values were minLength of %s, maxLength of %s.", |
| r.minLengthValue, r.maxLengthValue) |
| (r.minLengthValue, r.maxLengthValue) |
| } |
| case (_, _, LengthKind.Implicit) => SDE("When lengthKind='implicit', both minLength and maxLength facets must be specified.") |
| case (false, true, _) => (zeroBD, r.maxLengthValue) |
| case (false, false, _) => (zeroBD, unbBD) |
| case (true, false, _) => (r.minLengthValue, unbBD) |
| case _ => Assert.impossible() |
| } |
| res |
| } |
| case st: SimpleTypeDefBase => { |
| Assert.invariant(st.optRestriction.isEmpty) |
| (zeroBD, unbBD) |
| } |
| } |
| } |
| |
| // TODO: see code above that computes minLength, maxLength |
| // simultaneously to avoid redundant check code. |
| // |
| // Same thing applies to the other paired facets where there is lots of |
| // common logic associated with checking. |
| private lazy val minInclusive: java.math.BigDecimal = { |
| Assert.usage(hasMinInclusive) |
| typeDef.optRestriction.map { r => |
| if (r.hasMinExclusive) SDE("MinInclusive and MinExclusive cannot be specified for the same simple type.") |
| if (r.hasMaxExclusive) { |
| val res = r.minInclusiveValue.compareTo(r.maxExclusiveValue) |
| if (res > 0) SDE("MinInclusive(%s) must be less than or equal to MaxExclusive(%s).", r.minInclusiveValue, r.maxExclusiveValue) |
| } |
| if (r.hasMaxInclusive) { |
| val res = r.minInclusiveValue.compareTo(r.maxInclusiveValue) |
| if (res > 0) SDE("MinInclusive(%s) must be less than or equal to MaxInclusive(%s).", r.minInclusiveValue, r.maxInclusiveValue) |
| } |
| r.minInclusiveValue |
| }.get |
| } |
| |
| private lazy val maxInclusive: java.math.BigDecimal = { |
| Assert.usage(hasMaxInclusive) |
| typeDef.optRestriction.map { r => |
| if (r.hasMaxExclusive) SDE("MaxInclusive and MaxExclusive cannot be specified for the same simple type.") |
| if (r.hasMinExclusive) { |
| val res = r.minExclusiveValue.compareTo(r.maxInclusiveValue) |
| if (res > 0) SDE("MinExclusive(%s) must be less than or equal to MaxInclusive(%s)", r.minExclusiveValue, r.maxInclusiveValue) |
| } |
| if (r.hasMinInclusive) { |
| val res = r.minInclusiveValue.compareTo(r.maxInclusiveValue) |
| if (res > 0) SDE("MinInclusive(%s) must be less than or equal to MaxInclusive(%s)", r.minInclusiveValue, r.maxInclusiveValue) |
| } |
| r.maxInclusiveValue |
| }.get |
| } |
| |
| private lazy val minExclusive: java.math.BigDecimal = { |
| Assert.usage(hasMinExclusive) |
| typeDef.optRestriction.map { r => |
| if (r.hasMinInclusive) SDE("MinInclusive and MinExclusive cannot be specified for the same simple type.") |
| if (r.hasMaxInclusive) { |
| val res = r.minExclusiveValue.compareTo(r.maxInclusiveValue) |
| if (res > 0) SDE("MinExclusive(%s) must be less than or equal to MaxInclusive(%s)", r.minExclusiveValue, r.maxInclusiveValue) |
| } |
| if (r.hasMaxExclusive) { |
| val res = r.minExclusiveValue.compareTo(r.maxExclusiveValue) |
| if (res > 0) SDE("MinExclusive(%s) must be less than or equal to MaxExclusive(%s)", r.minExclusiveValue, r.maxExclusiveValue) |
| } |
| r.minExclusiveValue |
| }.get |
| } |
| |
| private lazy val maxExclusive: java.math.BigDecimal = { |
| Assert.invariant(hasMaxExclusive) |
| typeDef.optRestriction.map { r => |
| if (r.hasMaxInclusive) SDE("MaxExclusive and MaxInclusive cannot be specified for the same simple type.") |
| if (r.hasMinInclusive) { |
| val res = r.minInclusiveValue.compareTo(r.maxExclusiveValue) |
| if (res > 0) SDE("MinInclusive(%s) must be less than or equal to MaxExclusive(%s)", r.minInclusiveValue, r.maxExclusiveValue) |
| } |
| if (r.hasMinExclusive) { |
| val res = r.minExclusiveValue.compareTo(r.maxExclusiveValue) |
| if (res > 0) SDE("MinExclusive(%s) must be less than or equal to MaxExclusive(%s)", r.minExclusiveValue, r.maxExclusiveValue) |
| } |
| r.maxExclusiveValue |
| }.get |
| } |
| |
| private lazy val totalDigits: java.math.BigDecimal = { |
| Assert.usage(hasTotalDigits) |
| val st = simpleType |
| typeDef.optRestriction.map { r => |
| // Can only be applied to decimal or any of the integer types, and |
| // types derived from them |
| val isDerivedFromDecimal = st.primType.isSubtypeOf(NodeInfo.Decimal) |
| val isDerivedFromInteger = st.primType.isSubtypeOf(NodeInfo.Integer) |
| if (isDerivedFromDecimal || isDerivedFromInteger) r.totalDigitsValue |
| else { |
| SDE("TotalDigits facet can only be applied to decimal or any of the integer types, and types derived from them. Restriction base is %s", st.primType.name) |
| } |
| }.get |
| } |
| |
| private lazy val fractionDigits: java.math.BigDecimal = { |
| Assert.usage(hasFractionDigits) |
| typeDef.optRestriction.map { r => |
| // Can only be applied to decimal |
| val isDerivedFromDecimal = r.primType.isSubtypeOf(NodeInfo.Decimal) |
| if (isDerivedFromDecimal) { |
| if (r.hasTotalDigits) { |
| val res = r.fractionDigitsValue.compareTo(r.totalDigitsValue) |
| if (res > 0) SDE("FractionDigits facet must not exceed TotalDigits.") |
| } |
| r.fractionDigitsValue |
| } else { |
| SDE("FractionDigits facet can only be applied to decimal. Restriction base is %s", r.primType.name) |
| } |
| }.get |
| } |
| |
| /** |
| * Does the element have a default value? |
| */ |
| def defaultValueAsString: String |
| def hasDefaultValue: Boolean |
| |
| /** |
| * Does the element have a fixed value? |
| */ |
| def fixedValueAsString: String |
| def hasFixedValue: Boolean |
| |
| /** |
| * We require that there be a concept of empty if we're going to be able to default something |
| * and we are going to require that we can tell this statically. I.e., we're not going to defer this to runtime |
| * just in case the delimiters are being determined at runtime. |
| * |
| * That is to say, if a delimiter is an expression, then we're assuming that means |
| * at runtime it will not evaluate to empty string (so you can specify the delimiter |
| * at runtime, but you cannot turn on/off the whole delimited format at runtime.) |
| */ |
| final lazy val isDefaultable: Boolean = LV('isDefaultable) { |
| if (isSimpleType) { |
| if (!isRepresented) false |
| else if (!hasDefaultValue) false |
| else { |
| if (!isEmptyAnObservableConcept) |
| SDW(WarnID.NoEmptyDefault, "Element with no empty representation. XSD default='%s' can only be used when unparsing.", defaultValueAsString) |
| schemaDefinitionWhen(isOptional, "Optional elements cannot have default values but default='%s' was found.", defaultValueAsString) |
| if (isArray && !isArrayWithAtLeastOneRequiredArrayElement) { |
| (minOccurs, occursCountKind) match { |
| case (_, OccursCountKind.Parsed) | |
| (_, OccursCountKind.StopValue) => |
| SDE( |
| "XSD default='%s' can never be used since an element with dfdl:occursCountKind='%s' has no required occurrences.", |
| defaultValueAsString, occursCountKind) |
| case (0, _) => SDE( |
| "XSD default='%s' can never be used since an element with XSD minOccurs='0' has no required occurrences.", |
| defaultValueAsString) |
| case _ => // ok |
| } |
| } |
| Assert.invariant(hasDefaultValue) |
| !isOptional && |
| (isScalar || |
| isArrayWithAtLeastOneRequiredArrayElement) |
| } |
| } else { |
| // TODO: Implement complex element defaulting |
| // JIRA issue DFDL-1277 |
| // |
| // a complex element is defaultable |
| // recursively if everything in it is defaultable |
| // and everything in it has no required representation |
| // (e.g., no required delimiters, no alignment, no skip, etc.) |
| // furthermore, even the defaultable things inside must satisfy |
| // a stricter criterion. They must have emptyValueDelimiterPolicy='none' |
| // if delimiters are defined and they could be empty (which is implied if they are defaultable) |
| false |
| } |
| }.value |
| |
| final lazy val defaultParseUnparsePolicy = optionParseUnparsePolicy.getOrElse(ParseUnparsePolicy.Both) |
| |
| /** |
| * This function ensures that all children have a compatable |
| * parseUnparsePolicy with the root. In other words, if the root policy is |
| * 'Both', all children must also be 'Both'. If the root policy is 'Parse' or |
| * 'Unparse', then all children must have either the same policy, or must be |
| * 'Both'. |
| * |
| * If the context is None, then that means the policy was determined by the |
| * user (e.g. a tunable), rather than by using the default value of the root |
| * element |
| */ |
| final def checkParseUnparsePolicyCompatibility(context: Option[ElementBase], policy: ParseUnparsePolicy): Unit = { |
| elementChildren.foreach { child => |
| val childPolicy = child.defaultParseUnparsePolicy |
| val isCompatible = policy == childPolicy || childPolicy == ParseUnparsePolicy.Both |
| if (!isCompatible) { |
| if (context.isDefined) { |
| context.get.SDE("Child element '%s' with dfdlx:parseUnparsePolicy='%s' is not compatible with root elements dfdlx:parseUnparsePolicy='%s'", child, childPolicy, policy) |
| } else { |
| SDE("Element '%s' with dfdlx:parseUnparsePolicy='%s' is not compatible with user supplied dfdlx:parseUnparsePolicy='%s'", child, childPolicy, policy) |
| } |
| } |
| |
| // recursively check children |
| child.checkParseUnparsePolicyCompatibility(context, policy) |
| } |
| } |
| |
| /** |
| * Changed to a warning - DFDL WG decided to make this check optional, but it |
| * is still useful as a warning. |
| * |
| * Turns out that MIL STD 2045 header format needs to pad out to a byte boundary |
| * at the end of the structure. An optional, non-byte aligned field precedes |
| * the end of the structure; hence, putting a zero-length byte-aligned field |
| * at the end was crashing into this error. I couldn't think of a work-around, |
| * so changed this into a warning. |
| * |
| * The old requirement was: |
| * To avoid ambiguity when parsing, optional elements and variable-occurrence arrays |
| * where the minimum number of occurrences is zero cannot have alignment properties |
| * different from the items that follow them. It is a schema definition error otherwise. |
| * |
| * Part of the required evaluations for ElementBase. |
| */ |
| private lazy val checkForAlignmentAmbiguity: Unit = { |
| if (isOptional) { |
| this.laterSiblings.filterNot(m => m == this).foreach { that => |
| val isSame = this.alignmentValueInBits == that.alignmentValueInBits |
| if (!isSame) { |
| this.SDW(WarnID.AlignmentNotSame, "%s is an optional element or a variable-occurrence array and its alignment (%s) is not the same as %s's alignment (%s).", |
| this.toString, this.alignmentValueInBits, that.toString, that.alignmentValueInBits) |
| } |
| } |
| } |
| } |
| |
| private lazy val optionFloating = findPropertyOption("floating") |
| |
| private lazy val checkFloating = (optionFloating.isDefined, tunable.requireFloatingProperty) match { |
| case (false, false) => SDW(WarnID.FloatingError, "Property 'dfdl:floating' is required but not defined.") |
| case (false, true) => floating |
| case (_, _) => this.subset((floating eq YesNo.No), "Property value floating='yes' is not supported.") |
| } |
| |
| final lazy val quasiElementChildren: Seq[QuasiElementDeclBase] = { |
| val res = |
| if (!isQuasiElement) |
| if (this.lengthKind == LengthKind.Prefixed) |
| Seq(prefixedLengthElementDecl) |
| else if (this.isSimpleType) |
| this.simpleType.optRepTypeElement.toSeq |
| else |
| Nil |
| else |
| Nil |
| res |
| } |
| } |