Author: fanningpj
Date: Sun Jun 21 00:10:15 2026
New Revision: 1935532
Log:
reject exponent notation in lexDecimal. Thanks to aizu-m. This closes #63
Modified:
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/XmlOptions.java
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/store/Locale.java
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java
xmlbeans/trunk/src/test/java/misc/checkin/XmlOptionsTest.java
xmlbeans/trunk/src/test/java/misc/checkin/XsTypeConverterTest.java
Modified: xmlbeans/trunk/src/main/java/org/apache/xmlbeans/XmlOptions.java
==============================================================================
--- xmlbeans/trunk/src/main/java/org/apache/xmlbeans/XmlOptions.java Sat Jun
20 12:45:03 2026 (r1935531)
+++ xmlbeans/trunk/src/main/java/org/apache/xmlbeans/XmlOptions.java Sun Jun
21 00:10:15 2026 (r1935532)
@@ -158,7 +158,8 @@ public class XmlOptions implements java.
XPATH_USE_SAXON,
XPATH_USE_XMLBEANS,
ATTRIBUTE_VALIDATION_COMPAT_MODE,
- LOAD_STRICT_FLOATING_POINT
+ LOAD_STRICT_FLOATING_POINT,
+ LOAD_ALLOW_DECIMAL_EXPONENT
}
@@ -1208,6 +1209,45 @@ public class XmlOptions implements java.
}
/**
+ * If this option is set, xsd:decimal values are allowed to use
scientific/exponent
+ * notation (e.g. {@code 1E5}) when parsing. That form is outside the
xsd:decimal
+ * lexical space - it belongs to xsd:double/xsd:float - and {@link
java.math.BigDecimal}
+ * would otherwise parse it to a wrong value ({@code 1E5 -> 100000}). The
default is to
+ * disallow it: an exponent in a decimal is reported as invalid. Such
values can also be
+ * expensive to parse when the exponent is very large. Set this only to
restore the
+ * long-standing lenient behaviour.
+ *
+ * @return this
+ * @since 5.4.0
+ */
+ public XmlOptions setLoadAllowDecimalExponent() {
+ return setLoadAllowDecimalExponent(true);
+ }
+
+ /**
+ * Sets whether xsd:decimal values may use scientific/exponent notation
when parsing.
+ * See {@link #setLoadAllowDecimalExponent()}.
+ *
+ * @param b {@code true} to accept an exponent in a decimal lexical value
+ * @return this
+ * @since 5.4.0
+ */
+ public XmlOptions setLoadAllowDecimalExponent(boolean b) {
+ return set(XmlOptionsKeys.LOAD_ALLOW_DECIMAL_EXPONENT, b);
+ }
+
+ /**
+ * Returns whether xsd:decimal values may use scientific/exponent notation
when parsing.
+ * See {@link #setLoadAllowDecimalExponent()}.
+ *
+ * @return {@code true} if an exponent is accepted in a decimal lexical
value
+ * @since 5.4.0
+ */
+ public boolean isLoadAllowDecimalExponent() {
+ return hasOption(XmlOptionsKeys.LOAD_ALLOW_DECIMAL_EXPONENT);
+ }
+
+ /**
* Instructs the validator to skip elements matching an {@code <any>}
* particle with contentModel="lax". This is useful because,
* in certain situations, XmlBeans will find types on the
Modified:
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java
==============================================================================
--- xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java
Sat Jun 20 12:45:03 2026 (r1935531)
+++ xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java
Sun Jun 21 00:10:15 2026 (r1935532)
@@ -29,4 +29,9 @@ public interface XmlLocale
// the xsd:float/xsd:double space (hex floats, the java "Infinity" token
and
// the f/F/d/D suffix). Driven by XmlOptions.setLoadStrictFloatingPoint.
default boolean isLoadStrictFloatingPoint ( ) { return false; }
+
+ // whether xsd:decimal lexing should accept scientific/exponent notation
+ // (e.g. "1E5"), which is outside the xsd:decimal lexical space. Defaults
to
+ // false (reject). Driven by XmlOptions.setLoadAllowDecimalExponent.
+ default boolean isLoadAllowDecimalExponent ( ) { return false; }
}
Modified:
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/store/Locale.java
==============================================================================
--- xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/store/Locale.java
Sat Jun 20 12:45:03 2026 (r1935531)
+++ xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/store/Locale.java
Sun Jun 21 00:10:15 2026 (r1935532)
@@ -105,6 +105,8 @@ public final class Locale
_loadStrictFloatingPoint = options.isLoadStrictFloatingPoint();
+ _loadAllowDecimalExponent = options.isLoadAllowDecimalExponent();
+
//
// Check for Saaj implementation request
//
@@ -2077,6 +2079,10 @@ public final class Locale
return _loadStrictFloatingPoint;
}
+ public boolean isLoadAllowDecimalExponent() {
+ return _loadAllowDecimalExponent;
+ }
+
static boolean isWhiteSpace(String s) {
int l = s.length();
@@ -2797,6 +2803,8 @@ public final class Locale
boolean _loadStrictFloatingPoint;
+ boolean _loadAllowDecimalExponent;
+
int _posTemp;
nthCache _nthCache_A = new nthCache();
Modified:
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java
==============================================================================
---
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java
Sat Jun 20 12:45:03 2026 (r1935531)
+++
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java
Sun Jun 21 00:10:15 2026 (r1935532)
@@ -215,7 +215,30 @@ public final class XsTypeConverter {
// ======================== decimal ========================
public static BigDecimal lexDecimal(CharSequence cs)
throws NumberFormatException {
+ return lexDecimal(cs, false);
+ }
+
+ /**
+ * Parses an xsd:decimal lexical value.
+ *
+ * @param cs the lexical value
+ * @param allowExponent when {@code false} (the default)
scientific/exponent notation
+ * such as {@code 1E5} is rejected: it is outside the
xsd:decimal
+ * lexical space (that form belongs to
xsd:double/xsd:float) and
+ * {@link BigDecimal} would otherwise parse it to a
wrong value
+ * ({@code 1E5 -> 100000}). When {@code true} the
long-standing
+ * lenient behaviour applies and an exponent is
accepted. Driven by
+ * {@link
org.apache.xmlbeans.XmlOptions#setLoadAllowDecimalExponent()}.
+ * @return the parsed decimal
+ * @throws NumberFormatException if the value is not a valid xsd:decimal
+ * @since 5.4.0
+ */
+ public static BigDecimal lexDecimal(CharSequence cs, boolean allowExponent)
+ throws NumberFormatException {
rejectInvalidNumber(cs);
+ if (!allowExponent) {
+ rejectExponent(cs);
+ }
final String v = cs.toString();
//TODO: review this
@@ -754,4 +777,18 @@ public final class XsTypeConverter {
throw new NumberFormatException("For input string: \"" + cs +
"\"");
}
}
+
+ // BigDecimal accepts scientific notation such as "1E5", but the
xsd:decimal
+ // lexical space does not allow an exponent - that form belongs to
xsd:double
+ // and xsd:float. Without this check an exponent value reaching lexDecimal
via
+ // the rich parser parses to a wrong value (e.g. "1E5" -> 100000) instead
of
+ // being reported as invalid.
+ private static void rejectExponent(CharSequence cs) {
+ for (int i = 0, len = cs.length(); i < len; i++) {
+ final char c = cs.charAt(i);
+ if (c == 'e' || c == 'E') {
+ throw new NumberFormatException("invalid char '" + c + "' in
decimal value");
+ }
+ }
+ }
}
Modified:
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java
==============================================================================
---
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java
Sat Jun 20 12:45:03 2026 (r1935531)
+++
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolder.java
Sun Jun 21 00:10:15 2026 (r1935532)
@@ -41,8 +41,9 @@ public class JavaDecimalHolder extends X
}
protected void set_text(String s) {
+ boolean allowExponent = has_store() &&
get_store().get_locale().isLoadAllowDecimalExponent();
if (_validateOnSet()) {
- validateLexical(s, _voorVc);
+ validateLexical(s, _voorVc, allowExponent);
}
try {
@@ -61,6 +62,21 @@ public class JavaDecimalHolder extends X
*/
public static void validateLexical(String v, ValidationContext context) {
+ validateLexical(v, context, false);
+ }
+
+ public static void validateLexical(String v, ValidationContext context,
boolean allowExponent) {
+ if (allowExponent) {
+ // long-standing lenient behaviour: accept whatever BigDecimal
accepts,
+ // which includes scientific/exponent notation such as "1E5".
+ try {
+ new BigDecimal(v);
+ } catch (NumberFormatException e) {
+ context.invalid(XmlErrorCodes.DECIMAL, new Object[]{v});
+ }
+ return;
+ }
+
// TODO - will want to validate Chars with built in white space
handling
// However, this fcn sometimes takes a value with wsr applied
// already
Modified:
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java
==============================================================================
---
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java
Sat Jun 20 12:45:03 2026 (r1935531)
+++
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/values/JavaDecimalHolderEx.java
Sun Jun 21 00:10:15 2026 (r1935532)
@@ -38,7 +38,8 @@ public abstract class JavaDecimalHolderE
protected void set_text(String s) {
if (_validateOnSet()) {
- validateLexical(s, _schemaType, _voorVc);
+ boolean allowExponent = has_store() &&
get_store().get_locale().isLoadAllowDecimalExponent();
+ validateLexical(s, _schemaType, _voorVc, allowExponent);
}
BigDecimal v = null;
@@ -63,7 +64,11 @@ public abstract class JavaDecimalHolderE
}
public static void validateLexical(String v, SchemaType sType,
ValidationContext context) {
- JavaDecimalHolder.validateLexical(v, context);
+ validateLexical(v, sType, context, false);
+ }
+
+ public static void validateLexical(String v, SchemaType sType,
ValidationContext context, boolean allowExponent) {
+ JavaDecimalHolder.validateLexical(v, context, allowExponent);
// check pattern
if (sType.hasPatternFacet()) {
@@ -189,7 +194,8 @@ public abstract class JavaDecimalHolderE
}
protected void validate_simpleval(String lexical, ValidationContext ctx) {
- validateLexical(lexical, schemaType(), ctx);
+ boolean allowExponent = has_store() &&
get_store().get_locale().isLoadAllowDecimalExponent();
+ validateLexical(lexical, schemaType(), ctx, allowExponent);
validateValue(getBigDecimalValue(), schemaType(), ctx);
}
Modified: xmlbeans/trunk/src/test/java/misc/checkin/XmlOptionsTest.java
==============================================================================
--- xmlbeans/trunk/src/test/java/misc/checkin/XmlOptionsTest.java Sat Jun
20 12:45:03 2026 (r1935531)
+++ xmlbeans/trunk/src/test/java/misc/checkin/XmlOptionsTest.java Sun Jun
21 00:10:15 2026 (r1935532)
@@ -42,6 +42,16 @@ public class XmlOptionsTest {
}
@Test
+ void testLoadAllowDecimalExponentFlag() {
+ XmlOptions xmlOptions = new XmlOptions();
+ assertFalse(xmlOptions.isLoadAllowDecimalExponent());
+ xmlOptions.setLoadAllowDecimalExponent();
+ assertTrue(xmlOptions.isLoadAllowDecimalExponent());
+ xmlOptions.setLoadAllowDecimalExponent(false);
+ assertFalse(xmlOptions.isLoadAllowDecimalExponent());
+ }
+
+ @Test
void testSaveNoAttributeWhitespaceEscapeFlag() {
XmlOptions xmlOptions = new XmlOptions();
assertFalse(xmlOptions.isSaveNoAttributeWhitespaceEscape());
Modified: xmlbeans/trunk/src/test/java/misc/checkin/XsTypeConverterTest.java
==============================================================================
--- xmlbeans/trunk/src/test/java/misc/checkin/XsTypeConverterTest.java Sat Jun
20 12:45:03 2026 (r1935531)
+++ xmlbeans/trunk/src/test/java/misc/checkin/XsTypeConverterTest.java Sun Jun
21 00:10:15 2026 (r1935532)
@@ -14,6 +14,7 @@
*/
package misc.checkin;
+import org.apache.xmlbeans.XmlDecimal;
import org.apache.xmlbeans.XmlDouble;
import org.apache.xmlbeans.XmlFloat;
import org.apache.xmlbeans.XmlOptions;
@@ -198,6 +199,20 @@ public class XsTypeConverterTest {
}
@Test
+ void loadAllowDecimalExponentOptionGatesDecimalParsing() throws Exception {
+ // an exponent is outside the xsd:decimal lexical space, so validation
+ // rejects "1E5" by default
+ XmlOptions validate = new XmlOptions().setValidateOnSet();
+ assertThrows(XmlValueOutOfRangeException.class, () ->
+ XmlDecimal.Factory.parse("<xml-fragment>1E5</xml-fragment>",
validate).getBigDecimalValue());
+
+ // setLoadAllowDecimalExponent restores the lenient behaviour
+ XmlOptions lenient = new
XmlOptions().setValidateOnSet().setLoadAllowDecimalExponent();
+ assertEquals(0, new java.math.BigDecimal("100000").compareTo(
+ XmlDecimal.Factory.parse("<xml-fragment>1E5</xml-fragment>",
lenient).getBigDecimalValue()));
+ }
+
+ @Test
void lexLongRejectsDoubleSign() {
// trimInitialPlus drops the leading '+', then Long.parseLong accepts
its
// own sign, so "++5"/"+-5" used to parse as 5/-5 instead of being
rejected.
@@ -235,4 +250,35 @@ public class XsTypeConverterTest {
assertEquals(0, new
java.math.BigDecimal("1.5").compareTo(XsTypeConverter.lexDecimal("1.500")));
assertEquals(0, new
java.math.BigDecimal("12").compareTo(XsTypeConverter.lexDecimal("12.000")));
}
+
+ @Test
+ void lexDecimalRejectsExponent() {
+ // BigDecimal accepts scientific notation, but the xsd:decimal lexical
+ // space has no exponent - "1E5" used to parse to 100000 instead of
+ // being rejected as invalid. This is the default (allowExponent =
false).
+ assertThrows(NumberFormatException.class, () ->
XsTypeConverter.lexDecimal("1E5"));
+ assertThrows(NumberFormatException.class, () ->
XsTypeConverter.lexDecimal("1.5e3"));
+ assertThrows(NumberFormatException.class, () ->
XsTypeConverter.lexDecimal("-2E-3"));
+ assertThrows(NumberFormatException.class, () ->
XsTypeConverter.lexDecimal("1E5", false));
+ // plain decimals stay valid
+ assertEquals(0, new
java.math.BigDecimal("1.5").compareTo(XsTypeConverter.lexDecimal("1.5")));
+ }
+
+ @Test
+ void lexDecimalAllowsExponentWhenRequested() {
+ // XmlOptions.setLoadAllowDecimalExponent restores the lenient
behaviour:
+ // the exponent form is accepted again (e.g. "1E5" -> 100000).
+ assertEquals(0, new
java.math.BigDecimal("100000").compareTo(XsTypeConverter.lexDecimal("1E5",
true)));
+ assertEquals(0, new
java.math.BigDecimal("1500").compareTo(XsTypeConverter.lexDecimal("1.5e3",
true)));
+ // plain decimals are unaffected by the flag
+ assertEquals(0, new
java.math.BigDecimal("1.5").compareTo(XsTypeConverter.lexDecimal("1.5", true)));
+ }
+
+ @Test
+ void lexIntegerRejectsExponent() {
+ // xs:integer is parsed with BigInteger, which never accepts an
exponent,
+ // so the exponent form is already rejected for integer types.
+ assertThrows(NumberFormatException.class, () ->
XsTypeConverter.lexInteger("1E5"));
+ assertThrows(NumberFormatException.class, () ->
XsTypeConverter.lexInteger("1e5"));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]