PureXML/Tests/XSDImportTests.swift
Mihaela Mihaljevic 4a966260f3 feat(xsd): add include/import/redefine, substitution groups, and xsi:type
Compose schemas across documents and resolve types at the instance:

- xs:include, xs:import, and xs:redefine pull a transitive closure of schema
  documents through an injected loader (resolved by schemaLocation), base
  schemas first so the including schema wins; a redefine's nested definitions
  override the schema it includes.
- substitution groups: an xs:element ref to a head also admits every element
  that substitutes for it, expanded at parse time into a choice over the head
  and its transitive members.
- xsi:type overrides an element's declared type with the named type at the
  instance; XML Schema instance attributes are exempt from attribute checks.

The schema parser splits across XSDContext (shared tree helpers, document
collection, parsing context), XSDSimpleParser (simple types and facets), and
XSDParser (elements, complex types, model groups) to keep each within bounds.

Closes #66
2026-06-08 08:46:24 +02:00

94 lines
3.8 KiB
Swift

@testable import PureXML
import Testing
@Suite("XSD includes, substitution groups, xsi:type")
struct XSDImportTests {
private func validate(
_ xsd: String,
_ xml: String,
loader: @escaping (String) -> String? = { _ in nil },
) throws -> [PureXML.Validation.Issue] {
try PureXML.Schema.Document(xsd, schemaLoader: loader).validate(xml)
}
@Test("xs:include pulls in another schema's named type")
func test_include() throws {
let library = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="sku">
<xs:restriction base="xs:string"><xs:pattern value="[A-Z]{3}"/></xs:restriction>
</xs:simpleType>
</xs:schema>
"""
let main = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="lib.xsd"/>
<xs:element name="code" type="sku"/>
</xs:schema>
"""
let loader: (String) -> String? = { $0 == "lib.xsd" ? library : nil }
#expect(try validate(main, "<code>ABC</code>", loader: loader).isEmpty)
#expect(try !validate(main, "<code>abc</code>", loader: loader).isEmpty)
}
@Test("xs:redefine overrides an included type")
func test_redefine() throws {
let base = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="quantity">
<xs:restriction base="xs:integer"><xs:maxInclusive value="100"/></xs:restriction>
</xs:simpleType>
</xs:schema>
"""
let main = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:redefine schemaLocation="base.xsd">
<xs:simpleType name="quantity">
<xs:restriction base="xs:integer"><xs:maxInclusive value="10"/></xs:restriction>
</xs:simpleType>
</xs:redefine>
<xs:element name="q" type="quantity"/>
</xs:schema>
"""
let loader: (String) -> String? = { $0 == "base.xsd" ? base : nil }
#expect(try validate(main, "<q>5</q>", loader: loader).isEmpty)
#expect(try !validate(main, "<q>50</q>", loader: loader).isEmpty)
}
@Test("A substitution-group member is accepted where the head is referenced")
func test_substitutionGroup() throws {
let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="shape" type="xs:string"/>
<xs:element name="circle" type="xs:string" substitutionGroup="shape"/>
<xs:element name="canvas">
<xs:complexType>
<xs:sequence>
<xs:element ref="shape" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
"""
#expect(try validate(xsd, "<canvas><shape>a</shape><circle>b</circle></canvas>").isEmpty)
#expect(try !validate(xsd, "<canvas><square>b</square></canvas>").isEmpty)
}
@Test("xsi:type overrides the declared type at the instance")
func test_xsiType() throws {
let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="base"><xs:sequence/></xs:complexType>
<xs:complexType name="withValue">
<xs:sequence><xs:element name="v" type="xs:integer"/></xs:sequence>
</xs:complexType>
<xs:element name="item" type="base"/>
</xs:schema>
"""
let valid = "<item xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"withValue\"><v>3</v></item>"
let invalid = "<item xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"withValue\"><v>x</v></item>"
#expect(try validate(xsd, valid).isEmpty)
#expect(try !validate(xsd, invalid).isEmpty)
}
}