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.
This commit is contained in:
parent
8bb7568f6e
commit
f9b56077cc
14
CHANGELOG.md
14
CHANGELOG.md
@ -28,6 +28,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- The streaming XSD validator now agrees with the tree validator on `xsi:type` and
|
||||
type resolution (#186). Type resolution is keyed by namespaced identity
|
||||
(`{ns}local`) through in-scope prefix bindings accumulated down the element stack:
|
||||
the `xsi:type` override type, the undeclared-type check, and root/child element
|
||||
declarations (so two globals or types sharing a local name in different namespaces
|
||||
no longer collide; root resolution mirrors the tree path, with the bare local-name
|
||||
map used only for a no-namespace root). The streaming path now runs the full
|
||||
cvc-elt.4 gate the tree path runs (a blocked, abstract, not-validly-derived,
|
||||
list/union, or anyType-for-anySimpleType override, and an abstract declared type
|
||||
carrying no `xsi:type`, are rejected identically). And it resolves a type from an
|
||||
element's own `xsi:type` where there is no governing declaration: an undeclared
|
||||
root (the W3C Sun target-namespace fallback) and a strict-wildcard child, which the
|
||||
streaming path previously rejected as undeclared.
|
||||
|
||||
- An attribute declaration may no longer land in the XSI namespace (XSD 1.0 Schema
|
||||
Component Constraint "xsi: Not Allowed", §3.2.6). A schema may target the XSI
|
||||
namespace and declare unqualified attributes (they land in no namespace), but a
|
||||
|
||||
@ -26,14 +26,18 @@ public extension PureXML.Schema {
|
||||
private let nillableElements: Set<String>
|
||||
/// The `default`/`fixed` value constraint declared on each element name.
|
||||
private let elementConstraints: [String: ValueConstraint]
|
||||
/// Local names of complex types declared `abstract="true"`.
|
||||
/// Complex types declared `abstract="true"`, keyed by namespaced identity
|
||||
/// (`{ns}local`) so same-local-name types in different namespaces stay distinct.
|
||||
let abstractTypes: Set<String>
|
||||
/// Derivation methods each named type forbids through `xsi:type`.
|
||||
/// Derivation methods each named type forbids through `xsi:type`, keyed by
|
||||
/// namespaced identity (`{ns}local`).
|
||||
let typeBlock: [String: Set<DerivationMethod>]
|
||||
/// Derivation methods each element declaration forbids through `xsi:type`
|
||||
/// (the `block` on the element, distinct from the block on its type).
|
||||
/// (the `block` on the element, distinct from the block on its type), keyed by
|
||||
/// namespaced identity (`{ns}local`).
|
||||
let elementBlock: [String: Set<DerivationMethod>]
|
||||
/// Each named complex type's base and derivation method.
|
||||
/// Each named complex type's base and derivation method, keyed by namespaced
|
||||
/// identity (`{ns}local`).
|
||||
let typeDerivation: [String: TypeDerivation]
|
||||
/// Global attribute declarations for strict/lax `anyAttribute` validation.
|
||||
let globalAttributes: [String: AttributeUse]
|
||||
|
||||
@ -29,10 +29,16 @@ public extension PureXML.Schema.ComplexValidator {
|
||||
|
||||
/// The effective element type after an `xsi:type` override and following any
|
||||
/// `typeReference` chain, so the streaming driver resolves a type once per
|
||||
/// element rather than re-entering the tree walk.
|
||||
func effectiveType(_ declared: PureXML.Schema.ElementType, of element: PureXML.Model.Element) -> PureXML.Schema.ElementType {
|
||||
/// element rather than re-entering the tree walk. The `xsi:type` is resolved
|
||||
/// through its in-scope prefix bindings to a namespaced type key (as the tree
|
||||
/// path does), so two imported types sharing a local name do not collide.
|
||||
func effectiveType(
|
||||
_ declared: PureXML.Schema.ElementType,
|
||||
of element: PureXML.Model.Element,
|
||||
namespaceBindings: [String: String] = [:],
|
||||
) -> PureXML.Schema.ElementType {
|
||||
var type = declared
|
||||
if let overriding = Self.xsiTypeName(element), let resolved = types[overriding] {
|
||||
if let resolved = resolvedXsiType(element, namespaceBindings: namespaceBindings) {
|
||||
type = resolved
|
||||
}
|
||||
// An unknown or circular chain stays a typeReference, which
|
||||
@ -43,6 +49,16 @@ public extension PureXML.Schema.ComplexValidator {
|
||||
return type
|
||||
}
|
||||
|
||||
/// The type an element's `xsi:type` resolves to through its in-scope prefix
|
||||
/// bindings, or nil when the named type is undeclared. Used by the streaming
|
||||
/// driver to report an unknown `xsi:type` the way the tree path does.
|
||||
func resolvedXsiType(_ element: PureXML.Model.Element, namespaceBindings: [String: String]) -> PureXML.Schema.ElementType? {
|
||||
guard let reference = Self.xsiTypeReference(element, namespaceBindings: namespaceBindings) ?? Self.xsiTypeName(element) else {
|
||||
return nil
|
||||
}
|
||||
return Self.resolveNamedType(reference, in: types)
|
||||
}
|
||||
|
||||
/// The declared element type of a child `name` in `parent`'s content model: a
|
||||
/// named particle member, or a global declaration reached through a matching
|
||||
/// wildcard's `processContents`. Returns nil when the parent has no element
|
||||
|
||||
@ -58,21 +58,35 @@ public extension PureXML.Validation {
|
||||
|
||||
private mutating func open(_ name: PureXML.Model.QualifiedName, _ attributes: [PureXML.Model.Attribute]) {
|
||||
let path = openPath(name)
|
||||
let declared = resolveDeclared(name, path: path)
|
||||
let probe = PureXML.Model.Element(name: name, attributes: attributes)
|
||||
let parentBindings = stack.last?.bindings ?? [:]
|
||||
let bindings = PureXML.Schema.ComplexValidator.namespaceBindings(for: probe, inherited: parentBindings)
|
||||
let declared = resolveDeclared(name, path: path, root: probe, bindings: bindings)
|
||||
if !stack.isEmpty {
|
||||
stack[stack.count - 1].childElements.append(PureXML.Model.Element(name: name))
|
||||
}
|
||||
let probe = PureXML.Model.Element(name: name, attributes: attributes)
|
||||
// An xsi:type naming an undeclared type is an error here too, so the
|
||||
// streaming and tree validators report the same problem.
|
||||
let overriding = PureXML.Schema.ComplexValidator.xsiTypeName(probe)
|
||||
if declared != nil, let overriding, validator.types[overriding] == nil {
|
||||
collected.append(ValidationError(reason: "unknown xsi:type '\(overriding)' on '\(name.localName)'", at: path))
|
||||
stack.append(XSDStreamFrame(name: name, attributes: attributes, effective: nil, path: path))
|
||||
return
|
||||
// Run the same cvc-elt.4 gate the tree path runs unconditionally for a
|
||||
// declared type: a blocked / not-derived / abstract / list-or-union /
|
||||
// anyType-for-anySimpleType xsi:type override, AND an abstract declared
|
||||
// type that requires an xsi:type but carries none. Then, only when an
|
||||
// xsi:type is present, an override naming an undeclared type is reported
|
||||
// the same way. Both resolve through the element's in-scope prefix
|
||||
// bindings, so the streaming and tree validators agree on every input.
|
||||
if let declared {
|
||||
if let xsiError = validator.xsiTypeOverrideError(declared: declared, child: probe, at: path, namespaceBindings: bindings) {
|
||||
collected.append(xsiError)
|
||||
stack.append(XSDStreamFrame(name: name, attributes: attributes, effective: nil, path: path, bindings: bindings))
|
||||
return
|
||||
}
|
||||
if PureXML.Schema.ComplexValidator.xsiTypeAttributeValue(probe) != nil, validator.resolvedXsiType(probe, namespaceBindings: bindings) == nil {
|
||||
let overriding = PureXML.Schema.ComplexValidator.xsiTypeName(probe) ?? ""
|
||||
collected.append(ValidationError(reason: "unknown xsi:type '\(overriding)' on '\(name.localName)'", at: path))
|
||||
stack.append(XSDStreamFrame(name: name, attributes: attributes, effective: nil, path: path, bindings: bindings))
|
||||
return
|
||||
}
|
||||
}
|
||||
let effective = declared.map { validator.effectiveType($0, of: probe) }
|
||||
stack.append(XSDStreamFrame(name: name, attributes: attributes, effective: effective, path: path))
|
||||
let effective = declared.map { validator.effectiveType($0, of: probe, namespaceBindings: bindings) }
|
||||
stack.append(XSDStreamFrame(name: name, attributes: attributes, effective: effective, path: path, bindings: bindings))
|
||||
}
|
||||
|
||||
private mutating func close() {
|
||||
@ -89,40 +103,83 @@ public extension PureXML.Validation {
|
||||
/// root (with the root-level prolog checks), or the parent content model's
|
||||
/// child type otherwise. Records prolog errors and returns nil when the
|
||||
/// element has no type to validate against.
|
||||
private mutating func resolveDeclared(_ name: PureXML.Model.QualifiedName, path: [PathKey]) -> PureXML.Schema.ElementType? {
|
||||
private mutating func resolveDeclared(
|
||||
_ name: PureXML.Model.QualifiedName,
|
||||
path: [PathKey],
|
||||
root: PureXML.Model.Element,
|
||||
bindings: [String: String],
|
||||
) -> PureXML.Schema.ElementType? {
|
||||
guard stack.isEmpty else {
|
||||
guard let parent = stack.last?.effective else { return nil }
|
||||
if let declared = validator.childType(of: parent, child: name) {
|
||||
return declared
|
||||
}
|
||||
// A strict wildcard with no global declaration is still typed by the
|
||||
// child's own xsi:type, if present (mirrors the tree path's
|
||||
// validateWildcardChild); only without one is it undeclared.
|
||||
if let error = validator.strictWildcardError(for: name, in: parent, at: path) {
|
||||
if let type = xsiTypeNamedType(root, bindings) {
|
||||
return type
|
||||
}
|
||||
collected.append(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
sawRoot = true
|
||||
guard let declaration = rootElements[name.localName]
|
||||
?? validator.types[PureXML.Schema.XSDParser.elementDeclarationKey(name)]
|
||||
// Resolve the root exactly as the tree path's globalElementDeclaration does
|
||||
// (namespaced identity, with the bare map used only for a no-namespace root),
|
||||
// then fall back to the type an `xsi:type` names when there is no global
|
||||
// declaration. Sharing the bare fallback across namespaces would bind a
|
||||
// foreign-namespace root to a same-local-name target-namespace element.
|
||||
let override = xsiTypeNamedType(root, bindings)
|
||||
guard let declaration = globalDeclaration(name) ?? override
|
||||
else {
|
||||
collected.append(ValidationError(reason: "no element declaration for '\(name.localName)'", at: path))
|
||||
return nil
|
||||
}
|
||||
if override == nil, rootNamespaceMismatch(name) {
|
||||
collected.append(ValidationError(reason: "root element '\(name.localName)' is not in the schema target namespace '\(targetNamespace ?? "")'", at: path))
|
||||
return nil
|
||||
}
|
||||
if abstractElements.contains(name.localName) {
|
||||
collected.append(ValidationError(reason: "abstract element '\(name.localName)' must not appear in an instance", at: path))
|
||||
return nil
|
||||
}
|
||||
if rootNamespaceMismatch(name) {
|
||||
collected.append(ValidationError(reason: "root element '\(name.localName)' is not in the schema target namespace '\(targetNamespace ?? "")'", at: path))
|
||||
return nil
|
||||
}
|
||||
return declaration
|
||||
}
|
||||
|
||||
/// The global element declaration for a qualified root name, mirroring the
|
||||
/// tree path's `globalElementDeclaration`: a namespaced lookup, with the bare
|
||||
/// `rootElements` map used only for a no-namespace root (never across
|
||||
/// namespaces, which would collide same-local-name globals).
|
||||
private func globalDeclaration(_ name: PureXML.Model.QualifiedName) -> PureXML.Schema.ElementType? {
|
||||
if let declaration = validator.types[PureXML.Schema.XSDParser.elementDeclarationKey(name)] {
|
||||
return declaration
|
||||
}
|
||||
if name.namespaceURI == nil || name.namespaceURI?.isEmpty == true {
|
||||
return rootElements[name.localName]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func rootNamespaceMismatch(_ name: PureXML.Model.QualifiedName) -> Bool {
|
||||
guard let target = targetNamespace, !target.isEmpty, name.namespaceURI != target else { return false }
|
||||
return validator.types[PureXML.Schema.XSDParser.elementDeclarationKey(name)] == nil
|
||||
}
|
||||
|
||||
/// The type an element's `xsi:type` names, resolved through its in-scope
|
||||
/// prefix bindings. Used where an element has no governing declaration but a
|
||||
/// concrete `xsi:type` supplies the type: an undeclared root (the tree path's
|
||||
/// `SchemaDocument` Sun target-namespace fallback) and a strict-wildcard child
|
||||
/// with no global declaration (the tree path's `xsiDeclaredType`).
|
||||
private func xsiTypeNamedType(_ element: PureXML.Model.Element, _ bindings: [String: String]) -> PureXML.Schema.ElementType? {
|
||||
guard PureXML.Schema.ComplexValidator.xsiTypeAttributeValue(element) != nil else { return nil }
|
||||
guard let reference = PureXML.Schema.ComplexValidator.xsiTypeReference(element, namespaceBindings: bindings),
|
||||
let type = PureXML.Schema.ComplexValidator.resolveNamedType(reference, in: validator.types)
|
||||
else { return nil }
|
||||
return type
|
||||
}
|
||||
|
||||
/// The coding path for an opening element: the root unindexed, a child with
|
||||
/// a one-based occurrence index (the `xmlGetNodePath` convention).
|
||||
private mutating func openPath(_ name: PureXML.Model.QualifiedName) -> [PathKey] {
|
||||
@ -144,6 +201,9 @@ private struct XSDStreamFrame {
|
||||
let attributes: [PureXML.Model.Attribute]
|
||||
let effective: PureXML.Schema.ElementType?
|
||||
let path: [PureXML.Validation.PathKey]
|
||||
/// In-scope prefix bindings at this element (ancestors merged with own `xmlns`),
|
||||
/// so a descendant's `xsi:type` resolves against prefixes declared up the tree.
|
||||
var bindings: [String: String] = [:]
|
||||
var childElements: [PureXML.Model.Element] = []
|
||||
var childCounts: [String: Int] = [:]
|
||||
var text = ""
|
||||
|
||||
@ -58,4 +58,22 @@ struct SchemaNamespacedDerivationTests {
|
||||
"""
|
||||
#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)")
|
||||
}
|
||||
}
|
||||
|
||||
129
Tests/SchemaStreamingXsiTypeGateTests.swift
Normal file
129
Tests/SchemaStreamingXsiTypeGateTests.swift
Normal file
@ -0,0 +1,129 @@
|
||||
import Testing
|
||||
@testable import PureXML
|
||||
|
||||
/// The streaming validator must run the same `xsi:type` substitution-validity gate
|
||||
/// (cvc-elt.4.3) the tree path runs, so it rejects a blocked, abstract, or
|
||||
/// not-validly-derived override identically (#186). Each case asserts streaming
|
||||
/// agrees with the tree oracle and that the invalid override is rejected, and a
|
||||
/// valid override is accepted on both paths.
|
||||
@Suite("XSD streaming xsi:type substitution gate (#186)")
|
||||
struct SchemaStreamingXsiTypeGateTests {
|
||||
private let schema = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t" xmlns:t="urn:t">
|
||||
<xs:complexType name="Base"><xs:sequence><xs:element name="x" type="xs:string"/></xs:sequence></xs:complexType>
|
||||
<xs:complexType name="Derived">
|
||||
<xs:complexContent><xs:extension base="t:Base"><xs:sequence><xs:element name="z" type="xs:string"/></xs:sequence></xs:extension></xs:complexContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Abs" abstract="true">
|
||||
<xs:complexContent><xs:extension base="t:Base"><xs:sequence><xs:element name="z" type="xs:string"/></xs:sequence></xs:extension></xs:complexContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Unrelated"><xs:sequence><xs:element name="w" type="xs:string"/></xs:sequence></xs:complexType>
|
||||
<xs:complexType name="Blocked" block="#all"><xs:sequence><xs:element name="x" type="xs:string"/></xs:sequence></xs:complexType>
|
||||
<xs:complexType name="BlockedDerived">
|
||||
<xs:complexContent><xs:extension base="t:Blocked"><xs:sequence><xs:element name="z" type="xs:string"/></xs:sequence></xs:extension></xs:complexContent>
|
||||
</xs:complexType>
|
||||
<xs:element name="e" type="t:Base"/>
|
||||
<xs:element name="b" type="t:Blocked"/>
|
||||
<xs:element name="a" type="t:Abs"/>
|
||||
</xs:schema>
|
||||
"""
|
||||
|
||||
private func agreement(_ instance: String) throws -> [String] {
|
||||
let document = try PureXML.Schema.Document(schema)
|
||||
let tree = try document.validate(instance).map(\.reason).sorted()
|
||||
let streamed = try document.validate(streaming: instance).map(\.reason).sorted()
|
||||
#expect(tree == streamed, "streaming disagreed with tree on:\n\(instance)\n tree: \(tree)\n stream: \(streamed)")
|
||||
return streamed
|
||||
}
|
||||
|
||||
private func instance(_ element: String, _ type: String, _ body: String) -> String {
|
||||
"""
|
||||
<t:\(element) xmlns:t="urn:t" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:type="t:\(type)">\(body)</t:\(element)>
|
||||
"""
|
||||
}
|
||||
|
||||
@Test("a valid derived xsi:type is accepted on both paths")
|
||||
func test_validOverrideAccepted() throws {
|
||||
#expect(try agreement(instance("e", "Derived", "<x>a</x><z>b</z>")).isEmpty)
|
||||
}
|
||||
|
||||
@Test("a not-validly-derived xsi:type is rejected on both paths (cvc-elt.4.3.2.1)")
|
||||
func test_notDerivedRejected() throws {
|
||||
#expect(try !agreement(instance("e", "Unrelated", "<w>a</w>")).isEmpty)
|
||||
}
|
||||
|
||||
@Test("an abstract xsi:type is rejected on both paths")
|
||||
func test_abstractRejected() throws {
|
||||
#expect(try !agreement(instance("e", "Abs", "<x>a</x><z>b</z>")).isEmpty)
|
||||
}
|
||||
|
||||
@Test("a blocked xsi:type substitution is rejected on both paths")
|
||||
func test_blockedRejected() throws {
|
||||
#expect(try !agreement(instance("b", "BlockedDerived", "<x>a</x><z>b</z>")).isEmpty)
|
||||
}
|
||||
|
||||
@Test("an abstract declared type with no xsi:type is rejected on both paths (cvc-elt.4.2)")
|
||||
func test_abstractDeclaredTypeRequiresXsiType() throws {
|
||||
let noOverride = """
|
||||
<t:a xmlns:t="urn:t"><x>a</x></t:a>
|
||||
"""
|
||||
#expect(try !agreement(noOverride).isEmpty)
|
||||
}
|
||||
|
||||
@Test("a root with no global declaration but a valid xsi:type is accepted on both paths")
|
||||
func test_undeclaredRootWithXsiTypeAccepted() throws {
|
||||
// The root 'undeclared' has no global element declaration; its xsi:type names a
|
||||
// known type, so both paths validate the body against that type (Sun target-NS
|
||||
// tests). Streaming must not report a false "no element declaration".
|
||||
#expect(try agreement(instance("undeclared", "Base", "<x>a</x>")).isEmpty)
|
||||
}
|
||||
|
||||
@Test("an undeclared root with xsi:type and a wrong body is rejected with the same content error")
|
||||
func test_undeclaredRootWithXsiTypeWrongBody() throws {
|
||||
// Both paths must report the content-model error against the xsi:type, not a
|
||||
// root "no element declaration".
|
||||
let errors = try agreement(instance("undeclared", "Base", "<wrong>a</wrong>"))
|
||||
#expect(!errors.isEmpty)
|
||||
#expect(!errors.contains { $0.contains("no element declaration") }, "got: \(errors)")
|
||||
}
|
||||
|
||||
@Test("a foreign-namespace root sharing a local name with a target-ns element uses its xsi:type, not that element")
|
||||
func test_foreignNamespaceRootDoesNotBindLocalNameElement() throws {
|
||||
// 'e' is a target-ns element typed t:Base. The root here is o:e in urn:other:
|
||||
// it has no global declaration, so its xsi:type='t:Unrelated' (a sibling type
|
||||
// not derived from Base) names the validating type. The bare-local-name root
|
||||
// fallback must NOT bind the target-ns 'e' across namespaces (that would gate
|
||||
// the body against Base and falsely reject the valid Unrelated override).
|
||||
let valid = """
|
||||
<o:e xmlns:o="urn:other" xmlns:t="urn:t" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:type="t:Unrelated"><w>a</w></o:e>
|
||||
"""
|
||||
#expect(try agreement(valid).isEmpty)
|
||||
}
|
||||
|
||||
@Test("a strict-wildcard child typed only by its xsi:type is accepted on both paths")
|
||||
func test_strictWildcardChildTypedByXsiType() throws {
|
||||
// 'item' (urn:o) has no global declaration; a strict ##other wildcard admits it
|
||||
// and its xsi:type names a known type, so both paths validate it against that
|
||||
// type rather than reporting "no declaration for wildcard-matched element".
|
||||
let wildcardSchema = """
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t" xmlns:t="urn:t">
|
||||
<xs:complexType name="Thing"><xs:sequence><xs:element name="x" type="xs:string"/></xs:sequence></xs:complexType>
|
||||
<xs:element name="root">
|
||||
<xs:complexType><xs:sequence><xs:any namespace="##other" processContents="strict"/></xs:sequence></xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
"""
|
||||
let xml = """
|
||||
<t:root xmlns:t="urn:t" xmlns:o="urn:o" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<o:item xsi:type="t:Thing"><x>hello</x></o:item>
|
||||
</t:root>
|
||||
"""
|
||||
let document = try PureXML.Schema.Document(wildcardSchema)
|
||||
let tree = try document.validate(xml).map(\.reason).sorted()
|
||||
let streamed = try document.validate(streaming: xml).map(\.reason).sorted()
|
||||
#expect(tree == streamed, "tree: \(tree)\nstream: \(streamed)")
|
||||
#expect(streamed.isEmpty, "streaming rejected a valid wildcard-xsi:type child: \(streamed)")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user