diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e9b512..ebd5565 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,8 @@ Second pre-release. Continues the W3C XSTS schema-validity campaign: **invalid-s
### Fixed
+- XSTS invalid-schemas-accepted (#145): a `complexContent` or `simpleContent` must contain exactly one `restriction` or `extension`. An empty `` (or ``), which declares no derivation, is now rejected as a structural error. Caught three invalid schemas: invalid-schemas-accepted 200 → 197, no other bucket moved.
+
- XSTS invalid-schemas-accepted (#145): a `pattern` facet whose `\p{...}` or `\P{...}` property name can be no XSD `charProp` (`IsCategory | IsBlock`) under any repertoire is now rejected as a schema-validity error: an empty name, the bare prefix `Is`, a non-block name carrying a non-letter (`\p{\L}`), or `Is` followed by a non-block character. A well-formed but unrecognised category or block name (such as an unknown `Is`-block) stays a lenient engine limitation rather than an error, so only the unambiguously malformed forms reject. Caught three invalid schemas: invalid-schemas-accepted 203 → 200, no other bucket moved.
- XSTS invalid-schemas-accepted (#145): two attribute-declaration validity rules. An attribute `ref` may not also carry `form` (the form comes from the referenced global declaration; `src-attribute.3.2`), and no attribute declaration may be named `xmlns` (that name is reserved for namespace declarations; no-xmlns). Caught four invalid schemas: invalid-schemas-accepted 207 → 203, no other bucket moved.
diff --git a/Sources/Schema/SchemaContentOrder.swift b/Sources/Schema/SchemaContentOrder.swift
index 290d632..163deda 100644
--- a/Sources/Schema/SchemaContentOrder.swift
+++ b/Sources/Schema/SchemaContentOrder.swift
@@ -22,8 +22,21 @@ extension PureXML.Schema.XSDParser {
/// Only the `complexContent` context is checked, where a model group is part of
/// the model; a `simpleContent` derivation (facets, no model group) has a
/// different model and is left to its own rules.
+ /// The number of `restriction`/`extension` children of a `simpleContent` or
+ /// `complexContent`. Its content model requires exactly one, so zero (an empty
+ /// ``) or more than one is a structural error.
+ private static func derivationCount(_ content: XSDTree) -> Int {
+ PureXML.Schema.XSDNode.elementChildren(content).count { child in
+ guard child.name?.namespaceURI == xsdNamespace, let local = PureXML.Schema.XSDNode.localName(child) else { return false }
+ return local == "restriction" || local == "extension"
+ }
+ }
+
static func complexContentOrderErrors(_ complexContent: XSDTree) -> [String] {
var errors: [String] = []
+ if derivationCount(complexContent) != 1 {
+ errors.append("a complexContent must contain exactly one 'restriction' or 'extension'")
+ }
for derivation in PureXML.Schema.XSDNode.elementChildren(complexContent) {
guard let local = PureXML.Schema.XSDNode.localName(derivation),
local == "restriction" || local == "extension"
@@ -65,6 +78,9 @@ extension PureXML.Schema.XSDParser {
/// table but is invalid (the ctD family).
static func simpleContentOrderErrors(_ simpleContent: XSDTree) -> [String] {
var errors: [String] = []
+ if derivationCount(simpleContent) != 1 {
+ errors.append("a simpleContent must contain exactly one 'restriction' or 'extension'")
+ }
for derivation in PureXML.Schema.XSDNode.elementChildren(simpleContent) {
guard let local = PureXML.Schema.XSDNode.localName(derivation) else { continue }
let slots: [(members: Set, max: Int?)]
diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift
index 7d7b0f8..59bf6f4 100644
--- a/Tests/XSTSSuiteTests.swift
+++ b/Tests/XSTSSuiteTests.swift
@@ -125,8 +125,12 @@ struct XSTSSuiteTests {
/// followed by a non-block character: `\p{\L}`, `\p{Is}`) while staying lenient
/// on a well-formed but unrecognised block name then caught three more invalid
/// schemas: invalid-schemas accepted 203 → 200, no other bucket moved.
+ /// Requiring a `complexContent` or `simpleContent` to contain exactly one
+ /// `restriction` or `extension` (an empty `` has none) then
+ /// caught three more invalid schemas: invalid-schemas accepted 200 → 197, no
+ /// other bucket moved.
private let knownSchemaValidRejected = 1
- private let knownSchemaInvalidAccepted = 200
+ private let knownSchemaInvalidAccepted = 197
private let knownInstanceValidRejected = 3
private let knownInstanceInvalidAccepted = 132