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]

Reply via email to