PureXML/Sources/Validation/DTDSchema.swift
Mihaela Mihaljevic 7f1aada240 feat(dtd): conditional sections, notations, NDATA, external parameter entities
Deepen DTD processing in the doctype scanner:

- Conditional sections are processed, not skipped: INCLUDE sections have
  their declarations scanned, IGNORE sections are discarded, the keyword
  may be a parameter-entity reference, and nested sections are handled.
- <!NOTATION> declarations are parsed and stored on DocumentType.
- Unparsed entities (<!ENTITY ... NDATA n>) are parsed and stored with
  their notation, distinct from general entities.
- External parameter entities are loaded through the injected resolver so
  their declarations expand; the default refusing resolver loads nothing,
  keeping XXE closed.
- NOTATION attributes are a distinct type, validated against the declared
  notations (value must be listed and the notation declared).

Tracking unparsed entities also lets ENTITY/ENTITIES attribute values be
validated against the declared NDATA entities, closing the gap left when
#73 could only check name syntax.

Closes #72
2026-06-08 14:39:42 +02:00

37 lines
1.6 KiB
Swift

public extension PureXML.Validation {
/// A DTD schema: the element content models declared by `<!ELEMENT>` and the
/// attribute declarations from `<!ATTLIST>`, against which a parsed tree can
/// be validated. Built from the internal subset surfaced by the parser.
struct DTDSchema: Sendable {
let models: [String: ContentModel]
let attributes: [String: [AttributeDeclaration]]
/// The names declared by `<!NOTATION>`, against which a `NOTATION` attribute
/// value (and the names listed in its declaration) are checked.
let notations: Set<String>
/// The names of declared unparsed (`NDATA`) entities, against which an
/// `ENTITY`/`ENTITIES` attribute value is checked.
let unparsedEntities: Set<String>
init(_ documentType: PureXML.Parsing.DocumentType) {
notations = Set(documentType.notations.keys)
unparsedEntities = Set(documentType.unparsedEntities.keys)
var parsedModels: [String: ContentModel] = [:]
for (name, model) in documentType.elementModels {
parsedModels[name] = ContentModelParser.parse(model)
}
models = parsedModels
var parsedAttributes: [String: [AttributeDeclaration]] = [:]
for (name, body) in documentType.attributeLists {
parsedAttributes[name] = AttributeListParser.parse(body)
}
attributes = parsedAttributes
}
/// Whether the schema declares any elements or attributes.
public var isEmpty: Bool {
models.isEmpty && attributes.isEmpty
}
}
}