Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
build/
.gradle/
lib/**/*.jar
/bin
gradle/wrapper/gradle-wrapper.jar
Expand Down
44 changes: 43 additions & 1 deletion src/main/java/org/apache/xmlbeans/XmlOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ public enum XmlOptionsKeys {
LOAD_USE_LOCALE_CHAR_UTIL,
XPATH_USE_SAXON,
XPATH_USE_XMLBEANS,
ATTRIBUTE_VALIDATION_COMPAT_MODE
ATTRIBUTE_VALIDATION_COMPAT_MODE,
LOAD_STRICT_FLOATING_POINT
}


Expand Down Expand Up @@ -1123,6 +1124,47 @@ public boolean isValidateOnSet() {
return hasOption(XmlOptionsKeys.VALIDATE_ON_SET);
}

/**
* If this option is set, xsd:float and xsd:double values are held to the XSD
* lexical space when parsing. {@link Float#parseFloat}/{@link Double#parseDouble}
* also accept lexical forms that XSD does not allow: hexadecimal floats
* ({@code 0x1p4}), the Java {@code Infinity} token, and a trailing type suffix
* ({@code f}/{@code F}/{@code d}/{@code D}). With this option set those forms are
* rejected as invalid; XSD only permits a decimal number with an optional
* exponent, or the special values {@code INF}, {@code -INF} and {@code NaN}.
* The default value is false, so the long-standing lenient behaviour is
* unchanged unless this is set.
*
* @return this
* @since 5.4.0
*/
public XmlOptions setLoadStrictFloatingPoint() {
return setLoadStrictFloatingPoint(true);
}

/**
* Sets whether xsd:float and xsd:double values are held to the XSD lexical
* space when parsing. See {@link #setLoadStrictFloatingPoint()}.
*
* @param b {@code true} to reject lexical forms outside the XSD float/double space
* @return this
* @since 5.4.0
*/
public XmlOptions setLoadStrictFloatingPoint(boolean b) {
return set(XmlOptionsKeys.LOAD_STRICT_FLOATING_POINT, b);
}

/**
* Returns whether xsd:float and xsd:double values are held to the XSD lexical
* space when parsing. See {@link #setLoadStrictFloatingPoint()}.
*
* @return {@code true} if strict XSD float/double parsing is enabled
* @since 5.4.0
*/
public boolean isLoadStrictFloatingPoint() {
return hasOption(XmlOptionsKeys.LOAD_STRICT_FLOATING_POINT);
}

/**
* Instructs the validator to skip elements matching an {@code <any>}
* particle with contentModel="lax". This is useful because,
Expand Down
9 changes: 7 additions & 2 deletions src/main/java/org/apache/xmlbeans/impl/common/XmlLocale.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ public interface XmlLocale
{
boolean sync ( );
boolean noSync ( );

void enter ( );
void exit ( );
}

// whether lexFloat/lexDouble should reject lexical forms that are outside
// 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; }
}
8 changes: 8 additions & 0 deletions src/main/java/org/apache/xmlbeans/impl/store/Locale.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ private Locale(SchemaTypeLoader stl, XmlOptions options) {

_validateOnSet = options.isValidateOnSet();

_loadStrictFloatingPoint = options.isLoadStrictFloatingPoint();

//
// Check for Saaj implementation request
//
Expand Down Expand Up @@ -2071,6 +2073,10 @@ public boolean sync() {
return !_noSync;
}

public boolean isLoadStrictFloatingPoint() {
return _loadStrictFloatingPoint;
}

static boolean isWhiteSpace(String s) {
int l = s.length();

Expand Down Expand Up @@ -2789,6 +2795,8 @@ public QName getQName(char[] uriSrc, int uriPos, int uriCch,

boolean _validateOnSet;

boolean _loadStrictFloatingPoint;

int _posTemp;

nthCache _nthCache_A = new nthCache();
Expand Down
116 changes: 94 additions & 22 deletions src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,59 @@ public final class XsTypeConverter {
private static final String[] URI_CHARS_TO_BE_REPLACED = {" ", "{", "}", "|", "\\", "^", "[", "]", "`"};
private static final String[] URI_CHARS_REPLACED_WITH = {"%20", "%7b", "%7d", "%7c", "%5c", "%5e", "%5b", "%5d", "%60"};

// Float.parseFloat / Double.parseDouble accept lexical forms that are not
// in the XSD float/double value space: hexadecimal floats (0x1p4), the Java
// "Infinity" token, and a trailing type suffix (f/F/d/D). XSD only allows a
// decimal number with an optional exponent, or the special values INF, -INF
// and NaN. This is only applied when strict floating point parsing is
// requested (XmlOptions.setLoadStrictFloatingPoint); the default stays lenient.
private static void checkFloatingPointLexical(CharSequence cs) {
final int len = cs.length();
for (int i = 0; i < len; i++) {
switch (cs.charAt(i)) {
case 'x':
case 'X':
case 'p':
case 'P':
case 'i':
case 't':
case 'y':
throw new NumberFormatException("invalid char '" + cs.charAt(i) + "' in floating point value");
default:
break;
}
}
if (len > 0) {
final char last = cs.charAt(len - 1);
// a trailing 'F' is only valid as the last char of "INF"
if (last == 'd' || last == 'D' ||
((last == 'f' || last == 'F') && (len < 2 || cs.charAt(len - 2) != 'N'))) {
throw new NumberFormatException("invalid trailing char '" + last + "' in floating point value");
}
}
}

// ======================== float ========================
public static float lexFloat(CharSequence cs)
throws NumberFormatException {
return lexFloat(cs, false);
}

/**
* Parses an xsd:float lexical value.
*
* @param cs the lexical value
* @param strict when {@code true}, lexical forms that {@link Float#parseFloat} accepts
* but XSD does not are rejected: hexadecimal floats ({@code 0x1p4}), the
* Java {@code Infinity} token, and a trailing type suffix
* ({@code f}/{@code F}/{@code d}/{@code D}). When {@code false} the
* long-standing lenient behaviour applies. Driven by
* {@link org.apache.xmlbeans.XmlOptions#setLoadStrictFloatingPoint()}.
* @return the parsed float
* @throws NumberFormatException if the value is not a valid xsd:float
* @since 5.4.0
*/
public static float lexFloat(CharSequence cs, boolean strict)
throws NumberFormatException {
final String v = cs.toString();
switch (v) {
Expand All @@ -50,18 +101,19 @@ public static float lexFloat(CharSequence cs)
return Float.NEGATIVE_INFINITY;
case NAN_LEX:
return Float.NaN;
default:
//current jdk impl of parseFloat calls trim() on the string.
//Any other space is illegal anyway, whether there are one or more spaces.
//so no need to do a collapse pass through the string.
if (cs.length() > 1) {
char ch = cs.charAt(cs.length() - 1);
if ((ch == 'f' || ch == 'F') && cs.charAt(cs.length() - 2) != 'N') {
throw new NumberFormatException("Invalid char '" + ch + "' in float.");
}
}
return Float.parseFloat(v);
}
//current jdk impl of parseFloat calls trim() on the string.
//Any other space is illegal anyway, whether there are one or more spaces.
//so no need to do a collapse pass through the string.
if (strict) {
checkFloatingPointLexical(cs);
} else if (cs.length() > 1) {
char ch = cs.charAt(cs.length() - 1);
if ((ch == 'f' || ch == 'F') && cs.charAt(cs.length() - 2) != 'N') {
throw new NumberFormatException("Invalid char '" + ch + "' in float.");
}
}
return Float.parseFloat(v);
}

public static float lexFloat(CharSequence cs, Collection<XmlError> errors) {
Expand Down Expand Up @@ -90,6 +142,25 @@ public static String printFloat(float value) {

// ======================== double ========================
public static double lexDouble(CharSequence cs)
throws NumberFormatException {
return lexDouble(cs, false);
}

/**
* Parses an xsd:double lexical value.
*
* @param cs the lexical value
* @param strict when {@code true}, lexical forms that {@link Double#parseDouble} accepts
* but XSD does not are rejected: hexadecimal floats ({@code 0x1p4}), the
* Java {@code Infinity} token, and a trailing type suffix
* ({@code f}/{@code F}/{@code d}/{@code D}). When {@code false} the
* long-standing lenient behaviour applies. Driven by
* {@link org.apache.xmlbeans.XmlOptions#setLoadStrictFloatingPoint()}.
* @return the parsed double
* @throws NumberFormatException if the value is not a valid xsd:double
* @since 5.4.0
*/
public static double lexDouble(CharSequence cs, boolean strict)
throws NumberFormatException {
final String v = cs.toString();
switch (v) {
Expand All @@ -99,18 +170,19 @@ public static double lexDouble(CharSequence cs)
return Double.NEGATIVE_INFINITY;
case NAN_LEX:
return Double.NaN;
default:
//current jdk impl of parseDouble calls trim() on the string.
//Any other space is illegal anyway, whether there are one or more spaces.
//so no need to do a collapse pass through the string.
if (cs.length() > 0) {
char ch = cs.charAt(cs.length() - 1);
if (ch == 'd' || ch == 'D') {
throw new NumberFormatException("Invalid char '" + ch + "' in double.");
}
}
return Double.parseDouble(v);
}
//current jdk impl of parseDouble calls trim() on the string.
//Any other space is illegal anyway, whether there are one or more spaces.
//so no need to do a collapse pass through the string.
if (strict) {
checkFloatingPointLexical(cs);
} else if (cs.length() > 0) {
char ch = cs.charAt(cs.length() - 1);
if (ch == 'd' || ch == 'D') {
throw new NumberFormatException("Invalid char '" + ch + "' in double.");
}
}
return Double.parseDouble(v);
}

public static double lexDouble(CharSequence cs, Collection<XmlError> errors) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ public static String serialize(double d) {
}

protected void set_text(String s) {
set_double(validateLexical(s, _voorVc));
boolean strict = has_store() && get_store().get_locale().isLoadStrictFloatingPoint();
set_double(validateLexical(s, _voorVc, strict));
}

public static double validateLexical(String v, ValidationContext context) {
return validateLexical(v, context, false);
}

public static double validateLexical(String v, ValidationContext context, boolean strict) {
try {
return XsTypeConverter.lexDouble(v);
return XsTypeConverter.lexDouble(v, strict);
} catch (NumberFormatException e) {
context.invalid(XmlErrorCodes.DOUBLE, new Object[]{v});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ public static String serialize(float f) {
}

protected void set_text(String s) {
set_float(validateLexical(s, _voorVc));
boolean strict = has_store() && get_store().get_locale().isLoadStrictFloatingPoint();
set_float(validateLexical(s, _voorVc, strict));
}

public static float validateLexical(String v, ValidationContext context) {
return validateLexical(v, context, false);
}

public static float validateLexical(String v, ValidationContext context, boolean strict) {
try {
return XsTypeConverter.lexFloat(v);
return XsTypeConverter.lexFloat(v, strict);
} catch (NumberFormatException e) {
context.invalid(XmlErrorCodes.FLOAT, new Object[]{v});

Expand Down
10 changes: 10 additions & 0 deletions src/test/java/misc/checkin/XmlOptionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,14 @@ void testUnsynchronizedFlag() {
xmlOptions.setUnsynchronized(false);
assertFalse(xmlOptions.isUnsynchronized());
}

@Test
void testLoadStrictFloatingPointFlag() {
XmlOptions xmlOptions = new XmlOptions();
assertFalse(xmlOptions.isLoadStrictFloatingPoint());
xmlOptions.setLoadStrictFloatingPoint();
assertTrue(xmlOptions.isLoadStrictFloatingPoint());
xmlOptions.setLoadStrictFloatingPoint(false);
assertFalse(xmlOptions.isLoadStrictFloatingPoint());
}
}
Loading
Loading