PureXML/Tests/SchemaXSITypeNamespaceTests.swift
Mihaela Mihaljevic 20da41995a
fix(schema): resolve an unprefixed xsi:type in the instance namespace context (#383)
An xsi:type value is a QName: a prefix binds through that prefix, an unprefixed
name through the in-scope default namespace, and with no default namespace an
unprefixed name is in no namespace. PureXML stores each named type under both its
namespaced key and a bare local-name alias (for lenient lookup), so an unprefixed
xsi:type="Test" matched a {targetNS}Test through the alias even when the instance
had no default namespace, wrongly typing an otherwise-undeclared root and
accepting the document (W3C XSTS targetNS00101m; libxml2 rejects it identically).

xsiTypeReference now keys an unprefixed xsi:type to the in-scope default namespace
(none here) rather than returning the bare local name, so xsi:type="Test" names
no-namespace Test, which does not exist; the root then has neither a declaration
nor a usable type and the instance is invalid. A prefixed xsi:type and an
unprefixed one under a default namespace equal to the target namespace are
unaffected, and the child-element builtin-substitution path keeps its bare-name
fallback (xsiTypeName), so an unprefixed builtin still substitutes where valid.

Verified over the full W3C XSTS corpus in release: invalid-instances-accepted
6 -> 5, valid-instances-rejected held at 0 across all 14383 groups, full suite
(1840 tests) and wasm green. An adversarial critic traced every resolveNamedType
branch, including the unprefixed-builtin-via-key case, and confirmed the only
path that no longer resolves corresponds to a spec-invalid reference, so no valid
instance is newly rejected.
2026-06-29 10:10:04 +02:00

58 lines
2.8 KiB
Swift

import Testing
@testable import PureXML
/// An `xsi:type` value is a QName resolved in the instance's namespace context:
/// an unprefixed name uses the in-scope default namespace, and with none it is in
/// no namespace, so it does not name a namespaced type of the same local name.
/// Mirrors W3C XSTS targetNS00101m (CType): a schema with a `targetNamespace`
/// declares a type but no element, and a root with `xsi:type` but no element
/// declaration is valid only when the `xsi:type` QName resolves to that type.
@Suite("xsi:type QName resolves in the instance namespace context")
struct SchemaXSITypeNamespaceTests {
private func schema() throws -> PureXML.Schema.Document {
try PureXML.Schema.Document("""
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="targetNS" targetNamespace="targetNS">
<xsd:complexType name="Test"><xsd:sequence>
<xsd:element name="abc" type="xsd:string"/>
</xsd:sequence></xsd:complexType>
</xsd:schema>
""")
}
@Test("an unprefixed xsi:type with no default namespace does not name a namespaced type")
func test_unprefixedNoDefault() throws {
// xsi:type="Test" with no default namespace in scope -> no-namespace Test,
// which does not exist; the root `test` has no declaration -> invalid.
let xml = """
<t:test xmlns:t="targetNS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Test"><abc>1</abc></t:test>
"""
#expect(try !schema().validate(xml).isEmpty)
}
@Test("a prefixed xsi:type resolves through its prefix's namespace")
func test_prefixedResolves() throws {
let xml = """
<t:test xmlns:t="targetNS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="t:Test"><abc>1</abc></t:test>
"""
#expect(try schema().validate(xml).isEmpty)
}
@Test("an unprefixed xsi:type resolves through the in-scope default namespace")
func test_unprefixedDefaultNamespace() throws {
// A simpleContent type and a default namespace equal to the target namespace:
// unprefixed "T" names {targetNS}T and the text content validates, with no
// child-element qualification to confound the resolution under test.
let schema = try PureXML.Schema.Document("""
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="targetNS" targetNamespace="targetNS">
<xsd:complexType name="T"><xsd:simpleContent>
<xsd:extension base="xsd:string"/>
</xsd:simpleContent></xsd:complexType>
</xsd:schema>
""")
let xml = """
<e xmlns="targetNS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="T">hello</e>
"""
#expect(try schema.validate(xml).isEmpty)
}
}