diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaStringHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaStringHolderEx.java
index 1a7fd8df8..4e581c94e 100644
--- a/src/main/java/org/apache/xmlbeans/impl/values/JavaStringHolderEx.java
+++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaStringHolderEx.java
@@ -62,13 +62,17 @@ public static void validateLexical(String v, SchemaType sType, ValidationContext
return;
}
+ // xsd length facets count characters (code points), not Java UTF-16
+ // code units, so a supplementary character counts as one
+ int cch = v.codePointCount(0, v.length());
+
// check against length
XmlObject len = sType.getFacet(SchemaType.FACET_LENGTH);
if (len != null) {
int m = ((XmlObjectBase) len).getBigIntegerValue().intValue();
- if (v.length() != m) {
+ if (cch != m) {
context.invalid(XmlErrorCodes.DATATYPE_LENGTH_VALID$STRING,
- new Object[]{"string", v.length(), m, QNameHelper.readable(sType)});
+ new Object[]{"string", cch, m, QNameHelper.readable(sType)});
return;
}
}
@@ -77,9 +81,9 @@ public static void validateLexical(String v, SchemaType sType, ValidationContext
XmlObject min = sType.getFacet(SchemaType.FACET_MIN_LENGTH);
if (min != null) {
int m = ((XmlObjectBase) min).getBigIntegerValue().intValue();
- if (v.length() < m) {
+ if (cch < m) {
context.invalid(XmlErrorCodes.DATATYPE_MIN_LENGTH_VALID$STRING,
- new Object[]{"string", v.length(), m, QNameHelper.readable(sType)});
+ new Object[]{"string", cch, m, QNameHelper.readable(sType)});
return;
}
}
@@ -88,9 +92,9 @@ public static void validateLexical(String v, SchemaType sType, ValidationContext
XmlObject max = sType.getFacet(SchemaType.FACET_MAX_LENGTH);
if (max != null) {
int m = ((XmlObjectBase) max).getBigIntegerValue().intValue();
- if (v.length() > m) {
+ if (cch > m) {
context.invalid(XmlErrorCodes.DATATYPE_MAX_LENGTH_VALID$STRING,
- new Object[]{"string", v.length(), m, QNameHelper.readable(sType)});
+ new Object[]{"string", cch, m, QNameHelper.readable(sType)});
return;
}
}
diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaUriHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaUriHolderEx.java
index 41817bc3d..1d8f5a4f4 100644
--- a/src/main/java/org/apache/xmlbeans/impl/values/JavaUriHolderEx.java
+++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaUriHolderEx.java
@@ -95,22 +95,26 @@ public static void validateLexical(String v, SchemaType sType, ValidationContext
XmlObject x;
int i;
+ // xsd length facets count characters (code points), not Java UTF-16
+ // code units, so a supplementary character counts as one
+ int cch = v.codePointCount(0, v.length());
+
if ((x = sType.getFacet(SchemaType.FACET_LENGTH)) != null) {
- if ((i = ((SimpleValue) x).getBigIntegerValue().intValue()) != v.length()) {
+ if ((i = ((SimpleValue) x).getBigIntegerValue().intValue()) != cch) {
context.invalid(XmlErrorCodes.DATATYPE_LENGTH_VALID$STRING,
new Object[]{"anyURI", v, i, QNameHelper.readable(sType)});
}
}
if ((x = sType.getFacet(SchemaType.FACET_MIN_LENGTH)) != null) {
- if ((i = ((SimpleValue) x).getBigIntegerValue().intValue()) > v.length()) {
+ if ((i = ((SimpleValue) x).getBigIntegerValue().intValue()) > cch) {
context.invalid(XmlErrorCodes.DATATYPE_MIN_LENGTH_VALID$STRING,
new Object[]{"anyURI", v, i, QNameHelper.readable(sType)});
}
}
if ((x = sType.getFacet(SchemaType.FACET_MAX_LENGTH)) != null) {
- if ((i = ((SimpleValue) x).getBigIntegerValue().intValue()) < v.length()) {
+ if ((i = ((SimpleValue) x).getBigIntegerValue().intValue()) < cch) {
context.invalid(XmlErrorCodes.DATATYPE_MAX_LENGTH_VALID$STRING,
new Object[]{"anyURI", v, i, QNameHelper.readable(sType)});
}
diff --git a/src/test/java/misc/checkin/StringLengthFacetCodePointTest.java b/src/test/java/misc/checkin/StringLengthFacetCodePointTest.java
new file mode 100644
index 000000000..ebbe4deba
--- /dev/null
+++ b/src/test/java/misc/checkin/StringLengthFacetCodePointTest.java
@@ -0,0 +1,62 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package misc.checkin;
+
+import org.apache.xmlbeans.SchemaTypeLoader;
+import org.apache.xmlbeans.XmlBeans;
+import org.apache.xmlbeans.XmlObject;
+import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StringLengthFacetCodePointTest {
+
+ // U+1D54F MATHEMATICAL DOUBLE-STRUCK CAPITAL X: one character, encoded as a
+ // surrogate pair so String.length() is 2 but codePointCount is 1
+ private static final String SUPPLEMENTARY = "𝕏";
+
+ private static boolean validates(String base, String facet, String value) throws Exception {
+ String xsd =
+ "" +
+ " " +
+ " " + facet +
+ "" +
+ " " +
+ "";
+ SchemaTypeLoader loader = XmlBeans.loadXsd(new XmlObject[]{SchemaDocument.Factory.parse(xsd)});
+ XmlObject doc = loader.parse("" + value + "", null, null);
+ return doc.validate();
+ }
+
+ @Test
+ void stringLengthFacetsCountCharactersNotCodeUnits() throws Exception {
+ // one supplementary character is one xsd character, so it meets length/maxLength of 1
+ assertTrue(validates("xs:string", "", SUPPLEMENTARY));
+ assertTrue(validates("xs:string", "", SUPPLEMENTARY));
+ // it is one character, so it does not meet a length of 2
+ assertFalse(validates("xs:string", "", SUPPLEMENTARY));
+ }
+
+ @Test
+ void anyUriLengthFacetsCountCharactersNotCodeUnits() throws Exception {
+ assertTrue(validates("xs:anyURI", "", SUPPLEMENTARY));
+ assertTrue(validates("xs:anyURI", "", SUPPLEMENTARY));
+ }
+}