* 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.
65 lines
2.6 KiB
Swift
65 lines
2.6 KiB
Swift
extension PureXML.Model.Node {
|
|
/// Rebuilds this tree bottom-up without recursing on the tree's depth, so a
|
|
/// transform over a deeply-nested document cannot overflow the stack. Each
|
|
/// node is passed to `transform` once its children have been rebuilt, together
|
|
/// with those rebuilt children (empty for a leaf); the transform returns the
|
|
/// replacement node. An explicit work stack drives a post-order walk, so a
|
|
/// parent is transformed only after all its descendants.
|
|
///
|
|
/// This is the shared spine for the tree-to-tree transforms (DTD default
|
|
/// application, canonical prefix rewriting, and the like) that previously
|
|
/// recursed one native frame per level.
|
|
func rebuildingBottomUp(
|
|
_ transform: (_ node: PureXML.Model.Node, _ rebuiltChildren: [PureXML.Model.Node]) -> PureXML.Model.Node,
|
|
) -> PureXML.Model.Node {
|
|
guard let rootChildren = Self.branchChildren(self) else {
|
|
return transform(self, [])
|
|
}
|
|
let rootFrame = RebuildFrame(self, rootChildren)
|
|
var stack: [RebuildFrame] = [rootFrame]
|
|
while let frame = stack.last {
|
|
guard frame.next < frame.children.count else {
|
|
stack.removeLast()
|
|
let rebuilt = transform(frame.node, frame.built)
|
|
if let parent = stack.last {
|
|
parent.built.append(rebuilt)
|
|
} else {
|
|
return rebuilt
|
|
}
|
|
continue
|
|
}
|
|
let child = frame.children[frame.next]
|
|
frame.next += 1
|
|
if let grandchildren = Self.branchChildren(child) {
|
|
stack.append(RebuildFrame(child, grandchildren))
|
|
} else {
|
|
frame.built.append(transform(child, []))
|
|
}
|
|
}
|
|
return transform(self, [])
|
|
}
|
|
|
|
/// The children of a branch node (document or element), or nil for a leaf.
|
|
private static func branchChildren(_ node: PureXML.Model.Node) -> [PureXML.Model.Node]? {
|
|
switch node {
|
|
case let .document(children): children
|
|
case let .element(element): element.children
|
|
default: nil
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A node being rebuilt bottom-up: its source children, the cursor into them, and
|
|
/// the rebuilt children gathered so far.
|
|
private final class RebuildFrame {
|
|
let node: PureXML.Model.Node
|
|
let children: [PureXML.Model.Node]
|
|
var next = 0
|
|
var built: [PureXML.Model.Node] = []
|
|
|
|
init(_ node: PureXML.Model.Node, _ children: [PureXML.Model.Node]) {
|
|
self.node = node
|
|
self.children = children
|
|
}
|
|
}
|