PureXML/Sources/Model/TreeNodeNavigation.swift
Mihaela Mihaljevic 15de0c5344 feat(model): mutable parent-aware tree (libxml2 tree.h parity)
Add PureXML.Model.TreeNode, a reference-type document tree that knows its
parent and siblings. Navigation goes up and sideways (parent, nextSibling,
previousSibling, ancestors, root, elementChildren, stringValue) and editing
is in place (append, insert before/after, removeFromParent, replace, copy).
Children are held strongly and the parent weakly; attaching a node detaches
it from any prior parent and a node can never become its own ancestor.
parseTree builds one from XML; TreeNode.node converts back for serialization.

Closes #31
2026-06-08 03:13:59 +02:00

71 lines
2.3 KiB
Swift

public extension PureXML.Model.TreeNode {
/// The first child, or nil when there are none.
var firstChild: PureXML.Model.TreeNode? {
children.first
}
/// The last child, or nil when there are none.
var lastChild: PureXML.Model.TreeNode? {
children.last
}
/// The next sibling in document order, or nil when this is the last child or
/// has no parent.
var nextSibling: PureXML.Model.TreeNode? {
guard let siblings = parent?.children, let index = indexInParent else { return nil }
let next = index + 1
return next < siblings.count ? siblings[next] : nil
}
/// The previous sibling in document order, or nil when this is the first child
/// or has no parent.
var previousSibling: PureXML.Model.TreeNode? {
guard let siblings = parent?.children, let index = indexInParent, index > 0 else { return nil }
return siblings[index - 1]
}
/// The element children only, skipping text, comments, and the rest.
var elementChildren: [PureXML.Model.TreeNode] {
children.filter { $0.kind == .element }
}
/// The chain of ancestors from the immediate parent up to the root.
var ancestors: [PureXML.Model.TreeNode] {
var result: [PureXML.Model.TreeNode] = []
var current = parent
while let node = current {
result.append(node)
current = node.parent
}
return result
}
/// The topmost ancestor (the node itself when it has no parent).
var root: PureXML.Model.TreeNode {
var current = self
while let next = current.parent {
current = next
}
return current
}
/// The concatenated text of every text and CDATA node in this subtree, in
/// document order (the libxml2 `xmlNodeGetContent` behavior).
var stringValue: String {
switch kind {
case .text, .cdata:
value
case .element, .document:
children.reduce(into: "") { $0 += $1.stringValue }
case .comment, .processingInstruction:
""
}
}
/// This node's position among its parent's children, or nil when it has no
/// parent.
private var indexInParent: Int? {
parent?.children.firstIndex { $0 === self }
}
}