Building the mutable TreeNode tree from a parsed value Node ran append for every child. append -> insert detaches the child from any previous parent, rejects a self/ancestor cycle, and inserts into a children array that grows element by element. The children the conversion passes come from element.children.map and are freshly built and parentless, so all three guards are no-ops and the incremental growth rebuilds an array that was already prepared. TreeNode.init(adopting:) adopts the prepared array directly and wires each child's parent. The tree is identical: the same children in the same order with the same parent links. The adopting initializer is used only by the value-Node conversion; every public factory keeps the detaching, cycle-checking append path for externally supplied children that may already live in a tree. Measured parse ~12% faster on the 20k-item generated corpus via a same-state A/B (7.7x to ~6.5x slower than libxml2). Verified by TreeNodeAdoptingInitTests (parent-wiring and post-adoption mutability), the W3C XML conformance corpora, the Xalan XSLT gold-output corpus, the full suite, the WASM build, and an adversarial equivalence review.
71 lines
2.7 KiB
Swift
71 lines
2.7 KiB
Swift
public extension PureXML.Model.TreeNode {
|
|
/// Builds a mutable tree from a parsed value ``Node``, wiring up parent links.
|
|
convenience init(_ node: PureXML.Model.Node) {
|
|
switch node {
|
|
case let .document(children):
|
|
self.init(adopting: .document, children: children.map(PureXML.Model.TreeNode.init))
|
|
case let .element(element):
|
|
self.init(
|
|
adopting: .element,
|
|
name: element.name,
|
|
attributes: element.attributes,
|
|
children: element.children.map(PureXML.Model.TreeNode.init),
|
|
)
|
|
case let .text(value):
|
|
self.init(kind: .text, value: value)
|
|
case let .cdata(value):
|
|
self.init(kind: .cdata, value: value)
|
|
case let .comment(value):
|
|
self.init(kind: .comment, value: value)
|
|
case let .processingInstruction(target, data):
|
|
self.init(kind: .processingInstruction, name: PureXML.Model.QualifiedName(target), value: data)
|
|
}
|
|
}
|
|
|
|
/// Rebuilds an immutable value ``Node`` from this subtree, for serialization
|
|
/// or value comparison.
|
|
var node: PureXML.Model.Node {
|
|
switch kind {
|
|
case .document:
|
|
.document(projectedChildren)
|
|
case .element:
|
|
.element(PureXML.Model.Element(
|
|
name: name ?? PureXML.Model.QualifiedName(""),
|
|
attributes: attributes,
|
|
children: projectedChildren,
|
|
))
|
|
case .text:
|
|
.text(value)
|
|
case .entityReference:
|
|
.text(stringValue)
|
|
case .cdata:
|
|
.cdata(value)
|
|
case .comment:
|
|
.comment(value)
|
|
case .processingInstruction:
|
|
.processingInstruction(target: name?.description ?? "", data: value)
|
|
case .doctype, .namespace:
|
|
// DOM-structural kinds with no place in the content value model; they
|
|
// are dropped from a parent's projection (see projectedChildren), so a
|
|
// direct call here yields empty text only as a defensive fallback.
|
|
.text("")
|
|
}
|
|
}
|
|
|
|
/// The value-`Node` children of this subtree: doctype and namespace nodes are
|
|
/// dropped (they carry no content), and an entity-reference node is spliced
|
|
/// open into its replacement nodes, so the projection is a pure content tree.
|
|
private var projectedChildren: [PureXML.Model.Node] {
|
|
children.flatMap { child -> [PureXML.Model.Node] in
|
|
switch child.kind {
|
|
case .doctype, .namespace:
|
|
[]
|
|
case .entityReference:
|
|
child.projectedChildren
|
|
default:
|
|
[child.node]
|
|
}
|
|
}
|
|
}
|
|
}
|