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
94 lines
3.8 KiB
Swift
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)
|
|
}
|
|
}
|