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
71 lines
2.3 KiB
Swift
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 }
|
|
}
|
|
}
|