PureXML/Tests/XSDCompositionTests.swift
Mihaela Mihaljevic cadc63ba8b feat(xsd): add list/union simple types, attribute groups, and element/group refs
Extend the schema parser toward full structural parity:

- list simple types validate each whitespace-separated item against the item
  type and count items for the length facets; union simple types admit a value
  valid for any member type. A restriction over a named list or union inherits
  its variety.
- Named attribute groups are expanded (transitively) into every complex type
  that references them.
- Named model groups are expanded at each xs:group ref, with a cycle guard;
  element refs resolve to the referenced global element's declared type,
  through a reserved type-table key so forward and recursive refs settle at
  validation time.

Named simple types now resolve over repeated passes so they may restrict or
list one another regardless of document order. Simple-type and facet parsing
moves into a sibling XSDSimpleParser to keep each parser within bounds.

Closes #65
2026-06-08 08:28:17 +02:00

125 lines
4.6 KiB
Swift

@testable import PureXML
import Testing
@Suite("XSD list, union, groups, and references")
struct XSDCompositionTests {
private func validate(_ xsd: String, _ xml: String) throws -> [PureXML.Validation.Issue] {
try PureXML.Schema.Document(xsd).validate(xml)
}
@Test("A list simple type validates each whitespace-separated item")
func test_listType() throws {
let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="ints">
<xs:list itemType="xs:integer"/>
</xs:simpleType>
<xs:element name="nums" type="ints"/>
</xs:schema>
"""
#expect(try validate(xsd, "<nums>1 2 3</nums>").isEmpty)
#expect(try !validate(xsd, "<nums>1 x 3</nums>").isEmpty)
}
@Test("A restricted list bounds its item count")
func test_listLength() throws {
let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="pair">
<xs:restriction base="couples">
<xs:length value="2"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="couples">
<xs:list itemType="xs:string"/>
</xs:simpleType>
<xs:element name="p" type="pair"/>
</xs:schema>
"""
#expect(try validate(xsd, "<p>a b</p>").isEmpty)
#expect(try !validate(xsd, "<p>a b c</p>").isEmpty)
}
@Test("A union admits a value valid for any member type")
func test_unionType() throws {
let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="sizeOrAuto">
<xs:union memberTypes="xs:nonNegativeInteger">
<xs:simpleType>
<xs:restriction base="xs:string"><xs:enumeration value="auto"/></xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
<xs:element name="w" type="sizeOrAuto"/>
</xs:schema>
"""
#expect(try validate(xsd, "<w>10</w>").isEmpty)
#expect(try validate(xsd, "<w>auto</w>").isEmpty)
#expect(try !validate(xsd, "<w>-1</w>").isEmpty)
#expect(try !validate(xsd, "<w>tiny</w>").isEmpty)
}
@Test("An attribute group is expanded into the referencing type")
func test_attributeGroup() throws {
let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:attributeGroup name="dims">
<xs:attribute name="w" type="xs:integer" use="required"/>
<xs:attribute name="h" type="xs:integer" use="required"/>
</xs:attributeGroup>
<xs:element name="box">
<xs:complexType>
<xs:attributeGroup ref="dims"/>
</xs:complexType>
</xs:element>
</xs:schema>
"""
#expect(try validate(xsd, "<box w=\"3\" h=\"4\"/>").isEmpty)
#expect(try !validate(xsd, "<box w=\"3\"/>").isEmpty)
#expect(try !validate(xsd, "<box w=\"x\" h=\"4\"/>").isEmpty)
}
@Test("A named model group is expanded at its reference")
func test_groupReference() throws {
let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:group name="titleAndBody">
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="body" type="xs:string"/>
</xs:sequence>
</xs:group>
<xs:element name="doc">
<xs:complexType>
<xs:sequence>
<xs:group ref="titleAndBody"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
"""
#expect(try validate(xsd, "<doc><title>A</title><body>B</body></doc>").isEmpty)
#expect(try !validate(xsd, "<doc><body>B</body></doc>").isEmpty)
}
@Test("An element reference resolves to the global declaration's type")
func test_elementReference() throws {
let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="sku" type="xs:nonNegativeInteger"/>
<xs:element name="order">
<xs:complexType>
<xs:sequence>
<xs:element ref="sku" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
"""
#expect(try validate(xsd, "<order><sku>1</sku><sku>2</sku></order>").isEmpty)
#expect(try !validate(xsd, "<order><sku>-1</sku></order>").isEmpty)
#expect(try !validate(xsd, "<order><other>1</other></order>").isEmpty)
}
}