109 lines
4.7 KiB
Swift
109 lines
4.7 KiB
Swift
public extension PureXML.Model.TreeNode {
|
|
/// The document node this node ultimately lives under, or nil when the node
|
|
/// is detached or rooted at something other than a document (the DOM
|
|
/// `ownerDocument`). Derived from the parent chain, so it stays correct after
|
|
/// any move or adoption without a separate identity field.
|
|
var ownerDocument: PureXML.Model.TreeNode? {
|
|
let top = root
|
|
return top.kind == .document ? top : nil
|
|
}
|
|
|
|
/// Detaches `node` from its current tree and makes it self-contained for this
|
|
/// document (the DOM `adoptNode`): every namespace the subtree relied on from
|
|
/// an outer scope is re-declared on the subtree, so moving it never silently
|
|
/// rebinds a prefix. Returns the adopted node, ready to insert with
|
|
/// ``append(_:)`` or one of the `insert` methods.
|
|
@discardableResult
|
|
func adopt(_ node: PureXML.Model.TreeNode) -> PureXML.Model.TreeNode {
|
|
Self.rebindNamespaces(on: node)
|
|
node.removeFromParent()
|
|
return node
|
|
}
|
|
|
|
/// A self-contained copy of `node` for use in this document (the DOM
|
|
/// `importNode`), leaving the original where it is. Deep by default; pass
|
|
/// `deep: false` for a shallow copy. Namespaces the subtree relied on from an
|
|
/// outer scope are re-declared on the copy so it carries its own bindings.
|
|
func importNode(_ node: PureXML.Model.TreeNode, deep: Bool = true) -> PureXML.Model.TreeNode {
|
|
let copy = deep ? node.copy() : node.shallowCopy()
|
|
Self.rebindNamespaces(on: copy)
|
|
return copy
|
|
}
|
|
|
|
// MARK: Typed accessors for the structural node kinds
|
|
|
|
/// The document-type (root element) name of a `.doctype` node.
|
|
var doctypeName: String? {
|
|
kind == .doctype ? name?.description : nil
|
|
}
|
|
|
|
/// The public identifier of a `.doctype` node, when it has an external subset.
|
|
var publicID: String? {
|
|
kind == .doctype ? attributeValue("public") : nil
|
|
}
|
|
|
|
/// The system identifier of a `.doctype` node, when it has an external subset.
|
|
var systemID: String? {
|
|
kind == .doctype ? attributeValue("system") : nil
|
|
}
|
|
|
|
/// The internal subset text of a `.doctype` node (empty when it has none).
|
|
var internalSubset: String? {
|
|
kind == .doctype ? value : nil
|
|
}
|
|
|
|
/// The entity name of a `.entityReference` node (without the `&` and `;`).
|
|
var entityReferenceName: String? {
|
|
kind == .entityReference ? name?.description : nil
|
|
}
|
|
|
|
/// The prefix bound by a `.namespace` node, or nil for the default namespace.
|
|
var namespacePrefix: String? {
|
|
guard kind == .namespace, let local = name?.localName, !local.isEmpty else { return nil }
|
|
return local
|
|
}
|
|
|
|
/// The URI bound by a `.namespace` node.
|
|
var namespaceBinding: String? {
|
|
kind == .namespace ? value : nil
|
|
}
|
|
|
|
/// Re-declares on `root` every namespace its subtree uses, so a moved or
|
|
/// copied subtree no longer depends on declarations that lived on outer
|
|
/// ancestors. Only adds a declaration the node does not already carry, so an
|
|
/// inner rebinding (which travels as its own `xmlns` attribute) is preserved.
|
|
private static func rebindNamespaces(on root: PureXML.Model.TreeNode) {
|
|
guard root.kind == .element else { return }
|
|
var declared = Set(root.attributes.map(\.name.description))
|
|
for (attributeName, uri) in usedNamespaces(in: root) where !declared.contains(attributeName) {
|
|
root.attributes.append(PureXML.Model.Attribute(attributeName, uri))
|
|
declared.insert(attributeName)
|
|
}
|
|
}
|
|
|
|
/// The `xmlns`/`xmlns:prefix` declarations the subtree's resolved names imply,
|
|
/// keyed by the declaration's attribute name. An element contributes its own
|
|
/// namespace; a prefixed attribute contributes its prefix's namespace.
|
|
private static func usedNamespaces(in node: PureXML.Model.TreeNode) -> [String: String] {
|
|
var result: [String: String] = [:]
|
|
var stack = [node]
|
|
while let current = stack.popLast() {
|
|
if current.kind == .element, let uri = current.name?.namespaceURI {
|
|
result[declarationName(forPrefix: current.name?.prefix)] = uri
|
|
}
|
|
for attribute in current.attributes {
|
|
if let prefix = attribute.name.prefix, prefix != "xmlns", let uri = attribute.name.namespaceURI {
|
|
result["xmlns:\(prefix)"] = uri
|
|
}
|
|
}
|
|
stack.append(contentsOf: current.children)
|
|
}
|
|
return result
|
|
}
|
|
|
|
private static func declarationName(forPrefix prefix: String?) -> String {
|
|
guard let prefix, !prefix.isEmpty else { return "xmlns" }
|
|
return "xmlns:\(prefix)"
|
|
}
|
|
}
|