Slice 3 of #92. The DTD validator's imperative walk becomes declared Validation values over a DTDSchema document: - DTDSchema is now just the schema (content models + attribute declarations) and serves as the rules' Document context; its validate/walk methods are gone. - contentModel and attributeDeclarations are Validation<Element, DTDSchema>, so every content-model and attribute violation is now located by coding path ("... at path: r/a"). undeclaredElement is added only in strict mode. - identifierIntegrity is a Validation<Node, DTDSchema> gated to the root, doing the document-wide ID-uniqueness and IDREF-resolution checks in one pass. - DTD.validator(strict:) assembles the rule set; validateAgainstInternalDTD runs it and returns [ValidationError] instead of [Issue]. A new test pins the located result. Schema (XSD) and Schematron validators still use Issue and migrate in the following slices.
47 lines
1.9 KiB
Swift
47 lines
1.9 KiB
Swift
@testable import PureXML
|
|
import Testing
|
|
|
|
@Suite("DTD attribute validation")
|
|
struct DTDAttributeTests {
|
|
private func issues(_ xml: String, strict: Bool = false) throws -> [PureXML.Validation.ValidationError] {
|
|
try PureXML.validateAgainstInternalDTD(xml, strict: strict)
|
|
}
|
|
|
|
@Test("#REQUIRED attribute must be present")
|
|
func test_required() throws {
|
|
let dtd = "<!DOCTYPE r [<!ELEMENT r EMPTY><!ATTLIST r id CDATA #REQUIRED>]>"
|
|
try #expect(issues("\(dtd)<r id=\"x\"/>").isEmpty)
|
|
try #expect(!issues("\(dtd)<r/>").isEmpty)
|
|
}
|
|
|
|
@Test("#FIXED attribute must match when present and may be omitted")
|
|
func test_fixed() throws {
|
|
let dtd = "<!DOCTYPE r [<!ELEMENT r EMPTY><!ATTLIST r v CDATA #FIXED \"1\">]>"
|
|
try #expect(issues("\(dtd)<r v=\"1\"/>").isEmpty)
|
|
try #expect(issues("\(dtd)<r/>").isEmpty)
|
|
try #expect(!issues("\(dtd)<r v=\"2\"/>").isEmpty)
|
|
}
|
|
|
|
@Test("Enumerated attribute must take a listed value")
|
|
func test_enumeration() throws {
|
|
let dtd = "<!DOCTYPE r [<!ELEMENT r EMPTY><!ATTLIST r dir (ltr|rtl) #IMPLIED>]>"
|
|
try #expect(issues("\(dtd)<r dir=\"ltr\"/>").isEmpty)
|
|
try #expect(issues("\(dtd)<r/>").isEmpty)
|
|
try #expect(!issues("\(dtd)<r dir=\"up\"/>").isEmpty)
|
|
}
|
|
|
|
@Test("Multiple attributes in one ATTLIST are each checked")
|
|
func test_multipleAttributes() throws {
|
|
let dtd = "<!DOCTYPE r [<!ELEMENT r EMPTY><!ATTLIST r a CDATA #REQUIRED b (x|y) #IMPLIED>]>"
|
|
try #expect(issues("\(dtd)<r a=\"1\" b=\"x\"/>").isEmpty)
|
|
// a is missing and b is out of its enumeration: two violations.
|
|
try #expect(issues("\(dtd)<r b=\"z\"/>").count == 2)
|
|
}
|
|
|
|
@Test("Tokenized types (ID) do not constrain the value")
|
|
func test_idTypeUnconstrained() throws {
|
|
let dtd = "<!DOCTYPE r [<!ELEMENT r EMPTY><!ATTLIST r id ID #REQUIRED>]>"
|
|
try #expect(issues("\(dtd)<r id=\"anything\"/>").isEmpty)
|
|
}
|
|
}
|