809 lines
25 KiB
Swift
809 lines
25 KiB
Swift
//
|
|
// Node.swift
|
|
// SwifSoup
|
|
//
|
|
// Created by Nabil Chatbi on 29/09/16.
|
|
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
open class Node: Equatable, Hashable {
|
|
private static let abs = "abs:"
|
|
fileprivate static let empty = ""
|
|
private static let EMPTY_NODES: Array<Node> = Array<Node>()
|
|
unowned var parentNode: Node?
|
|
var childNodes: Array <Node>
|
|
var attributes: Attributes?
|
|
var baseUri: String?
|
|
|
|
/**
|
|
* Get the list index of this node in its node sibling list. I.e. if this is the first node
|
|
* sibling, returns 0.
|
|
* @return position in node sibling list
|
|
* @see Element#elementSiblingIndex()
|
|
*/
|
|
public private(set) var siblingIndex: Int = 0
|
|
|
|
/**
|
|
Create a new Node.
|
|
@param baseUri base URI
|
|
@param attributes attributes (not null, but may be empty)
|
|
*/
|
|
public init(_ baseUri: String, _ attributes: Attributes) {
|
|
self.childNodes = Node.EMPTY_NODES
|
|
self.baseUri = baseUri.trim()
|
|
self.attributes = attributes
|
|
}
|
|
|
|
public init(_ baseUri: String) {
|
|
childNodes = Node.EMPTY_NODES
|
|
self.baseUri = baseUri.trim()
|
|
self.attributes = Attributes()
|
|
}
|
|
|
|
/**
|
|
* Default constructor. Doesn't setup base uri, children, or attributes; use with caution.
|
|
*/
|
|
public init() {
|
|
self.childNodes = Node.EMPTY_NODES
|
|
self.attributes = nil
|
|
self.baseUri = nil
|
|
}
|
|
|
|
/**
|
|
Get the node name of this node. Use for debugging purposes and not logic switching (for that, use instanceof).
|
|
@return node name
|
|
*/
|
|
public func nodeName() -> String {
|
|
preconditionFailure("This method must be overridden")
|
|
}
|
|
|
|
/**
|
|
* Get an attribute's value by its key. <b>Case insensitive</b>
|
|
* <p>
|
|
* To get an absolute URL from an attribute that may be a relative URL, prefix the key with <code><b>abs</b></code>,
|
|
* which is a shortcut to the {@link #absUrl} method.
|
|
* </p>
|
|
* E.g.:
|
|
* <blockquote><code>String url = a.attr("abs:href");</code></blockquote>
|
|
*
|
|
* @param attributeKey The attribute key.
|
|
* @return The attribute, or empty string if not present (to avoid nulls).
|
|
* @see #attributes()
|
|
* @see #hasAttr(String)
|
|
* @see #absUrl(String)
|
|
*/
|
|
open func attr(_ attributeKey: String)throws ->String {
|
|
let val: String = try attributes!.getIgnoreCase(key: attributeKey)
|
|
if (val.count > 0) {
|
|
return val
|
|
} else if (attributeKey.lowercased().startsWith(Node.abs)) {
|
|
return try absUrl(attributeKey.substring(Node.abs.count))
|
|
} else {return Node.empty}
|
|
}
|
|
|
|
/**
|
|
* Get all of the element's attributes.
|
|
* @return attributes (which implements iterable, in same order as presented in original HTML).
|
|
*/
|
|
open func getAttributes() -> Attributes? {
|
|
return attributes
|
|
}
|
|
|
|
/**
|
|
* Set an attribute (key=value). If the attribute already exists, it is replaced.
|
|
* @param attributeKey The attribute key.
|
|
* @param attributeValue The attribute value.
|
|
* @return this (for chaining)
|
|
*/
|
|
@discardableResult
|
|
open func attr(_ attributeKey: String, _ attributeValue: String)throws->Node {
|
|
try attributes?.put(attributeKey, attributeValue)
|
|
return self
|
|
}
|
|
|
|
/**
|
|
* Test if this element has an attribute. <b>Case insensitive</b>
|
|
* @param attributeKey The attribute key to check.
|
|
* @return true if the attribute exists, false if not.
|
|
*/
|
|
open func hasAttr(_ attributeKey: String) -> Bool {
|
|
guard let attributes = attributes else {
|
|
return false
|
|
}
|
|
if (attributeKey.startsWith(Node.abs)) {
|
|
let key: String = attributeKey.substring(Node.abs.count)
|
|
do {
|
|
let abs = try absUrl(key)
|
|
if (attributes.hasKeyIgnoreCase(key: key) && !Node.empty.equals(abs)) {
|
|
return true
|
|
}
|
|
} catch {
|
|
return false
|
|
}
|
|
|
|
}
|
|
return attributes.hasKeyIgnoreCase(key: attributeKey)
|
|
}
|
|
|
|
/**
|
|
* Remove an attribute from this element.
|
|
* @param attributeKey The attribute to remove.
|
|
* @return this (for chaining)
|
|
*/
|
|
@discardableResult
|
|
open func removeAttr(_ attributeKey: String)throws->Node {
|
|
try attributes?.removeIgnoreCase(key: attributeKey)
|
|
return self
|
|
}
|
|
|
|
/**
|
|
Get the base URI of this node.
|
|
@return base URI
|
|
*/
|
|
open func getBaseUri() -> String {
|
|
return baseUri!
|
|
}
|
|
|
|
/**
|
|
Update the base URI of this node and all of its descendants.
|
|
@param baseUri base URI to set
|
|
*/
|
|
open func setBaseUri(_ baseUri: String)throws {
|
|
class nodeVisitor: NodeVisitor {
|
|
private let baseUri: String
|
|
init(_ baseUri: String) {
|
|
self.baseUri = baseUri
|
|
}
|
|
|
|
func head(_ node: Node, _ depth: Int)throws {
|
|
node.baseUri = baseUri
|
|
}
|
|
|
|
func tail(_ node: Node, _ depth: Int)throws {
|
|
}
|
|
}
|
|
try traverse(nodeVisitor(baseUri))
|
|
}
|
|
|
|
/**
|
|
* Get an absolute URL from a URL attribute that may be relative (i.e. an <code><a href></code> or
|
|
* <code><img src></code>).
|
|
* <p>
|
|
* E.g.: <code>String absUrl = linkEl.absUrl("href");</code>
|
|
* </p>
|
|
* <p>
|
|
* If the attribute value is already absolute (i.e. it starts with a protocol, like
|
|
* <code>http://</code> or <code>https://</code> etc), and it successfully parses as a URL, the attribute is
|
|
* returned directly. Otherwise, it is treated as a URL relative to the element's {@link #baseUri}, and made
|
|
* absolute using that.
|
|
* </p>
|
|
* <p>
|
|
* As an alternate, you can use the {@link #attr} method with the <code>abs:</code> prefix, e.g.:
|
|
* <code>String absUrl = linkEl.attr("abs:href");</code>
|
|
* </p>
|
|
*
|
|
* @param attributeKey The attribute key
|
|
* @return An absolute URL if one could be made, or an empty string (not null) if the attribute was missing or
|
|
* could not be made successfully into a URL.
|
|
* @see #attr
|
|
* @see java.net.URL#URL(java.net.URL, String)
|
|
*/
|
|
open func absUrl(_ attributeKey: String)throws->String {
|
|
try Validate.notEmpty(string: attributeKey)
|
|
|
|
if (!hasAttr(attributeKey)) {
|
|
return Node.empty // nothing to make absolute with
|
|
} else {
|
|
return StringUtil.resolve(baseUri!, relUrl: try attr(attributeKey))
|
|
}
|
|
}
|
|
|
|
/**
|
|
Get a child node by its 0-based index.
|
|
@param index index of child node
|
|
@return the child node at this index. Throws a {@code IndexOutOfBoundsException} if the index is out of bounds.
|
|
*/
|
|
open func childNode(_ index: Int) -> Node {
|
|
return childNodes[index]
|
|
}
|
|
|
|
/**
|
|
Get this node's children. Presented as an unmodifiable list: new children can not be added, but the child nodes
|
|
themselves can be manipulated.
|
|
@return list of children. If no children, returns an empty list.
|
|
*/
|
|
open func getChildNodes()->Array<Node> {
|
|
return childNodes
|
|
}
|
|
|
|
/**
|
|
* Returns a deep copy of this node's children. Changes made to these nodes will not be reflected in the original
|
|
* nodes
|
|
* @return a deep copy of this node's children
|
|
*/
|
|
open func childNodesCopy()->Array<Node> {
|
|
var children: Array<Node> = Array<Node>()
|
|
for node: Node in childNodes {
|
|
children.append(node.copy() as! Node)
|
|
}
|
|
return children
|
|
}
|
|
|
|
/**
|
|
* Get the number of child nodes that this node holds.
|
|
* @return the number of child nodes that this node holds.
|
|
*/
|
|
public func childNodeSize() -> Int {
|
|
return childNodes.count
|
|
}
|
|
|
|
final func childNodesAsArray() -> [Node] {
|
|
return childNodes as Array
|
|
}
|
|
|
|
/**
|
|
Gets this node's parent node.
|
|
@return parent node or null if no parent.
|
|
*/
|
|
open func parent() -> Node? {
|
|
return parentNode
|
|
}
|
|
|
|
/**
|
|
Gets this node's parent node. Node overridable by extending classes, so useful if you really just need the Node type.
|
|
@return parent node or null if no parent.
|
|
*/
|
|
final func getParentNode() -> Node? {
|
|
return parentNode
|
|
}
|
|
|
|
/**
|
|
* Gets the Document associated with this Node.
|
|
* @return the Document associated with this Node, or null if there is no such Document.
|
|
*/
|
|
open func ownerDocument() -> Document? {
|
|
if let this = self as? Document {
|
|
return this
|
|
} else if (parentNode == nil) {
|
|
return nil
|
|
} else {
|
|
return parentNode!.ownerDocument()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove (delete) this node from the DOM tree. If this node has children, they are also removed.
|
|
*/
|
|
open func remove()throws {
|
|
try parentNode?.removeChild(self)
|
|
}
|
|
|
|
/**
|
|
* Insert the specified HTML into the DOM before this node (i.e. as a preceding sibling).
|
|
* @param html HTML to add before this node
|
|
* @return this node, for chaining
|
|
* @see #after(String)
|
|
*/
|
|
@discardableResult
|
|
open func before(_ html: String)throws->Node {
|
|
try addSiblingHtml(siblingIndex, html)
|
|
return self
|
|
}
|
|
|
|
/**
|
|
* Insert the specified node into the DOM before this node (i.e. as a preceding sibling).
|
|
* @param node to add before this node
|
|
* @return this node, for chaining
|
|
* @see #after(Node)
|
|
*/
|
|
@discardableResult
|
|
open func before(_ node: Node)throws ->Node {
|
|
try Validate.notNull(obj: node)
|
|
try Validate.notNull(obj: parentNode)
|
|
|
|
try parentNode?.addChildren(siblingIndex, node)
|
|
return self
|
|
}
|
|
|
|
/**
|
|
* Insert the specified HTML into the DOM after this node (i.e. as a following sibling).
|
|
* @param html HTML to add after this node
|
|
* @return this node, for chaining
|
|
* @see #before(String)
|
|
*/
|
|
@discardableResult
|
|
open func after(_ html: String)throws ->Node {
|
|
try addSiblingHtml(siblingIndex + 1, html)
|
|
return self
|
|
}
|
|
|
|
/**
|
|
* Insert the specified node into the DOM after this node (i.e. as a following sibling).
|
|
* @param node to add after this node
|
|
* @return this node, for chaining
|
|
* @see #before(Node)
|
|
*/
|
|
@discardableResult
|
|
open func after(_ node: Node)throws->Node {
|
|
try Validate.notNull(obj: node)
|
|
try Validate.notNull(obj: parentNode)
|
|
|
|
try parentNode?.addChildren(siblingIndex+1, node)
|
|
return self
|
|
}
|
|
|
|
private func addSiblingHtml(_ index: Int, _ html: String)throws {
|
|
try Validate.notNull(obj: parentNode)
|
|
|
|
let context: Element? = parent() as? Element
|
|
|
|
let nodes: Array<Node> = try Parser.parseFragment(html, context, getBaseUri())
|
|
try parentNode?.addChildren(index, nodes)
|
|
}
|
|
|
|
/**
|
|
* Insert the specified HTML into the DOM after this node (i.e. as a following sibling).
|
|
* @param html HTML to add after this node
|
|
* @return this node, for chaining
|
|
* @see #before(String)
|
|
*/
|
|
@discardableResult
|
|
open func after(html: String)throws->Node {
|
|
try addSiblingHtml(siblingIndex + 1, html)
|
|
return self
|
|
}
|
|
|
|
/**
|
|
* Insert the specified node into the DOM after this node (i.e. as a following sibling).
|
|
* @param node to add after this node
|
|
* @return this node, for chaining
|
|
* @see #before(Node)
|
|
*/
|
|
@discardableResult
|
|
open func after(node: Node)throws->Node {
|
|
try Validate.notNull(obj: node)
|
|
try Validate.notNull(obj: parentNode)
|
|
|
|
try parentNode?.addChildren(siblingIndex + 1, node)
|
|
return self
|
|
}
|
|
|
|
open func addSiblingHtml(index: Int, _ html: String)throws {
|
|
try Validate.notNull(obj: html)
|
|
try Validate.notNull(obj: parentNode)
|
|
|
|
let context: Element? = parent() as? Element
|
|
let nodes: Array<Node> = try Parser.parseFragment(html, context, getBaseUri())
|
|
try parentNode?.addChildren(index, nodes)
|
|
}
|
|
|
|
/**
|
|
Wrap the supplied HTML around this node.
|
|
@param html HTML to wrap around this element, e.g. {@code <div class="head"></div>}. Can be arbitrarily deep.
|
|
@return this node, for chaining.
|
|
*/
|
|
@discardableResult
|
|
open func wrap(_ html: String)throws->Node? {
|
|
try Validate.notEmpty(string: html)
|
|
|
|
let context: Element? = parent() as? Element
|
|
var wrapChildren: Array<Node> = try Parser.parseFragment(html, context, getBaseUri())
|
|
let wrapNode: Node? = wrapChildren.count > 0 ? wrapChildren[0] : nil
|
|
if (wrapNode == nil || !(((wrapNode as? Element) != nil))) { // nothing to wrap with; noop
|
|
return nil
|
|
}
|
|
|
|
let wrap: Element = wrapNode as! Element
|
|
let deepest: Element = getDeepChild(el: wrap)
|
|
try parentNode?.replaceChild(self, wrap)
|
|
wrapChildren = wrapChildren.filter { $0 != wrap}
|
|
try deepest.addChildren(self)
|
|
|
|
// remainder (unbalanced wrap, like <div></div><p></p> -- The <p> is remainder
|
|
if (wrapChildren.count > 0) {
|
|
for i in 0..<wrapChildren.count {
|
|
let remainder: Node = wrapChildren[i]
|
|
try remainder.parentNode?.removeChild(remainder)
|
|
try wrap.appendChild(remainder)
|
|
}
|
|
}
|
|
return self
|
|
}
|
|
|
|
/**
|
|
* Removes this node from the DOM, and moves its children up into the node's parent. This has the effect of dropping
|
|
* the node but keeping its children.
|
|
* <p>
|
|
* For example, with the input html:
|
|
* </p>
|
|
* <p>{@code <div>One <span>Two <b>Three</b></span></div>}</p>
|
|
* Calling {@code element.unwrap()} on the {@code span} element will result in the html:
|
|
* <p>{@code <div>One Two <b>Three</b></div>}</p>
|
|
* and the {@code "Two "} {@link TextNode} being returned.
|
|
*
|
|
* @return the first child of this node, after the node has been unwrapped. Null if the node had no children.
|
|
* @see #remove()
|
|
* @see #wrap(String)
|
|
*/
|
|
@discardableResult
|
|
open func unwrap()throws ->Node? {
|
|
try Validate.notNull(obj: parentNode)
|
|
|
|
let firstChild: Node? = childNodes.count > 0 ? childNodes[0] : nil
|
|
try parentNode?.addChildren(siblingIndex, self.childNodesAsArray())
|
|
try self.remove()
|
|
|
|
return firstChild
|
|
}
|
|
|
|
private func getDeepChild(el: Element) -> Element {
|
|
let children = el.children()
|
|
if (children.size() > 0) {
|
|
return getDeepChild(el: children.get(0))
|
|
} else {
|
|
return el
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replace this node in the DOM with the supplied node.
|
|
* @param in the node that will will replace the existing node.
|
|
*/
|
|
public func replaceWith(_ input: Node)throws {
|
|
try Validate.notNull(obj: input)
|
|
try Validate.notNull(obj: parentNode)
|
|
try parentNode?.replaceChild(self, input)
|
|
}
|
|
|
|
public func setParentNode(_ parentNode: Node)throws {
|
|
if (self.parentNode != nil) {
|
|
try self.parentNode?.removeChild(self)
|
|
}
|
|
self.parentNode = parentNode
|
|
}
|
|
|
|
public func replaceChild(_ out: Node, _ input: Node)throws {
|
|
try Validate.isTrue(val: out.parentNode === self)
|
|
try Validate.notNull(obj: input)
|
|
if (input.parentNode != nil) {
|
|
try input.parentNode?.removeChild(input)
|
|
}
|
|
|
|
let index: Int = out.siblingIndex
|
|
childNodes[index] = input
|
|
input.parentNode = self
|
|
input.setSiblingIndex(index)
|
|
out.parentNode = nil
|
|
}
|
|
|
|
public func removeChild(_ out: Node)throws {
|
|
try Validate.isTrue(val: out.parentNode === self)
|
|
let index: Int = out.siblingIndex
|
|
childNodes.remove(at: index)
|
|
reindexChildren(index)
|
|
out.parentNode = nil
|
|
}
|
|
|
|
public func addChildren(_ children: Node...)throws {
|
|
//most used. short circuit addChildren(int), which hits reindex children and array copy
|
|
try addChildren(children)
|
|
}
|
|
|
|
public func addChildren(_ children: [Node])throws {
|
|
//most used. short circuit addChildren(int), which hits reindex children and array copy
|
|
for child in children {
|
|
try reparentChild(child)
|
|
ensureChildNodes()
|
|
childNodes.append(child)
|
|
child.setSiblingIndex(childNodes.count-1)
|
|
}
|
|
}
|
|
|
|
public func addChildren(_ index: Int, _ children: Node...)throws {
|
|
try addChildren(index, children)
|
|
}
|
|
|
|
public func addChildren(_ index: Int, _ children: [Node])throws {
|
|
ensureChildNodes()
|
|
for i in (0..<children.count).reversed() {
|
|
let input: Node = children[i]
|
|
try reparentChild(input)
|
|
childNodes.insert(input, at: index)
|
|
reindexChildren(index)
|
|
}
|
|
}
|
|
|
|
public func ensureChildNodes() {
|
|
// if (childNodes === Node.EMPTY_NODES) {
|
|
// childNodes = Array<Node>()
|
|
// }
|
|
}
|
|
|
|
public func reparentChild(_ child: Node)throws {
|
|
if (child.parentNode != nil) {
|
|
try child.parentNode?.removeChild(child)
|
|
}
|
|
try child.setParentNode(self)
|
|
}
|
|
|
|
private func reindexChildren(_ start: Int) {
|
|
for i in start..<childNodes.count {
|
|
childNodes[i].setSiblingIndex(i)
|
|
}
|
|
}
|
|
|
|
/**
|
|
Retrieves this node's sibling nodes. Similar to {@link #childNodes() node.parent.childNodes()}, but does not
|
|
include this node (a node is not a sibling of itself).
|
|
@return node siblings. If the node has no parent, returns an empty list.
|
|
*/
|
|
open func siblingNodes()->Array<Node> {
|
|
if (parentNode == nil) {
|
|
return Array<Node>()
|
|
}
|
|
|
|
let nodes: Array<Node> = parentNode!.childNodes
|
|
var siblings: Array<Node> = Array<Node>()
|
|
for node in nodes {
|
|
if (node !== self) {
|
|
siblings.append(node)
|
|
}
|
|
}
|
|
|
|
return siblings
|
|
}
|
|
|
|
/**
|
|
Get this node's next sibling.
|
|
@return next sibling, or null if this is the last sibling
|
|
*/
|
|
open func nextSibling() -> Node? {
|
|
guard let siblings: Array<Node> = parentNode?.childNodes else {
|
|
return nil
|
|
}
|
|
|
|
let index: Int = siblingIndex+1
|
|
if (siblings.count > index) {
|
|
return siblings[index]
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/**
|
|
Get this node's previous sibling.
|
|
@return the previous sibling, or null if this is the first sibling
|
|
*/
|
|
open func previousSibling() -> Node? {
|
|
if (parentNode == nil) {
|
|
return nil // root
|
|
}
|
|
|
|
if (siblingIndex > 0) {
|
|
return parentNode?.childNodes[siblingIndex-1]
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func setSiblingIndex(_ siblingIndex: Int) {
|
|
self.siblingIndex = siblingIndex
|
|
}
|
|
|
|
/**
|
|
* Perform a depth-first traversal through this node and its descendants.
|
|
* @param nodeVisitor the visitor callbacks to perform on each node
|
|
* @return this node, for chaining
|
|
*/
|
|
@discardableResult
|
|
open func traverse(_ nodeVisitor: NodeVisitor)throws->Node {
|
|
let traversor: NodeTraversor = NodeTraversor(nodeVisitor)
|
|
try traversor.traverse(self)
|
|
return self
|
|
}
|
|
|
|
/**
|
|
Get the outer HTML of this node.
|
|
@return HTML
|
|
*/
|
|
open func outerHtml()throws->String {
|
|
let accum: StringBuilder = StringBuilder(128)
|
|
try outerHtml(accum)
|
|
return accum.toString()
|
|
}
|
|
|
|
public func outerHtml(_ accum: StringBuilder)throws {
|
|
try NodeTraversor(OuterHtmlVisitor(accum, getOutputSettings())).traverse(self)
|
|
}
|
|
|
|
// if this node has no document (or parent), retrieve the default output settings
|
|
func getOutputSettings() -> OutputSettings {
|
|
return ownerDocument() != nil ? ownerDocument()!.outputSettings() : (Document(Node.empty)).outputSettings()
|
|
}
|
|
|
|
/**
|
|
Get the outer HTML of this node.
|
|
@param accum accumulator to place HTML into
|
|
@throws IOException if appending to the given accumulator fails.
|
|
*/
|
|
func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) throws {
|
|
preconditionFailure("This method must be overridden")
|
|
}
|
|
|
|
func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) throws {
|
|
preconditionFailure("This method must be overridden")
|
|
}
|
|
|
|
/**
|
|
* Write this node and its children to the given {@link Appendable}.
|
|
*
|
|
* @param appendable the {@link Appendable} to write to.
|
|
* @return the supplied {@link Appendable}, for chaining.
|
|
*/
|
|
open func html(_ appendable: StringBuilder)throws -> StringBuilder {
|
|
try outerHtml(appendable)
|
|
return appendable
|
|
}
|
|
|
|
public func indent(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
|
|
accum.append(UnicodeScalar.BackslashN).append(StringUtil.padding(depth * Int(out.indentAmount())))
|
|
}
|
|
|
|
/**
|
|
* Check if this node is the same instance of another (object identity test).
|
|
* @param o other object to compare to
|
|
* @return true if the content of this node is the same as the other
|
|
* @see Node#hasSameValue(Object) to compare nodes by their value
|
|
*/
|
|
|
|
open func equals(_ o: Node) -> Bool {
|
|
// implemented just so that javadoc is clear this is an identity test
|
|
return self === o
|
|
}
|
|
|
|
/**
|
|
* Check if this node is has the same content as another node. A node is considered the same if its name, attributes and content match the
|
|
* other node; particularly its position in the tree does not influence its similarity.
|
|
* @param o other object to compare to
|
|
* @return true if the content of this node is the same as the other
|
|
*/
|
|
|
|
open func hasSameValue(_ o: Node)throws->Bool {
|
|
if (self === o) {return true}
|
|
// if (type(of:self) != type(of: o))
|
|
// {
|
|
// return false
|
|
// }
|
|
|
|
return try self.outerHtml() == o.outerHtml()
|
|
}
|
|
|
|
/**
|
|
* Create a stand-alone, deep copy of this node, and all of its children. The cloned node will have no siblings or
|
|
* parent node. As a stand-alone object, any changes made to the clone or any of its children will not impact the
|
|
* original node.
|
|
* <p>
|
|
* The cloned node may be adopted into another Document or node structure using {@link Element#appendChild(Node)}.
|
|
* @return stand-alone cloned node
|
|
*/
|
|
public func copy(with zone: NSZone? = nil) -> Any {
|
|
return copy(clone: Node())
|
|
}
|
|
|
|
public func copy(parent: Node?) -> Node {
|
|
let clone = Node()
|
|
return copy(clone: clone, parent: parent)
|
|
}
|
|
|
|
public func copy(clone: Node) -> Node {
|
|
let thisClone: Node = copy(clone: clone, parent: nil) // splits for orphan
|
|
|
|
// Queue up nodes that need their children cloned (BFS).
|
|
var nodesToProcess: Array<Node> = Array<Node>()
|
|
nodesToProcess.append(thisClone)
|
|
|
|
while (!nodesToProcess.isEmpty) {
|
|
let currParent: Node = nodesToProcess.removeFirst()
|
|
|
|
for i in 0..<currParent.childNodes.count {
|
|
let childClone: Node = currParent.childNodes[i].copy(parent: currParent)
|
|
currParent.childNodes[i] = childClone
|
|
nodesToProcess.append(childClone)
|
|
}
|
|
}
|
|
return thisClone
|
|
}
|
|
|
|
/*
|
|
* Return a clone of the node using the given parent (which can be null).
|
|
* Not a deep copy of children.
|
|
*/
|
|
public func copy(clone: Node, parent: Node?) -> Node {
|
|
clone.parentNode = parent // can be null, to create an orphan split
|
|
clone.siblingIndex = parent == nil ? 0 : siblingIndex
|
|
clone.attributes = attributes != nil ? attributes?.clone() : nil
|
|
clone.baseUri = baseUri
|
|
clone.childNodes = Array<Node>()
|
|
|
|
for child in childNodes {
|
|
clone.childNodes.append(child)
|
|
}
|
|
|
|
return clone
|
|
}
|
|
|
|
private class OuterHtmlVisitor: NodeVisitor {
|
|
private var accum: StringBuilder
|
|
private var out: OutputSettings
|
|
static private let text = "#text"
|
|
|
|
init(_ accum: StringBuilder, _ out: OutputSettings) {
|
|
self.accum = accum
|
|
self.out = out
|
|
}
|
|
|
|
open func head(_ node: Node, _ depth: Int)throws {
|
|
|
|
try node.outerHtmlHead(accum, depth, out)
|
|
}
|
|
|
|
open func tail(_ node: Node, _ depth: Int)throws {
|
|
// When compiling a release optimized swift linux 4.2 version the "saves a void hit."
|
|
// causes a SIL error. Removing optimization on linux until a fix is found.
|
|
#if os(Linux)
|
|
try node.outerHtmlTail(accum, depth, out)
|
|
#else
|
|
if (!(node.nodeName() == OuterHtmlVisitor.text)) { // saves a void hit.
|
|
try node.outerHtmlTail(accum, depth, out)
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// Returns a Boolean value indicating whether two values are equal.
|
|
///
|
|
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
|
/// `a == b` implies that `a != b` is `false`.
|
|
///
|
|
/// - Parameters:
|
|
/// - lhs: A value to compare.
|
|
/// - rhs: Another value to compare.
|
|
public static func ==(lhs: Node, rhs: Node) -> Bool {
|
|
return lhs === rhs
|
|
}
|
|
|
|
/// The hash value.
|
|
///
|
|
/// Hash values are not guaranteed to be equal across different executions of
|
|
/// your program. Do not save hash values to use during a future execution.
|
|
public func hash(into hasher: inout Hasher) {
|
|
hasher.combine(description)
|
|
hasher.combine(baseUri)
|
|
}
|
|
}
|
|
|
|
extension Node: CustomStringConvertible {
|
|
public var description: String {
|
|
do {
|
|
return try outerHtml()
|
|
} catch {
|
|
|
|
}
|
|
return Node.empty
|
|
}
|
|
}
|
|
|
|
extension Node: CustomDebugStringConvertible {
|
|
private static let space = " "
|
|
public var debugDescription: String {
|
|
do {
|
|
return try String(describing: type(of: self)) + Node.space + outerHtml()
|
|
} catch {
|
|
|
|
}
|
|
return String(describing: type(of: self))
|
|
}
|
|
}
|