PureXML/Tests/SchemaSubstitutionBlockTests.swift

115 lines
6.3 KiB
Swift

import Testing
@testable import PureXML
/// `block="substitution"` on a substitution-group head forbids any member from
/// standing in for it, regardless of the member's type derivation (#147, XSTS
/// disallowedSubst set). Only type-derivation blocks (`extension`/`restriction`)
/// had been applied, so a same-type member still substituted under a
/// substitution block.
@Suite("Substitution-group block")
struct SchemaSubstitutionBlockTests {
private func schema(headBlock: String) throws -> PureXML.Schema.Document {
let attribute = headBlock.isEmpty ? "" : " block=\"\(headBlock)\""
return try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:s" targetNamespace="urn:s" elementFormDefault="qualified">
<xs:element name="root">
<xs:complexType><xs:sequence>
<xs:element ref="Head" maxOccurs="unbounded"/>
</xs:sequence></xs:complexType>
</xs:element>
<xs:element name="Head" type="T"\(attribute)/>
<xs:complexType name="T"><xs:sequence><xs:element name="x" type="xs:string"/></xs:sequence></xs:complexType>
<xs:element name="Member" type="T" substitutionGroup="Head"/>
</xs:schema>
""")
}
private let withMember = #"<root xmlns="urn:s"><Head><x>a</x></Head><Member><x>b</x></Member></root>"#
private let headsOnly = #"<root xmlns="urn:s"><Head><x>a</x></Head><Head><x>b</x></Head></root>"#
@Test("block='substitution' on the head rejects a substituting member")
func test_substitutionBlocked() throws {
#expect(try !schema(headBlock: "substitution").validate(withMember).isEmpty)
// Plain heads are still fine.
#expect(try schema(headBlock: "substitution").validate(headsOnly).isEmpty)
}
@Test("without a substitution block the member may substitute")
func test_memberAllowedWithoutBlock() throws {
#expect(try schema(headBlock: "").validate(withMember).isEmpty)
}
/// block="substitution" bars members even when the head is an ABSTRACT, UNTYPED
/// element (the substitution block is type-independent). Previously the filter was
/// skipped for a head with no type, so a member still substituted (particlesDc).
@Test("block='substitution' on an untyped abstract head bars members")
func test_substitutionBlockedOnUntypedAbstractHead() throws {
let schema = try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="urn:s" targetNamespace="urn:s" elementFormDefault="qualified">
<xs:element name="doc"><xs:complexType><xs:sequence><xs:element ref="h"/></xs:sequence></xs:complexType></xs:element>
<xs:element name="h" abstract="true" block="substitution"/>
<xs:element name="m" substitutionGroup="h"/>
</xs:schema>
""")
// A member substituting the substitution-blocked head is invalid.
#expect(try !schema.validate(#"<doc xmlns="urn:s"><m/></doc>"#).isEmpty)
}
@Test("an intermediate substitution block stops descendants from restricting an ancestor head")
func test_substitutionBlockedHeadDoesNotChainDescendants() {
#expect((try? PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="a" substitutionGroup="b" type="xs:anyType"/>
<xs:element name="b" substitutionGroup="c" type="xs:anyType" block="substitution"/>
<xs:element name="c" substitutionGroup="d" type="xs:anyType"/>
<xs:element name="d" type="xs:anyType"/>
<xs:complexType name="base"><xs:sequence><xs:element ref="d"/></xs:sequence></xs:complexType>
<xs:complexType name="derived"><xs:complexContent><xs:restriction base="base">
<xs:sequence><xs:choice><xs:element ref="a"/></xs:choice></xs:sequence>
</xs:restriction></xs:complexContent></xs:complexType>
</xs:schema>
""")) == nil)
}
@Test("a substitution-blocked member can still restrict its own ancestor head")
func test_substitutionBlockedHeadItselfStillRestrictsAncestor() {
#expect((try? PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="a" substitutionGroup="b" type="xs:anyType"/>
<xs:element name="b" substitutionGroup="c" type="xs:anyType"/>
<xs:element name="c" substitutionGroup="d" type="xs:anyType" block="substitution"/>
<xs:element name="d" type="xs:anyType"/>
<xs:complexType name="base"><xs:sequence><xs:element ref="d"/></xs:sequence></xs:complexType>
<xs:complexType name="derived"><xs:complexContent><xs:restriction base="base">
<xs:sequence><xs:choice><xs:element ref="c"/></xs:choice></xs:sequence>
</xs:restriction></xs:complexContent></xs:complexType>
</xs:schema>
""")) != nil)
}
/// cvc-elt.4.3.2.1: an xsi:type naming a complex type must be derived from the
/// element's declared type. A complex type on a different branch of the
/// hierarchy is rejected; the declared type itself is valid.
@Test("an xsi:type must be derived from the element's declared type")
func test_xsiTypeMustDeriveFromDeclared() throws {
let schema = try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="urn:s" targetNamespace="urn:s" elementFormDefault="qualified">
<xs:complexType name="B"><xs:sequence><xs:element name="f" type="xs:string"/></xs:sequence></xs:complexType>
<xs:complexType name="De"><xs:complexContent><xs:extension base="B"/></xs:complexContent></xs:complexType>
<xs:complexType name="Dr"><xs:complexContent><xs:restriction base="B">
<xs:sequence><xs:element name="f" type="xs:string"/></xs:sequence>
</xs:restriction></xs:complexContent></xs:complexType>
<xs:element name="e" type="De"/>
</xs:schema>
""")
func doc(_ type: String) -> String {
#"<e xmlns="urn:s" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="\#(type)"><f>x</f></e>"#
}
// Dr (restriction of B) is on a different branch, not derived from De: invalid.
#expect(try !schema.validate(doc("Dr")).isEmpty)
// The declared type itself is valid.
#expect(try schema.validate(doc("De")).isEmpty)
}
}