| /* Copyright (c) 2012-2015 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.dsom |
| |
| import scala.xml.Node |
| import scala.xml.NamespaceBinding |
| import edu.illinois.ncsa.daffodil.exceptions.Assert |
| import edu.illinois.ncsa.daffodil.grammar._ |
| import edu.illinois.ncsa.daffodil.schema.annotation.props._ |
| import edu.illinois.ncsa.daffodil.schema.annotation.props.gen._ |
| import edu.illinois.ncsa.daffodil.xml._ |
| import edu.illinois.ncsa.daffodil.util.Misc |
| import edu.illinois.ncsa.daffodil.processors._ |
| import edu.illinois.ncsa.daffodil.dpath.NodeInfo |
| import edu.illinois.ncsa.daffodil.dpath.NodeInfo.PrimType |
| import edu.illinois.ncsa.daffodil.grammar.ElementBaseGrammarMixin |
| import edu.illinois.ncsa.daffodil.processors.unparsers.NextElementResolver |
| import edu.illinois.ncsa.daffodil.processors.unparsers.SeveralPossibilitiesForNextElement |
| import edu.illinois.ncsa.daffodil.equality._ |
| import edu.illinois.ncsa.daffodil.processors.unparsers.NoNextElement |
| import edu.illinois.ncsa.daffodil.processors.unparsers.OnlyOnePossibilityForNextElement |
| import edu.illinois.ncsa.daffodil.processors.unparsers.ChildResolver |
| import edu.illinois.ncsa.daffodil.processors.unparsers.SiblingResolver |
| import edu.illinois.ncsa.daffodil.processors.unparsers.ResolverType |
| import edu.illinois.ncsa.daffodil.processors.UseNilForDefault |
| import edu.illinois.ncsa.daffodil.util.Maybe |
| import edu.illinois.ncsa.daffodil.util.Maybe._ |
| import java.lang.{ Integer => JInt } |
| import edu.illinois.ncsa.daffodil.processors._ |
| |
| /** |
| * 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. |
| */ |
| |
| object ElementBase { |
| var count = 0 |
| } |
| |
| /** |
| * Shared by all forms of elements, local or global or element reference. |
| */ |
| abstract class ElementBase(xmlArg: Node, parent: SchemaComponent, position: Int) |
| extends Term(xmlArg, parent, position) |
| with Element_AnnotationMixin |
| with NillableMixin |
| with DFDLStatementMixin |
| with ElementBaseGrammarMixin |
| with ElementRuntimeValuedPropertiesMixin |
| with StringTextMixin |
| with NumberTextMixin |
| with CalendarTextMixin |
| with BooleanTextMixin |
| with TextNumberFormatMixin |
| with RealTermMixin { |
| |
| override final def eBase = this |
| |
| ElementBase.count += 1 // how many elements in this schema. |
| |
| requiredEvaluations(typeDef) |
| requiredEvaluations(isSimpleType) |
| requiredEvaluations(if (hasPattern) patternValues) |
| requiredEvaluations(if (hasEnumeration) enumerationValues) |
| requiredEvaluations(if (hasMinLength) minLength) |
| requiredEvaluations(if (hasMaxLength) maxLength) |
| requiredEvaluations(if (hasMinInclusive) minInclusive) |
| requiredEvaluations(if (hasMaxInclusive) maxInclusive) |
| requiredEvaluations(if (hasMinExclusive) minExclusive) |
| requiredEvaluations(if (hasMaxExclusive) maxExclusive) |
| requiredEvaluations(if (hasTotalDigits) totalDigits) |
| requiredEvaluations(if (hasFractionDigits) fractionDigits) |
| |
| def name: String |
| |
| def inputValueCalcOption: PropertyLookupResult |
| def outputValueCalcOption: PropertyLookupResult |
| def isNillable: Boolean |
| def isSimpleType: Boolean |
| def isComplexType: Boolean |
| def elementComplexType: ComplexTypeBase |
| def elementSimpleType: SimpleTypeBase |
| def typeDef: TypeBase |
| |
| def isRequired = true // overridden in particle mixin. |
| |
| override final protected def calcContentParserReferencedElementInfos = |
| if (this.inputValueCalcOption.isDefined) |
| ivcCompiledExpression.contentReferencedElementInfos |
| else |
| ReferencedElementInfos.None |
| |
| override final protected def calcContentUnparserReferencedElementInfos = |
| if (this.outputValueCalcOption.isDefined) |
| ovcCompiledExpression.contentReferencedElementInfos |
| else |
| ReferencedElementInfos.None |
| |
| override final protected def calcValueParserReferencedElementInfos = |
| if (this.inputValueCalcOption.isDefined) { |
| val ivcCE = ivcCompiledExpression |
| val setERs = ivcCE.valueReferencedElementInfos |
| setERs |
| } else |
| ReferencedElementInfos.None |
| |
| override final protected def calcValueUnparserReferencedElementInfos = |
| if (this.outputValueCalcOption.isDefined) |
| ovcCompiledExpression.valueReferencedElementInfos |
| else |
| ReferencedElementInfos.None |
| |
| final lazy val isReferencedByContentLengthParserExpressions: Boolean = |
| rootElement.get.contentLengthParserReferencedElementInfos.contains(this.dpathElementCompileInfo) |
| |
| final lazy val isReferencedByContentLengthUnparserExpressions: Boolean = |
| rootElement.get.contentLengthUnparserReferencedElementInfos.contains(this.dpathElementCompileInfo) |
| |
| final lazy val isReferencedByValueLengthParserExpressions: Boolean = { |
| val setElems = rootElement.get.valueLengthParserReferencedElementInfos |
| // if (this eq rootElement.get) |
| // println("PARSER these are referenced by valueCalc: " + setElems.toString) |
| val res = setElems.contains(this.dpathElementCompileInfo) |
| res |
| } |
| |
| final lazy val isReferencedByValueLengthUnparserExpressions: Boolean = { |
| val setElems = rootElement.get.valueLengthUnparserReferencedElementInfos |
| // if (this eq rootElement.get) |
| // println("UNPARSER these are referenced by valueCalc: " + setElems.toString) |
| val isInExprs = setElems.contains(this.dpathElementCompileInfo) |
| val res = isInExprs || |
| otherwiseShouldCaptureValueRegionLength |
| res |
| } |
| |
| /** |
| * Besides being referenced by the dfdl:valueLength function, |
| * We need the valueLength to be computed for unparser pad/fill, to check |
| * excess length, and for alignmentFills. |
| * |
| * TBD: why for alignment fills? Don't see using it in the code. Try without this? |
| */ |
| private lazy val otherwiseShouldCaptureValueRegionLength: Boolean = { |
| val pad = this.shouldAddPadding |
| val fill = this.shouldAddFill |
| val len = this.shouldCheckExcessLength |
| val alg = !this.isKnownToBeAligned // alignment fill uses the value length. |
| val res = pad || fill || len || alg |
| res |
| } |
| |
| final lazy val simpleType = { |
| Assert.usage(isSimpleType) |
| typeDef.asInstanceOf[SimpleTypeBase] |
| } |
| |
| final lazy val optPrimType: Option[PrimType] = Misc.boolToOpt(isSimpleType, primType) // .typeRuntimeData) |
| |
| def isScalar: Boolean |
| def isRequiredArrayElement: Boolean |
| |
| // override in Particle |
| lazy val optMinOccurs: Option[Int] = None |
| lazy val optMaxOccurs: Option[Int] = None |
| |
| def elementRef: Option[ElementRef] |
| |
| final override lazy val dpathCompileInfo = dpathElementCompileInfo |
| |
| final lazy val dpathElementCompileInfo: DPathElementCompileInfo = { |
| val eci = new DPathElementCompileInfo( |
| enclosingElement.map { _.dpathElementCompileInfo }, |
| variableMap, |
| namespaces, |
| slashPath, |
| slotIndexInParent, |
| name, |
| isArray, |
| namedQName, |
| optPrimType, |
| schemaFileLocation, |
| elementChildrenCompileInfo) |
| eci |
| } |
| |
| private lazy val thisElementsNamespace: NS = this.namedQName.namespace |
| private lazy val thisElementsNamespacePrefix: String = this.namespaces.getPrefix(thisElementsNamespace.toString) |
| |
| 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 containsNoNamespaceElement: Boolean = { |
| // thisElementsNamespace.isNoNamespace || |
| // elementChildren.exists { _.containsNoNamespaceElement } |
| // } |
| // |
| // private def bindingsHaveDefaultNamespaceBinding(pairs: Set[(String, NS)]) = { |
| // val map = pairs.toMap |
| // val optDefaultNS = map.get(null) |
| // val res = optDefaultNS.isDefined |
| // res |
| // } |
| |
| private lazy val thisElementsRequiredNamespaceBindings: Set[(String, NS)] = { |
| val childrenRequiredNSBindings = |
| this.elementChildren.flatMap { _.thisElementsRequiredNamespaceBindings }.toSet |
| |
| val myRequiredNSBinding = Set((thisElementsNamespacePrefix, thisElementsNamespace)) |
| 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 |
| } |
| |
| private lazy val emptyNSPairs = nsBindingsToSet(scala.xml.TopScope) |
| |
| private lazy val myOwnNSPairs: Set[(String, NS)] = thisElementsRequiredNamespaceBindings |
| private lazy val myParentNSPairs = enclosingElement match { |
| case None => emptyNSPairs |
| case Some(parent) => parent.myOwnNSPairs |
| } |
| |
| private lazy val myUniquePairs: Set[(String, NS)] = { |
| val res = myOwnNSPairs -- myParentNSPairs |
| res |
| } |
| |
| private def pairsToNSBinding(pairs: Set[(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 res = NamespaceBinding(pre, ns.toString, parentNSBinding) |
| res |
| } |
| } |
| |
| private lazy val parentMinimizedScope = enclosingElement.map { _.minimizedScope }.getOrElse(scala.xml.TopScope) |
| |
| /** |
| * 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. |
| */ |
| private lazy val minimizedScope: NamespaceBinding = { |
| val uniquePairs = |
| if (enclosingComponent.isEmpty) { |
| // 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) => prefix == null && ns.isNoNamespace } |
| } else { |
| myUniquePairs |
| } |
| |
| pairsToNSBinding(uniquePairs, parentMinimizedScope) |
| } |
| |
| override lazy val runtimeData: RuntimeData = elementRuntimeData |
| override lazy val termRuntimeData: TermRuntimeData = elementRuntimeData |
| |
| final lazy val defaultValue = { |
| if (isDefaultable && (isScalar || isRequiredArrayElement)) { |
| val dv = |
| if (isNillable && useNilForDefault =:= YesNo.Yes) { |
| UseNilForDefault // singleton object indicator |
| } 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 = Infoset.convertToInfosetRepType( |
| primType, |
| str, this) |
| value |
| } |
| Some(dv) |
| } else None |
| } |
| |
| /** |
| * The NextElementResolver is used to determine what infoset event comes next, and "resolves" which is to say |
| * determines the ElementRuntimeData for that infoset event. This can be used to construct the initial |
| * infoset from a stream of XML events. |
| */ |
| final def computeNextElementResolver(possibles: Seq[ElementBase], resolverType: ResolverType): NextElementResolver = { |
| // |
| // Annoying, but scala's immutable Map is not covariant in its first argument |
| // the way one would normally expect a collection to be. |
| // So Map[NamedQName, ElementRuntimeData] is not a subtype of Map[QNameBase, ElementRuntimeData] |
| // So we need a cast upward to Map[QNameBase,ElementRuntimeData] |
| // |
| val eltMap = possibles.map { |
| e => (e.namedQName, e.elementRuntimeData) |
| }.toMap.asInstanceOf[Map[QNameBase, ElementRuntimeData]] |
| val resolver = eltMap.size match { |
| case 0 => new NoNextElement(schemaFileLocation, resolverType) |
| case 1 => new OnlyOnePossibilityForNextElement(schemaFileLocation, eltMap.values.head, resolverType) |
| case _ => new SeveralPossibilitiesForNextElement(schemaFileLocation, eltMap, resolverType) |
| } |
| resolver |
| } |
| |
| lazy val unparserInfosetElementDefaultingBehavior: UnparserInfo.InfosetEventBehavior = { |
| import UnparserInfo._ |
| //if (isScalar && isDefaultable) ScalarDefaultable |
| //else if (isArray && isDefaultable) ArrayDefaultable |
| if (!isRepresented) MustExist |
| else if (isOutputValueCalc) Computed |
| else if (isOptional) Optional |
| else if (isArray && !isRequiredArrayElement) Optional |
| else MustExist |
| } |
| |
| lazy val canBeAbsentFromUnparseInfoset: Boolean = { |
| import UnparserInfo._ |
| unparserInfosetElementDefaultingBehavior !=:= MustExist |
| } |
| |
| // private lazy val mustBeAbsentFromUnparseInfoset: Boolean = { |
| // isOutputValueCalc |
| // } |
| |
| final lazy val nextElementResolver: NextElementResolver = { |
| computeNextElementResolver(possibleNextChildElementsInInfoset, SiblingResolver) |
| } |
| |
| final lazy val childElementResolver: NextElementResolver = |
| computeNextElementResolver(possibleFirstChildElementsInInfoset, ChildResolver) |
| |
| lazy val elementRuntimeData: ElementRuntimeData = LV('elementRuntimeData) { |
| val ee = enclosingElement |
| // |
| // Must be lazy below, because we are defining the elementRuntimeData in terms of |
| // the elementRuntimeData of its enclosing element. This backpointer must be |
| // constructed lazily so that we first connect up all the erds to their children, |
| // and only subsequently ask for these parents to be elaborated. |
| // |
| lazy val optERD = ee.map { enc => |
| Assert.invariant(this != enc) |
| enc.elementRuntimeData |
| } |
| lazy val maybeTRD = this.enclosingTerm.map { enc => |
| Assert.invariant(this != enc) |
| enc.termRuntimeData |
| } |
| createElementRuntimeData(optERD, maybeTRD) |
| }.value |
| |
| def erd = elementRuntimeData // just an abbreviation |
| |
| /** |
| * Everything needed at runtime about the element |
| * in order to compile expressions using it, (for debug) |
| * and issue proper diagnostics in error situations. |
| */ |
| private def createElementRuntimeData(parent: => Option[ElementRuntimeData], |
| parentTerm: => Maybe[TermRuntimeData]): ElementRuntimeData = { |
| |
| // |
| // I got sick of initialization time problems, so this mutual recursion |
| // defines the tree of ERDs. |
| // |
| // This works because of deferred arguments and lazy evaluation |
| // |
| lazy val childrenERDs: Seq[ElementRuntimeData] = |
| elementChildren.map { _.elementRuntimeData } |
| val newERD: ElementRuntimeData = new ElementRuntimeData( |
| parent, |
| parentTerm, |
| childrenERDs, |
| schemaSet.variableMap, |
| nextElementResolver, |
| childElementResolver, |
| encodingInfo, |
| dpathElementCompileInfo, |
| schemaFileLocation, |
| prettyName, |
| path, |
| namespaces, |
| minimizedScope, |
| defaultBitOrder, |
| optPrimType, |
| targetNamespace, |
| thisElementsNamespace, |
| Misc.boolToOpt(hasPattern, patternValues), |
| Misc.boolToOpt(hasEnumeration, enumerationValues), |
| Misc.boolToOpt(hasMinLength, minLength), |
| Misc.boolToOpt(hasMaxLength, maxLength), |
| Misc.boolToOpt(hasMinInclusive, minInclusive), |
| Misc.boolToOpt(hasMaxInclusive, maxInclusive), |
| Misc.boolToOpt(hasMinExclusive, minExclusive), |
| Misc.boolToOpt(hasMaxExclusive, maxExclusive), |
| Misc.boolToOpt(hasTotalDigits, totalDigits), |
| Misc.boolToOpt(hasFractionDigits, fractionDigits), |
| optMinOccurs, |
| optMaxOccurs, |
| name, |
| targetNamespacePrefix, |
| thisElementsNamespacePrefix, |
| isHidden, |
| nChildSlots, |
| slotIndexInParent, |
| isNillable, |
| isArray, // can have more than 1 occurrence |
| isOptional, // can have exactly 0 or 1 occurrence |
| isRequired, // must have at least 1 occurrence |
| namedQName, |
| isRepresented, |
| couldHaveText, |
| alignmentValueInBits, |
| hasNoSkipRegions, |
| impliedRepresentation, |
| optIgnoreCase, |
| defaultValue, |
| // |
| // unparser specific items |
| // |
| false, // !isReferencedByExpressions, // assume it is always to be referenced by expressions |
| optTruncateSpecifiedLengthString, |
| if (isOutputValueCalc) Some(ovcCompiledExpression) else None) |
| newERD |
| } |
| |
| private 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.primitiveType.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 |
| |
| /** |
| * Each distinctly named child gets a slot. |
| * |
| * In the case where you have several elements with the same name and type, |
| * but separated by other non-optional elements, then those are considered |
| * to be different slots. |
| * |
| * Also, an xs:choice of elements, each child element gets its own slot. We |
| * are not attempting to multiplex slots so that they are cleverly being shared. |
| * |
| * If a choice had many many disjoint children, then probably a map should be |
| * used, not an array (as the infoset representation) so as to avoid wasting |
| * all the slot space in these objects. This would still be constant-time access, |
| * just with a larger constant overhead. It's a time/space tradeoff. |
| */ |
| final lazy val nChildSlots: Int = { |
| if (isSimpleType) 0 |
| else elementChildren.length |
| } |
| |
| final lazy val slotIndexInParent: Int = { |
| if (!nearestEnclosingElement.isDefined) 0 |
| else { |
| val elemParent = nearestEnclosingElementNotRef.get |
| val realElement = this.referredToComponent // because we might be an ElementRef |
| val realChildren = elemParent.elementChildren.map { _.referredToComponent } |
| val pos = realChildren.indexOf(realElement) |
| Assert.invariant(pos >= 0) |
| pos |
| } |
| } |
| |
| /** |
| * Direct element children of a complex element. |
| * |
| * Include both represented and non-represented elements. |
| */ |
| final lazy val elementChildren: Seq[ElementBase] = { |
| this.typeDef match { |
| case ct: ComplexTypeBase => { |
| ct.group.elementChildren.asInstanceOf[Seq[ElementBase]] |
| } |
| case _ => Nil |
| } |
| } |
| |
| final lazy val elementChildrenCompileInfo = elementChildren.map { _.dpathElementCompileInfo } |
| |
| final override lazy val isRepresented = { |
| val isRep = inputValueCalcOption.isInstanceOf[NotFound] |
| if (!isRep) { |
| if (isOptional) { |
| SDE("inputValueCalc property can not appear on optional elements") |
| } |
| if (!isScalar) { |
| SDE("inputValueCalc property can not appear on array elements") |
| } |
| } |
| isRep |
| } |
| |
| 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 _ => representation |
| } |
| } else { |
| representation |
| } |
| rep |
| } |
| |
| final override lazy val couldHaveText: Boolean = { |
| hasDelimiters || |
| (isSimpleType && impliedRepresentation == Representation.Text) || |
| (isComplexType && elementComplexType.group.couldHaveText) |
| } |
| |
| final override lazy val termChildren: Seq[Term] = { |
| if (isSimpleType) Nil |
| else Seq(elementComplexType.group) |
| } |
| |
| final lazy val isParentUnorderedSequence: Boolean = { |
| parent match { |
| case s: Sequence if !s.isOrdered => true |
| case _ => false |
| } |
| } |
| |
| protected final def annotationFactory(node: Node): Option[DFDLAnnotation] = { |
| node match { |
| case <dfdl:element>{ contents @ _* }</dfdl:element> => Some(new DFDLElement(node, this)) |
| case _ => annotationFactoryForDFDLStatement(node, this) |
| } |
| } |
| |
| protected final def emptyFormatFactory = new DFDLElement(newDFDLAnnotationXML("element"), this) |
| |
| protected final def isMyFormatAnnotation(a: DFDLAnnotation) = a.isInstanceOf[DFDLElement] |
| |
| private def getImplicitAlignmentInBits(thePrimType: PrimType, theRepresentation: Representation): Int = { |
| theRepresentation match { |
| case Representation.Text => |
| thePrimType match { |
| case PrimType.HexBinary => Assert.impossible("type xs:hexBinary with representation='text'") |
| case _ => knownEncodingAlignmentInBits |
| } |
| case Representation.Binary => |
| thePrimType match { |
| case PrimType.String => Assert.impossible("type xs:string with representation='binary'") |
| case PrimType.Double | PrimType.Long | PrimType.UnsignedLong => 64 |
| case PrimType.Float | PrimType.Int | PrimType.UnsignedInt | PrimType.Boolean => 32 |
| case PrimType.Short | PrimType.UnsignedShort => 16 |
| case PrimType.Integer | PrimType.Decimal | PrimType.Byte | PrimType.UnsignedByte | PrimType.NonNegativeInteger => 8 |
| case 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) |
| } |
| case PrimType.HexBinary => 8 |
| } |
| } |
| } |
| |
| private lazy val implicitAlignmentInBits: Int = getImplicitAlignmentInBits(primType, impliedRepresentation) |
| |
| final lazy val alignmentValueInBits: JInt = { |
| alignment match { |
| case AlignmentType.Implicit => { |
| if (this.isComplexType) { |
| val ct = this.elementComplexType |
| ct.alignmentValueInBits |
| } else implicitAlignmentInBits |
| } |
| case align: JInt => { |
| val alignInBits: JInt = this.alignmentUnits match { |
| case AlignmentUnits.Bits => align |
| case AlignmentUnits.Bytes => 8 * align |
| } |
| if (this.isSimpleType) { |
| impliedRepresentation match { |
| case Representation.Text => { |
| 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 _ => /* Non textual data, no need to compare alignment to encoding's expected alignment */ |
| } |
| } |
| 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 |
| // TODO: 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. |
| } |
| |
| final def isImplicitLengthString = isSimpleType && primType =:= PrimType.String && lengthKind =:= LengthKind.Implicit |
| |
| final lazy val fixedLengthValue: Long = { |
| 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) = { |
| 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 // shouldn't even be asking for this if not isFixedLength |
| } |
| |
| /** |
| * 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 |
| |
| private lazy val hasNilValueInitiator = initTermTestExpression(initiatorParseEv, nilValueDelimiterPolicy, NVDP.Both, NVDP.Initiator) |
| private lazy val hasNilValueTerminator = initTermTestExpression(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. |
| */ |
| lazy val cookedNilValuesForParse = cookedNilValue(forUnparse = false) |
| lazy val rawNilValuesForParse = rawNilValueList(forUnparse = false) |
| |
| lazy val cookedNilValuesForUnparse = cookedNilValue(forUnparse = true) |
| lazy val rawNilValuesForUnparse = rawNilValueList(forUnparse = false) |
| |
| lazy val hasESNilValue = rawNilValuesForParse.contains("%ES;") |
| |
| 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.primitiveType =:= PrimType.String || simpleType.primitiveType =:= PrimType.HexBinary) && !hasESNilValue))) |
| |
| final lazy val hasEmptyValueInitiator = initTermTestExpression(initiatorParseEv, emptyValueDelimiterPolicy, EVDP.Both, EVDP.Initiator) |
| final lazy val hasEmptyValueTerminator = initTermTestExpression(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.isKnownNotEmpty turns out to be false. |
| private def initTermTestExpression(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 true. |
| if (expr.isKnownNonEmpty) |
| if (prop == true1 || prop == true2) true |
| else false |
| else false |
| } |
| |
| /** |
| * Means the element is in a context where there is a separator (from some enclosing sequence) |
| * expected after it. |
| * |
| * Abstract here because implementations are different for local vs. global things. |
| */ |
| def hasSep: Boolean |
| |
| /** |
| * 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) |
| */ |
| final def emptyIsAnObservableConcept = LV('emptyIsAnObservableConcept) { |
| if (this.isLengthAlwaysNonZero) false |
| else { |
| val res = if (hasSep || //FIXME: not sufficient unless it's a postfix separator, or we know there will be some other terminating markup after. |
| hasEmptyValueInitiator || |
| hasEmptyValueTerminator) { |
| true |
| } else false |
| res |
| } |
| }.value |
| |
| // 11/1/2012 - moved to base since needed by patternValue |
| final lazy val isPrimType = typeDef.isInstanceOf[PrimType] |
| |
| import edu.illinois.ncsa.daffodil.dsom.FacetTypes._ |
| |
| private lazy val hasPattern: Boolean = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasPattern |
| } else { false } |
| } |
| private lazy val hasEnumeration: Boolean = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasEnumeration |
| } else { false } |
| } |
| |
| protected lazy val hasMinLength = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasMinLength |
| } else { false } |
| } |
| |
| protected lazy val hasMaxLength = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasMaxLength |
| } else { false } |
| } |
| |
| private lazy val hasMinInclusive = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasMinInclusive |
| } else { false } |
| } |
| |
| private lazy val hasMaxInclusive = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasMaxInclusive |
| } else { false } |
| } |
| |
| private lazy val hasMinExclusive = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasMinExclusive |
| } else { false } |
| } |
| |
| private lazy val hasMaxExclusive = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasMaxExclusive |
| } else { false } |
| } |
| |
| private lazy val hasTotalDigits = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasTotalDigits |
| } else { false } |
| } |
| |
| private lazy val hasFractionDigits = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| st.hasFractionDigits |
| } else { false } |
| } |
| |
| final lazy val patternValues: Seq[FacetValueR] = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| if (st.hasPattern) { |
| val pt = st.primitiveType |
| if (pt != PrimType.String) SDE("Pattern is only allowed to be applied to string and types derived from string.") |
| st.patternValues |
| } else SDE("Pattern was not found in this context.") |
| } else SDE("Pattern was asked for when isSimpleType(%s) and isPrimType(%s)", isSimpleType, isPrimType) |
| } |
| |
| private lazy val enumerationValues: String = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| if (st.hasEnumeration) st.enumerationValues |
| else SDE("Enumeration was not found in this context.") |
| } else SDE("Enumeration was asked for when isSimpleType(%s) and isPrimType(%s)", isSimpleType, isPrimType) |
| } |
| |
| /** |
| * 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.") |
| elementSimpleType match { |
| case prim: PrimType => { |
| schemaDefinitionWhen((prim == PrimType.String || prim == PrimType.HexBinary) && lengthKind == LengthKind.Implicit, |
| "Facets minLength and maxLength must be defined for type %s with lengthKind='implicit'", |
| prim.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. |
| return (zeroBD, unbBD) |
| } |
| case st: SimpleTypeDefBase => { |
| val pt = st.primitiveType |
| 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( |
| st.minLengthValue.compareTo(st.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, st.minLengthValue, st.maxLengthValue) |
| (st.minLengthValue, st.maxLengthValue) |
| } |
| case (true, true, _) => { |
| schemaDefinitionWhen( |
| st.minLengthValue.compareTo(st.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.", |
| st.minLengthValue, st.maxLengthValue) |
| (st.minLengthValue, st.maxLengthValue) |
| } |
| case (_, _, LengthKind.Implicit) => SDE("When lengthKind='implicit', both minLength and maxLength facets must be specified.") |
| case (false, true, _) => (zeroBD, st.maxLengthValue) |
| case (false, false, _) => (zeroBD, unbBD) |
| case (true, false, _) => (st.minLengthValue, unbBD) |
| case _ => Assert.impossible() |
| } |
| res |
| } |
| case _ => Assert.invariantFailed("should only be a PrimType or SimpleTypeDefBase") |
| } |
| } |
| |
| // 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 = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| if (st.hasMinInclusive && st.hasMinExclusive) SDE("MinInclusive and MinExclusive cannot be specified for the same simple type.") |
| if (st.hasMinInclusive && st.hasMaxExclusive) { |
| val res = st.minInclusiveValue.compareTo(st.maxExclusiveValue) |
| if (res > 0) SDE("MinInclusive(%s) must be less than or equal to MaxExclusive(%s).", st.minInclusiveValue, st.maxExclusiveValue) |
| } |
| if (st.hasMinInclusive && st.hasMaxInclusive) { |
| val res = st.minInclusiveValue.compareTo(st.maxInclusiveValue) |
| if (res > 0) SDE("MinInclusive(%s) must be less than or equal to MaxInclusive(%s).", st.minInclusiveValue, st.maxInclusiveValue) |
| } |
| if (st.hasMinInclusive) st.minInclusiveValue |
| else SDE("MinInclusive was not found in this context.") |
| } else SDE("MinInclusive was asked for when isSimpleType(%s) and isPrimType(%s)", isSimpleType, isPrimType) |
| } |
| |
| private lazy val maxInclusive: java.math.BigDecimal = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| if (st.hasMaxInclusive && st.hasMaxExclusive) SDE("MaxInclusive and MaxExclusive cannot be specified for the same simple type.") |
| if (st.hasMaxInclusive && st.hasMinExclusive) { |
| val res = st.minExclusiveValue.compareTo(st.maxInclusiveValue) |
| if (res > 0) SDE("MinExclusive(%s) must be less than or equal to MaxInclusive(%s)", st.minExclusiveValue, st.maxInclusiveValue) |
| } |
| if (st.hasMaxInclusive && st.hasMinInclusive) { |
| val res = st.minInclusiveValue.compareTo(st.maxInclusiveValue) |
| if (res > 0) SDE("MinInclusive(%s) must be less than or equal to MaxInclusive(%s)", st.minInclusiveValue, st.maxInclusiveValue) |
| } |
| if (st.hasMaxInclusive) st.maxInclusiveValue |
| else SDE("MaxInclusive was not found in this context.") |
| } else SDE("MaxInclusive was asked for when isSimpleType(%s) and isPrimType(%s)", isSimpleType, isPrimType) |
| } |
| |
| private lazy val minExclusive: java.math.BigDecimal = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| if (st.hasMinInclusive && st.hasMinExclusive) SDE("MinInclusive and MinExclusive cannot be specified for the same simple type.") |
| if (st.hasMaxInclusive && st.hasMinExclusive) { |
| val res = st.minExclusiveValue.compareTo(st.maxInclusiveValue) |
| if (res > 0) SDE("MinExclusive(%s) must be less than or equal to MaxInclusive(%s)", st.minExclusiveValue, st.maxInclusiveValue) |
| } |
| if (st.hasMaxExclusive && st.hasMinExclusive) { |
| val res = st.minExclusiveValue.compareTo(st.maxExclusiveValue) |
| if (res > 0) SDE("MinExclusive(%s) must be less than or equal to MaxExclusive(%s)", st.minExclusiveValue, st.maxExclusiveValue) |
| } |
| if (st.hasMinExclusive) st.minExclusiveValue |
| else SDE("MinExclusive was not found in this context.") |
| } else SDE("MinExclusive was asked for when isSimpleType(%s) and isPrimType(%s)", isSimpleType, isPrimType) |
| } |
| |
| private lazy val maxExclusive: java.math.BigDecimal = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| if (st.hasMaxExclusive && st.hasMaxInclusive) SDE("MaxExclusive and MaxInclusive cannot be specified for the same simple type.") |
| if (st.hasMaxExclusive && st.hasMinInclusive) { |
| val res = st.minInclusiveValue.compareTo(st.maxExclusiveValue) |
| if (res > 0) SDE("MinInclusive(%s) must be less than or equal to MaxExclusive(%s)", st.minInclusiveValue, st.maxExclusiveValue) |
| } |
| if (st.hasMaxExclusive && st.hasMinExclusive) { |
| val res = st.minExclusiveValue.compareTo(st.maxExclusiveValue) |
| if (res > 0) SDE("MinExclusive(%s) must be less than or equal to MaxExclusive(%s)", st.minExclusiveValue, st.maxExclusiveValue) |
| } |
| if (st.hasMaxExclusive) st.maxExclusiveValue |
| else SDE("MaxExclusive was not found in this context.") |
| } else SDE("MaxExclusive was asked for when isSimpleType(%s) and isPrimType(%s)", isSimpleType, isPrimType) |
| } |
| |
| private lazy val totalDigits: java.math.BigDecimal = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| if (st.hasTotalDigits) { |
| // Can only be applied to decimal or any of the integer types, and |
| // types derived from them |
| val isDerivedFromDecimal = NodeInfo.isXDerivedFromY(st.primitiveType.name, "decimal") |
| val isDerivedFromInteger = NodeInfo.isXDerivedFromY(st.primitiveType.name, "integer") |
| if (isDerivedFromDecimal || isDerivedFromInteger) st.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.primitiveType.name) |
| } |
| } else SDE("TotalDigits was not found in this context.") |
| } else SDE("TotalDigits was asked for when isSimpleType(%s) and isPrimType(%s)", isSimpleType, isPrimType) |
| } |
| |
| private lazy val fractionDigits: java.math.BigDecimal = { |
| if (isSimpleType && !isPrimType) { |
| val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| if (st.hasFractionDigits) { |
| // Can only be applied to decimal |
| val isDerivedFromDecimal = NodeInfo.isXDerivedFromY(st.primitiveType.name, "decimal") |
| if (isDerivedFromDecimal) { |
| if (st.hasTotalDigits) { |
| val res = st.fractionDigitsValue.compareTo(st.totalDigitsValue) |
| if (res > 0) SDE("FractionDigits facet must not exceed TotalDigits.") |
| } |
| st.fractionDigitsValue |
| } else { |
| SDE("FractionDigits facet can only be applied to decimal. Restriction base is %s", st.primitiveType.name) |
| } |
| } else SDE("FractionDigits was not found in this context.") |
| } else SDE("FractionDigits was asked for when isSimpleType(%s) and isPrimType(%s)", isSimpleType, isPrimType) |
| } |
| |
| // private lazy val allFacets: Seq[FacetValue] = { |
| // if (isSimpleType && !isPrimType) { |
| // val st = elementSimpleType.asInstanceOf[SimpleTypeDefBase] |
| // st.combinedBaseFacets |
| // } else scala.collection.mutable.Seq.empty |
| // } |
| |
| /** |
| * Does the element have a default value? |
| */ |
| def isDefaultable: Boolean |
| def defaultValueAsString: String |
| |
| /** |
| * Combine our statements with those of the ref that is referencing us (if there is one), and |
| * those of our simpleType (if we're a simple type element) |
| * |
| * The order here is important. The statements from type come first, then from declaration, then from |
| * reference. |
| */ |
| lazy val statements: Seq[DFDLStatement] = |
| stForStatements.map { _.statements }.getOrElse(Nil) ++ |
| localStatements ++ |
| elementRef.map { _.statements }.getOrElse(Nil) |
| |
| lazy val newVariableInstanceStatements: Seq[DFDLNewVariableInstance] = |
| stForStatements.map { _.newVariableInstanceStatements }.getOrElse(Nil) ++ |
| localNewVariableInstanceStatements ++ |
| elementRef.map { _.newVariableInstanceStatements }.getOrElse(Nil) |
| |
| lazy val (discriminatorStatements, assertStatements) = |
| checkDiscriminatorsAssertsDisjoint(combinedDiscrims, combinedAsserts) |
| |
| private lazy val combinedAsserts: Seq[DFDLAssert] = |
| stForStatements.map { _.assertStatements }.getOrElse(Nil) ++ |
| localAssertStatements ++ |
| elementRef.map { _.assertStatements }.getOrElse(Nil) |
| |
| private lazy val combinedDiscrims: Seq[DFDLDiscriminator] = |
| stForStatements.map { _.discriminatorStatements }.getOrElse(Nil) ++ |
| localDiscriminatorStatements ++ |
| elementRef.map { _.discriminatorStatements }.getOrElse(Nil) |
| |
| lazy val setVariableStatements: Seq[DFDLSetVariable] = { |
| val combinedSvs = |
| stForStatements.map { _.setVariableStatements }.getOrElse(Nil) ++ |
| localSetVariableStatements ++ |
| elementRef.map { _.setVariableStatements }.getOrElse(Nil) |
| checkDistinctVariableNames(combinedSvs) |
| } |
| |
| private lazy val stForStatements = typeDef match { |
| case st: SimpleTypeDefBase => Some(st) |
| case _ => None |
| } |
| |
| protected final def possibleFirstChildTerms: Seq[Term] = termChildren |
| |
| protected final def couldBeLastElementInModelGroup: Boolean = LV('couldBeLastElementInModelGroup) { |
| val couldBeLast = enclosingTerm match { |
| case None => true |
| case Some(s: Sequence) if s.isOrdered => { |
| !possibleNextSiblingTerms.exists { |
| case e: ElementBase => !e.isOptional || e.isRequiredArrayElement |
| case mg: ModelGroup => mg.mustHaveRequiredElement |
| } |
| } |
| case _ => true |
| } |
| couldBeLast |
| }.value |
| |
| final lazy val nextParentElements: Seq[ElementBase] = { |
| if (enclosingTerm.isDefined && couldBeLastElementInModelGroup) { |
| enclosingTerm.get.asInstanceOf[ModelGroup].possibleNextChildElementsInInfoset |
| } else { |
| Nil |
| } |
| } |
| |
| protected 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 daf:parseUnparsePolicy='%s' is not compatible with root elements daf:parseUnparsePolicy='%s'", child, childPolicy, policy) |
| } else { |
| SDE("Element '%s' with daf:parseUnparsePolicy='%s' is not compatible with user supplied daf:parseUnparsePolicy='%s'", child, childPolicy, policy) |
| } |
| } |
| |
| // recursively check children |
| child.checkParseUnparsePolicyCompatibility(context, policy) |
| } |
| } |
| |
| } |