PureXML/Tests/SchemaIDIDREFTests.swift
Mihaela Mihaljevic 77db89397b fix(schema): enforce document-scoped xs:ID uniqueness and xs:IDREF resolution
xs:ID, xs:IDREF, and xs:IDREFS were validated only lexically (as NCNames); the
document-wide rules that every ID is unique and every IDREF/IDREFS item matches
some ID were not enforced, so duplicate IDs and dangling references passed. The
tree validator now records ID and reference values during its typed walk over
elements and attributes (a new IDTracker), and reports duplicate IDs and
unresolved references after the whole document is seen, so a forward reference
resolves against an ID declared later.

XSTS: invalid instances accepted 179 to 166. Baseline updated; regression tests
cover unique IDs, a forward reference, a duplicate ID, an unresolved IDREF, and
IDREFS item resolution. The ID/IDREF helpers live in a ComplexValidator
extension to keep the type within the file/body length limits.

Refs #147, #129.
2026-06-13 19:17:34 +02:00

53 lines
2.0 KiB
Swift

@testable import PureXML
import Testing
/// Document-scoped xs:ID/xs:IDREF validation (#147, XSTS idConstrDefs set):
/// every xs:ID value is unique across the document, and every xs:IDREF/xs:IDREFS
/// item matches some ID. These had been checked only lexically (as NCNames).
@Suite("ID and IDREF document constraints")
struct SchemaIDIDREFTests {
private var schema: PureXML.Schema.Document {
get throws {
try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="root">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="id" type="xs:ID"/>
<xs:element name="ref" type="xs:IDREF"/>
<xs:element name="refs" type="xs:IDREFS"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
""")
}
}
@Test("Unique IDs and a resolving reference validate")
func test_valid() throws {
#expect(try schema.validate("<root><id>a</id><id>b</id><ref>a</ref></root>").isEmpty)
}
@Test("A forward IDREF resolves against an ID declared later")
func test_forwardReference() throws {
#expect(try schema.validate("<root><ref>a</ref><id>a</id></root>").isEmpty)
}
@Test("A duplicate ID is rejected")
func test_duplicateID() throws {
#expect(try !schema.validate("<root><id>a</id><id>a</id></root>").isEmpty)
}
@Test("An IDREF with no matching ID is rejected")
func test_unresolvedReference() throws {
#expect(try !schema.validate("<root><id>a</id><ref>x</ref></root>").isEmpty)
}
@Test("IDREFS resolves each item; one unmatched item fails")
func test_idrefs() throws {
#expect(try schema.validate("<root><id>a</id><id>b</id><refs>a b</refs></root>").isEmpty)
#expect(try !schema.validate("<root><id>a</id><refs>a x</refs></root>").isEmpty)
}
}