PureXML/Tests/DTDIdentifierTests.swift
Mihaela Mihaljevic 537b455ce8 feat(validation): add DTD ID/IDREF/IDREFS validation
Complete the structural DTD validation layer. AttributeType now distinguishes
the ID family; the validator does a second pass after the structural walk
(since an IDREF may point forward to an ID later in the document):

- ID-typed attribute values must be unique across the document.
- IDREF values must each resolve to a declared ID.
- IDREFS values are split on whitespace and each token must resolve.

Reimplemented from the DTD ID/IDREF spec; no upstream code copied. 4 tests; 79
total.
2026-06-08 02:13:11 +02:00

37 lines
1.5 KiB
Swift

@testable import PureXML
import Testing
@Suite("DTD ID and IDREF validation")
struct DTDIdentifierTests {
private func issues(_ xml: String) throws -> [PureXML.Validation.Issue] {
try PureXML.validateAgainstInternalDTD(xml)
}
private let idDTD = "<!DOCTYPE root [<!ELEMENT root (item)*><!ELEMENT item EMPTY>"
+ "<!ATTLIST item id ID #IMPLIED ref IDREF #IMPLIED refs IDREFS #IMPLIED>]>"
@Test("Unique IDs validate; duplicate IDs are rejected")
func test_idUniqueness() throws {
try #expect(issues("\(idDTD)<root><item id=\"a\"/><item id=\"b\"/></root>").isEmpty)
try #expect(!issues("\(idDTD)<root><item id=\"a\"/><item id=\"a\"/></root>").isEmpty)
}
@Test("IDREF resolves to an ID anywhere in the document, including forward")
func test_idrefForwardReference() throws {
try #expect(issues("\(idDTD)<root><item ref=\"b\"/><item id=\"b\"/></root>").isEmpty)
}
@Test("A dangling IDREF is rejected")
func test_danglingIdref() throws {
try #expect(!issues("\(idDTD)<root><item ref=\"x\"/><item id=\"b\"/></root>").isEmpty)
}
@Test("IDREFS resolves each token; a dangling token is rejected")
func test_idrefs() throws {
let valid = "\(idDTD)<root><item id=\"a\"/><item id=\"b\"/><item refs=\"a b\"/></root>"
try #expect(issues(valid).isEmpty)
let dangling = "\(idDTD)<root><item id=\"a\"/><item refs=\"a z\"/></root>"
try #expect(!issues(dangling).isEmpty)
}
}