This is an automated email from the ASF dual-hosted git repository.
mbeckerle pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git
The following commit(s) were added to refs/heads/main by this push:
new d5919e053 Implement 'P' textNumberPattern feature.
d5919e053 is described below
commit d5919e05398f734a09801bd4207ec16b078181ef
Author: Michael Beckerle <[email protected]>
AuthorDate: Wed Jan 11 14:24:53 2023 -0500
Implement 'P' textNumberPattern feature.
The 'P' is also a COBOL feature from their picture
clauses.
Also this change set incorporates additional review feedback on the
'V' feature change set.
Note that the 'P' and 'V' feature does not support some obscure
textNumberPattern as yet such as:
1. a textNumberPattern with a trailing 'V' (no 0 digits after)
2. combining 'V' with an exponent specification.
Those usages are allowed technically by the DFDL v1.0
specification (the grammar in section 13.6.1.1 labeled Figure 4), but
that usage is so obscure that I am NOT going to create a bug report
about implementing these behaviors until someone complains.
Similarly it is technically possible to use say, CR
(carriage return) or ASCII NUL as your plus sign
character. I claim that is a mistake in the DFDL spec to
allow that, so use of line-ending characters is not
allowed.
DAFFODIL-2763
---
.../grammar/primitives/PrimitivesTextNumber.scala | 350 ++++++++++++++++-----
.../grammar/primitives/PrimitivesZoned.scala | 48 ++-
.../grammar/primitives/TestPrimitives.scala | 153 +++++++--
.../resources/org/apache/daffodil/xsd/dafext.xsd | 1 +
.../parsers/ConvertTextStandardNumberParser.scala | 60 +++-
.../daffodil/extensions/enum/enumInvalid.tdml | 2 +-
.../org/apache/daffodil/section13/zoned/pv.tdml | 68 ++--
.../apache/daffodil/section13/zoned/TestPV.scala | 8 +-
8 files changed, 554 insertions(+), 136 deletions(-)
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesTextNumber.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesTextNumber.scala
index 20907a2b8..9e68df592 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesTextNumber.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesTextNumber.scala
@@ -18,6 +18,7 @@
package org.apache.daffodil.grammar.primitives
import com.ibm.icu.text.DecimalFormat
+import org.apache.daffodil.api.WarnID
import org.apache.daffodil.cookers.EntityReplacer
import org.apache.daffodil.dpath.NodeInfo.PrimType
import org.apache.daffodil.dsom._
@@ -45,27 +46,56 @@ case class ConvertTextCombinator(e: ElementBase, value:
Gram, converter: Gram)
override lazy val unparser = new
ConvertTextCombinatorUnparser(e.termRuntimeData, value.unparser,
converter.unparser)
}
-// This is a separate object for unit testing purposes.
+/**
+ * This is a separate object so that we can easily unit test these subtle
regular
+ * expressions.
+ */
private[primitives]
object TextNumberPatternUtils {
+ // DFDL v1.0 Spec says
+ // It is a Schema Definition Error if any symbols other than "0", "1"
through "9" or #
+ // are used in the vpinteger region of the pattern.
+ //
+ // The prefix and suffix chars can surround the vpinteger region, and there
can be
+ // a positive and a negative pattern.
+
+ /**
+ * The prefix and suffix are quoted any single character
+ * or a single non-special character (excepting + and - which are allowed
+ * though they appear in the table of special characters for
textNumberFormat).
+ *
+ * By default these regex will not allow line-ending chars either.
+ */
+ val prefixChars = """(?:'.'|[^0-9#PVE\.\,\;\*\@\'])?""" // same for suffix.
+
+ /**
+ * sharps and at least 1 digit before the V.
+ */
+ val beforeVChars = """#*[0-9]+"""
+
+ /**
+ * one or more digits after the V. (No trailing sharps)
+ */
+ val afterVChars = """[0-9]+"""
+
+ /**
+ * The negative pattern must be a valid pattern, but
+ * does NOT have to match the positive one, as it is
+ * always ignored.
+ *
+ * The shortest possible match is just a # or just a digit.
+ * If a V appears it must have a digit on either side.
+ */
+ val negVNumberChars = """#|#+|#*[0-9]+(?:V[0-9]+)?"""
+
+
/**
* A regex which matches textNumberPatterns that legally use the V
* (implied decimal point) character.
*/
- private[primitives] lazy val vregexStandard = {
- // DFDL v1.0 Spec says
- // It is a Schema Definition Error if any symbols other than "0", "1"
through "9" or #
- // are used in the vpinteger region of the pattern.
- //
- // The prefix and suffix chars can surround the vpinteger region, and
there can be
- // a positive and a negative pattern.
- //
- // The prefix and suffix cannot be digits, # or P or V. No quoted chars in
prefix either.
- //
- val prefixChars = """[^0-9#PV']?""" // same for suffix.
- val beforeVChars = """#*[0-9]+"""
- val afterVChars = """[0-9]+"""
+ lazy val vRegexStandard = {
+
//
// Each of the capture groups is defined here in order
//
@@ -74,20 +104,87 @@ object TextNumberPatternUtils {
val posAfterV = s"""($afterVChars)"""
val posSuffix = posPrefix
val negPrefix = posPrefix
- val negBeforeV = posBeforeV
- val negAfterV = posAfterV
+ val negNumber = s"""($negVNumberChars)"""
val negSuffix = posPrefix
// don't forget the ^ and $ (start of data, end of data) because we want
// the match to consume all the characters, starting at the beginning.
val vPattern=
-
s"""^${posPrefix}${posBeforeV}V${posAfterV}${posSuffix}(?:;${negPrefix}${negBeforeV}V${negAfterV}${negSuffix})?$$"""
+ s"""^${posPrefix}${posBeforeV}V${posAfterV}${posSuffix}""" +
+ s"""(?:;${negPrefix}${negNumber}${negSuffix})?$$"""
val re = vPattern.r
re
}
+ /*
+ * There are two regex used for 'P' pattern case. One if the P chars
+ * appear to the left of the digits, the other for P chars on the right
+ * of the digits.
+ *
+ * When P is on left of the digits, there must be 1 or more digits.
+ * When P is on right of the digits there must be 1 or more digits, and
there may be sharps.
+ */
+ val afterPChars = """[0-9]+""" // P is left, digits are after
+ val beforePChars = """#*[0-9]+""" // P is right, digits are before (sharps
before the digits).
+
+ /**
+ * For P patterns, with P on the left, the negative pattern can be just # or
just a digit
+ * or can be P chars followed by one or more digits.
+ */
+ val negPOnLeftNumberChars = "#|P*[0-9]+"
+
+ /**
+ * For P patterns, with P on the right, the negative patterncan be just # or
just a digit
+ * or can be sharps, then digits, then P chars.
+ */
+ val negPOnRightNumberChars = "#|#*[0-9]+P*"
+
+ /**
+ * A regex which matches textNumberPatterns that legally use the P
+ * (implied decimal point) character, on the LEFT of the digits.
+ */
+ lazy val pOnLeftRegexStandard = {
+ //
+ // Each of the capture groups is defined here in order
+ //
+ val posPrefix = s"""($prefixChars)"""
+ val posPs = s"""(P+)"""
+ val posAfterP = s"""($afterPChars)"""
+ val posSuffix = posPrefix
+ val negPrefix = posPrefix
+ val negPNumber = s"""($negPOnLeftNumberChars)"""
+ val negSuffix = posPrefix
+ // don't forget the ^ and $ (start of data, end of data) because we want
+ // the match to consume all the characters, starting at the beginning.
+ val vPattern =
+
s"""^${posPrefix}$posPs${posAfterP}${posSuffix}(?:;${negPrefix}${negPNumber}${negSuffix})?$$"""
+ val re = vPattern.r
+ re
+ }
+ /**
+ * A regex which matches textNumberPatterns that legally use the P
+ * (implied decimal point) character, on the RIGHT of the digits.
+ */
+ lazy val pOnRightRegexStandard = {
+ //
+ // Each of the capture groups is defined here in order
+ //
+ val posPrefix = s"""($prefixChars)"""
+ val posBeforeP = s"""($beforePChars)"""
+ val posPs = s"""(P+)"""
+ val posSuffix = posPrefix
+ val negPrefix = posPrefix
+ val negPNumber = s"""($negPOnRightNumberChars)"""
+ val negSuffix = posPrefix
+ // don't forget the ^ and $ (start of data, end of data) because we want
+ // the match to consume all the characters, starting at the beginning.
+ val vPattern =
+
s"""^${posPrefix}${posBeforeP}$posPs${posSuffix}(?:;${negPrefix}${negPNumber}${negSuffix})?$$"""
+ val re = vPattern.r
+ re
+ }
- private[primitives] lazy val vregexZoned= {
+ lazy val vRegexZoned= {
// Note: for zoned, can only have a positive pattern
// Prefix or suffix can only be '+' character, to
// indicate leading or trailing sign, and can only
@@ -114,21 +211,66 @@ object TextNumberPatternUtils {
* Checks a pattern for suitability with V (virtual decimal point) in the
pattern
* in the context of zoned textNumberRep.
*
- * This is broken out separately for unit testing purposes.
+ * This method is here in this object for unit testing purposes.
*
* @param patternStripped the dfdl:textNumberPattern pattern - with all
quoting removed.
* @return None if the pattern is illegal syntax for use with V (virtual
decimal point)
* otherwise Some(N) where N is the number of digits to the right of
the V character.
*/
- private[primitives] def textDecimalVirtualPointForZoned(patternStripped:
String): Option[Int] = {
- val r = TextNumberPatternUtils.vregexZoned
+ def textNumber_V_DecimalVirtualPointForZoned(patternStripped: String):
Option[Int] = {
+ val r = TextNumberPatternUtils.vRegexZoned
r.findFirstMatchIn(patternStripped) match {
// note: cannot have both a prefix and suffix. Only one of them.
- case Some(r(pre, _, afterV, suf)) if (pre.length + suf.length <= 1) =>
Some(afterV.length)
+ case Some(r(pre, _, afterV, suf)) if (pre.length + suf.length <= 1) =>
+ Some(afterV.length)
case _ => None
}
}
+ lazy val pOnLeftRegexZoned = {
+ // Note: for zoned, can only have a positive pattern
+ // Prefix or suffix can only be '+' character, to
+ // indicate leading or trailing sign, and can only
+ // one of those.
+ //
+ // Also we're not allowing the # character, since I think that
+ // makes no sense for zoned with virtual decimal point.
+ //
+ val prefixChars = """\+?""" // only + for prefix/suffix
+ //
+ // capture groups are defined here in sequence
+ //
+ val prefix = s"""($prefixChars)"""
+ val ps = s"""(P+)"""
+ val afterP = s"""($afterPChars)"""
+ val suffix = prefix
+ val vPattern = s"""^${prefix}$ps${afterP}${suffix}$$""" // only positive
pattern allowed
+ val re = vPattern.r
+ re
+ }
+
+ lazy val pOnRightRegexZoned = {
+ // Note: for zoned, can only have a positive pattern
+ // Prefix or suffix can only be '+' character, to
+ // indicate leading or trailing sign, and can only
+ // one of those.
+ //
+ // Also we're not allowing the # character, since I think that
+ // makes no sense for zoned with virtual decimal point.
+ //
+ val prefixChars = """\+?""" // only + for prefix/suffix
+ //
+ // capture groups are defined here in sequence
+ //
+ val prefix = s"""($prefixChars)"""
+ val beforeP = s"""($beforePChars)"""
+ val ps = s"""(P+)"""
+ val suffix = prefix
+ val vPattern = s"""^${prefix}${beforeP}$ps${suffix}$$""" // only positive
pattern allowed
+ val re = vPattern.r
+ re
+ }
+
/**
* The apos character (') is the quoting character in ICU patterns (and DFDL
textNumberPattern).
* This removes any unquoted P or V characters (which are not implemented by
ICU)
@@ -137,25 +279,12 @@ object TextNumberPatternUtils {
* @return the pattern string with all unquoted P and unquoted V removed.
*/
def removeUnquotedPV(pattern: String) : String = {
- val uniqueQQString = "alsdflslkjskjslkkjlkkjfppooiipsldsflj"
- // A single regex that matches an unquoted character
- // where the quoting char is self quoting requires
- // a zero-width look behind that matches a potentially
- // unbounded number of quote chars. That's not allowed.
- //
- // Consider ''''''P. We want a regex that matches only the P here
- // because it is preceded by an even number of quotes.
- //
- // I did some tests, and the formulations you think might work
- // such as from stack-overflow, don't work.
- // (See test howNotToUseRegexLookBehindWithReplaceAll)
- //
- // So we use this brute force technique of replacing all ''
- // first, so we have only single quotes to deal with.
- pattern.replaceAll("''", uniqueQQString).
- replaceAll("(?<!')P", "").
- replaceAll("(?<!')V", "").
- replaceAll(uniqueQQString, "''")
+ val regex = "'.*?'|.".r
+ val res = regex.findAllIn(pattern)
+ .filterNot(_ == "P")
+ .filterNot(_ == "V")
+ .mkString("")
+ res
}
}
@@ -170,13 +299,23 @@ trait ConvertTextNumberMixin {
final protected def pattern = e.textNumberPattern
/**
- * Checks a pattern for suitability with V (virtual decimal point) in the
pattern
- * in the context of the corresponding textNumberRep. Computes number of
digits to right of the V.
+ * Analogous to the property dfdl:binaryDecimalVirtualPoint
+ *
+ * Value is 0 if there is no virtual decimal point.
*
- * SDE if pattern has a syntax error.
+ * Value is the number of digits to the right of the 'V' for V patterns.
*
- * @return the number of digits to the right of the V character. Must be 1
or greater.
+ * For P patterns, the value is the number of P characters when
+ * the P chars are on the right of the digits, or it is negated if the
+ * P chars are on the left of the digits.
*
+ * Examples:
+ *
+ * "000V00" returns 2, so text "12345" yields 123.45
+ *
+ * "PP000" returns 5, so text "123" yields 0.00123
+ *
+ * "000PP" returns -2 so text "123" yields 12300.0
*/
protected def textDecimalVirtualPointFromPattern: Int
@@ -186,41 +325,57 @@ trait ConvertTextNumberMixin {
* but cannot be used as an operational pattern given that
* potentially lots of things have been removed from it.
*/
- final protected lazy val patternStripped = {
+ final protected lazy val patternWithoutEscapedChars = {
// note: tick == apos == ' == single quote.
// First remove entirely all escaped ticks ie., ''
val noEscapedTicksRegex = """''""".r
val patternNoEscapedTicks = noEscapedTicksRegex.replaceAllIn(pattern, "")
// Next remove all tick-escaped characters entirely
val noQuotedRegex = """'[^']+'""".r
- val patternNoQuoted = noQuotedRegex.replaceAllIn(patternNoEscapedTicks, "")
+ val res = noQuotedRegex.replaceAllIn(patternNoEscapedTicks, "")
// the remaining string contains only pattern special characters
// and regular non-pattern characters
- patternNoQuoted
+ res
}
- protected final lazy val hasV = patternStripped.contains("V")
- protected final lazy val hasP = patternStripped.contains("P")
+ protected final lazy val hasV = patternWithoutEscapedChars.contains("V")
+ protected final lazy val hasP = patternWithoutEscapedChars.contains("P")
/**
- * analogous to the property dfdl:binaryDecimalVirtualPoint
+ * Analogous to the property dfdl:binaryDecimalVirtualPoint
*
* Value is 0 if there is no virtual decimal point.
- * Value is the number of digits to the right of the 'V'
+ *
+ * Value is the number of digits to the right of the 'V' for V patterns.
+ *
+ * For P patterns, the value is the number of P characters when
+ * the P chars are on the right of the digits, or it is negated if the
+ * P chars are on the left of the digits.
+ *
+ * Examples:
+ *
+ * "000V00" returns 2, so text "12345" yields 123.45
+ *
+ * "PP000" returns 2, so text "123" yields 0.00123
+ *
+ * "000PP" returns -2 so text "123" yields 12300.0
*/
final lazy val textDecimalVirtualPoint: Int = {
- if (!hasV && !hasP) 0 // no virtual point
- else {
- if (hasP) {
- e.notYetImplemented("textNumberPattern with P symbol")
- }
- Assert.invariant(hasV)
+ lazy val virtualPoint = textDecimalVirtualPointFromPattern
+ if (hasV) {
//
// check for things incompatible with "V"
//
- val virtualPoint = textDecimalVirtualPointFromPattern
Assert.invariant(virtualPoint >= 1) // if this fails the regex is broken.
virtualPoint
+ } else if (hasP) {
+ //
+ // check for things incompatible with "P"
+ //
+ Assert.invariant(virtualPoint != 0) // if this fails the regex is broken.
+ virtualPoint
+ } else {
+ 0 // no virtual point since we have neither V nor P in the pattern.
}
}
@@ -275,17 +430,72 @@ case class ConvertTextStandardNumberPrim(e: ElementBase)
extends Terminal(e, true)
with ConvertTextNumberMixin {
+
final override protected lazy val textDecimalVirtualPointFromPattern: Int = {
- val r = TextNumberPatternUtils.vregexStandard
- r.findFirstMatchIn(patternStripped) match {
- case Some(r(_, _, afterV, _, _, _, _, _)) => afterV.length
- case None =>
- e.SDE(
- s"""The dfdl:textNumberPattern '%s' contains 'V' (virtual decimal
point).
- | Other than the sign indicators, it can contain only
- | '#', then digits 0-9 then 'V' then digits 0-9.
- | The positive part of the dfdl:textNumberPattern is
mandatory.""".stripMargin('|'),
+ if (hasV) {
+ val r = TextNumberPatternUtils.vRegexStandard
+ r.findFirstMatchIn(patternWithoutEscapedChars) match {
+ case Some(r(_, beforeV, afterV, _, _, negNum, _)) => {
+ checkPosNegNumPartSyntax(beforeV + "V" + afterV, negNum)
+ afterV.length
+ }
+ case None =>
+ e.SDE(
+ s"""The dfdl:textNumberPattern '%s' contains 'V' (virtual decimal
point).
+ | Other than the sign indicators, it can contain only
+ | '#', then digits 0-9 then 'V' then digits 0-9.
+ | The positive part of the dfdl:textNumberPattern is
mandatory.""".stripMargin('|'),
+ pattern)
+ }
+ } else if (hasP) {
+ val rr = TextNumberPatternUtils.pOnRightRegexStandard
+ val rl = TextNumberPatternUtils.pOnLeftRegexStandard
+ val rightMatch = rr.findFirstMatchIn(patternWithoutEscapedChars)
+ val leftMatch = rl.findFirstMatchIn(patternWithoutEscapedChars)
+ (leftMatch, rightMatch) match {
+ case (None, None) => e.SDE(
+ """The dfdl:textNumberPattern '%s' contains 'P' (virtual decimal
point positioners).
+ |However, it did not match the allowed syntax which allows the
sign indicator
+ |plus digits on only one side of the P symbols.""".stripMargin,
pattern)
+ case (Some(rl(_, ps, digits, _, negPre, negNum, _)), None) => {
+ checkPosNegNumPartSyntax(ps + digits, negNum)
+ ps.length + digits.length
+ }
+ case (None, Some(rr(_, digits, ps, _, negPre, negNum, _))) => {
+ checkPosNegNumPartSyntax(digits + ps, negNum)
+ -ps.length // negate value.
+ }
+ case _ => Assert.invariantFailed("Should not match both left P and
right P regular expressions.")
+ }
+ } else {
+ 0 // there is no V nor P, so there is no virtual decimal point scaling
involved.
+ }
+ }
+
+ /**
+ * Check for common errors in the pattern matches for 'P' patterns.
+ *
+ * @param posNum positive number pattern part (combined ps and digits on
left or right)
+ * @param negNum negative number pattern part
+ */
+ private def checkPosNegNumPartSyntax(
+ posNum: String,
+ negNum: String): Unit = {
+ if (negNum ne null) {
+ // there is a negative part
+ if (negNum.length == 1) {
+ Assert.invariant(negNum.matches("#|[0-9]")) // must be # or digit or
wouldn't have matched.
+ // do nothing. This is ok. (The negNum is ignored anyway)
+ } else {
+ // we issue a warning if there is a negNum pattern part and it is not
+ // identical to the positive part.
+ e.schemaDefinitionWarningUnless(WarnID.TextNumberPatternWarning,
posNum == negNum,
+ s"""The negative numeric part of the textNumberPattern: '${negNum}' is
being ignored.
+ | It should either be just '#' or should exactly
+ | match the numeric part of the positive pattern,
+ | which is '${posNum}'.""".stripMargin)
+ }
}
}
@@ -346,8 +556,8 @@ case class ConvertTextStandardNumberPrim(e: ElementBase)
// also require the separators if the prim type is not an integer type,
// since ICU will use them even if the pattern does not specify them.
val requireDecGroupSeps =
- patternStripped.contains(",") || patternStripped.contains(".") ||
- patternStripped.contains("E") || patternStripped.contains("@") ||
+ patternWithoutEscapedChars.contains(",") ||
patternWithoutEscapedChars.contains(".") ||
+ patternWithoutEscapedChars.contains("E") ||
patternWithoutEscapedChars.contains("@") ||
!isInt
val decSep =
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesZoned.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesZoned.scala
index dc1394dd6..bb83b1993 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesZoned.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesZoned.scala
@@ -20,6 +20,7 @@ package org.apache.daffodil.grammar.primitives
import org.apache.daffodil.dpath.NodeInfo.PrimType
import org.apache.daffodil.dsom._
+import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.grammar.Gram
import org.apache.daffodil.grammar.Terminal
import org.apache.daffodil.processors.TextNumberFormatEv
@@ -49,51 +50,70 @@ case class ConvertZonedNumberPrim(e: ElementBase)
with ConvertTextNumberMixin {
final override protected lazy val textDecimalVirtualPointFromPattern: Int = {
-
TextNumberPatternUtils.textDecimalVirtualPointForZoned(patternStripped).getOrElse
{
- e.SDE(
- s"""The dfdl:textNumberPattern '%s' contains 'V' (virtual decimal
point).
- |Other than the leading or trailing '+' sign indicator,
- |it can contain only digits 0-9.""".stripMargin('|'),
- pattern)
+ if (hasV) {
+
TextNumberPatternUtils.textNumber_V_DecimalVirtualPointForZoned(patternWithoutEscapedChars).getOrElse
{
+ e.SDE(
+ s"""The dfdl:textNumberPattern '%s' contains 'V' (virtual decimal
point).
+ |Other than the leading or trailing '+' sign indicator,
+ |it can contain only digits 0-9.""".stripMargin('|'),
+ pattern)
+ }
+ } else if (hasP) {
+ val rr = TextNumberPatternUtils.pOnRightRegexZoned
+ val rl = TextNumberPatternUtils.pOnLeftRegexZoned
+ val rightMatch = rr.findFirstMatchIn(patternWithoutEscapedChars)
+ val leftMatch = rl.findFirstMatchIn(patternWithoutEscapedChars)
+ (leftMatch, rightMatch) match {
+ case (None, None) => e.SDE(
+ """The dfdl:textNumberPattern '%s' contains 'P' (decimal scaling
position symbol(s)).
+ |However, it did not match the allowed syntax which allows the
sign indicator
+ |plus digits on only one side of the P symbols.""".stripMargin,
+ pattern)
+ case (Some(rl(_, ps, digits, _)), None) => ps.length + digits.length
+ case (None, Some(rr(_, digits, ps, _))) => -ps.length // negate value.
+ case _ => Assert.invariantFailed("Should not match both left P and
right P regular expressions.")
+ }
+ } else {
+ 0 // neither P nor V in pattern
}
}
lazy val textNumberFormatEv: TextNumberFormatEv = {
- if (patternStripped.contains("@")) {
+ if (patternWithoutEscapedChars.contains("@")) {
e.SDE("The '@' symbol may not be used in textNumberPattern for
textNumberRep='zoned'")
}
- if (patternStripped.contains("E")) {
+ if (patternWithoutEscapedChars.contains("E")) {
e.SDE("The 'E' symbol may not be used in textNumberPattern for
textNumberRep='zoned'")
}
- e.schemaDefinitionWhen(patternStripped.contains(";"),
+ e.schemaDefinitionWhen(patternWithoutEscapedChars.contains(";"),
"Negative patterns may not be used in textNumberPattern for
textNumberRep='zoned'")
e.primType match {
case PrimType.Double | PrimType.Float => e.SDE("textNumberRep='zoned'
does not support Doubles/Floats")
case PrimType.UnsignedLong | PrimType.UnsignedInt |
PrimType.UnsignedShort | PrimType.UnsignedByte => {
if (e.textNumberCheckPolicy == TextNumberCheckPolicy.Lax) {
- if ((patternStripped(0) != '+') &&
(patternStripped(patternStripped.length - 1) != '+'))
+ if ((patternWithoutEscapedChars(0) != '+') &&
(patternWithoutEscapedChars(patternWithoutEscapedChars.length - 1) != '+'))
e.SDE("textNumberPattern must have '+' at the beginning or the end
of the pattern when textNumberRep='zoned' and textNumberPolicy='lax' for
unsigned numbers")
}
}
case _ => {
- if ((patternStripped(0) != '+') &&
(patternStripped(patternStripped.length - 1) != '+'))
+ if ((patternWithoutEscapedChars(0) != '+') &&
(patternWithoutEscapedChars(patternWithoutEscapedChars.length - 1) != '+'))
e.SDE("textNumberPattern must have '+' at the beginning or the end
of the pattern when textNumberRep='zoned' for signed numbers")
}
}
- if ((patternStripped(0) == '+') && (patternStripped(patternStripped.length
- 1) == '+'))
+ if ((patternWithoutEscapedChars(0) == '+') &&
(patternWithoutEscapedChars(patternWithoutEscapedChars.length - 1) == '+'))
e.SDE("The textNumberPattern may either begin or end with a '+', not
both.")
if (textDecimalVirtualPoint > 0) {
e.primType match {
case PrimType.Decimal => // ok
case _ => e.SDE(
- """The dfdl:textNumberPattern has a virtual decimal point 'V' and
dfdl:textNumberRep='zoned'.
- | The type must be xs:decimal, but was: %s.""".stripMargin,
e.primType.globalQName.toPrettyString)
+ """The dfdl:textNumberPattern has a virtual decimal point 'V' or
decimal scaling 'P' and dfdl:textNumberRep='zoned'.
+ | The type must be xs:decimal but was: %s.""".stripMargin,
e.primType.globalQName.toPrettyString)
}
}
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/grammar/primitives/TestPrimitives.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/grammar/primitives/TestPrimitives.scala
index 428a245b0..288e25d62 100644
---
a/daffodil-core/src/test/scala/org/apache/daffodil/grammar/primitives/TestPrimitives.scala
+++
b/daffodil-core/src/test/scala/org/apache/daffodil/grammar/primitives/TestPrimitives.scala
@@ -17,6 +17,7 @@
package org.apache.daffodil.grammar.primitives
+import com.ibm.icu.text.DecimalFormat
import org.apache.daffodil.Implicits.intercept
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
@@ -29,51 +30,77 @@ import java.util.regex.PatternSyntaxException
class TestPrimitives {
@Test def testVRegexPositiveAndNegativeWithPrefixesAndSuffixes() : Unit = {
- val re = TextNumberPatternUtils.vregexStandard
+ val re = TextNumberPatternUtils.vRegexStandard
val Some(myMatch) = re.findFirstMatchIn("A###012V34B;C####56V78D")
myMatch match {
- case re("A", "###012", "34", "B", "C", "####56", "78", "D") => // ok
+ case re("A", "###012", "34", "B", "C", "####56V78", "D") => // ok
+ }
+ }
+
+ @Test def testVRegexPositiveAndNegativeWithPrefixesAndSuffixes2(): Unit = {
+ val re = TextNumberPatternUtils.vRegexStandard
+ val Some(myMatch) = re.findFirstMatchIn("'P'###012V34B;C####56V78D")
+ myMatch match {
+ case re("'P'", "###012", "34", "B", "C", "####56V78", "D") => // ok
+ }
+ }
+
+ @Test def testVRegexPositiveAndNegativeWithPrefixesAndSuffixes3(): Unit = {
+ val re = TextNumberPatternUtils.vRegexStandard
+ val optMatch = re.findFirstMatchIn("'P'###012V34'P';N0V0N")
+ println(optMatch)
+ optMatch match {
+ case Some(re("'P'", "###012", "34", "'P'", "N", "0V0", "N")) => // ok
+ }
+ }
+
+ @Test def testVRegexPositiveAndNegativeWithPrefixesAndSuffixes4(): Unit = {
+ val re = TextNumberPatternUtils.vRegexStandard
+ val optMatch = re.findFirstMatchIn("'P'###012V34'P';N#N")
+ println(optMatch)
+ optMatch match {
+ case Some(re("'P'", "###012", "34", "'P'", "N", "#", "N")) => // ok
}
}
@Test def testVRegexOnlyPositivePattern(): Unit = {
- val re = TextNumberPatternUtils.vregexStandard
+ val re = TextNumberPatternUtils.vRegexStandard
val Some(myMatch) = re.findFirstMatchIn("A###012V34B")
myMatch match {
- case re("A", "###012", "34", "B", null, null, null, null) =>
+ case re("A", "###012", "34", "B", null, null, null) =>
}
}
@Test def testVRegexOnlyPositivePatternNoPrefixNorSuffix(): Unit = {
- val re = TextNumberPatternUtils.vregexStandard
+ val re = TextNumberPatternUtils.vRegexStandard
val Some(myMatch) = re.findFirstMatchIn("###012V34")
myMatch match {
- case re("", "###012", "34", "", null, null, null, null) =>
+ case re("", "###012", "34", "", null, null, null) =>
}
}
@Test def testVRegexTrailingSign(): Unit = {
- val re = TextNumberPatternUtils.vregexStandard
+ val re = TextNumberPatternUtils.vRegexStandard
val Some(myMatch) = re.findFirstMatchIn("012V34+") // for zoned,
overpunched trailing sign.
myMatch match {
- case re("", "012", "34", "+", null, null, null, null) =>
+ case re("", "012", "34", "+", null, null, null) =>
}
}
@Test def testVRegexZonedLeadingSign(): Unit = {
- val re = TextNumberPatternUtils.vregexZoned
+ val re = TextNumberPatternUtils.vRegexZoned
val optMyMatch = re.findFirstMatchIn("+012V34") // for zoned, overpunched
leading sign.
val Some(re("+", "012", "34", "")) = optMyMatch
}
@Test def testVRegexZonedTrailingSign(): Unit = {
- val re = TextNumberPatternUtils.vregexZoned
+ val re = TextNumberPatternUtils.vRegexZoned
val optMyMatch = re.findFirstMatchIn("012V34+") // for zoned, overpunched
trailing sign.
val Some(re("", "012", "34", "+")) = optMyMatch
}
@Test def testVRegexZonedNothingAfterSuffix(): Unit = {
- val re = TextNumberPatternUtils.vregexZoned
+ val re = TextNumberPatternUtils.vRegexZoned
val optMyMatch = re.findFirstMatchIn("012V34+garbage") // for zoned,
overpunched trailing sign.
optMyMatch match {
case Some(re("", "012", "34", "+")) => fail("accepted trash at end of
pattern")
@@ -82,7 +109,7 @@ class TestPrimitives {
}
@Test def testVRegexZonedSignNotPlus(): Unit = {
- val re = TextNumberPatternUtils.vregexZoned
+ val re = TextNumberPatternUtils.vRegexZoned
val optMyMatch = re.findFirstMatchIn("A012V34")
optMyMatch match {
case Some(x @ re(_*)) => fail(s"accepted A as leading sign: $x")
@@ -92,16 +119,45 @@ class TestPrimitives {
@Test def testVRegexZonedTwoSigns(): Unit = {
assertTrue(
-
TextNumberPatternUtils.textDecimalVirtualPointForZoned("+012V34+").isEmpty
+
TextNumberPatternUtils.textNumber_V_DecimalVirtualPointForZoned("+012V34+").isEmpty
)
}
@Test def testRemoveUnquotedPAndV_01(): Unit = {
- val actually = "zVz''Va'PPP'''V'b'''"
- val expected = "zz''a'P'''V'b'''"
- assertEquals(expected, TextNumberPatternUtils.removeUnquotedPV(actually))
+ val pattern = "PPzVz''Va'PPP'''V'b'''"
+ val expected = "zz''a'PPP''''b'''"
+ assertEquals(expected, TextNumberPatternUtils.removeUnquotedPV(pattern))
+ }
+
+ /**
+ * This test shows that quoting of characters in ICU text number patterns
+ * and hence DFDL text number patterns can quote strings, not just individual
+ * characters.
+ *
+ * I found no examples of this anywhere. Everything shows patterns where
+ * quotes surround only single characters, or are doubled up for self
quoting.
+ *
+ * This is ICU behavior however, DFDL v1.0 specifies that the prefix/suffix
+ * can only be single characters.
+ */
+ @Test def testICUDecimalFormatQuoting_01(): Unit = {
+ {
+ // Note that E is a pattern special character
+ val pattern = "'POSITIVE' #.0###;'NEGATIVE' #.0###"
+ val df = new DecimalFormat(pattern)
+ val actual = df.format(-6.847)
+ assertEquals("NEGATIVE 6.847", actual)
+ }
+ {
+ // here we quote only the E, not the other characters.
+ val pattern = "POSITIV'E' #.0###;NEGATIV'E' #.0###"
+ val df = new DecimalFormat(pattern)
+ val actual = df.format(6.847)
+ assertEquals("POSITIVE 6.847", actual)
+ }
}
+
@Test def howToUseRegexLookBehindWithReplaceAll(): Unit = {
// despite the fact that we say "P not preceded by ...." that's not how you
// structure the regex.
@@ -142,7 +198,7 @@ class TestPrimitives {
}
@Test def testZonedVRegexWithPrefix(): Unit = {
- val re = TextNumberPatternUtils.vregexZoned
+ val re = TextNumberPatternUtils.vRegexZoned
val Some(myMatch) = re.findFirstMatchIn("+012V34")
myMatch match {
case re("+", "012", "34", "") => // ok
@@ -150,10 +206,71 @@ class TestPrimitives {
}
@Test def testZonedVRegexWithSuffix(): Unit = {
- val re = TextNumberPatternUtils.vregexZoned
+ val re = TextNumberPatternUtils.vRegexZoned
val Some(myMatch) = re.findFirstMatchIn("012V34+")
myMatch match {
case re("", "012", "34", "+") => // ok
}
}
+
+ @Test def testStandardPOnLeft(): Unit = {
+ val re = TextNumberPatternUtils.pOnLeftRegexStandard
+ println(re)
+ val Some(myMatch) = re.findFirstMatchIn("+PPP000")
+ myMatch match {
+ case re("+", "PPP", "000", "", null, null, null) => // ok
+ }
+ }
+
+ @Test def testStandardPOnLeft2(): Unit = {
+ val re = TextNumberPatternUtils.pOnLeftRegexStandard
+ println(re)
+ val Some(myMatch) = re.findFirstMatchIn("+PPP000;-#")
+ myMatch match {
+ case re("+", "PPP", "000", "", "-", "#", "") => // ok
+ }
+ }
+
+ @Test def testStandardPOnLeft3(): Unit = {
+ val re = TextNumberPatternUtils.pOnLeftRegexStandard
+ println(re)
+ val Some(myMatch) = re.findFirstMatchIn("+PPP000;-P0")
+ myMatch match {
+ case re("+", "PPP", "000", "", "-", "P0", "") => // ok
+ }
+ }
+
+ @Test def testStandardPOnRight(): Unit = {
+ val re = TextNumberPatternUtils.pOnRightRegexStandard
+ val Some(myMatch) = re.findFirstMatchIn("000PPP+")
+ myMatch match {
+ case re("", "000", "PPP", "+", null, null, null) => // ok
+ }
+ }
+
+ @Test def testStandardPOnRight2(): Unit = {
+ val re = TextNumberPatternUtils.pOnRightRegexStandard
+ val Some(myMatch) = re.findFirstMatchIn("000PPP+;0P-")
+ myMatch match {
+ case re("", "000", "PPP", "+", "", "0P", "-") => // ok
+ }
+ }
+
+ @Test def testZonedPOnLeft(): Unit = {
+ val re = TextNumberPatternUtils.pOnLeftRegexZoned
+ val Some(myMatch) = re.findFirstMatchIn("+PPP000")
+ myMatch match {
+ case re("+", "PPP", "000", "") => // ok
+ }
+ }
+
+ @Test def testZonedPOnRight(): Unit = {
+ val re = TextNumberPatternUtils.pOnRightRegexZoned
+ val Some(myMatch) = re.findFirstMatchIn("000PPP+")
+ myMatch match {
+ case re("", "000", "PPP", "+") => // ok
+ }
+ }
+
+
}
diff --git
a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
index acd5d78e1..dc4f4dec4 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
@@ -684,6 +684,7 @@
<xs:enumeration value="queryStylePathExpression" />
<xs:enumeration value="regexPatternZeroLength" />
<xs:enumeration value="textBidiError" />
+ <xs:enumeration value="textNumberPatternWarning" />
<xs:enumeration value="textOutputMinLengthOutOfRange" />
<xs:enumeration value="textStandardBaseUndefined" />
<xs:enumeration value="unsupportedAttributeBlockDefault" />
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ConvertTextStandardNumberParser.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ConvertTextStandardNumberParser.scala
index 81b20d4e2..f7dd1a4c5 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ConvertTextStandardNumberParser.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ConvertTextStandardNumberParser.scala
@@ -17,13 +17,11 @@
package org.apache.daffodil.processors.parsers
-import com.ibm.icu.math.{ BigDecimal => ICUBigDecimal }
+import com.ibm.icu.math.{BigDecimal => ICUBigDecimal}
import com.ibm.icu.text.DecimalFormat
import java.text.ParsePosition
-
import scala.util.matching.Regex
-
import org.apache.daffodil.dpath.NodeInfo
import org.apache.daffodil.dpath.InvalidPrimitiveDataException
import org.apache.daffodil.exceptions.Assert
@@ -34,7 +32,8 @@ import org.apache.daffodil.processors.Success
import org.apache.daffodil.processors.TermRuntimeData
import org.apache.daffodil.processors.TextNumberFormatEv
-import java.lang.{Long => JLong, Float => JFloat, Double => JDouble, Number =>
JNumber}
+import java.lang.{Float => JFloat, Number => JNumber, Long => JLong, Double =>
JDouble}
+import java.math.MathContext
import java.math.{BigDecimal => JBigDecimal}
case class ConvertTextCombinatorParser(
@@ -59,7 +58,7 @@ case class ConvertTextCombinatorParser(
trait TextDecimalVirtualPointMixin {
def textDecimalVirtualPoint: Int
- final protected val virtualPointScaleFactor = scala.math.pow(10.0,
textDecimalVirtualPoint)
+ final protected lazy val virtualPointScaleFactor = scala.math.pow(10.0,
textDecimalVirtualPoint)
final protected def applyTextDecimalVirtualPointForParse(num1: JNumber):
JNumber = {
if (textDecimalVirtualPoint == 0) num1
@@ -68,7 +67,11 @@ trait TextDecimalVirtualPointMixin {
val scaledNum: JNumber = num1 match {
// Empirically, in our test suite, we do get Long back here, so the
runtime sometimes represents small integer
// (or possibly even smaller decimal numbers with no fraction part) as
Long.
- case l: JLong =>
JBigDecimal.valueOf(l).scaleByPowerOfTen(-textDecimalVirtualPoint)
+ case l: JLong => {
+ val scaled =
JBigDecimal.valueOf(l).scaleByPowerOfTen(-textDecimalVirtualPoint)
+ if (textDecimalVirtualPoint < 0) scaled.toBigIntegerExact()
+ else scaled
+ }
case bd: JBigDecimal => bd.scaleByPowerOfTen(-textDecimalVirtualPoint)
case f: JFloat => (f / virtualPointScaleFactor).toFloat
case d: JDouble => (d / virtualPointScaleFactor).toDouble
@@ -89,21 +92,52 @@ trait TextDecimalVirtualPointMixin {
}
// $COVERAGE-ON$
+
+ /**
+ * Always creates an integer from a JFloat, JDouble, or JBigDecimal
+ * by scaling the argument by the virtual decimal point scaling factor.
+ *
+ * If the argument is some other JNumber, i.e., not a JFloat, JDouble, or
JBigDecimal
+ * it can only be an integer type. This method returns the argument value
+ * only if no virtual decimal point scaling is needed.
+ * Otherwise aborts since only JFloat, JDouble, or JDecimal can be scaled in
this way.
+ *
+ * Floating point NaNs and Infinities are tolerated. (No scaling occurs.)
+ *
+ * @param valueAsAnyRef value to be scaled. Should be a JNumber. Aborts
otherwise.
+ * @return a JNumber of the same concrete type as the argument.
+ */
final protected def applyTextDecimalVirtualPointForUnparse(valueAsAnyRef:
AnyRef) : JNumber = {
- valueAsAnyRef match {
+ val res: JNumber = valueAsAnyRef match {
+ case jn: JNumber if (textDecimalVirtualPoint == 0) => jn
// This is not perfectly symmetrical with the parse side equivalent.
// Empirically in our test suite, we do not see JLong here.
- case f: JFloat => (f * virtualPointScaleFactor).toFloat
- case d: JDouble => (d * virtualPointScaleFactor).toDouble
- case bd: JBigDecimal => bd.scaleByPowerOfTen(textDecimalVirtualPoint)
-
+ //
+ // If the base 10 data is 1.23 and it becomes a float, that's
1.2300000190734863
+ // That is to say, it isn't a perfect representation of 1.23, so we
don't know for
+ // certain that when we unscale it by the virtualPointScaleFactor that
it will
+ // become an integer. We just know it will be close to an integer.
+ // The same holds for a decimal number that was created via math
involving division.
+ // In that case there may be fraction digits that will still exist even
after we scale
+ // the value so that it "should be" an integer.
+ //
+ // So if it is float/double/decimal after scaling we round it to be
exactly an
+ // integer.
+ //
+ case f: JFloat if f.isNaN || f.isInfinite => f
+ case f: JFloat => (f * virtualPointScaleFactor).round.toFloat
+ case d: JDouble if d.isNaN || d.isInfinite => d
+ case d: JDouble => (d * virtualPointScaleFactor).round.toDouble
+ case bd: JBigDecimal =>
bd.scaleByPowerOfTen(textDecimalVirtualPoint).round(MathContext.UNLIMITED)
case n: JNumber =>
- if (textDecimalVirtualPoint == 0) n
// $COVERAGE-OFF$ // both badType and the next case are coverage-off
- else badType(n)
+ badType(n)
case _ => Assert.invariantFailed("Not a JNumber")
// $COVERAGE-ON$
}
+ // Result type is same as argument type.
+ Assert.invariant(res.getClass == valueAsAnyRef.getClass)
+ res
}
}
diff --git
a/daffodil-test/src/test/resources/org/apache/daffodil/extensions/enum/enumInvalid.tdml
b/daffodil-test/src/test/resources/org/apache/daffodil/extensions/enum/enumInvalid.tdml
index 7a33662e0..ef0444d5c 100644
---
a/daffodil-test/src/test/resources/org/apache/daffodil/extensions/enum/enumInvalid.tdml
+++
b/daffodil-test/src/test/resources/org/apache/daffodil/extensions/enum/enumInvalid.tdml
@@ -46,7 +46,7 @@
</complexType>
</element>
- <simpleType name="enum2" dfdlx:repType="ex:myByte">
+ <simpleType name="enum2" dfdlx:repType="xs:byte">
<restriction base="xs:string">
<enumeration value="valid" dfdlx:repValues="0"/>
<enumeration value="empty" dfdlx:repValues=""/>
diff --git
a/daffodil-test/src/test/resources/org/apache/daffodil/section13/zoned/pv.tdml
b/daffodil-test/src/test/resources/org/apache/daffodil/section13/zoned/pv.tdml
index 82ae777a0..eea73dec3 100644
---
a/daffodil-test/src/test/resources/org/apache/daffodil/section13/zoned/pv.tdml
+++
b/daffodil-test/src/test/resources/org/apache/daffodil/section13/zoned/pv.tdml
@@ -47,21 +47,48 @@
<element name="money" type="xs:decimal"
dfdl:textNumberPattern="######0V00;-######0V00"/>
<element name="money2" type="xs:decimal"
dfdl:textNumberPattern="[######0V00];(######0V00)"/>
+ <element name="money3" type="xs:decimal"
dfdl:textNumberPattern="[######0V00];(#)"/>
+
<element name="bad01" type="xs:decimal"
dfdl:textNumberPattern=";-######0V00"/>
<element name="bad02" type="xs:decimal"
dfdl:textNumberPattern="######0V0#"/>
<element name="bad03" type="xs:decimal"
dfdl:textNumberPattern="##0###0V0#"/>
- <element name="badP01" type="xs:decimal"
dfdl:textNumberPattern="PPP000V00"/>
-
+ <!--
+ Should produce warning that negative pattern and positive pattern are not
matching
+ -->
+ <element name="warn04" type="xs:decimal"
dfdl:textNumberPattern="#0V00;-###"/>
<element name="float" type="xs:float"
dfdl:textNumberPattern="##0V00;-##0V00"/>
<element name="double" type="xs:double"
dfdl:textNumberPattern="##0V00;-##0V00"/>
<element name="byte" type="xs:byte" dfdl:textNumberPattern="0V0"/>
+
+ <element name="pOnLeft" type="xs:decimal"
dfdl:textNumberPattern="PP000;-PP000"/>
+
+ <element name="pOnRight" type="xs:decimal"
dfdl:textNumberPattern="##0PP+;##0PP-"/>
+
</tdml:defineSchema>
+ <parserTestCase name="ppattern_01" root="pOnLeft" model="s1">
+ <document>123</document>
+ <infoset>
+ <dfdlInfoset>
+ <ex:pOnLeft>0.00123</ex:pOnLeft>
+ </dfdlInfoset>
+ </infoset>
+ </parserTestCase>
+
+ <parserTestCase name="ppattern_02" root="pOnRight" model="s1">
+ <document>123-</document>
+ <infoset>
+ <dfdlInfoset>
+ <ex:pOnRight>-12300</ex:pOnRight>
+ </dfdlInfoset>
+ </infoset>
+ </parserTestCase>
+
<parserTestCase name="vpattern_01" root="money" model="s1">
<document>999999999</document>
@@ -119,6 +146,15 @@
</infoset>
</parserTestCase>
+ <parserTestCase name="vpattern_07" root="money3" model="s1">
+ <document>(999)</document>
+ <infoset>
+ <dfdlInfoset>
+ <ex:money3>-9.99</ex:money3>
+ </dfdlInfoset>
+ </infoset>
+ </parserTestCase>
+
<parserTestCase name="vpattern_zero" root="money" model="s1">
<document>zero</document>
<infoset>
@@ -215,12 +251,19 @@
</errors>
</parserTestCase>
- <parserTestCase name="vpattern_bad_P01" root="badP01" model="s1">
+ <parserTestCase name="vpattern_warn_04" root="warn04" model="s1">
<document>999999999</document>
- <errors>
- <error>textNumberPattern</error>
- <error>not yet implemented</error>
- </errors>
+ <infoset>
+ <dfdlInfoset>
+ <ex:warn04>9999999.99</ex:warn04>
+ </dfdlInfoset>
+ </infoset>
+ <warnings>
+ <warning>textNumberPattern</warning>
+ <warning>negative numeric part</warning>
+ <warning>###</warning>
+ <warning>#0V00</warning>
+ </warnings>
</parserTestCase>
<parserTestCase name="float_vpattern_01" root="float" model="s1">
@@ -276,9 +319,6 @@
<element name="bad02" type="xs:decimal"
dfdl:textNumberPattern="######0V0#"/>
<element name="bad03" type="xs:decimal"
dfdl:textNumberPattern="##0###0V0#"/>
- <element name="badP01" type="xs:decimal"
dfdl:textNumberPattern="PPP000V00"/>
-
-
<element name="float" type="xs:float" dfdl:textNumberPattern="##0V00"/>
<element name="double" type="xs:double" dfdl:textNumberPattern="##0V00"/>
@@ -362,14 +402,6 @@
</errors>
</parserTestCase>
- <parserTestCase name="zoned_vpattern_bad_P01" root="badP01" model="zoned1">
- <document>999999999</document>
- <errors>
- <error>textNumberPattern</error>
- <error>not yet implemented</error>
- </errors>
- </parserTestCase>
-
<parserTestCase name="zoned_float_vpattern_01" root="float" model="zoned1">
<document>99999</document>
<errors>
diff --git
a/daffodil-test/src/test/scala/org/apache/daffodil/section13/zoned/TestPV.scala
b/daffodil-test/src/test/scala/org/apache/daffodil/section13/zoned/TestPV.scala
index 64213aaa4..3fd00029c 100644
---
a/daffodil-test/src/test/scala/org/apache/daffodil/section13/zoned/TestPV.scala
+++
b/daffodil-test/src/test/scala/org/apache/daffodil/section13/zoned/TestPV.scala
@@ -40,6 +40,7 @@ class TestPV {
@Test def vpattern_04(): Unit = { runner.runOneTest("vpattern_04") }
@Test def vpattern_05(): Unit = { runner.runOneTest("vpattern_05") }
@Test def vpattern_06(): Unit = { runner.runOneTest("vpattern_06") }
+ @Test def vpattern_07(): Unit = { runner.runOneTest("vpattern_07") }
@Test def vpattern_zero(): Unit = { runner.runOneTest("vpattern_zero") }
@Test def vpattern_ZZZ(): Unit = { runner.runOneTest("vpattern_ZZZ") }
@@ -57,7 +58,7 @@ class TestPV {
@Test def vpattern_bad_01(): Unit = { runner.runOneTest("vpattern_bad_01") }
@Test def vpattern_bad_02(): Unit = { runner.runOneTest("vpattern_bad_02") }
@Test def vpattern_bad_03(): Unit = { runner.runOneTest("vpattern_bad_03") }
- @Test def vpattern_bad_P01(): Unit = { runner.runOneTest("vpattern_bad_P01")
}
+ @Test def vpattern_warn_04(): Unit = { runner.runOneTest("vpattern_warn_04")
}
@Test def zoned_vpattern_01(): Unit = {
runner.runOneTest("zoned_vpattern_01") }
@@ -74,7 +75,10 @@ class TestPV {
@Test def zoned_vpattern_bad_01(): Unit = {
runner.runOneTest("zoned_vpattern_bad_01") }
@Test def zoned_vpattern_bad_02(): Unit = {
runner.runOneTest("zoned_vpattern_bad_02") }
@Test def zoned_vpattern_bad_03(): Unit = {
runner.runOneTest("zoned_vpattern_bad_03") }
- @Test def zoned_vpattern_bad_P01(): Unit = {
runner.runOneTest("zoned_vpattern_bad_P01") }
+
+ @Test def ppattern_01(): Unit = { runner.runOneTest("ppattern_01") }
+ @Test def ppattern_02(): Unit = { runner.runOneTest("ppattern_02") }
+
}