PureXML/Tests/SchemaDerivationControlTests.swift
Mihaela Mihaljevic 11c4eacdff feat(schema): validate final/block derivation-control value space
The final and block attributes are #all or a whitespace list of method tokens,
but the admitted tokens depend on the component: element final is
extension/restriction, element block adds substitution, complexType final/block
are extension/restriction, simpleType final is list/union/restriction. The
tokens were parsed leniently (unknown ignored, substitution admitted
everywhere), so final="substitution" on an element, a misspelled or miscased
token, or #all mixed with other tokens was accepted.

The list is validated against the component's exact set; finalDefault/
blockDefault (the wider schema-level value space) and foreign attributes are
left alone.

XSTS: invalid schemas accepted 580 to 537, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 14:19:10 +02:00

51 lines
2.5 KiB
Swift

@testable import PureXML
import Testing
/// Value-space validity of the `final`/`block` derivation-control attributes
/// (XSD 1.0): `#all` or a whitespace list of method tokens, with the admitted
/// tokens depending on the component (element/complexType/simpleType) and on
/// whether it is `final` or `block`. The elemF / st_final families.
@Suite("Derivation control (final/block) value space")
struct SchemaDerivationControlTests {
private func rejects(_ body: String) -> Bool {
do {
_ = try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
\(body)
</xs:schema>
""")
return false
} catch {
return true
}
}
@Test("element final admits only extension/restriction; block also substitution")
func test_element() {
// substitution is a block token, not a final token.
#expect(rejects(#"<xs:element name="e" type="xs:string" final="substitution"/>"#))
#expect(rejects(#"<xs:element name="e" type="xs:string" final="extension restriction substitution"/>"#))
// unknown token and wrong case.
#expect(rejects(#"<xs:element name="e" type="xs:string" final="foo"/>"#))
#expect(rejects(##"<xs:element name="e" type="xs:string" final="#All"/>"##))
#expect(rejects(#"<xs:element name="e" type="xs:string" final="Extension"/>"#))
// valid finals and a valid block with substitution.
#expect(!rejects(##"<xs:element name="e" type="xs:string" final="#all"/>"##))
#expect(!rejects(#"<xs:element name="e" type="xs:string" final="extension restriction"/>"#))
#expect(!rejects(#"<xs:element name="e" type="xs:string" block="substitution"/>"#))
}
@Test("simpleType final admits list/union/restriction, not extension")
func test_simpleType() {
#expect(rejects(#"<xs:simpleType name="t" final="extension"><xs:restriction base="xs:string"/></xs:simpleType>"#))
#expect(!rejects(#"<xs:simpleType name="t" final="list union restriction"><xs:restriction base="xs:string"/></xs:simpleType>"#))
#expect(!rejects(##"<xs:simpleType name="t" final="#all"><xs:restriction base="xs:string"/></xs:simpleType>"##))
}
@Test("complexType block/final admit only extension/restriction")
func test_complexType() {
#expect(rejects(#"<xs:complexType name="t" block="substitution"><xs:sequence/></xs:complexType>"#))
#expect(!rejects(#"<xs:complexType name="t" final="restriction"><xs:sequence/></xs:complexType>"#))
}
}