PureXML/Sources/Validation/DTDDefaults.swift
Mihaela Mihaljevic 00c55e642f
perf: make tree-to-tree transforms depth-safe (DTD defaults, canonicalization) (#352)
* perf(model): rebuild DTD-default application iteratively, not recursively

applyingDefaults rebuilt the tree with a per-node recursive walk, so applying
DTD attribute defaults to a deeply-nested document overflowed the stack (around
2000 deep on a Swift Task stack). It now rebuilds bottom-up through a new shared
iterative spine, Node.rebuildingBottomUp, which transforms each node once its
children are rebuilt via an explicit work stack.

The same primitive will carry the other tree-to-tree transforms (canonical
prefix rewrite, XSLT namespace fixup) in follow-ups. Defaults are applied
identically: 1788 tests pass. Part of #350.

* perf(canonical): serialize C14N iteratively, not recursively

Canonicalizer.emit recursed on element children, so canonicalizing a
deeply-nested document overflowed the stack (crashed around 5000 deep).
The element serializer now drives a deferred-close work stack (the same
shape as the main serializer): each step carries the in-scope and
already-rendered namespace context, children push reversed to emit in
document order, and a close step emits the end tag. The shallow document
case and all namespace-rendering logic are unchanged.

The canonical bytes are byte-identical: the C14N 1.0, 1.1, and exclusive
conformance corpora pass, full suite 1789. A 50000-deep document now
canonicalizes without crashing. The node-subset helpers moved to their
own file to keep within the file-length limit. Part of #350.
2026-06-26 19:27:37 +02:00

48 lines
2.2 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 {
// Rebuilt bottom-up (iterative) so a deeply-nested document does not
// overflow the stack; each element gains its DTD-declared defaults once its
// children are rebuilt.
node.rebuildingBottomUp { original, rebuiltChildren in
switch original {
case let .element(element):
var result = element
result.children = rebuiltChildren
return .element(withDefaults(result))
case .document:
return .document(rebuiltChildren)
default:
return original
}
}
}
/// Adds the attributes `element`'s `<!ATTLIST>` declares with a default or
/// `#FIXED` value and that the element omits. `result.children` is assumed
/// already set by the caller.
private func withDefaults(_ element: PureXML.Model.Element) -> PureXML.Model.Element {
var result = element
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
}
}
}