PureXML/Sources/Validation/DTDDefaults.swift
Mihaela Mihaljevic a041b8beca feat(dtd): attribute defaulting, tokenized types, value normalization
Complete the DTD attribute handling found in the audit:

- Default and #FIXED attribute values are applied to the parsed tree.
  parseApplyingInternalDTDDefaults returns a tree where every element
  carries the attributes its <!ATTLIST> defaults, so an editor sees the
  same effective attributes a validating processor would.
- NMTOKEN, NMTOKENS, ENTITY, ENTITIES are validated: NMTOKEN(S) must be
  XML name tokens, ENTITY/ENTITIES must be XML names. Previously all fell
  through to unconstrained CDATA.
- DTD default values are normalized per XML 1.0: character and predefined
  entity references are expanded, literal whitespace becomes a space, and
  non-CDATA values are collapsed and trimmed.

ENTITY/ENTITIES check name syntax only; that the named entity is a
declared unparsed entity is not verified, since NDATA entity tracking is
not yet modeled.

Closes #73
2026-06-08 14:31:42 +02:00

39 lines
1.8 KiB
Swift

public extension PureXML.Validation.DTDSchema {
/// Returns `node` with DTD attribute defaults applied: every element gains any
/// attribute its `<!ATTLIST>` declares with a default or `#FIXED` value and
/// that the element omits. `#REQUIRED` and `#IMPLIED` add nothing. The injected
/// values are the normalized defaults from the declaration, so an editor reads
/// the same effective attributes a validating processor would see.
func applyingDefaults(to node: PureXML.Model.Node) -> PureXML.Model.Node {
switch node {
case let .document(children):
.document(children.map(applyingDefaults(to:)))
case let .element(element):
.element(applyingDefaults(to: element))
case .text, .cdata, .comment, .processingInstruction:
node
}
}
private func applyingDefaults(to element: PureXML.Model.Element) -> PureXML.Model.Element {
var result = element
result.children = element.children.map { applyingDefaults(to: $0) }
guard let declarations = attributes[element.name.description] else { return result }
for declaration in declarations {
guard let value = Self.defaultValue(declaration) else { continue }
let present = result.attributes.contains {
$0.name.description == declaration.name || $0.name.localName == declaration.name
}
if !present { result.attributes.append(PureXML.Model.Attribute(declaration.name, value)) }
}
return result
}
private static func defaultValue(_ declaration: PureXML.Validation.AttributeDeclaration) -> String? {
switch declaration.defaultDecl {
case let .value(value), let .fixed(value): value
case .required, .implied: nil
}
}
}