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.
80 lines
4.0 KiB
Swift
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)")
|
|
}
|
|
}
|