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
5 changes: 2 additions & 3 deletions schema/json/metaschema-datatypes.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@
"pattern": "^-?P([0-9]+D(T(([0-9]+H([0-9]+M)?(([0-9]+|[0-9]+(\\.[0-9]+)?)S)?)|([0-9]+M(([0-9]+|[0-9]+(\\.[0-9]+)?)S)?)|([0-9]+|[0-9]+(\\.[0-9]+)?)S))?)|T(([0-9]+H([0-9]+M)?(([0-9]+|[0-9]+(\\.[0-9]+)?)S)?)|([0-9]+M(([0-9]+|[0-9]+(\\.[0-9]+)?)S)?)|([0-9]+|[0-9]+(\\.[0-9]+)?)S)$"
},
"DecimalDatatype": {
"description": "A real number expressed using a whole and optional fractional part separated by a period.",
"type": "number",
"pattern": "^(\\+|-)?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)$"
"description": "A real number expressed using a whole and optional fractional part separated by a period, with optional exponential notation. No leading '+' is allowed.",
"type": "number"
},
"EmailAddressDatatype": {
"description": "An email address string formatted according to RFC 6531.",
Expand Down
18 changes: 8 additions & 10 deletions schema/xml/metaschema-datatypes.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,15 @@

<xs:simpleType name="DecimalDatatype">
<xs:annotation>
<xs:documentation>A real number expressed using a whole and optional fractional part
separated by a period.</xs:documentation>
<xs:documentation>
A real number expressed using a whole and optional fractional part
separated by a period, with optional exponential notation.
No leading '+' is allowed. Leading zeros are not allowed except
for the integer 0 itself or numbers less than 1 (e.g., 0.5).
</xs:documentation>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</xs:annotation>
<xs:restriction base="xs:decimal">
<xs:pattern value="\S(.*\S)?">
<xs:annotation>
<xs:documentation>This pattern ensures that leading and trailing whitespace is
disallowed. This helps to even the user experience between implementations
related to whitespace.</xs:documentation>
</xs:annotation>
</xs:pattern>
<xs:restriction base="xs:double">
<xs:pattern value="-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?"/>
</xs:restriction>
</xs:simpleType>

Expand Down
5 changes: 5 additions & 0 deletions test/decimal-type/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Compiled Java classes
*.class

# Maven build output
target/
68 changes: 68 additions & 0 deletions test/decimal-type/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Decimal Datatype Test Cases

This directory contains test schemas and content examples to validate the decimal datatype behavior as defined in PR #135.

## Schemas

- `decimal-test.xsd` - XML Schema with inline DecimalDatatype using pattern `-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?` and base type `xs:double`
- `decimal-test.json` - JSON Schema with inline DecimalDatatype using `type: "number"`

## Expected Behavior Matrix (Tested)

| Test Case | Example | JSON | XML |
|-----------|---------|:----:|:---:|
| Integers | `12` | ✓ | ✓ |
| Decimals | `12.34` | ✓ | ✓ |
| Positive with leading + | `+12.34` | ✗ | ✗ |
| Exponential notation | `1e3`, `1E+4`, `-2.5e-10` | ✓ | ✓ |
| Leading zeros | `01.23` | ✗ | ✗ |
| No leading digit | `.47` | ✗ | ✗ |
| Trailing decimal point | `123.` | ✗ | ✗ |
| Leading/trailing whitespace | ` 12.34 ` | ✓ | ✓ |

**Legend:** ✓ = Valid, ✗ = Invalid

### Notes

Both JSON and XML ignore/normalize whitespace around numeric values, so leading/trailing whitespace is accepted in both formats.

## Test Files

### XML Test Cases

- `xml-valid-cases.xml` - Contains values that SHOULD pass XML Schema validation
- `xml-invalid-cases.xml` - Contains commented-out invalid cases for individual testing

### JSON Test Cases

- `json-valid-cases.json` - Contains values that SHOULD pass JSON Schema validation
- `json-invalid-cases.json` - Documents invalid cases (many cannot be represented in valid JSON syntax)

## Key Differences Between JSON and XML

1. **Whitespace Handling**: Both XML Schema and JSON ignore whitespace around numeric values. XML Schema applies whitespace normalization (collapse) for `xs:double` before pattern matching. JSON ignores whitespace around values during parsing.

2. **Syntax Restrictions**: JSON syntax itself prohibits leading `+`, leading zeros (e.g., `01.23`), `.47`, and `123.` formats, making these parse errors rather than validation errors. The XML pattern explicitly rejects these as well.

## Running Validation Tests

### XML Validation

#### Using xmllint
```bash
xmllint --schema decimal-test.xsd xml-valid-cases.xml --noout
```

#### Using the Java XmlValidator (via Maven)
```bash
# Compile the validator
mvn compile

# Run validation on a specific XML file
mvn exec:java -Dexec.args="decimal-test.xsd xml-valid-cases.xml"
```

### JSON Validation (using ajv-cli)
```bash
ajv validate -s decimal-test.json -d json-valid-cases.json
```
62 changes: 62 additions & 0 deletions test/decimal-type/XmlValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
import java.io.File;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;

public class XmlValidator {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: java XmlValidator <schema.xsd> <document.xml>");
System.exit(1);
}

String schemaPath = args[0];
String xmlPath = args[1];

File schemaFile = new File(schemaPath);
File xmlFile = new File(xmlPath);

if (!schemaFile.exists()) {
System.out.println("Schema file not found: " + schemaPath);
System.exit(1);
}
if (!xmlFile.exists()) {
System.out.println("XML file not found: " + xmlPath);
System.exit(1);
}

try {
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

// Harden against XXE / SSRF-style external resolution
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
try {
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (IllegalArgumentException | SAXNotRecognizedException | SAXNotSupportedException ex) {
// Some JAXP implementations don't support these properties
System.err.println("Warning: could not set external access restrictions: " + ex.getMessage());
}

Schema schema = factory.newSchema(schemaFile);
Validator validator = schema.newValidator();

try {
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (IllegalArgumentException ex) {
System.err.println("Warning: could not set validator external access restrictions: " + ex.getMessage());
}

validator.validate(new StreamSource(xmlFile));
System.out.println(xmlPath + " is VALID");
} catch (Exception e) {
System.err.println(xmlPath + " is INVALID: " + e.getMessage());
System.exit(1);
}
}
}
38 changes: 38 additions & 0 deletions test/decimal-type/decimal-test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://csrc.nist.gov/ns/metaschema/test/decimal/decimal-test-schema.json",
"$comment": "Schema for testing decimal datatype validation",
"type": "object",
"properties": {
"decimal-tests": {
"type": "object",
"properties": {
"test-cases": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"description": {
"type": "string"
},
"expected": {
"type": "string",
"enum": ["valid", "invalid"]
},
"value": {
"description": "A real number expressed using a whole and optional fractional part separated by a period, with optional exponential notation. No leading '+' is allowed.",
"type": "number"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
},
"required": ["id", "description", "expected", "value"]
}
}
},
"required": ["test-cases"]
}
},
"required": ["decimal-tests"]
}
46 changes: 46 additions & 0 deletions test/decimal-type/decimal-test.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:m="http://csrc.nist.gov/ns/metaschema/test/decimal"
targetNamespace="http://csrc.nist.gov/ns/metaschema/test/decimal"
elementFormDefault="qualified">

<!-- Root element for testing -->
<xs:element name="decimal-tests">
<xs:complexType>
<xs:sequence>
<xs:element name="test-case" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="value">
<xs:simpleType>
<xs:annotation>
<xs:documentation>
A real number expressed using a whole and optional fractional part
separated by a period, with optional exponential notation.
No leading '+' is allowed. Leading zeros are not allowed except
for the integer 0 itself or numbers less than 1 (e.g., 0.5).
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:double">
<xs:pattern value="-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="description" type="xs:string" use="required"/>
<xs:attribute name="expected" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="valid"/>
<xs:enumeration value="invalid"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>

</xs:schema>
47 changes: 47 additions & 0 deletions test/decimal-type/json-invalid-cases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"$schema": "decimal-test.json",
"_comment": "Invalid decimal test cases for JSON validation. These values SHOULD FAIL JSON Schema validation or are invalid JSON syntax.",
"_note": "JSON does not allow: leading +, leading/trailing whitespace, .47 syntax, 123. syntax. These would be syntax errors or string values.",
"decimal-tests": {
"test-cases": [
{
"id": "json-placeholder",
"description": "Placeholder - see comments below for invalid cases that cannot be represented in JSON",
"expected": "valid",
"value": 0
}
]
},
"_invalid_cases_documentation": {
"leading_plus": {
"example": "+12.34",
"reason": "JSON syntax does not allow leading + on numbers. This would be a parse error.",
"expected": "invalid"
},
"leading_zeros": {
"example": "01.23",
"reason": "JSON syntax does not allow leading zeros on numbers (except 0 itself). This would be a parse error.",
"expected": "invalid"
},
"no_leading_digit": {
"example": ".47",
"reason": "JSON syntax requires a digit before the decimal point. This would be a parse error.",
"expected": "invalid"
},
"trailing_decimal": {
"example": "123.",
"reason": "JSON syntax requires digits after the decimal point. This would be a parse error.",
"expected": "invalid"
},
"leading_whitespace": {
"example": " 12.34",
"reason": "If represented as a string \" 12.34\", it would fail type: number validation.",
"expected": "invalid"
},
"trailing_whitespace": {
"example": "12.34 ",
"reason": "If represented as a string \"12.34 \", it would fail type: number validation.",
"expected": "invalid"
}
}
}
13 changes: 13 additions & 0 deletions test/decimal-type/json-test-string-value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "decimal-test.json",
"decimal-tests": {
"test-cases": [
{
"id": "json-string-with-whitespace",
"description": "String value with whitespace should fail type: number",
"expected": "invalid",
"value": " 12.34 "
}
]
}
}
68 changes: 68 additions & 0 deletions test/decimal-type/json-valid-cases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"$schema": "decimal-test.json",
"_comment": "Valid decimal test cases for JSON validation. All values SHOULD pass JSON Schema validation with type: number",
"decimal-tests": {
"test-cases": [
{
"id": "json-int-positive",
"description": "Positive integer (e.g., 12)",
"expected": "valid",
"value": 12
},
{
"id": "json-int-negative",
"description": "Negative integer",
"expected": "valid",
"value": -42
},
{
"id": "json-int-zero",
"description": "Zero",
"expected": "valid",
"value": 0
},
{
"id": "json-dec-positive",
"description": "Positive decimal (e.g., 12.34)",
"expected": "valid",
"value": 12.34
},
{
"id": "json-dec-negative",
"description": "Negative decimal",
"expected": "valid",
"value": -12.34
},
{
"id": "json-dec-small",
"description": "Small decimal",
"expected": "valid",
"value": 0.001
},
{
"id": "json-exp-lower",
"description": "Exponential notation lowercase (e.g., 1e3) - Valid in JSON",
"expected": "valid",
"value": 1e3
},
{
"id": "json-exp-upper-plus",
"description": "Exponential notation uppercase with plus (e.g., 1E+4) - Valid in JSON",
"expected": "valid",
"value": 1E+4
},
{
"id": "json-exp-negative",
"description": "Exponential notation negative (e.g., -2.5e-10) - Valid in JSON",
"expected": "valid",
"value": -2.5e-10
},
{
"id": "json-decimal-no-leading-zero",
"description": "Decimal without unnecessary leading zeros (1.23 is valid; 01.23 is invalid JSON syntax)",
"expected": "valid",
"value": 1.23
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
]
}
}
Loading
Loading