fix(schema): a simpleContent restriction's base must be a complex type (#145)

Restricting a built-in or user simple type through <simpleContent><restriction>
is invalid (cos-ct-restricts.1 / src-ct.2): a complex type with simple content
is built by extending a simple type, not by restricting one. A unit-test
fixture that restricted xs:string directly was spec-invalid and now restricts a
complexType with simple content instead.

XSTS invalid-schemas-accepted 142 to 137, no other bucket moved.
This commit is contained in:
Mihaela Mihaljevic 2026-06-17 17:37:55 +02:00
parent bdd9e96634
commit 903345e764
6 changed files with 41 additions and 5 deletions

View File

@ -22,6 +22,8 @@ Second pre-release. Continues the W3C XSTS schema-validity campaign: **invalid-s
### Fixed
- XSTS invalid-schemas-accepted (#145): a `simpleContent` restriction's base must be a complex type. Restricting a built-in (`xs:integer`, `xs:anySimpleType`) or a user `simpleType` through `<simpleContent><restriction>` is rejected (`src-ct.2`); a complex type with simple content is built by extending a simple type, not by restricting one. Caught five invalid schemas: invalid-schemas-accepted 142 → 137, no other bucket moved.
- XSTS invalid-schemas-accepted (#145): Particle Valid (Restriction) now enforces the NameAndTypeOK nillable clause. A restricting element may not be `nillable` unless the base element it restricts is also nillable. Caught two invalid schemas: invalid-schemas-accepted 144 → 142, no other bucket moved.
- XSTS invalid-schemas-accepted (#145): Particle Valid (Restriction) now enforces the NameAndTypeOK fixed-value clause. When the base element has a `fixed` value constraint, the restricting element must also be `fixed` to the same value (compared in the base type's value space, so a value-equal pair in different lexical forms is not wrongly flagged). Caught four invalid schemas: invalid-schemas-accepted 148 → 144, no other bucket moved.

View File

@ -360,11 +360,12 @@ extension PureXML.Validation.SchemaCompile {
/// cos-ct-extends.1.4.2.2: a complexContent extension adding element content
/// must have a base with complex (or empty) content, not simpleContent.
static var complexExtensionBaseValid: PureXML.Validation.Validation<PureXML.Schema.SchemaCompileRoot, PureXML.Schema.SchemaCompileContext> {
compileRule("A complexContent extension is consistent with its base type") { document in
compileRule("A content-model derivation is consistent with its base type") { document in
guard let namedTypes = document.namedTypes else { return [] }
return PureXML.Schema.SchemaLocatedFinding.unlocated(
PureXML.Schema.XSDParser.simpleContentExtensionBaseErrors(document.schema, document.context, namedTypes)
+ PureXML.Schema.XSDParser.extensionMixedAgreementErrors(document.schema, document.context, namedTypes),
+ PureXML.Schema.XSDParser.extensionMixedAgreementErrors(document.schema, document.context, namedTypes)
+ PureXML.Schema.XSDParser.simpleContentRestrictionBaseErrors(document.schema, document.context, namedTypes),
)
}
}

View File

@ -113,6 +113,31 @@ extension PureXML.Schema.XSDParser {
return errors
}
/// XSD 1.0 `src-ct.2` / Derivation Valid (Restriction): the base of a
/// `simpleContent` RESTRICTION must be a complex type (whose content is simple).
/// A built-in (`xs:integer`, `xs:anySimpleType`) or a user `simpleType` base is
/// invalid: a complex type with simple content is built by EXTENDING a simple
/// type, never by restricting one through `simpleContent`. Self-contained schema
/// only; a foreign or unresolved base is left alone.
static func simpleContentRestrictionBaseErrors(_ schema: XSDTree, _ context: PureXML.Schema.XSDContext, _ types: [String: PureXML.Schema.ElementType]) -> [String] {
guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] }
let bindings = context.namespaceBindings
var errors: [String] = []
for content in descendants(schema, named: "simpleContent") {
guard let restriction = ExtAllNode.firstChild(content, named: "restriction"),
let base = ExtAllNode.attribute(restriction, "base")
else { continue }
let namespace = ExtAllNode.referenceNamespace(base, bindings)
let baseName = ExtAllNode.stripPrefix(base)
if namespace == xsdNamespace {
errors.append("a simpleContent restriction's base must be a complex type, not the built-in simple type '\(baseName)'")
} else if namespace == context.targetNamespace, case .simple? = types[baseName] {
errors.append("a simpleContent restriction's base must be a complex type, not the simple type '\(baseName)'")
}
}
return errors
}
/// XSD 1.0 `cos-ct-extends.1.4.3.2.2.1`: a `complexContent` extension's mixedness
/// must match its base type's. The effective content joins the base content and
/// the extension's, so a mixed extension may extend only a mixed base and an

View File

@ -44,6 +44,11 @@ struct SchemaContentOrderTests {
@Test("well-formed simpleContent derivations compile")
func test_valid() {
#expect(!rejects(simpleContent(#"<xs:extension base="xs:string"><xs:attribute name="a" type="xs:string"/></xs:extension>"#)))
#expect(!rejects(simpleContent(#"<xs:restriction base="xs:string"><xs:maxLength value="3"/><xs:attribute name="a"/></xs:restriction>"#)))
// A simpleContent restriction's base must be a complex type (src-ct.2), so it
// restricts a complexType with simple content, not xs:string directly.
#expect(!rejects(#"""
<xs:complexType name="base"><xs:simpleContent><xs:extension base="xs:string"><xs:attribute name="a"/></xs:extension></xs:simpleContent></xs:complexType>
<xs:complexType name="t"><xs:simpleContent><xs:restriction base="base"><xs:maxLength value="3"/></xs:restriction></xs:simpleContent></xs:complexType>
"""#))
}
}

View File

@ -101,7 +101,7 @@ struct ValidationConfigurationTests {
"Element value constraints are valid against their declared types",
"Complex types extending xs:all groups satisfy XSD placement rules",
"Anonymous complex-type restrictions accept a subset of their base's",
"A complexContent extension is consistent with its base type",
"A content-model derivation is consistent with its base type",
"Attribute restrictions are faithful to their bases",
"Simple types do not derive from complex types",
"Simple-type varieties are declared consistently",

View File

@ -102,8 +102,11 @@ struct XSTSSuiteTests {
/// Enforcing the NameAndTypeOK nillable clause (a restriction may not be nillable
/// unless the base element is) then caught two more invalid schemas:
/// invalid-schemas accepted 144 → 142, no other bucket moved.
/// Requiring a `simpleContent` restriction's base to be a complex type, not a
/// built-in or user simple type (src-ct.2), then caught five more invalid
/// schemas: invalid-schemas accepted 142 → 137, no other bucket moved.
private let knownSchemaValidRejected = 1
private let knownSchemaInvalidAccepted = 142
private let knownSchemaInvalidAccepted = 137
private let knownInstanceValidRejected = 3
private let knownInstanceInvalidAccepted = 132