diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b1ffd3..bb94bc8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
+- A schema reference (`ref`/`substitutionGroup`) that resolves to the XSD namespace is now rejected as undeclared. The XSD namespace declares no referenceable user component, so `isUndeclaredReferenceType` already rejected a `type`/`base` in it, but `isUndeclaredReferenceRef` fell through to a lenient accept. This is reached when an unprefixed reference picks up a default `xmlns` of the XSD namespace, so it is not in the target namespace where the schema's own components live: e.g. `xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="foo"` with `` resolves to `{XSD}root`, not `{foo}root`, and is undeclared. A schema that itself targets the XSD namespace, and a genuinely loaded XSD-namespace document, are unaffected. XSTS invalid-schemas-accepted 29 → 27 (xsd013/xsd014), valid-schemas-rejected held at 0.
- Unique Particle Attribution now checks a `complexContent` extension against its EFFECTIVE content model (the base type's content followed by the extension's), not just the extension's own particles. A base whose trailing particle can recur or end optionally competes with the extension's leading particles, so an element could match either across the extension boundary; that cross-boundary non-determinism was missed and the schema wrongly accepted. The base content is resolved and prepended only for a local (target-namespace), non-cyclic base chain; a foreign, built-in (`xs:anyType`), unresolved, or cyclic base leaves the check on the extension's own content, so no valid schema is newly rejected. XSTS invalid-schemas-accepted 31 → 29 (particlesZ022 and one more), valid-schemas-rejected held at 0.
- Unique Particle Attribution now detects overlapping `xs:any` wildcards involving `##other`. `wildcardsOverlap` returned false for any pair involving `##other`, so two competing wildcards whose namespaces actually intersect, two `##other` constraints (which always share infinitely many namespaces) or `##other` against an enumerated set naming a non-target, non-absent namespace, were not flagged as non-deterministic, and the schema was wrongly accepted. The overlap is now computed correctly: `##other`∩`##other` always overlaps, and `##other`∩enumerated overlaps iff the set names a namespace that is neither absent (`##local`) nor the target. Disjoint cases (`##other` against `##local` or against the target namespace) remain deterministic, so no valid schema is newly rejected. XSTS invalid-schemas-accepted 35 → 31 (wildI009/wildI013/wildI014 and one more), valid-schemas-rejected held at 0.
- The content-model matcher no longer blows up on a nested model group with a huge but
diff --git a/Sources/Schema/SchemaReferenceResolution.swift b/Sources/Schema/SchemaReferenceResolution.swift
index 6d5f9a8..835ccb8 100644
--- a/Sources/Schema/SchemaReferenceResolution.swift
+++ b/Sources/Schema/SchemaReferenceResolution.swift
@@ -73,6 +73,16 @@ extension PureXML.Schema.XSDParser {
if let foreign = context.foreignPools[uri]?[poolName] {
return !foreign.contains(localPart)
}
+ if uri == xsdNamespace {
+ // The XSD namespace declares no referenceable user component (element,
+ // group, attribute, attribute group), so a reference resolving to it is
+ // undeclared. This is reached when an unprefixed reference picks up a
+ // default `xmlns` of the XSD namespace, so it is NOT in the target
+ // namespace (XSTS xsd013/xsd014); the schema that itself targets the XSD
+ // namespace is the `inTargetNamespace` case above, and a genuinely loaded
+ // XSD-namespace document is the `foreignPools` case just before.
+ return true
+ }
if uri == nil || uri == "" {
if context.chameleonNamespace, context.pools[poolName]?.contains(localPart) == true { return false }
if !(context.targetNamespace == nil || context.targetNamespace == "") { return true }
diff --git a/Tests/SchemaReferenceTests.swift b/Tests/SchemaReferenceTests.swift
index 9756e69..93fab0f 100644
--- a/Tests/SchemaReferenceTests.swift
+++ b/Tests/SchemaReferenceTests.swift
@@ -44,6 +44,33 @@ struct SchemaReferenceTests {
try compile("")
}
+ @Test("a ref resolving to the XSD namespace is undeclared")
+ func test_refResolvingToXSDNamespaceIsUndeclared() throws {
+ // The default xmlns is the XSD namespace, so an unprefixed `ref="root"`
+ // resolves to {XSD}root, not {foo}root, and the XSD namespace declares no
+ // referenceable user element: undeclared (XSTS xsd013/xsd014).
+ let refRoot = """
+
+
+
+ """
+ #expect(throws: (any Error).self) { _ = try PureXML.Schema.Document(refRoot) }
+ let refUndef = """
+
+
+
+ """
+ #expect(throws: (any Error).self) { _ = try PureXML.Schema.Document(refUndef) }
+ // Guard: the same shape with the ref qualified to the target resolves, so the
+ // XSD-namespace rule does not reject a correctly-namespaced reference.
+ let qualified = """
+
+
+
+ """
+ _ = try PureXML.Schema.Document(qualified)
+ }
+
@Test("references are not checked when an import may supply them")
func test_importSkipsCheck() throws {
// A dangling reference into an imported namespace is tolerated: the default
diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift
index 9d9f448..563ef3c 100644
--- a/Tests/XSTSSuiteTests.swift
+++ b/Tests/XSTSSuiteTests.swift
@@ -146,9 +146,9 @@ struct XSTSSuiteTests {
/// model-group occurrence, identity-constraint content, derivation base kind, attribute/simple-type,
/// composition, simpleContent inline-type, complexContent mixedness, and substitution-group anySimpleType checks.
/// src-resolve (#185) 43->39; xsi:Not-Allowed (#183) 39->37; NameAndTypeOK restriction-only + cos-all-limited group-ref (#158) 37->35.
- /// UPA: `##other` overlap 35->31 (wildI009/I013/I014+1); effective extension content model 31->29 (particlesZ022+1); valid-rejected held at 0.
+ /// UPA `##other` overlap 35->31; UPA effective extension model 31->29; ref to XSD namespace is undeclared 29->27 (xsd013/xsd014); valid-rejected held at 0.
private let knownSchemaValidRejected = 0
- private let knownSchemaInvalidAccepted = 29
+ private let knownSchemaInvalidAccepted = 27
private let knownInstanceValidRejected = 0
/// Instance bucket (133 -> 26), per-step deltas in CHANGELOG.md: xsi:type must derive from the
/// declared type (complex/atomic/list/union via the element-ref chain); anyType cannot stand in