diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ElementBase.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ElementBase.scala
index 29bf03b311..606f0b3f0f 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ElementBase.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ElementBase.scala
@@ -970,8 +970,6 @@ trait ElementBase
(simpleType.primType =:= PrimType.String ||
simpleType.primType =:= PrimType.HexBinary)
- import org.apache.daffodil.runtime1.dsom.FacetTypes.*
-
private lazy val hasPattern: Boolean = typeDef.optRestriction.exists(_.hasPattern)
private lazy val hasEnumeration: Boolean = typeDef.optRestriction.exists(_.hasEnumeration)
protected lazy val hasLength: Boolean = typeDef.optRestriction.exists(_.hasLength)
@@ -984,7 +982,7 @@ trait ElementBase
private lazy val hasTotalDigits = typeDef.optRestriction.exists(_.hasTotalDigits)
private lazy val hasFractionDigits = typeDef.optRestriction.exists(_.hasFractionDigits)
- final lazy val patternValues: Seq[FacetValueR] = {
+ final lazy val patternValues: Seq[FacetPattern] = {
Assert.invariant(hasPattern)
typeDef.optRestriction.map { _.patternValues }.getOrElse(Nil)
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Facets.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Facets.scala
index a3ca0c4455..bb1057d86e 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Facets.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Facets.scala
@@ -17,17 +17,14 @@
package org.apache.daffodil.core.dsom
-import java.math.BigInteger
import scala.xml.Node
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.iapi.InvalidRestrictionPolicy
-import org.apache.daffodil.lib.xml.XMLUtils
import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
import org.apache.daffodil.runtime1.dsom.*
trait Facets { self: Restriction =>
- import org.apache.daffodil.runtime1.dsom.FacetTypes.*
private def retrieveFacetValueFromRestrictionBase(
xml: Node,
@@ -159,75 +156,49 @@ trait Facets { self: Restriction =>
}
final lazy val hasEnumeration: Boolean =
- (localEnumerationValue.length > 0) || (getRemoteFacetValues(Facet.enumeration).size > 0)
+ localEnumerationValue.nonEmpty || remoteBaseFacets.collectFirst {
+ case _: FacetEnumeration =>
+ }.isDefined
final lazy val hasPattern: Boolean =
- (localPatternValue.length > 0) || (getRemoteFacetValues(Facet.pattern).size > 0)
+ localPatternValue.nonEmpty || remoteBaseFacets.collectFirst { case _: FacetPattern =>
+ }.isDefined
final lazy val hasLength: Boolean =
- (localLengthValue != "") || (getRemoteFacetValues(Facet.length).size > 0)
+ localLengthValue.nonEmpty || remoteBaseFacets.collectFirst { case _: FacetLength =>
+ }.isDefined
final lazy val hasMinLength: Boolean =
- (localMinLengthValue != "") || (getRemoteFacetValues(Facet.minLength).size > 0)
+ localMinLengthValue.nonEmpty || remoteBaseFacets.collectFirst { case _: FacetMinLength =>
+ }.isDefined
final lazy val hasMaxLength: Boolean =
- (localMaxLengthValue != "") || (getRemoteFacetValues(Facet.maxLength).size > 0)
+ localMaxLengthValue.nonEmpty || remoteBaseFacets.collectFirst { case _: FacetMaxLength =>
+ }.isDefined
final lazy val hasMinInclusive: Boolean =
- (localMinInclusiveValue != "") || (getRemoteFacetValues(Facet.minInclusive).size > 0)
+ localMinInclusiveValue.nonEmpty || remoteBaseFacets.collectFirst {
+ case _: FacetMinInclusive =>
+ }.isDefined
final lazy val hasMaxInclusive: Boolean =
- (localMaxInclusiveValue != "") || (getRemoteFacetValues(Facet.maxInclusive).size > 0)
+ localMaxInclusiveValue.nonEmpty || remoteBaseFacets.collectFirst {
+ case _: FacetMaxInclusive =>
+ }.isDefined
final lazy val hasMinExclusive: Boolean =
- (localMinExclusiveValue != "") || (getRemoteFacetValues(Facet.minExclusive).size > 0)
+ localMinExclusiveValue.nonEmpty || remoteBaseFacets.collectFirst {
+ case _: FacetMinExclusive =>
+ }.isDefined
final lazy val hasMaxExclusive: Boolean =
- (localMaxExclusiveValue != "") || (getRemoteFacetValues(Facet.maxExclusive).size > 0)
+ localMaxExclusiveValue.nonEmpty || remoteBaseFacets.collectFirst {
+ case _: FacetMaxExclusive =>
+ }.isDefined
final lazy val hasTotalDigits: Boolean =
- (localTotalDigitsValue != "") || (getRemoteFacetValues(Facet.totalDigits).size > 0)
+ localTotalDigitsValue.nonEmpty || remoteBaseFacets.collectFirst {
+ case _: FacetTotalDigits =>
+ }.isDefined
final lazy val hasFractionDigits: Boolean =
- (localFractionDigitsValue != "") || (getRemoteFacetValues(Facet.fractionDigits).size > 0)
+ localFractionDigitsValue.nonEmpty || remoteBaseFacets.collectFirst {
+ case _: FacetFractionDigits =>
+ }.isDefined
- final lazy val patternValues: Seq[FacetValueR] = {
- val values: Seq[(Facet.Type, Values)] = combinedBaseFacets.filter { case (f, _) =>
- f == Facet.pattern
- }
- val valuesRemapped: Seq[FacetValueR] = values.map {
- case (f, v: String) => {
- //
- // The DFDL Infoset can contain strings which hold characters
- // that are not allowed in XML at all.
- //
- // In order to talk about these characters in a XSD pattern facet
- // we use a remapping of such characters into the Unicode
- // Private Use Area, so as to have XML-legal characters.
- //
- // See the section titled "XML Illegal Characters" on this web page:
- // https://daffodil.apache.org/infoset/
- //
- // Before processing a regex of these characters in Daffodil's pattern facet
- // validation, we must remap these PUA characters back to the originally
- // intended code points, since that's what the Infoset strings will contain.
- //
- // Consider the character code 0xB. This is illegal in XML v1.0 documents.
- // A DFDL Schema is an XML Schema, which is an XML document; hence, we cannot
- // use the character with code 0xB directly, nor can we use an XML numeric
- // character entity like for it. The character is simply disallowed in
- // XML, including DFDL schemas. Hence, we mention 0xB by using a remapping of it
- // to the PUA area character 0xE00B, which we express by
- // Hence a pattern regex like "[ 0-9a-zA-Z]" will match
- // the character with char code 0xB (remapped from E00B), as well as spaces,
- // and alphanumeric characters.
- //
- // The XSD numeric character entity can be used to match ASCII NUL
- // (char code 0).
- //
- // This remapping is for pattern facets, which are inside a DFDL schema,
- // and so will not contain CR characters, since XML reading will convert those
- // to LF. To discuss CR in this pattern we can't use `
` syntax because that
- // turns into a CR which gets turned into a LF. Plus the pattern value is
- // an XML attribute, the value of which gets its whitespace collapsed, all
- // line-ending chars converted to spaces, and adjacent spaces collapsed to one.
- //
- // So a pattern facet must use `\r` and '\n' to describe line-endings within the pattern.
- // And in general one must be careful about whitespace.
- //
- val remapped: String = XMLUtils.remapPUAToXMLIllegalCharacters(v)
- (f, remapped.r)
- }
+ final lazy val patternValues: Seq[FacetPattern] = {
+ val valuesRemapped: Seq[FacetPattern] = combinedBaseFacets.collect { case f: FacetPattern =>
+ f
}
(primType, tunable.invalidRestrictionPolicy) match {
case (PrimType.String, _) =>
@@ -243,515 +214,51 @@ trait Facets { self: Restriction =>
}
}
- final lazy val enumerationValues: Option[String] = {
- // Should only ever have one set per SimpleType
- val values = combinedBaseFacets.filter { case (f, _) => f == Facet.enumeration }
- if (values.size > 0) {
- val (_, value) = values(0)
- Some(value)
- } else None
- }
- // TODO: Tidy up. Can likely replace getFacetValue with a similar call to combinedBaseFacets
- // as combinedBaseFacets should contain the 'narrowed' values.
- //
+ final lazy val enumerationValues: Option[String] =
+ combinedBaseFacets.collectFirst { case f: FacetEnumeration => f }.map(_.xmlValue)
final lazy val lengthValue: java.math.BigDecimal =
- getFacetValue(localLengthValue, Facet.length, hasLength)
+ combinedBaseFacets
+ .collectFirst { case f: FacetLength => f }
+ .map(f => new java.math.BigDecimal(f.bigIntValue))
+ .getOrElse(SDE("The length facet was not found."))
final lazy val minLengthValue: java.math.BigDecimal =
- getFacetValue(localMinLengthValue, Facet.minLength, hasMinLength)
+ combinedBaseFacets
+ .collectFirst { case f: FacetMinLength => f }
+ .map(f => new java.math.BigDecimal(f.bigIntValue))
+ .getOrElse(SDE("The minLength facet was not found."))
final lazy val maxLengthValue: java.math.BigDecimal =
- getFacetValue(localMaxLengthValue, Facet.maxLength, hasMaxLength)
+ combinedBaseFacets
+ .collectFirst { case f: FacetMaxLength => f }
+ .map(f => new java.math.BigDecimal(f.bigIntValue))
+ .getOrElse(SDE("The maxLength facet was not found."))
final lazy val minInclusiveValue: java.math.BigDecimal =
- getFacetValue(localMinInclusiveValue, Facet.minInclusive, hasMinInclusive)
+ combinedBaseFacets
+ .collectFirst { case f: FacetMinInclusive => f }
+ .map(_.bigDecimalValue)
+ .getOrElse(SDE("The minInclusive facet was not found."))
final lazy val maxInclusiveValue: java.math.BigDecimal =
- getFacetValue(localMaxInclusiveValue, Facet.maxInclusive, hasMaxInclusive)
+ combinedBaseFacets
+ .collectFirst { case f: FacetMaxInclusive => f }
+ .map(_.bigDecimalValue)
+ .getOrElse(SDE("The maxInclusive facet was not found."))
final lazy val minExclusiveValue: java.math.BigDecimal =
- getFacetValue(localMinExclusiveValue, Facet.minExclusive, hasMinExclusive)
+ combinedBaseFacets
+ .collectFirst { case f: FacetMinExclusive => f }
+ .map(_.bigDecimalValue)
+ .getOrElse(SDE("The minExclusive facet was not found."))
final lazy val maxExclusiveValue: java.math.BigDecimal =
- getFacetValue(localMaxExclusiveValue, Facet.maxExclusive, hasMaxExclusive)
+ combinedBaseFacets
+ .collectFirst { case f: FacetMaxExclusive => f }
+ .map(_.bigDecimalValue)
+ .getOrElse(SDE("The maxExclusive facet was not found."))
final lazy val totalDigitsValue: java.math.BigDecimal =
- getFacetValue(localTotalDigitsValue, Facet.totalDigits, hasTotalDigits)
+ combinedBaseFacets
+ .collectFirst { case f: FacetTotalDigits => f }
+ .map(f => new java.math.BigDecimal(f.bigIntValue))
+ .getOrElse(SDE("The totalDigits facet was not found."))
final lazy val fractionDigitsValue: java.math.BigDecimal =
- getFacetValue(localFractionDigitsValue, Facet.fractionDigits, hasFractionDigits)
-
- private def errorOnLocalLessThanBaseFacet(
- local: BigInteger,
- base: BigInteger,
- theFacetType: Facet.Type
- ) = {
- val res = local.compareTo(base)
- if (res < 0)
- SDE(
- "SimpleTypes: The local %s (%s) was less than the base %s (%s) ",
- theFacetType,
- local,
- theFacetType,
- base
- )
- }
- private def errorOnLocalGreaterThanBaseFacet(
- local: BigInteger,
- base: BigInteger,
- theFacetType: Facet.Type
- ) = {
- val res = local.compareTo(base)
- if (res > 0)
- SDE(
- "SimpleTypes: The local %s (%s) was greater than the base %s (%s) ",
- theFacetType,
- local,
- theFacetType,
- base
- )
- }
- private def errorOnLocalLessThanBaseFacet(
- local: java.math.BigDecimal,
- base: java.math.BigDecimal,
- theFacetType: Facet.Type
- ) = {
- val res = local.compareTo(base)
- if (res < 0)
- SDE(
- "SimpleTypes: The local %s (%s) was less than the base %s (%s) ",
- theFacetType,
- local,
- theFacetType,
- base
- )
- }
- private def errorOnLocalGreaterThanBaseFacet(
- local: java.math.BigDecimal,
- base: java.math.BigDecimal,
- theFacetType: Facet.Type
- ) = {
- val res = local.compareTo(base)
- if (res > 0)
- SDE(
- "SimpleTypes: The local %s (%s) was greater than the base %s (%s) ",
- theFacetType,
- local,
- theFacetType,
- base
- )
- }
- private def errorOnLocalNotEqualToBaseFacet(
- local: BigInteger,
- base: BigInteger,
- theFacetType: Facet.Type
- ) = {
- val res = local.compareTo(base)
- if (res != 0)
- SDE(
- "SimpleTypes: The local %s (%s) was not equal to the base %s (%s) ",
- theFacetType,
- local,
- theFacetType,
- base
- )
- }
-
- private def getRemoteFacetValues(theFacetType: Facet.Type): Seq[FacetValue] = {
- val res = remoteBaseFacets.filter { case (f, _) => f == theFacetType }
- res
- }
-
- private def getRemoteFacetValue(theFacetType: Facet.Type): String = {
- // Filtering works more appropriately here
- val res = remoteBaseFacets.filter { case (f, v) => f == theFacetType }
- if (res.size > 0) {
- val (_, theFacetValue) = res(0)
- return theFacetValue
- }
- "" // Indicates the facet doesn't exist
- }
-
- private def getFacetValue(
- theLocalValue: String,
- theRemoteValue: String,
- theType: Facet.Type,
- exists: Boolean
- ): java.math.BigDecimal = {
- if (!exists) SDE("The facet %s was not found.", theType)
- else if (theLocalValue != "" && theRemoteValue != "") {
- val resFacet = doNumericFacetNarrowing(theLocalValue, theRemoteValue, theType)
- new java.math.BigDecimal(resFacet)
- } else if (theLocalValue != "") {
- checkValueSpaceFacetRange(theLocalValue, theType)
- } else {
- checkValueSpaceFacetRange(theRemoteValue, theType)
- }
- }
-
- private def getFacetValue(
- theLocalValue: String,
- theType: Facet.Type,
- exists: Boolean
- ): java.math.BigDecimal = {
- val remoteFacets = getRemoteFacetValues(theType)
- if (!exists) SDE("The facet %s was not found.", theType)
- else if (theLocalValue != "" && remoteFacets.size > 0) {
- val (_, remoteValue) = getRemoteFacetValues(theType)(0)
- val resFacet = doNumericFacetNarrowing(theLocalValue, remoteValue, theType)
- new java.math.BigDecimal(resFacet)
- } else if (theLocalValue != "") {
- checkValueSpaceFacetRange(theLocalValue, theType)
- } else {
- val (_, remoteValue) = remoteFacets(0)
- checkValueSpaceFacetRange(remoteValue, theType)
- }
- }
-
- private def narrowNonNegativeFacets(
- localFacet: String,
- remoteFacet: String,
- facetType: Facet.Type
- ): String = {
- val theLocalFacet = new BigInteger(localFacet)
- val theRemoteFacet = new BigInteger(remoteFacet)
- if (theLocalFacet.signum() != 1)
- SDE("The %s facet must be a non-negative integer.", facetType)
- facetType match {
- case Facet.minLength => {
- errorOnLocalLessThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
- localFacet
- }
- case Facet.length => {
- errorOnLocalNotEqualToBaseFacet(theLocalFacet, theRemoteFacet, facetType)
- localFacet
- }
- case Facet.maxLength | Facet.fractionDigits => {
- errorOnLocalGreaterThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
- localFacet
- }
- case _ => {
- val errMsg = "narrowNonNegativeFacets is not valid for %s facet".format(facetType)
- Assert.usageError(errMsg)
- }
- }
- }
-
- private def narrowPositiveIntegerFacets(
- localFacet: String,
- remoteFacet: String,
- facetType: Facet.Type
- ): String = {
- val theLocalFacet = new BigInteger(localFacet)
- val theRemoteFacet = new BigInteger(remoteFacet)
- if ((theLocalFacet.signum() != 1) || (theLocalFacet.compareTo(BigInteger.ZERO) == 0))
- SDE("The %s facet must be a positive integer.", facetType)
- facetType match {
- case Facet.totalDigits => {
- errorOnLocalGreaterThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
- localFacet
- }
- case _ => {
- val errMsg = "narrowPositiveIntegerFacets is not valid for %s facet".format(facetType)
- Assert.usageError(errMsg)
- }
- }
- }
-
- private def narrowValueSpaceFacets(
- localFacet: String,
- remoteFacet: String,
- facetType: Facet.Type
- ) = {
- val (theLocalFacet, theRemoteFacet) =
- checkValueSpaceFacetRange(localFacet, remoteFacet, facetType)
-
- // Made it here so range checks were successful
- // Now just validate/compare local and base/remote facet
- facetType match {
- case Facet.minInclusive => {
- errorOnLocalLessThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
- }
- case Facet.maxInclusive => {
- errorOnLocalGreaterThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
- }
- case Facet.minExclusive => {
- errorOnLocalLessThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
- }
- case Facet.maxExclusive => {
- errorOnLocalGreaterThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
- }
- case _ => {
- val errMsg =
- "Unrecognized facet type (%s) for narrowing of value-space facets.".format(facetType)
- Assert.usageError(errMsg)
- }
- }
- localFacet
- }
-
- private def convertFacetToBigDecimal(facet: String): java.math.BigDecimal = {
- self.primType match {
- case PrimType.DateTime =>
- dateToBigDecimal(
- facet,
- "uuuu-MM-dd'T'HH:mm:ss.SSSSSSxxx",
- PrimType.DateTime.toString(),
- this
- )
- case PrimType.Date =>
- dateToBigDecimal(facet, "uuuu-MM-ddxxx", PrimType.Date.toString(), this)
- case PrimType.Time =>
- dateToBigDecimal(facet, "HH:mm:ss.SSSSSSxxx", PrimType.Time.toString(), this)
- case _ => new java.math.BigDecimal(facet)
- }
- }
-
- private def checkValueSpaceFacetRange(
- localFacet: String,
- facetType: Facet.Type
- ): java.math.BigDecimal = {
- // Necessary for min/max Inclusive/Exclusive Facets
-
- // Perform conversions once
- val theLocalFacet = convertFacetToBigDecimal(localFacet)
-
- facetType match {
- case Facet.maxExclusive | Facet.maxInclusive | Facet.minExclusive | Facet.minInclusive |
- Facet.enumeration => {
- // Here we're just doing range checking for the
- // specified primitive type
- primType match {
- case PrimType.Int => {
- if (!isFacetInIntRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of Int range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.Byte => {
- if (!isFacetInByteRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of Byte range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.Short => {
- if (!isFacetInShortRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of Short range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.Long => {
- if (!isFacetInLongRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of Long range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.Integer => {
- // Unbounded integer
- if (!isFacetInIntegerRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of Integer range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.UnsignedInt => {
- if (!isFacetInUnsignedIntRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of unsigned int range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.UnsignedByte => {
- if (!isFacetInUnsignedByteRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of unsigned byte range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.UnsignedShort => {
- if (!isFacetInUnsignedShortRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of unsigned short range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.UnsignedLong => {
- if (!isFacetInUnsignedLongRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of unsigned long range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.Double => {
- if (!isFacetInDoubleRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of Double range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.Float => {
- if (!isFacetInFloatRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of Float range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.NonNegativeInteger => {
- // Unsigned Unbounded Integer
- if (!isFacetInNonNegativeIntegerRange(theLocalFacet)) {
- SDE(
- "%s facet value (%s) was found to be outside of NonNegativeInteger range.",
- facetType,
- localFacet
- )
- }
- }
- case PrimType.Decimal => { /* Nothing to do here */ }
- case PrimType.DateTime => { /* Nothing to do here */ }
- case PrimType.Date => { /* Nothing to do here */ }
- case PrimType.Time => { /* Nothing to do here */ }
- case PrimType.Boolean => notYetImplemented("checkValueSpaceFacetRange - Boolean")
- case PrimType.HexBinary => { /* Nothing to do here */ }
- case PrimType.String => { /* Nothing to do here */ }
- case _ =>
- Assert.usageError(
- "checkValueSpaceFacetRange - Unrecognized primitive type: " + primType.name
- )
- }
- }
- case _ => { /* Nothing to do */ }
- }
- theLocalFacet
- }
-
- private def checkValueSpaceFacetRange(
- localFacet: String,
- remoteFacet: String,
- facetType: Facet.Type
- ): (java.math.BigDecimal, java.math.BigDecimal) = {
- // Neccessary for min/max Inclusive/Exclusive Facets
-
- // TODO: I think the performance here can be improved.
- //
- // Consider storing the remoteBaseFacets as their actual evaluated values
- // rather than just as String. This would prevent us from having to perform
- // the checkValueSpaceFacetRange on the remoteFacet here as it would've already
- // been done in the base. --TRW
-
- // Perform conversions once
- val theRemoteFacet = checkValueSpaceFacetRange(remoteFacet, facetType)
- val theLocalFacet = checkValueSpaceFacetRange(localFacet, facetType)
-
- (theLocalFacet, theRemoteFacet)
- }
-
- private def isFacetInByteRange(facet: java.math.BigDecimal): Boolean =
- self.isInByteRange(facet)
-
- private def isFacetInShortRange(facet: java.math.BigDecimal): Boolean =
- self.isInShortRange(facet)
- private def isFacetInIntRange(facet: java.math.BigDecimal): Boolean = self.isInIntRange(facet)
- private def isFacetInIntegerRange(facet: java.math.BigDecimal): Boolean =
- self.isInIntegerRange(facet)
- private def isFacetInLongRange(facet: java.math.BigDecimal): Boolean =
- self.isInLongRange(facet)
- private def isFacetInDoubleRange(facet: java.math.BigDecimal): Boolean =
- self.isInDoubleRange(facet)
- private def isFacetInFloatRange(facet: java.math.BigDecimal): Boolean =
- self.isInFloatRange(facet)
- // private def isFacetInDecimalRange(facet: java.math.BigDecimal): Boolean = {
- // BigDecimal is unbounded? So nothing outside of its range?
- // true
- // }
- // private def isFacetInNegativeIntegerRange(facet: java.math.BigDecimal): Boolean = self.isInNonNegativeIntegerRange(facet)
- private def isFacetInNonNegativeIntegerRange(facet: java.math.BigDecimal): Boolean =
- self.isInNonNegativeIntegerRange(facet)
- private def isFacetInUnsignedLongRange(facet: java.math.BigDecimal): Boolean =
- isInUnsignedLongRange(facet)
-
- private def isFacetInUnsignedIntRange(facet: java.math.BigDecimal): Boolean =
- isInUnsignedIntRange(facet)
-
- private def isFacetInUnsignedShortRange(facet: java.math.BigDecimal): Boolean =
- isInUnsignedShortRange(facet)
-
- private def isFacetInUnsignedByteRange(facet: java.math.BigDecimal): Boolean =
- isInUnsignedByteRange(facet)
-
- protected def doNumericFacetNarrowing(
- localFacet: String,
- remoteFacet: String,
- facetType: Facet.Type
- ) = {
- // Assumes both local and remote facets exist
- // Only for Numeric facets
- //
- // Can likely do narrowing checks here
- //
- // BigInt use compareTo
- // a negative number, zero, or a positive number as this BigInteger is numerically less than,
- // equal to, or greater than o, which must be a BigInteger.
- facetType match {
- case Facet.length | Facet.minLength | Facet.maxLength | Facet.fractionDigits => {
- // Non-negative Integers. BigInt
- narrowNonNegativeFacets(localFacet, remoteFacet, facetType)
- }
- case Facet.minInclusive | Facet.maxInclusive | Facet.minExclusive |
- Facet.maxExclusive => {
- // In value-space of base type. BigDecimal?
- narrowValueSpaceFacets(localFacet, remoteFacet, facetType)
- }
- case Facet.totalDigits => {
- // Positive Integer (value greater than 0). BigInt
- narrowPositiveIntegerFacets(localFacet, remoteFacet, facetType)
- }
- case _ =>
- Assert.usageError("Call to 'doNumericFacetNarrowing' only valid for Numeric Facets.")
- }
- }
-
- private def getLocalValue(theType: Facet.Type) = {
- val res = localBaseFacets.filter { case (f, v) => f == theType }
- if (res.length > 0) {
- val (_, theFacetValue) = res(0)
- theFacetValue
- } else ""
- }
-
- protected def getCombinedValue(theType: Facet.Type) = {
- val lValue = getLocalValue(theType)
- val rValue = getRemoteFacetValue(theType)
- val cValue = getFacetValue(lValue, rValue, theType, true)
- cValue
- }
-
- protected lazy val getCombinedValueEnum = {
- val lValue = getLocalValue(Facet.enumeration)
- val rValue = getRemoteFacetValue(Facet.enumeration)
- lValue.foreach(e => {
- if (rValue.length() > 0 && !rValue.contains(e))
- SDE("Local enumerations must be a subset of base enumerations.")
- })
- if (lValue.length() > 0) { lValue }
- else { rValue }
- }
-
+ combinedBaseFacets
+ .collectFirst { case f: FacetFractionDigits => f }
+ .map(f => new java.math.BigDecimal(f.bigIntValue))
+ .getOrElse(SDE("The fractionDigits facet was not found."))
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/RestrictionUnion.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/RestrictionUnion.scala
index 14f0025c58..7af4c1f694 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/RestrictionUnion.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/RestrictionUnion.scala
@@ -24,18 +24,11 @@ import scala.xml.Node
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.exceptions.ThrowsSDE
-import org.apache.daffodil.lib.exceptions.UnsuppressableException
import org.apache.daffodil.lib.xml.QName
import org.apache.daffodil.lib.xml.RefQName
import org.apache.daffodil.runtime1.dpath.NodeInfo
import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
import org.apache.daffodil.runtime1.dsom.*
-import org.apache.daffodil.runtime1.dsom.FacetTypes.ElemFacets
-import org.apache.daffodil.runtime1.dsom.FacetTypes.FacetValue
-
-import com.ibm.icu.text.SimpleDateFormat
-import com.ibm.icu.util.GregorianCalendar
-import com.ibm.icu.util.TimeZone
object Restriction {
def apply(xmlArg: Node, simpleTypeDef: SimpleTypeDefBase) = {
@@ -48,12 +41,165 @@ object Restriction {
/**
* A schema component for simple type restrictions
*/
+
final class Restriction private (xmlArg: Node, val simpleTypeDef: SimpleTypeDefBase)
extends SchemaComponentImpl(xmlArg, simpleTypeDef)
with Facets
with NestingLexicalMixin
with TypeChecks {
+ private def checkValueSpaceFacetRange(
+ localFacet: java.math.BigDecimal,
+ facetType: Facet.Type
+ ): Unit = {
+ // Necessary for min/max Inclusive/Exclusive Facets
+ facetType match {
+ case Facet.maxExclusive | Facet.maxInclusive | Facet.minExclusive | Facet.minInclusive |
+ Facet.enumeration => {
+ // Here we're just doing range checking for the
+ // specified primitive type
+ primType match {
+ case PrimType.Int => {
+ if (!isInIntRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of Int range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.Byte => {
+ if (!isInByteRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of Byte range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.Short => {
+ if (!isInShortRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of Short range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.Long => {
+ if (!isInLongRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of Long range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.Integer => {
+ // Unbounded integer
+ if (!isInIntegerRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of Integer range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.UnsignedInt => {
+ if (!isInUnsignedIntRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of unsigned int range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.UnsignedByte => {
+ if (!isInUnsignedByteRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of unsigned byte range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.UnsignedShort => {
+ if (!isInUnsignedShortRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of unsigned short range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.UnsignedLong => {
+ if (!isInUnsignedLongRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of unsigned long range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.Double => {
+ if (!isInDoubleRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of Double range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.Float => {
+ if (!isInFloatRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of Float range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.NonNegativeInteger => {
+ // Unsigned Unbounded Integer
+ if (!isInNonNegativeIntegerRange(localFacet)) {
+ SDE(
+ "%s facet value (%s) was found to be outside of NonNegativeInteger range.",
+ facetType,
+ localFacet
+ )
+ }
+ }
+ case PrimType.Decimal => {
+ /* Nothing to do here */
+ }
+ case PrimType.DateTime => {
+ /* Nothing to do here */
+ }
+ case PrimType.Date => {
+ /* Nothing to do here */
+ }
+ case PrimType.Time => {
+ /* Nothing to do here */
+ }
+ case PrimType.Boolean => notYetImplemented("checkValueSpaceFacetRange - Boolean")
+ case PrimType.HexBinary => {
+ /* Nothing to do here */
+ }
+ case PrimType.String => {
+ /* Nothing to do here */
+ }
+ case _ =>
+ Assert.usageError(
+ "checkValueSpaceFacetRange - Unrecognized primitive type: " + primType.name
+ )
+ }
+ }
+ case _ => {
+ /* Nothing to do */
+ }
+ }
+ }
+
protected[dsom] override def initialize() = {
super.initialize()
optUnion
@@ -121,92 +267,139 @@ final class Restriction private (xmlArg: Node, val simpleTypeDef: SimpleTypeDefB
}
lazy val localBaseFacets: ElemFacets = {
- val myFacets: Queue[FacetValue] = Queue.empty // val not var - it's a mutable collection
- if (localPatternValue.length > 0) { myFacets.enqueue((Facet.pattern, localPatternValue)) }
- if (localLengthValue.length > 0) {
- myFacets.enqueue((Facet.length, localLengthValue))
- }
- if (localMinLengthValue.length > 0) {
- myFacets.enqueue((Facet.minLength, localMinLengthValue))
- }
- if (localMaxLengthValue.length > 0) {
- myFacets.enqueue((Facet.maxLength, localMaxLengthValue))
+ // val not var - it's a mutable collection
+ val myFacets: Queue[FacetOrdered] = Queue.empty[FacetOrdered]
+
+ if (localPatternValue.nonEmpty)
+ myFacets.enqueue(new FacetPattern(localPatternValue))
+ if (localLengthValue.nonEmpty)
+ myFacets.enqueue(new FacetLength(localLengthValue, this))
+ if (localMinLengthValue.nonEmpty)
+ myFacets.enqueue(new FacetMinLength(localMinLengthValue, this))
+ if (localMaxLengthValue.nonEmpty)
+ myFacets.enqueue(new FacetMaxLength(localMaxLengthValue, this))
+ if (localMinInclusiveValue.nonEmpty) {
+ val facet = new FacetMinInclusive(localMinInclusiveValue, primType, this)
+ checkValueSpaceFacetRange(facet.bigDecimalValue, Facet.minInclusive)
+ myFacets.enqueue(facet)
}
- if (localMinInclusiveValue.length > 0) {
- myFacets.enqueue((Facet.minInclusive, localMinInclusiveValue))
+ if (localMaxInclusiveValue.nonEmpty) {
+ val facet = new FacetMaxInclusive(localMaxInclusiveValue, primType, this)
+ checkValueSpaceFacetRange(facet.bigDecimalValue, Facet.maxInclusive)
+ myFacets.enqueue(facet)
}
- if (localMaxInclusiveValue.length > 0) {
- myFacets.enqueue((Facet.maxInclusive, localMaxInclusiveValue))
+ if (localMinExclusiveValue.nonEmpty) {
+ val facet = new FacetMinExclusive(localMinExclusiveValue, primType, this)
+ checkValueSpaceFacetRange(facet.bigDecimalValue, Facet.minExclusive)
+ myFacets.enqueue(facet)
}
- if (localMinExclusiveValue.length > 0) {
- myFacets.enqueue((Facet.minExclusive, localMinExclusiveValue))
- }
- if (localMaxExclusiveValue.length > 0) {
- myFacets.enqueue((Facet.maxExclusive, localMaxExclusiveValue))
- }
- if (localTotalDigitsValue.length > 0) {
- myFacets.enqueue((Facet.totalDigits, localTotalDigitsValue))
- }
- if (localFractionDigitsValue.length > 0) {
- myFacets.enqueue((Facet.fractionDigits, localFractionDigitsValue))
- }
- if (localEnumerationValue.length > 0) {
- myFacets.enqueue((Facet.enumeration, localEnumerationValue))
+ if (localMaxExclusiveValue.nonEmpty) {
+ val facet = new FacetMaxExclusive(localMaxExclusiveValue, primType, this)
+ checkValueSpaceFacetRange(facet.bigDecimalValue, Facet.maxExclusive)
+ myFacets.enqueue(facet)
}
+ if (localTotalDigitsValue.nonEmpty)
+ myFacets.enqueue(new FacetTotalDigits(localTotalDigitsValue, this))
+ if (localFractionDigitsValue.nonEmpty)
+ myFacets.enqueue(new FacetFractionDigits(localFractionDigitsValue, this))
+ if (localEnumerationValue.nonEmpty)
+ myFacets.enqueue(new FacetEnumeration(localEnumerationValue))
- val res: ElemFacets = myFacets.toSeq
- res
+ myFacets.toSeq
}
- final lazy val combinedBaseFacets: Seq[FacetValue] = {
- val combined: Queue[FacetValue] = Queue.empty
+ final lazy val combinedBaseFacets: ElemFacets = {
+ val combined: Queue[FacetOrdered] = Queue.empty
+ // enumeration - string based, combine local and remote
if (hasEnumeration) {
- val enumVal = getCombinedValueEnum
- combined.enqueue((Facet.enumeration, enumVal))
+ val lValue = localBaseFacets.collectFirst { case f: FacetEnumeration => f }
+ val rValue = remoteBaseFacets.collectFirst { case f: FacetEnumeration => f }
+ val result = (lValue, rValue) match {
+ case (Some(l), Some(r)) =>
+ // local enumerations must be subset of base enumerations
+ l.xmlValue.foreach(e => {
+ if (r.xmlValue.nonEmpty && !r.xmlValue.contains(e))
+ SDE("Local enumerations must be a subset of base enumerations.")
+ })
+ l // local wins if it exists
+ case (Some(l), None) => l
+ case (None, Some(r)) => r
+ case (None, None) => Assert.impossibleCase
+ }
+ combined.enqueue(result)
}
+ // Patterns within a type are OR'd, collect all
+ // per http://www.xfront.com/XML-Schema-library/papers/Algorithm-for-Merging-a-simpleType-Dependency-Chain.pdf
if (hasPattern) {
- val lPattern = localBaseFacets.filter { case (f, v) => f == Facet.pattern }
- val rPattern = remoteBaseFacets.filter { case (f, v) => f == Facet.pattern }
- val cPattern = lPattern ++: rPattern
- cPattern.foreach(x => combined.enqueue(x))
+ val lPattern = localBaseFacets.collect { case f: FacetPattern => f }
+ val rPattern = remoteBaseFacets.collect { case f: FacetPattern => f }
+ (lPattern ++: rPattern).foreach(combined.enqueue)
}
if (hasLength) {
- val cValue = getCombinedValue(Facet.length)
- combined.enqueue((Facet.length, cValue.toString()))
+ val facets =
+ localBaseFacets.collect { case f: FacetLength => f } ++
+ remoteBaseFacets.collect { case f: FacetLength => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
}
if (hasMinLength) {
- val cValue = getCombinedValue(Facet.minLength)
- combined.enqueue((Facet.minLength, cValue.toString()))
+ val facets =
+ localBaseFacets.collect { case f: FacetMinLength => f } ++
+ remoteBaseFacets.collect { case f: FacetMinLength => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
}
if (hasMaxLength) {
- val cValue = getCombinedValue(Facet.maxLength)
- combined.enqueue((Facet.maxLength, cValue.toString()))
- }
- if (hasMaxInclusive) {
- val cValue = getCombinedValue(Facet.maxInclusive)
- combined.enqueue((Facet.maxInclusive, cValue.toString()))
- }
- if (hasMaxExclusive) {
- val cValue = getCombinedValue(Facet.maxExclusive)
- combined.enqueue((Facet.maxExclusive, cValue.toString()))
+ val facets =
+ localBaseFacets.collect { case f: FacetMaxLength => f } ++
+ remoteBaseFacets.collect { case f: FacetMaxLength => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
}
if (hasMinInclusive) {
- val cValue = getCombinedValue(Facet.minInclusive)
- combined.enqueue((Facet.minInclusive, cValue.toString()))
+ val facets =
+ localBaseFacets.collect { case f: FacetMinInclusive => f } ++
+ remoteBaseFacets.collect { case f: FacetMinInclusive => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
+ }
+ if (hasMaxInclusive) {
+ val facets =
+ localBaseFacets.collect { case f: FacetMaxInclusive => f } ++
+ remoteBaseFacets.collect { case f: FacetMaxInclusive => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
}
if (hasMinExclusive) {
- val cValue = getCombinedValue(Facet.minExclusive)
- combined.enqueue((Facet.minExclusive, cValue.toString()))
+ val facets =
+ localBaseFacets.collect { case f: FacetMinExclusive => f } ++
+ remoteBaseFacets.collect { case f: FacetMinExclusive => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
+ }
+ if (hasMaxExclusive) {
+ val facets =
+ localBaseFacets.collect { case f: FacetMaxExclusive => f } ++
+ remoteBaseFacets.collect { case f: FacetMaxExclusive => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
}
if (hasTotalDigits) {
- val cValue = getCombinedValue(Facet.totalDigits)
- combined.enqueue((Facet.totalDigits, cValue.toString()))
+ val facets =
+ localBaseFacets.collect { case f: FacetTotalDigits => f } ++
+ remoteBaseFacets.collect { case f: FacetTotalDigits => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
}
if (hasFractionDigits) {
- val cValue = getCombinedValue(Facet.fractionDigits)
- combined.enqueue((Facet.fractionDigits, cValue.toString()))
+ val facets =
+ localBaseFacets.collect { case f: FacetFractionDigits => f } ++
+ remoteBaseFacets.collect { case f: FacetFractionDigits => f }
+ val narrowed = facets.reduce[FacetOrdered]((l, r) => l.narrow(r))
+ combined.enqueue(narrowed)
}
+
combined.toSeq
}
@@ -306,145 +499,6 @@ final class Union private (val xmlArg: Node, simpleTypeDef: SimpleTypeDefBase)
}
sealed trait TypeChecks { self: Restriction =>
- protected def dateToBigDecimal(
- date: String,
- format: String,
- dateType: String,
- context: ThrowsSDE
- ): JBigDecimal = {
- val df = new SimpleDateFormat(format)
- df.setCalendar(new GregorianCalendar())
- df.setTimeZone(TimeZone.GMT_ZONE)
- val bd =
- try {
- val dt = df.parse(date)
- new JBigDecimal(dt.getTime())
- } catch {
- case s: scala.util.control.ControlThrowable => throw s
- case u: UnsuppressableException => throw u
- case e1: Exception => {
- try {
- // Could already be a BigDecimal
- new JBigDecimal(date)
- } catch {
- case s: scala.util.control.ControlThrowable => throw s
- case u: UnsuppressableException => throw u
- case e2: Exception =>
- context.SDE(
- "Failed to parse (%s) to %s (%s) due to %s (after %s).",
- date,
- dateType,
- format,
- e2.getMessage(),
- e1.getMessage()
- )
- }
- }
- }
- bd
- }
-
- private def convertStringToBigDecimal(
- value: String,
- primType: PrimType,
- context: ThrowsSDE
- ): JBigDecimal = {
- primType match {
- case PrimType.DateTime =>
- dateToBigDecimal(
- value,
- "uuuu-MM-dd'T'HH:mm:ss.SSSSSSxxx",
- PrimType.DateTime.toString(),
- context
- )
- case PrimType.Date =>
- dateToBigDecimal(value, "uuuu-MM-ddxxx", PrimType.Date.toString(), context)
- case PrimType.Time =>
- dateToBigDecimal(value, "HH:mm:ss.SSSSSSxxx", PrimType.Time.toString(), context)
- case _ => new JBigDecimal(value)
- }
- }
-
- def checkRangeReturnsValue(
- value: String,
- primType: PrimType,
- theContext: ThrowsSDE
- ): (Boolean, Option[JBigDecimal]) = {
- // EmptyString is only valid for hexBinary and String
- if ((value == null | value.length() == 0)) {
- return primType match {
- case PrimType.HexBinary | PrimType.String => (true, None)
- case _ => (false, None)
- }
- }
-
- // Don't need to range check String or HexBinary or blobs.
- // no point attempting a conversion to BigDecimal so
- // return early here.
- primType match {
- case PrimType.String | PrimType.HexBinary | PrimType.AnyURI => return (true, None)
- case _ => /* Continue on below */
- }
-
- // Check Boolean, and the Numeric types.
- (value.toLowerCase(), primType) match {
- case ("true", PrimType.Boolean) => (true, Some(JBigDecimal.ONE))
- case ("false", PrimType.Boolean) => (true, Some(JBigDecimal.ZERO))
- case (x, PrimType.Boolean) =>
- theContext.SDE("%s is not a valid Boolean value. Expected 'true' or 'false'.", x)
- case (_, _) => {
- // Perform conversions once
- val theValue = convertStringToBigDecimal(value, primType, theContext)
-
- // Here we're just doing range checking for the
- // specified primitive type
- val res: Boolean = primType match {
- case PrimType.Int => isInIntRange(theValue)
- case PrimType.Byte => isInByteRange(theValue)
- case PrimType.Short => isInShortRange(theValue)
- case PrimType.Long => isInLongRange(theValue)
- case PrimType.Integer => true // Unbounded Integer
- case PrimType.UnsignedInt => isInUnsignedIntRange(theValue)
- case PrimType.UnsignedByte => isInUnsignedByteRange(theValue)
- case PrimType.UnsignedShort => isInUnsignedShortRange(theValue)
- case PrimType.UnsignedLong => isInUnsignedLongRange(theValue)
- case PrimType.Double => isInDoubleRange(theValue)
- case PrimType.Float => isInFloatRange(theValue)
- case PrimType.DateTime => true
- case PrimType.Date => true
- case PrimType.Time => true
- case PrimType.Boolean => Assert.impossibleCase // Handled earlier, shouldn't get here
- case PrimType.Decimal => true // Unbounded Decimal
- case PrimType.HexBinary =>
- Assert.impossibleCase // Handled earlier, shouldn't get here
- case PrimType.String => Assert.impossibleCase // Handled earlier, shouldn't get here
- case PrimType.AnyURI => Assert.impossibleCase // Handled earlier, shouldn't get here
- case PrimType.NonNegativeInteger => isInNonNegativeIntegerRange(theValue)
- }
- val isValueWhole = {
- val IsWholeRegex = """^[^.]*(\.0*)?$""".r
- value match {
- case IsWholeRegex(_) => true
- case _ => false
- }
- }
- primType match {
- case PrimType.Int | PrimType.Byte | PrimType.Short | PrimType.Long |
- PrimType.Integer | PrimType.UnsignedInt | PrimType.UnsignedByte |
- PrimType.UnsignedShort | PrimType.UnsignedLong =>
- if (!isValueWhole)
- theContext.SDE("checkRange - Value (%s) must be a whole number.", value)
- case _ => // OK
- }
- (res, Some(theValue))
- }
- }
- }
-
- def checkRange(value: String, primType: PrimType, theContext: ThrowsSDE): Boolean = {
- val (boolResult, _) = checkRangeReturnsValue(value, primType, theContext)
- boolResult
- }
protected def isNumInRange(num: JBigDecimal, min: JBigDecimal, max: JBigDecimal): Boolean = {
val checkMin = num.compareTo(min)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesDateTime.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesDateTime.scala
index af70bdd3c0..46d2d6885f 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesDateTime.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/primitives/PrimitivesDateTime.scala
@@ -17,7 +17,6 @@
package org.apache.daffodil.core.grammar.primitives
-import java.text.ParsePosition
import scala.Boolean
import org.apache.daffodil.core.dsom.ElementBase
@@ -29,6 +28,8 @@ import org.apache.daffodil.lib.schema.annotation.props.gen.CalendarCheckPolicy
import org.apache.daffodil.lib.schema.annotation.props.gen.CalendarFirstDayOfWeek
import org.apache.daffodil.lib.schema.annotation.props.gen.CalendarPatternKind
import org.apache.daffodil.lib.schema.annotation.props.gen.Representation
+import org.apache.daffodil.runtime1.dpath.InvalidPrimitiveDataException
+import org.apache.daffodil.runtime1.dpath.NodeInfo
import org.apache.daffodil.runtime1.processors.CalendarEv
import org.apache.daffodil.runtime1.processors.CalendarLanguageEv
import org.apache.daffodil.runtime1.processors.DateTimeFormatterEv
@@ -37,10 +38,8 @@ import org.apache.daffodil.runtime1.processors.parsers.ConvertTextCalendarParser
import org.apache.daffodil.unparsers.runtime1.ConvertBinaryCalendarSecMilliUnparser
import org.apache.daffodil.unparsers.runtime1.ConvertTextCalendarUnparser
-import com.ibm.icu.text.SimpleDateFormat
import com.ibm.icu.util.Calendar
import com.ibm.icu.util.TimeZone
-import com.ibm.icu.util.ULocale
abstract class ConvertCalendarPrimBase(e: ElementBase, guard: Boolean)
extends Terminal(e, guard) {
@@ -54,7 +53,6 @@ abstract class ConvertCalendarPrimBase(e: ElementBase, guard: Boolean)
abstract class ConvertTextCalendarPrimBase(e: ElementBase, guard: Boolean)
extends ConvertCalendarPrimBase(e, guard) {
- protected def infosetPattern: String
protected def implicitPattern: String
protected def validFormatCharacters: Seq[Char]
@@ -241,8 +239,7 @@ abstract class ConvertTextCalendarPrimBase(e: ElementBase, guard: Boolean)
case class ConvertTextDatePrim(e: ElementBase) extends ConvertTextCalendarPrimBase(e, true) {
protected override val xsdType = "date"
protected override val prettyType = "Date"
- protected override val infosetPattern = "uuuu-MM-ddxxx"
- protected override val implicitPattern = "uuuu-MM-dd"
+ protected override val implicitPattern = "yyyy-MM-dd"
protected override val validFormatCharacters =
if (e.representation == Representation.Binary) {
"dDeFMuwWyY".toSeq
@@ -254,7 +251,6 @@ case class ConvertTextDatePrim(e: ElementBase) extends ConvertTextCalendarPrimBa
case class ConvertTextTimePrim(e: ElementBase) extends ConvertTextCalendarPrimBase(e, true) {
protected override val xsdType = "time"
protected override val prettyType = "Time"
- protected override val infosetPattern = "HH:mm:ss.SSSSSSxxx"
protected override val implicitPattern = "HH:mm:ssZ"
protected override val validFormatCharacters =
if (e.representation == Representation.Binary) {
@@ -268,8 +264,7 @@ case class ConvertTextDateTimePrim(e: ElementBase)
extends ConvertTextCalendarPrimBase(e, true) {
protected override val xsdType = "dateTime"
protected override val prettyType = "DateTime"
- protected override val infosetPattern = "uuuu-MM-dd'T'HH:mm:ss.SSSSSSxxx"
- protected override val implicitPattern = "uuuu-MM-dd'T'HH:mm:ss"
+ protected override val implicitPattern = "yyyy-MM-dd'T'HH:mm:ss"
protected override val validFormatCharacters =
if (e.representation == Representation.Binary) {
"dDeFhHkKmMsSuwWyY".toSeq
@@ -284,36 +279,18 @@ case class ConvertBinaryDateTimeSecMilliPrim(e: ElementBase, lengthInBits: Long)
protected override val prettyType = "DateTime"
lazy val epochCalendar: Calendar = {
- val cal = Calendar.getInstance
- cal.clear()
- cal.setLenient(false)
-
- val sdfWithTZ = new SimpleDateFormat("uuuu-MM-dd'T'HH:mm:ssZZZZ", ULocale.ENGLISH)
- var pos = new ParsePosition(0)
- sdfWithTZ.parse(e.binaryCalendarEpoch, cal, pos)
-
- if (pos.getIndex != e.binaryCalendarEpoch.length || pos.getErrorIndex >= 0) {
- // binaryCalendarEpoch didn't match the first format with timezone, so try without
- cal.clear()
- pos = new ParsePosition(0)
- val sdf = new SimpleDateFormat("uuuu-MM-dd'T'HH:mm:ss", ULocale.ENGLISH)
- cal.setTimeZone(TimeZone.UNKNOWN_ZONE)
- sdf.parse(e.binaryCalendarEpoch, cal, pos)
-
- if (pos.getIndex != e.binaryCalendarEpoch.length || pos.getErrorIndex >= 0) {
- SDE(
- "Failed to parse binaryCalendarEpoch - Format must match the pattern 'uuuu-MM-dd'T'HH:mm:ss' or 'uuuu-MM-dd'T'HH:mm:ssZZZZ'"
- )
- }
- }
-
- try {
- cal.getTime
- } catch {
- case e: IllegalArgumentException => {
- SDE("Failed to parse binaryCalendarEpoch: %s.", e.getMessage())
+ val dfdlDateTime =
+ try {
+ NodeInfo.DateTime.fromXMLString(e.binaryCalendarEpoch).getCalendar
+ } catch {
+ case exception: InvalidPrimitiveDataException =>
+ SDE(
+ "Failed to parse binaryCalendarEpoch '%s' as an xs:dateTime.",
+ e.binaryCalendarEpoch,
+ exception.getMessage
+ )
}
- }
+ val cal = dfdlDateTime.calendar
cal
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/lib/calendar/DFDLCalendar.scala b/daffodil-core/src/main/scala/org/apache/daffodil/lib/calendar/DFDLCalendar.scala
index 2dd24555e6..1f7493da71 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/lib/calendar/DFDLCalendar.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/lib/calendar/DFDLCalendar.scala
@@ -22,6 +22,7 @@ import java.math.BigDecimal as JBigDecimal
import org.apache.daffodil.lib.exceptions.Assert
import com.ibm.icu.util.Calendar
+import com.ibm.icu.util.GregorianCalendar
import com.ibm.icu.util.TimeZone
object DFDLCalendarOrder extends Enumeration {
@@ -29,7 +30,8 @@ object DFDLCalendarOrder extends Enumeration {
val P_LESS_THAN_Q, P_GREATER_THAN_Q, P_EQUAL_Q, P_NOT_EQUAL_Q = Value
val fieldsForComparison = Array(
- Calendar.EXTENDED_YEAR,
+ Calendar.ERA,
+ Calendar.YEAR,
Calendar.MONTH,
Calendar.DAY_OF_MONTH,
Calendar.HOUR_OF_DAY,
@@ -242,7 +244,7 @@ case class DFDLTime(calendar: Calendar, override val hasTimeZone: Boolean)
// values and achieve the same effect, but optimized
//
timeCal.clear(Calendar.YEAR)
- timeCal.clear(Calendar.EXTENDED_YEAR)
+ timeCal.clear(Calendar.ERA)
timeCal.clear(Calendar.MONTH)
timeCal.clear(Calendar.DAY_OF_MONTH)
DFDLDateTime(timeCal, hasTimeZone)
@@ -400,5 +402,16 @@ abstract class DFDLCalendar extends OrderedCalendar with Serializable {
def hasTimeZone: Boolean
+ /**
+ * Returns the XSD 1.0 signed year of a calendar. YEAR is the positive
+ * year-of-era; a BCE year is returned negated.
+ *
+ * @return signed year (e.g. 1 BCE -> -1, 1 CE -> 1)
+ */
+ final def signedYear: Int = {
+ val y = calendar.get(Calendar.YEAR)
+ if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) -y else y
+ }
+
final def toJBigDecimal: JBigDecimal = new JBigDecimal(calendar.getTimeInMillis())
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/lib/calendar/DFDLCalendarConversion.scala b/daffodil-core/src/main/scala/org/apache/daffodil/lib/calendar/DFDLCalendarConversion.scala
index d03de5bdd7..e5181084b4 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/lib/calendar/DFDLCalendarConversion.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/lib/calendar/DFDLCalendarConversion.scala
@@ -20,6 +20,7 @@ package org.apache.daffodil.lib.calendar
import java.lang.Integer
import com.ibm.icu.util.Calendar
+import com.ibm.icu.util.GregorianCalendar
import com.ibm.icu.util.SimpleTimeZone
import com.ibm.icu.util.TimeZone
@@ -51,7 +52,7 @@ object DFDLCalendarConversion {
def datePartToXMLString(dfdlcal: DFDLCalendar): String = {
val calendar = dfdlcal.calendar
- val y = calendar.get(Calendar.EXTENDED_YEAR)
+ val y = dfdlcal.signedYear
val m = calendar.get(Calendar.MONTH) + 1
val d = calendar.get(Calendar.DAY_OF_MONTH)
@@ -61,14 +62,16 @@ object DFDLCalendarConversion {
}
/**
- * Parses a string that begins with the pattern "uuuu-MM-dd" and sets the
+ * Parses a string that begins with the pattern "yyyy-MM-dd" and sets the
* appropriate values in the calendar. The year part may be 1 or more digits
- * (including an optional sign) and must be a valid postive or negative
- * integer. The month and day parts must be zero padded digits.
+ * (including an optional leading minus sign for BCE) and must be a valid
+ * integer in XSD 1.0 numbering (no year zero; -0001 is 1 BCE). A negative
+ * year is stored as ERA=BC with a positive YEAR. The month and day parts must
+ * be zero padded digits.
*
* If the pattern is not followed, an IllegalArgumentException is thrown.
*
- * @return if the date part was succesfully parsed, returns a substring of
+ * @return if the date part was successfully parsed, returns a substring of
* the remaining characters
*/
def datePartFromXMLString(string: String, calendar: Calendar): String = {
@@ -95,7 +98,22 @@ object DFDLCalendarConversion {
val d = string.substring(endYear + 4, endYear + 6)
try {
- calendar.set(Calendar.EXTENDED_YEAR, Integer.parseInt(y))
+ // Use YEAR + ERA rather than EXTENDED_YEAR to match XSD 1.0 numbering:
+ // a negative lexical year is BCE, stored as ERA=BC with positive YEAR.
+ val yInt = Integer.parseInt(y)
+
+ // calendar.getTimeInMillis() in fromXMLString would eventually catch year 0
+ // and call invalidCalendar anyway — explicitly rejecting it here is a bit more
+ // efficient as it skips all the calendar building steps and fails immediately.
+ // In addition, we do not have to rely on downstream validation.
+ if (yInt == 0) invalidValue
+ else if (yInt < 0) {
+ calendar.set(Calendar.ERA, GregorianCalendar.BC)
+ calendar.set(Calendar.YEAR, -yInt)
+ } else {
+ calendar.set(Calendar.ERA, GregorianCalendar.AD)
+ calendar.set(Calendar.YEAR, yInt)
+ }
calendar.set(Calendar.MONTH, Integer.parseInt(m) - 1)
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(d))
} catch {
@@ -298,12 +316,17 @@ object DFDLDateTimeConversion extends DFDLCalendarConversion {
/**
* Supported patterns:
- * uuuu-MM-dd'T'HH:mm:ss.SSSSSSxxxxx
- * uuuu-MM-dd'T'HH:mm:ss.SSSSSS
- * uuuu-MM-dd'T'HH:mm:ssxxxxx
- * uuuu-MM-dd'T'HH:mm:ss
- * uuuu-MM-ddxxxxx
- * uuuu-MM-dd
+ * [-]yyyy-MM-dd'T'HH:mm:ss.SSSSSSxxxxx
+ * [-]yyyy-MM-dd'T'HH:mm:ss.SSSSSS
+ * [-]yyyy-MM-dd'T'HH:mm:ssxxxxx
+ * [-]yyyy-MM-dd'T'HH:mm:ss
+ * [-]yyyy-MM-ddxxxxx
+ * [-]yyyy-MM-dd
+ *
+ * Negative (BCE) years are also supported: the year may carry a leading minus
+ * (e.g. -0001 = 1 BCE). These are XSD 1.0 lexical strings parsed directly, not
+ * dfdl:calendarPattern tokens, so the minus is part of the lexical year rather
+ * than what the yyyy-MM-dd calendar pattern would generally imply.
*/
def fromXMLString(string: String): DFDLDateTime = {
val calendar = emptyCalendar.clone().asInstanceOf[Calendar]
@@ -344,8 +367,8 @@ object DFDLDateConversion extends DFDLCalendarConversion {
/**
* Supported patterns:
- * uuuu-MM-ddxxxxx
- * uuuu-MM-dd
+ * yyyy-MM-ddxxxxx
+ * yyyy-MM-dd
*/
def fromXMLString(string: String): DFDLDate = {
val calendar = emptyCalendar.clone().asInstanceOf[Calendar]
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/dpath/FNFunctions.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/dpath/FNFunctions.scala
index a040c2677d..8088718f54 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/dpath/FNFunctions.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/dpath/FNFunctions.scala
@@ -983,7 +983,18 @@ abstract class FNFromTime(recipe: CompiledDPath, argType: NodeInfo.Kind)
case class FNYearFromDateTime(recipe: CompiledDPath, argType: NodeInfo.Kind)
extends FNFromDateTime(recipe, argType) {
val fieldName = "year"
- val field = Calendar.EXTENDED_YEAR
+ val field = Calendar.YEAR
+
+ override def computeValue(a: DataValuePrimitive, dstate: DState): DataValueNumber = {
+ a.getAnyRef match {
+ case dt: DFDLDateTime =>
+ JBigInt.valueOf(dt.signedYear)
+ case _ =>
+ throw new NumberFormatException(
+ "fn:" + fieldName + "-from-dateTime only accepts xs:dateTime."
+ )
+ }
+ }
}
case class FNMonthFromDateTime(recipe: CompiledDPath, argType: NodeInfo.Kind)
extends FNFromDateTime(recipe, argType) {
@@ -1052,7 +1063,16 @@ case class FNSecondsFromDateTime(recipe: CompiledDPath, argType: NodeInfo.Kind)
case class FNYearFromDate(recipe: CompiledDPath, argType: NodeInfo.Kind)
extends FNFromDate(recipe, argType) {
val fieldName = "year"
- val field = Calendar.EXTENDED_YEAR
+ val field = Calendar.YEAR
+
+ override def computeValue(a: DataValuePrimitive, dstate: DState): DataValueNumber = {
+ a.getAnyRef match {
+ case d: DFDLDate =>
+ JBigInt.valueOf(d.signedYear)
+ case _ =>
+ throw new NumberFormatException("fn:" + fieldName + "-from-date only accepts xs:date.")
+ }
+ }
}
case class FNMonthFromDate(recipe: CompiledDPath, argType: NodeInfo.Kind)
extends FNFromDate(recipe, argType) {
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/dsom/Facets1.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/dsom/Facets1.scala
index 15bf73f7dd..819b829809 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/dsom/Facets1.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/dsom/Facets1.scala
@@ -16,12 +16,19 @@
*/
package org.apache.daffodil.runtime1.dsom
+
+import java.math.BigDecimal as JBigDecimal
+import java.math.BigInteger as JBigInt
import scala.util.matching.Regex
+import org.apache.daffodil.lib.exceptions.ThrowsSDE
import org.apache.daffodil.lib.util.Enum
+import org.apache.daffodil.lib.xml.XMLUtils
+import org.apache.daffodil.runtime1.dpath.InvalidPrimitiveDataException
+import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
object Facet extends Enum {
- sealed abstract trait Type extends EnumValueType
+ sealed trait Type extends EnumValueType
case object enumeration extends Type
case object fractionDigits extends Type
case object length extends Type
@@ -36,11 +43,278 @@ object Facet extends Enum {
case object whiteSpace extends Type
}
-object FacetTypes {
- type Values = String
- type ValuesR = Regex
- type FacetValue = (Facet.Type, Values)
- type FacetValueR = (Facet.Type, ValuesR)
- type ElemFacets = Seq[FacetValue]
- type ElemFacetsR = Seq[FacetValueR]
+/**
+ * Base sealed trait for compiled XSD facet constraints.
+ *
+ * Each subclass represents a specific XSD facet, carrying both the original
+ * XML string value and, where applicable, a converted representation used for
+ * runtime validation.
+ *
+ * Previously facets were stored as `(Facet.Type, String)` tuples, which
+ * are serializable by default. Typed facet objects now require an explicit
+ * Serializable declaration otherwise a java.io.NotSerializableException
+ * will be thrown during serialization.
+ */
+sealed trait FacetOrdered extends Serializable {
+ def xmlValue: String
+
+ /**
+ * Validates this local facet against the remote base facet.
+ * Throws SDE if this facet does not correctly narrow the base.
+ * Returns this facet if valid.
+ *
+ * The default implementation is a no-op for facets where narrowing does not apply
+ * (for example, pattern and enumeration).
+ */
+ def narrow(remote: FacetOrdered): FacetOrdered = this
+}
+
+type ElemFacets = Seq[FacetOrdered]
+
+/**
+ * Base abstract class for integer facets. Parses [[xmlValue]] to [[JBigInt]]
+ * once, validating sign via [[isNonNegative]] — non-negative facets reject
+ * negative values, positive facets reject zero and negative values.
+ */
+abstract class FacetInteger(val xmlValue: String, val context: ThrowsSDE) extends FacetOrdered {
+ def facetName: String
+ def isNonNegative: Boolean
+
+ lazy val bigIntValue: JBigInt = {
+ val localFacet =
+ try { new JBigInt(xmlValue) }
+ catch {
+ case e: IllegalArgumentException =>
+ context.SDE("invalid %s facet restriction: %s", facetName, e.getMessage)
+ }
+ if (isNonNegative && localFacet.signum() == -1)
+ context.SDE("The %s facet must be a non-negative integer.", facetName)
+ else if (!isNonNegative && localFacet.signum() != 1)
+ context.SDE("The %s facet must be a positive integer.", facetName)
+ localFacet
+ }
+}
+
+/**
+ * Base abstract class for non-negative integer facets (length, minLength,
+ * maxLength, fractionDigits). Uses [[BigInteger]] for comparison.
+ */
+abstract class FacetNonNegativeInteger(xmlValue: String, context: ThrowsSDE)
+ extends FacetInteger(xmlValue, context) {
+ final val isNonNegative = true
+
+ protected def isInvalidNarrowing(remoteValue: JBigInt): Boolean
+ protected def narrowingRelation: String
+
+ override final def narrow(remote: FacetOrdered): FacetOrdered = {
+ val remoteFacet = remote.asInstanceOf[FacetNonNegativeInteger].bigIntValue
+ if (isInvalidNarrowing(remoteFacet))
+ context.SDE(
+ "SimpleTypes: The local %s (%s) was %s the base %s (%s)",
+ facetName,
+ bigIntValue,
+ narrowingRelation,
+ facetName,
+ remoteFacet
+ )
+ this
+ }
+}
+
+/**
+ * Base abstract class for value-space facets (minInclusive, maxInclusive,
+ * minExclusive, maxExclusive). Uses [[JBigDecimal]] for comparison.
+ *
+ * Range validation against the primitive type's value space is
+ * performed separately prior to storage in localBaseFacets.
+ */
+abstract class FacetValueSpace(
+ val xmlValue: String,
+ val primType: PrimType,
+ val context: ThrowsSDE
+) extends FacetOrdered {
+
+ def facetName: String
+ protected def isInvalidNarrowing(remoteValue: JBigDecimal): Boolean
+ protected def narrowingRelation: String
+
+ lazy val bigDecimalValue: JBigDecimal = {
+ try {
+ primType match {
+ case PrimType.DateTime | PrimType.Date | PrimType.Time =>
+ try {
+ primType.fromXMLString(xmlValue).getCalendar.toJBigDecimal
+ } catch {
+ case e: InvalidPrimitiveDataException =>
+ context.SDE(
+ "Failed to parse %s facet value (%s) to %s.",
+ facetName,
+ xmlValue,
+ primType.toString()
+ )
+ }
+ case _ => new JBigDecimal(xmlValue)
+ }
+ } catch {
+ case e: IllegalArgumentException =>
+ context.SDE("invalid %s facet restriction: %s", facetName, e.getMessage)
+ }
+ }
+
+ override final def narrow(remote: FacetOrdered): FacetOrdered = {
+ val remoteFacet = remote.asInstanceOf[FacetValueSpace].bigDecimalValue
+ if (isInvalidNarrowing(remoteFacet))
+ context.SDE(
+ "SimpleTypes: The local %s (%s) was %s the base %s (%s)",
+ facetName,
+ bigDecimalValue,
+ narrowingRelation,
+ facetName,
+ remoteFacet
+ )
+ this
+ }
+}
+
+/**
+ * Facet for xs:totalDigits. Narrowing requires local <= base.
+ * Inlines positive integer validation — value must be strictly
+ * greater than zero.
+ */
+final class FacetTotalDigits(xmlValue: String, context: ThrowsSDE)
+ extends FacetInteger(xmlValue, context) {
+ val facetName = "totalDigits"
+ val isNonNegative = false
+
+ override def narrow(remote: FacetOrdered): FacetOrdered = {
+ val remoteFacet = remote.asInstanceOf[FacetTotalDigits].bigIntValue
+ if (bigIntValue.compareTo(remoteFacet) > 0)
+ context.SDE(
+ "SimpleTypes: The local %s (%s) was greater than the base %s (%s)",
+ facetName,
+ bigIntValue,
+ facetName,
+ remoteFacet
+ )
+ this
+ }
+}
+
+/** Facet for xs:enumeration. */
+final class FacetEnumeration(val xmlValue: String) extends FacetOrdered
+
+/**
+ * Facet for xs:pattern.
+ *
+ * The DFDL Infoset can contain strings which hold characters
+ * that are not allowed in XML at all.
+ *
+ * In order to talk about these characters in a XSD pattern facet
+ * we use a remapping of such characters into the Unicode
+ * Private Use Area, so as to have XML-legal characters.
+ *
+ * See the section titled "XML Illegal Characters" on this web page:
+ * https://daffodil.apache.org/infoset/
+ *
+ * Before processing a regex of these characters in Daffodil's pattern facet
+ * validation, we must remap these PUA characters back to the originally
+ * intended code points, since that's what the Infoset strings will contain.
+ *
+ * Consider the character code 0xB. This is illegal in XML v1.0 documents.
+ * A DFDL Schema is an XML Schema, which is an XML document; hence, we cannot
+ * use the character with code 0xB directly, nor can we use an XML numeric
+ * character entity like for it. The character is simply disallowed in
+ * XML, including DFDL schemas. Hence, we mention 0xB by using a remapping of it
+ * to the PUA area character 0xE00B, which we express by
+ * Hence a pattern regex like "[ 0-9a-zA-Z]" will match
+ * the character with char code 0xB (remapped from E00B), as well as spaces,
+ * and alphanumeric characters.
+ *
+ * The XSD numeric character entity can be used to match ASCII NUL
+ * (char code 0).
+ *
+ * This remapping is for pattern facets, which are inside a DFDL schema,
+ * and so will not contain CR characters, since XML reading will convert those
+ * to LF. To discuss CR in this pattern we can't use `
` syntax because that
+ * turns into a CR which gets turned into a LF. Plus the pattern value is
+ * an XML attribute, the value of which gets its whitespace collapsed, all
+ * line-ending chars converted to spaces, and adjacent spaces collapsed to one.
+ *
+ * So a pattern facet must use `\r` and '\n' to describe line-endings within the pattern.
+ * And in general one must be careful about whitespace.
+ */
+final class FacetPattern(val xmlValue: String) extends FacetOrdered {
+ lazy val regex: Regex = XMLUtils.remapPUAToXMLIllegalCharacters(xmlValue).r
+}
+
+/** Facet for xs:length. Narrowing requires local == base exactly. */
+final class FacetLength(xmlValue: String, context: ThrowsSDE)
+ extends FacetNonNegativeInteger(xmlValue, context) {
+ val facetName = "length"
+ protected def isInvalidNarrowing(remote: JBigInt): Boolean =
+ bigIntValue.compareTo(remote) != 0
+ protected def narrowingRelation = "not equal to"
+}
+
+/** Facet for xs:minLength. Narrowing requires local >= base. */
+final class FacetMinLength(xmlValue: String, context: ThrowsSDE)
+ extends FacetNonNegativeInteger(xmlValue, context) {
+ val facetName = "minLength"
+ protected def isInvalidNarrowing(remote: JBigInt): Boolean =
+ bigIntValue.compareTo(remote) < 0
+ protected def narrowingRelation = "less than"
+}
+
+/** Facet for xs:maxLength. Narrowing requires local <= base. */
+final class FacetMaxLength(xmlValue: String, context: ThrowsSDE)
+ extends FacetNonNegativeInteger(xmlValue, context) {
+ val facetName = "maxLength"
+ protected def isInvalidNarrowing(remote: JBigInt): Boolean =
+ bigIntValue.compareTo(remote) > 0
+ protected def narrowingRelation = "greater than"
+}
+
+/** Facet for xs:fractionDigits. Narrowing requires local <= base. */
+final class FacetFractionDigits(xmlValue: String, context: ThrowsSDE)
+ extends FacetNonNegativeInteger(xmlValue, context) {
+ val facetName = "fractionDigits"
+ protected def isInvalidNarrowing(remote: JBigInt): Boolean =
+ bigIntValue.compareTo(remote) > 0
+ protected def narrowingRelation = "greater than"
+}
+
+/** Facet for xs:minInclusive. Narrowing requires local >= base. */
+final class FacetMinInclusive(xmlValue: String, primType: PrimType, context: ThrowsSDE)
+ extends FacetValueSpace(xmlValue, primType, context) {
+ val facetName = "minInclusive"
+ protected def isInvalidNarrowing(remote: JBigDecimal): Boolean =
+ bigDecimalValue.compareTo(remote) < 0
+ protected def narrowingRelation = "less than"
+}
+
+/** Facet for xs:maxInclusive. Narrowing requires local <= base. */
+final class FacetMaxInclusive(xmlValue: String, primType: PrimType, context: ThrowsSDE)
+ extends FacetValueSpace(xmlValue, primType, context) {
+ val facetName = "maxInclusive"
+ protected def isInvalidNarrowing(remote: JBigDecimal): Boolean =
+ bigDecimalValue.compareTo(remote) > 0
+ protected def narrowingRelation = "greater than"
+}
+
+/** Facet for xs:minExclusive. Narrowing requires local >= base. */
+final class FacetMinExclusive(xmlValue: String, primType: PrimType, context: ThrowsSDE)
+ extends FacetValueSpace(xmlValue, primType, context) {
+ val facetName = "minExclusive"
+ protected def isInvalidNarrowing(remote: JBigDecimal): Boolean =
+ bigDecimalValue.compareTo(remote) < 0
+ protected def narrowingRelation = "less than"
+}
+
+/** Facet for xs:maxExclusive. Narrowing requires local <= base. */
+final class FacetMaxExclusive(xmlValue: String, primType: PrimType, context: ThrowsSDE)
+ extends FacetValueSpace(xmlValue, primType, context) {
+ val facetName = "maxExclusive"
+ protected def isInvalidNarrowing(remote: JBigDecimal): Boolean =
+ bigDecimalValue.compareTo(remote) > 0
+ protected def narrowingRelation = "greater than"
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
index 49b0d0b1a6..d130a84fe7 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
@@ -21,7 +21,6 @@ import java.lang.Double as JDouble
import java.lang.Float as JFloat
import java.lang.Long as JLong
import java.util.regex.Matcher
-import scala.util.matching.Regex
import scala.xml.NamespaceBinding
import org.apache.daffodil.api.DFDLPrimType
@@ -64,7 +63,7 @@ import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
import org.apache.daffodil.runtime1.dsom.CompiledExpression
import org.apache.daffodil.runtime1.dsom.DPathCompileInfo
import org.apache.daffodil.runtime1.dsom.DPathElementCompileInfo
-import org.apache.daffodil.runtime1.dsom.FacetTypes
+import org.apache.daffodil.runtime1.dsom.FacetPattern
import org.apache.daffodil.runtime1.dsom.ImplementsThrowsSDE
import org.apache.daffodil.runtime1.infoset.DISimple
import org.apache.daffodil.runtime1.infoset.DataValue
@@ -233,7 +232,7 @@ final class SimpleTypeRuntimeData(
noPrefixNamespaceArg: NS,
val primType: NodeInfo.PrimType,
val noFacetChecks: Boolean,
- val patternValues: Seq[FacetTypes.FacetValueR],
+ val patternValues: Seq[FacetPattern],
val enumerationValues: Option[String],
val length: Option[java.math.BigDecimal],
val minLength: Option[java.math.BigDecimal],
@@ -266,8 +265,10 @@ final class SimpleTypeRuntimeData(
*/
private lazy val matcherPool = new ThreadSafePool[(Seq[Matcher], Option[Matcher])] {
protected final override def allocate() = {
- val patternMatchers = patternValues.map { case (_, r) => r.pattern.matcher("") }
- val optEnumMatcher = enumerationValues.map { en => en.r.pattern.matcher("") }
+ val patternMatchers = patternValues.map { p => p.regex.pattern.matcher("") }
+ val optEnumMatcher = enumerationValues.map { enumeration =>
+ enumeration.r.pattern.matcher("")
+ }
(patternMatchers, optEnumMatcher)
}
}
@@ -342,7 +343,7 @@ final class SimpleTypeRuntimeData(
if (!check) {
// The escaping is important here as error messages were impossible to figure out when control chars were involved.
val patternStrings = e.patternValues
- .map { case (_, r: Regex) => XMLUtils.escape(r.pattern.pattern()) }
+ .map { pattern => XMLUtils.escape(pattern.regex.pattern.pattern()) }
.mkString(",")
return Error("facet pattern(s): %s".format(patternStrings))
}
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertTextCalendarUnparser.scala b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertTextCalendarUnparser.scala
index b570a6eb28..64664173b7 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertTextCalendarUnparser.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/ConvertTextCalendarUnparser.scala
@@ -93,13 +93,14 @@ case class ConvertTextCalendarUnparser(
// that. This ensures there are no locale specific issues related to
// unparsing calendars.
calendar.set(
- infosetCalendar.get(Calendar.EXTENDED_YEAR),
+ infosetCalendar.get(Calendar.YEAR),
infosetCalendar.get(Calendar.MONTH),
infosetCalendar.get(Calendar.DAY_OF_MONTH),
infosetCalendar.get(Calendar.HOUR_OF_DAY),
infosetCalendar.get(Calendar.MINUTE),
infosetCalendar.get(Calendar.SECOND)
)
+ calendar.set(Calendar.ERA, infosetCalendar.get(Calendar.ERA))
calendar.set(Calendar.MILLISECOND, infosetCalendar.get(Calendar.MILLISECOND))
calendar.setTimeZone(infosetCalendar.getTimeZone)
diff --git a/daffodil-core/src/test/scala/org/apache/daffodil/core/dsom/TestDsomCompiler.scala b/daffodil-core/src/test/scala/org/apache/daffodil/core/dsom/TestDsomCompiler.scala
index fb139ebfd7..ffc162958c 100644
--- a/daffodil-core/src/test/scala/org/apache/daffodil/core/dsom/TestDsomCompiler.scala
+++ b/daffodil-core/src/test/scala/org/apache/daffodil/core/dsom/TestDsomCompiler.scala
@@ -1170,7 +1170,7 @@ class TestDsomCompiler {
val decl = declf.asRoot
assertEquals(1, decl.patternValues.length)
- val (_, pattern) = decl.patternValues(0)
+ val pattern = decl.patternValues.head.regex
assertEquals("1|2|3", pattern.toString())
}
@@ -1210,9 +1210,9 @@ class TestDsomCompiler {
val decl = declf.asRoot
assertEquals(3, decl.patternValues.length)
- val (_, st1) = decl.patternValues(0)
- val (_, st2) = decl.patternValues(1)
- val (_, st3) = decl.patternValues(2)
+ val st1 = decl.patternValues.head.regex
+ val st2 = decl.patternValues(1).regex
+ val st3 = decl.patternValues(2).regex
assertEquals("1|2|3", st1.toString())
assertEquals("4|5|6", st2.toString())
diff --git a/daffodil-tdml-processor/src/test/scala/org/apache/daffodil/processor/tdml/TestTDMLRunner.scala b/daffodil-tdml-processor/src/test/scala/org/apache/daffodil/processor/tdml/TestTDMLRunner.scala
index 59f9af9abc..796bbdb7e9 100644
--- a/daffodil-tdml-processor/src/test/scala/org/apache/daffodil/processor/tdml/TestTDMLRunner.scala
+++ b/daffodil-tdml-processor/src/test/scala/org/apache/daffodil/processor/tdml/TestTDMLRunner.scala
@@ -1077,4 +1077,49 @@ f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff
assertTrue(msg.contains("/this/does/not/exist.xml"))
}
+ // Verifies that an expected infoset containing year 0000 (which is invalid
+ // in XSD 1.0 — no year zero exists) causes a TDMLException when the TDML
+ // runner tries to compare it against the actual parsed result using
+ // XMLGregorianCalendar. This is expected behavior — an invalid expected
+ // infoset value is a TDML test error, not a parse error.
+ @Test def testYearZeroThrows(): Unit = {
+ val inputValueCalc = "{ fn:year-from-dateTime(../ex:date) }"
+ val testSuite =
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0001-01-01T23:00:00 BC
+
+
+
+ 0000-01-01T23:00:00
+ -1
+
+
+
+
+
+ val runner = new Runner(testSuite)
+ val e = intercept[TDMLException] {
+ runner.runOneTest("yearZeroThrows")
+ }
+ runner.reset()
+ assertTrue(e.getMessage().contains("0000-01-01T23:00:00"))
+ assertTrue(e.getMessage().contains("not a valid representation"))
+ assertTrue(e.getMessage().contains("XML Gregorian Calendar"))
+ }
}
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
index 61808e7ece..984139a8ae 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
@@ -2652,7 +2652,7 @@
00000000 00000000 00000000 00111101
- Schema Definition Error: Failed to parse binaryCalendarEpoch - Format must match the pattern 'uuuu-MM-dd'T'HH:mm:ss' or 'uuuu-MM-dd'T'HH:mm:ssZZZZ'
+ Schema Definition Error: Failed to parse binaryCalendarEpoch '2000-1-1T00:00' as an xs:dateTime.
@@ -2663,7 +2663,7 @@
00000000 00000000 00000000 00111101
- Schema Definition Error: Failed to parse binaryCalendarEpoch: DAY_OF_MONTH=2000, valid range=1..31
+ Schema Definition Error: Failed to parse binaryCalendarEpoch '01-01-2000T00:00:00' as an xs:dateTime.
@@ -5673,7 +5673,7 @@
- -2012-02-13
+ -2013-02-13
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
index 77db13fb1e..1bc329e027 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
@@ -707,6 +707,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -802,8 +820,30 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -824,6 +864,15 @@
+
+
+
+
+
+
+
+
@@ -857,7 +906,6 @@
-
@@ -870,6 +918,18 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -2257,24 +2317,76 @@
-
+ 0001-01-01T23:00:00 BC
+
+
+
+ -0001-01-01T23:00:00
+ -1
+
+
+
+
+
+
+
+
+
0000-01-01T23:00:00
-
- 0000-01-01T23:00:00
- 0
-
+
+ -0001-01-01T23:00:00
+ -1
+
+
+
+
+
+
+
+ 0000-01-01T23:00:00
+ 0
+
+
+
+
+ Unparse error
+ 0000-01-01T23:00:00
+ not a valid xs:dateTime
+
+
+
+
+
+
+ -1233-01-30T10:01:02-08:00
+
+
+
+ -1234-01-30T10:01:02-08:00
+ -1234
+ 1
+ 30
+ 10
+ 1
+ 2
+ -08:00
+
+
+
+
+
-
- 0000-01-01
+ 0001-01-01 BC
-
- 0000-01-01
- 0
-
+
+ -0001-01-01
+ -1
+
@@ -2793,24 +2935,24 @@
-
- -0020-01-99
+ 0020-01-99 BC
-
- -0020-04-08
+
+ -0020-04-09
-20
4
- 8
+ 9
-
+
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
index dead3d8de0..820ece07a5 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
@@ -766,8 +766,9 @@ class TestDFDLFunctions extends TdmlTests {
@Test def yearfromdatetime_01 = test
@Test def yearfromdatetime_02 = test
- // DAFFODIL-3084
- @Ignore @Test def yearfromdatetime_03 = test
+ @Test def yearfromdatetime_03 = test
+ @Test def yearfromdatetime_04 = test
+ @Test def yearfromdatetime_05 = test
@Test def monthfromdatetime_01 = test
@Test def monthfromdatetime_02 = test
@Test def dayfromdatetime_01 = test
@@ -789,11 +790,11 @@ class TestDFDLFunctions extends TdmlTests {
@Test def xfromdatetime_02 = test
@Test def xfromdatetime_03 = test
@Test def xfromdatetime_04 = test
+ @Test def xfromdatetime_05 = test
@Test def yearfromdate_01 = test
@Test def yearfromdate_02 = test
- // DAFFODIL-3084
- @Ignore @Test def yearfromdate_03 = test
+ @Test def yearfromdate_03 = test
@Test def monthfromdate_01 = test
@Test def monthfromdate_02 = test
@Test def dayfromdate_01 = test