Skip to content
Merged
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
53 changes: 35 additions & 18 deletions src/main/java/org/apache/xmlbeans/impl/xsd2inst/SampleXmlUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,24 @@ private String formatToLength(String s, SchemaType sType) {
return result;
}

/**
* The number of digits before the decimal point, which is the same for every
* representation of a value.
*/
private static long integerDigits(BigDecimal value) {
return (long) value.precision() - value.scale();
}

/**
* The largest value that fits in the given number of digits, ie 999...9. Computed as
* 10^digits - 1 rather than built as a string of nines, which the maximum number of
* characters allowed for a number rejects once the schema asks for more digits than
* that - xsd:totalDigits is a positiveInteger and has no such ceiling.
*/
private static BigDecimal widestValue(int digits) {
return BigDecimal.TEN.pow(digits).subtract(BigDecimal.ONE);
}

private String formatDecimal(String start, SchemaType sType) {
BigDecimal result = MathUtil.parseAsBigDecimal(start);
XmlDecimal xmlD;
Expand Down Expand Up @@ -411,19 +429,22 @@ private String formatDecimal(String start, SchemaType sType) {
if (xmlD != null) {
totalDigits = MathUtil.toInt(xmlD.getBigDecimalValue());

StringBuilder sb = new StringBuilder(totalDigits);
for (int i = 0; i < totalDigits; i++) {
sb.append('9');
}
BigDecimal digitsLimit = MathUtil.parseAsBigDecimal(sb.toString());
if (max != null && max.compareTo(digitsLimit) > 0) {
max = digitsLimit;
maxInclusive = true;
// the widest value the facet allows can only narrow a bound that is at least
// as wide, so it is computed only when it can bind - which also keeps it away
// from a totalDigits far larger than any bound the schema declares
if (max != null && integerDigits(max) >= totalDigits) {
BigDecimal digitsLimit = widestValue(totalDigits);
if (max.compareTo(digitsLimit) > 0) {
max = digitsLimit;
maxInclusive = true;
}
}
digitsLimit = digitsLimit.negate();
if (min != null && min.compareTo(digitsLimit) < 0) {
min = digitsLimit;
minInclusive = true;
if (min != null && integerDigits(min) >= totalDigits) {
BigDecimal digitsLimit = widestValue(totalDigits).negate();
if (min.compareTo(digitsLimit) < 0) {
min = digitsLimit;
minInclusive = true;
}
}
}

Expand All @@ -441,12 +462,8 @@ private String formatDecimal(String start, SchemaType sType) {
} else {
fractionDigits = MathUtil.toInt(xmlD.getBigDecimalValue());
if (fractionDigits > 0) {
StringBuilder sb = new StringBuilder("0.");
for (int i = 1; i < fractionDigits; i++) {
sb.append('0');
}
sb.append('1');
increment = MathUtil.parseAsBigDecimal(sb.toString());
// 1 shifted fractionDigits places right, ie 0.00...1
increment = BigDecimal.ONE.scaleByPowerOfTen(-fractionDigits);
} else {
increment = BigDecimal.ONE;
}
Expand Down
44 changes: 44 additions & 0 deletions src/test/java/tools/xsd2inst/checkin/Xsd2InstTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,50 @@ void testBase64Binary() throws Exception {
}
}

private static String decimalSchema(String facets) {
return "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>" +
"<xs:element name='value' type='constrainedDecimal'/>" +
"<xs:simpleType name='constrainedDecimal'>" +
"<xs:restriction base='xs:decimal'>" + facets + "</xs:restriction>" +
"</xs:simpleType></xs:schema>";
}

private static String sampleFor(String facets) throws Exception {
XmlObject xsd = XmlObject.Factory.parse(decimalSchema(facets));
SchemaTypeSystem sts = XmlBeans.compileXsd(new XmlObject[]{xsd},
XmlBeans.getBuiltinTypeSystem(), new XmlOptions());
return SampleXmlUtil.createSampleForType(sts.globalElements()[0]);
}

@Test
void testDigitFacetsWiderThanTheNumberLengthLimit() throws Exception {
// xsd:totalDigits and xsd:fractionDigits are positiveInteger and so have no
// ceiling of their own; building the bounds they imply as strings of digits ran
// them into the maximum number of characters allowed for a number
String result = sampleFor("<xs:totalDigits value='2000'/>");
assertTrue(result.contains("<value>"), result);
try (InputStream docStream = new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8))) {
assertNotNull(DocumentHelper.readDocument(new XmlOptions(), docStream));
}

result = sampleFor("<xs:fractionDigits value='1500'/>");
assertTrue(result.contains("<value>"), result);
try (InputStream docStream = new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8))) {
assertNotNull(DocumentHelper.readDocument(new XmlOptions(), docStream));
}
}

@Test
void testTotalDigitsStillNarrowsTheBounds() throws Exception {
// the sample seed for a decimal is 1000.00, which xsd:totalDigits has to pull
// down to the widest value the facet allows
String result = sampleFor("<xs:maxInclusive value='999999'/><xs:totalDigits value='3'/>");
assertTrue(result.contains("<value>999</value>"), result);

String unconstrained = sampleFor("<xs:maxInclusive value='999999'/>");
assertTrue(unconstrained.contains("<value>1000.00</value>"), unconstrained);
}

@Test
void testSampleXmlUtil() throws Exception {
XmlObject xobj;
Expand Down
Loading