PureXML/Tests/SchemaNamespacedDerivationTests.swift
Mihaela Mihaljevic f9b56077cc fix(schema): streaming xsi:type and type resolution match the tree path
The streaming XSD validator resolved element and xsi:type references by bare
local name, while its type tables are keyed by namespaced identity
({ns}local). A prefixed xsi:type in an imported namespace missed (a false
"unknown xsi:type") or hit a same-local-name type in the wrong namespace, and
two global elements sharing a local name across namespaces collided.

Resolve through in-scope prefix bindings accumulated down the element stack,
keyed by namespaced identity, mirroring the tree path:

- the xsi:type override type, the undeclared-type check, and root/child
  element declarations are namespaced; root resolution mirrors the tree's
  globalElementDeclaration (bare local-name map only for a no-namespace root).
- run the full cvc-elt.4 substitution gate the tree path runs (blocked /
  abstract / not-validly-derived / list-or-union / anyType-for-anySimpleType
  override, and an abstract declared type carrying no xsi:type).
- resolve a type from an element's own xsi:type where there is no governing
  declaration: an undeclared root (the Sun target-namespace fallback) and a
  strict-wildcard child, both via a shared xsiTypeNamedType helper.

Streaming now agrees with the tree-path oracle across these, verified by an
adversarial streaming-vs-tree differential. No XSTS bucket changes (the tree
path is the conformance oracle); gate held at 0/37/0/26.
2026-06-20 16:38:56 +02:00

80 lines
4.0 KiB
Swift

import Testing
@testable import PureXML
/// The instance-validity derivation/substitution subsystem keys its `block`,
/// abstract-type, and derivation-backbone tables by namespaced identity
/// (`{ns}local`), so two complex types sharing a local name in different
/// namespaces do not collide. Without this, a `blockDefault` in one namespace
/// would wrongly block an `xsi:type` substitution among the same-named types of
/// another namespace (a false rejection).
@Suite("namespaced derivation keys do not cross-block")
struct SchemaNamespacedDerivationTests {
/// Schema A (`urn:a`, `blockDefault="#all"`) imports schema B (`urn:b`, no
/// block). Both declare a complex type `T`, a `TD` extending their own `T`, and
/// an element `e` typed by their own `T`.
private let schemaA = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:a" xmlns:a="urn:a" blockDefault="#all">
<xs:import namespace="urn:b" schemaLocation="b.xsd"/>
<xs:complexType name="T"><xs:sequence><xs:element name="x" type="xs:string"/></xs:sequence></xs:complexType>
<xs:complexType name="TD">
<xs:complexContent><xs:extension base="a:T"><xs:sequence><xs:element name="y" type="xs:string"/></xs:sequence></xs:extension></xs:complexContent>
</xs:complexType>
<xs:element name="e" type="a:T"/>
</xs:schema>
"""
private let schemaB = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:b" xmlns:b="urn:b">
<xs:complexType name="T"><xs:sequence><xs:element name="x" type="xs:string"/></xs:sequence></xs:complexType>
<xs:complexType name="TD">
<xs:complexContent><xs:extension base="b:T"><xs:sequence><xs:element name="y" type="xs:string"/></xs:sequence></xs:extension></xs:complexContent>
</xs:complexType>
<xs:element name="e" type="b:T"/>
</xs:schema>
"""
private var loader: (String) -> String? {
let schemaB = schemaB
return { $0 == "b.xsd" ? schemaB : nil }
}
@Test("an extension xsi:type in urn:b is not blocked by urn:a's blockDefault")
func test_otherNamespaceNotBlocked() throws {
let document = try PureXML.Schema.Document(schemaA, schemaLoader: loader)
let instance = """
<b:e xmlns:b="urn:b" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="b:TD"><x>a</x><y>b</y></b:e>
"""
#expect(try document.validate(instance, schemaLoader: loader).isEmpty)
}
@Test("the same extension xsi:type in urn:a IS blocked by its own blockDefault")
func test_ownNamespaceBlocked() throws {
let document = try PureXML.Schema.Document(schemaA, schemaLoader: loader)
let instance = """
<a:e xmlns:a="urn:a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="a:TD"><x>a</x><y>b</y></a:e>
"""
#expect(try !document.validate(instance, schemaLoader: loader).isEmpty)
}
/// The streaming driver resolves `xsi:type` through its in-scope prefix bindings,
/// not the bare local name (#186). With the namespaced type table keyed by
/// `{ns}local`, a bare `"TD"` lookup misses, so the old streaming path reported a
/// false "unknown xsi:type"; the prefixed `b:TD` must resolve to urn:b's type and
/// agree with the tree path.
@Test("streaming xsi:type resolves by namespace and agrees with the tree path (#186)")
func test_streamingNamespacedXsiType() throws {
let document = try PureXML.Schema.Document(schemaA, schemaLoader: loader)
let valid = """
<b:e xmlns:b="urn:b" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="b:TD"><x>a</x><y>b</y></b:e>
"""
let tree = try document.validate(valid, schemaLoader: loader).map(\.reason).sorted()
let streamed = try document.validate(streaming: valid).map(\.reason).sorted()
#expect(tree.isEmpty, "tree rejected a valid namespaced xsi:type: \(tree)")
#expect(streamed == tree, "streaming disagreed with tree: \(streamed)")
}
}