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.
62 lines
3.1 KiB
Swift
62 lines
3.1 KiB
Swift
/// File-scope aliases for the DTD validation rules and helpers, kept out of the
|
|
/// `DTD` enum to avoid nesting a type two levels deep.
|
|
typealias DTDElement = PureXML.Model.Element
|
|
typealias DTDPath = [PureXML.Validation.PathKey]
|
|
typealias DTDFailure = PureXML.Validation.ValidationError
|
|
|
|
public extension PureXML.Validation {
|
|
/// DTD validation as composable rules over a ``DTDSchema`` document: every
|
|
/// element is checked against its content model and attribute declarations,
|
|
/// and ID/IDREF integrity is checked once over the whole tree. Replaces the
|
|
/// imperative walk with declared ``Validation`` values.
|
|
enum DTD {
|
|
/// A validator for a ``DTDSchema``. In strict mode every element must also
|
|
/// be declared.
|
|
static func validator(strict: Bool) -> Validator<DTDSchema> {
|
|
var validator = Validator<DTDSchema>.blank
|
|
.validating(contentModel, attributeDeclarations)
|
|
.validating(identifierIntegrity)
|
|
if strict { validator = validator.validating(undeclaredElement) }
|
|
return validator
|
|
}
|
|
|
|
/// Each element's content matches its `<!ELEMENT>` model.
|
|
static var contentModel: Validation<DTDElement, DTDSchema> {
|
|
.init(description: "Element content matches its DTD content model") { context in
|
|
let element = context.subject
|
|
guard let model = context.document.models[element.name.description] else { return [] }
|
|
return contentViolations(element, model: model, at: context.codingPath)
|
|
}
|
|
}
|
|
|
|
/// Each element's attributes satisfy their `<!ATTLIST>` declarations
|
|
/// (required presence, `#FIXED` value, and enumerations).
|
|
static var attributeDeclarations: Validation<DTDElement, DTDSchema> {
|
|
.init(description: "Element attributes satisfy their DTD declarations") { context in
|
|
let element = context.subject
|
|
guard let declarations = context.document.attributes[element.name.description] else { return [] }
|
|
return declarations.flatMap { declaration in
|
|
attributeViolations(declaration, on: element, at: context.codingPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// In strict mode, every element is declared in the DTD.
|
|
static var undeclaredElement: Validation<DTDElement, DTDSchema> {
|
|
.init(description: "Element is declared in the DTD") { context in
|
|
context.document.models[context.subject.name.description] != nil
|
|
}
|
|
}
|
|
|
|
/// ID values are unique and every IDREF resolves. Runs once over the whole
|
|
/// tree (a reference may point forward), so it is gated to the root.
|
|
static var identifierIntegrity: Validation<PureXML.Model.Node, DTDSchema> {
|
|
.init(
|
|
description: "DTD ID values are unique and IDREFs resolve",
|
|
check: { context in identifierErrors(context.subject, schema: context.document, at: context.codingPath) },
|
|
when: { $0.codingPath.isEmpty },
|
|
)
|
|
}
|
|
}
|
|
}
|