diff --git a/CHANGELOG.md b/CHANGELOG.md index bb94bc8..413f984 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 +- An unprefixed `type`/`base`/`itemType`/`memberTypes` reference to a type declared in a non-empty target namespace is now rejected as undeclared. With no default `xmlns`, an unprefixed QName resolves to the absent namespace, so `type="t"` is `{}t`, not `{urn:x}t` where the type lives; `isUndeclaredReferenceType` matched the type by local name and accepted it. It now mirrors `isUndeclaredReferenceRef`: a no-namespace reference is undeclared when the schema has a non-empty target namespace (a chameleon-included global, and an imported no-namespace type via the foreign pools, remain accepted). XSTS invalid-schemas-accepted 27 → 25 (addB009/test66059 and one more), valid-schemas-rejected held at 0. - 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. diff --git a/Sources/Schema/SchemaReferenceResolution.swift b/Sources/Schema/SchemaReferenceResolution.swift index 835ccb8..fdc3d43 100644 --- a/Sources/Schema/SchemaReferenceResolution.swift +++ b/Sources/Schema/SchemaReferenceResolution.swift @@ -54,6 +54,14 @@ extension PureXML.Schema.XSDParser { return !foreignTypes.contains(localPart) } if uri == nil || uri == "" { + // A no-namespace (unprefixed, no default `xmlns`) reference resolves to + // the absent namespace. It matches a chameleon-included global adopting + // the includer's namespace; otherwise, when the schema has a non-empty + // target namespace, it cannot name a type that lives there, so it is + // undeclared (XSTS addB009 / test66059). This mirrors the same branch in + // `isUndeclaredReferenceRef`. + if context.chameleonNamespace, context.types.contains(localPart) { return false } + if !(context.targetNamespace == nil || context.targetNamespace == "") { return true } return !context.types.contains(localPart) } return false diff --git a/Tests/SchemaReferenceTests.swift b/Tests/SchemaReferenceTests.swift index 93fab0f..d043068 100644 --- a/Tests/SchemaReferenceTests.swift +++ b/Tests/SchemaReferenceTests.swift @@ -71,6 +71,27 @@ struct SchemaReferenceTests { _ = try PureXML.Schema.Document(qualified) } + @Test("an unprefixed type reference to a target-namespace type is undeclared") + func test_unprefixedTypeRefToTargetNamespace() throws { + // No default xmlns, so `type="t"` resolves to {}t, not {urn:x}t where the + // type is declared: undeclared (XSTS addB009 / test66059). + let unprefixed = """ + + + + + """ + #expect(throws: (any Error).self) { _ = try PureXML.Schema.Document(unprefixed) } + // Guard: qualifying the reference to the target namespace resolves. + 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/SchemaXSIAttributeTests.swift b/Tests/SchemaXSIAttributeTests.swift index 90c8402..0bd74bd 100644 --- a/Tests/SchemaXSIAttributeTests.swift +++ b/Tests/SchemaXSIAttributeTests.swift @@ -80,13 +80,16 @@ struct SchemaXSIAttributeTests { @Test("the same qualified attribute targeting a non-XSI namespace is valid") func test_qualifiedAttributeOutsideXSIValid() { + // The type reference is qualified to the target namespace (`tns:attRef`); an + // unprefixed `attRef` with no default xmlns would resolve to {}attRef, not + // {urn:ok}attRef, and is itself invalid (see SchemaReferenceTests). let schema = """ - - + """ #expect(compiles(schema), "qualifying into a non-XSI namespace is fine") diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift index 563ef3c..6e92759 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; UPA effective extension model 31->29; ref to XSD namespace is undeclared 29->27 (xsd013/xsd014); valid-rejected held at 0. + /// UPA `##other` 35->31; UPA effective-extension model 31->29; ref to XSD ns undeclared 29->27; unprefixed type-ref to target ns undeclared 27->25; valid-rejected 0. private let knownSchemaValidRejected = 0 - private let knownSchemaInvalidAccepted = 27 + private let knownSchemaInvalidAccepted = 25 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