// // Elements.swift // SwiftSoup // // Created by Nabil Chatbi on 20/10/16. // Copyright © 2016 Nabil Chatbi.. All rights reserved. // /** A list of {@link Element}s, with methods that act on every element in the list.

To get an {@code Elements} object, use the {@link Element#select(String)} method.

*/ import Foundation //open typealias Elements = Array //typealias E = Element open class Elements: NSCopying { fileprivate var this: Array = Array() ///base init public init() { } ///Initialized with an array public init(_ a: Array) { this = a } ///Initialized with an order set public init(_ a: OrderedSet) { this.append(contentsOf: a) } /** * Creates a deep copy of these elements. * @return a deep copy */ public func copy(with zone: NSZone? = nil) -> Any { let clone: Elements = Elements() for e: Element in this { clone.add(e.copy() as! Element) } return clone } // attribute methods /** Get an attribute value from the first matched element that has the attribute. @param attributeKey The attribute key. @return The attribute value from the first matched element that has the attribute.. If no elements were matched (isEmpty() == true), or if the no elements have the attribute, returns empty string. @see #hasAttr(String) */ open func attr(_ attributeKey: String)throws->String { for element in this { if (element.hasAttr(attributeKey)) { return try element.attr(attributeKey) } } return "" } /** Checks if any of the matched elements have this attribute set. @param attributeKey attribute key @return true if any of the elements have the attribute; false if none do. */ open func hasAttr(_ attributeKey: String) -> Bool { for element in this { if element.hasAttr(attributeKey) {return true} } return false } /** * Set an attribute on all matched elements. * @param attributeKey attribute key * @param attributeValue attribute value * @return this */ @discardableResult open func attr(_ attributeKey: String, _ attributeValue: String)throws->Elements { for element in this { try element.attr(attributeKey, attributeValue) } return self } /** * Remove an attribute from every matched element. * @param attributeKey The attribute to remove. * @return this (for chaining) */ @discardableResult open func removeAttr(_ attributeKey: String)throws->Elements { for element in this { try element.removeAttr(attributeKey) } return self } /** Add the class name to every matched element's {@code class} attribute. @param className class name to add @return this */ @discardableResult open func addClass(_ className: String)throws->Elements { for element in this { try element.addClass(className) } return self } /** Remove the class name from every matched element's {@code class} attribute, if present. @param className class name to remove @return this */ @discardableResult open func removeClass(_ className: String)throws->Elements { for element: Element in this { try element.removeClass(className) } return self } /** Toggle the class name on every matched element's {@code class} attribute. @param className class name to add if missing, or remove if present, from every element. @return this */ @discardableResult open func toggleClass(_ className: String)throws->Elements { for element: Element in this { try element.toggleClass(className) } return self } /** Determine if any of the matched elements have this class name set in their {@code class} attribute. @param className class name to check for @return true if any do, false if none do */ open func hasClass(_ className: String) -> Bool { for element: Element in this { if (element.hasClass(className)) { return true } } return false } /** * Get the form element's value of the first matched element. * @return The form element's value, or empty if not set. * @see Element#val() */ open func val()throws->String { if (size() > 0) { return try first()!.val() } return "" } /** * Set the form element's value in each of the matched elements. * @param value The value to set into each matched element * @return this (for chaining) */ @discardableResult open func val(_ value: String)throws->Elements { for element: Element in this { try element.val(value) } return self } /** * Get the combined text of all the matched elements. *

* Note that it is possible to get repeats if the matched elements contain both parent elements and their own * children, as the Element.text() method returns the combined text of a parent and all its children. * @return string of all text: unescaped and no HTML. * @see Element#text() */ open func text()throws->String { let sb: StringBuilder = StringBuilder() for element: Element in this { if (sb.length != 0) { sb.append(" ") } sb.append(try element.text()) } return sb.toString() } /// Check if an element has text open func hasText() -> Bool { for element: Element in this { if (element.hasText()) { return true } } return false } /** * Get the combined inner HTML of all matched elements. * @return string of all element's inner HTML. * @see #text() * @see #outerHtml() */ open func html()throws->String { var text = try this.reduce("") {result, name in "\(result)\(try name.html())\n"} text.removeLast() return text } /** * Get the combined outer HTML of all matched elements. * @return string of all element's outer HTML. * @see #text() * @see #html() */ open func outerHtml()throws->String { let sb: StringBuilder = StringBuilder() for element in this { if (sb.length != 0) { sb.append("\n") } sb.append(try element.outerHtml()) } return sb.toString() } /** * Get the combined outer HTML of all matched elements. Alias of {@link #outerHtml()}. * @return string of all element's outer HTML. * @see #text() * @see #html() */ open func toString()throws->String { return try outerHtml() } /** * Update the tag name of each matched element. For example, to change each {@code } to a {@code }, do * {@code doc.select("i").tagName("em");} * @param tagName the new tag name * @return this, for chaining * @see Element#tagName(String) */ @discardableResult open func tagName(_ tagName: String)throws->Elements { for element: Element in this { try element.tagName(tagName) } return self } /** * Set the inner HTML of each matched element. * @param html HTML to parse and set into each matched element. * @return this, for chaining * @see Element#html(String) */ @discardableResult open func html(_ html: String)throws->Elements { for element: Element in this { try element.html(html) } return self } /** * Add the supplied HTML to the start of each matched element's inner HTML. * @param html HTML to add inside each element, before the existing HTML * @return this, for chaining * @see Element#prepend(String) */ @discardableResult open func prepend(_ html: String)throws->Elements { for element: Element in this { try element.prepend(html) } return self } /** * Add the supplied HTML to the end of each matched element's inner HTML. * @param html HTML to add inside each element, after the existing HTML * @return this, for chaining * @see Element#append(String) */ @discardableResult open func append(_ html: String)throws->Elements { for element: Element in this { try element.append(html) } return self } /** * Insert the supplied HTML before each matched element's outer HTML. * @param html HTML to insert before each element * @return this, for chaining * @see Element#before(String) */ @discardableResult open func before(_ html: String)throws->Elements { for element: Element in this { try element.before(html) } return self } /** * Insert the supplied HTML after each matched element's outer HTML. * @param html HTML to insert after each element * @return this, for chaining * @see Element#after(String) */ @discardableResult open func after(_ html: String)throws->Elements { for element: Element in this { try element.after(html) } return self } /** Wrap the supplied HTML around each matched elements. For example, with HTML {@code

This is Jsoup

}, doc.select("b").wrap("<i></i>"); becomes {@code

This is jsoup

} @param html HTML to wrap around each element, e.g. {@code
}. Can be arbitrarily deep. @return this (for chaining) @see Element#wrap */ @discardableResult open func wrap(_ html: String)throws->Elements { try Validate.notEmpty(string: html) for element: Element in this { try element.wrap(html) } return self } /** * Removes the matched elements from the DOM, and moves their children up into their parents. This has the effect of * dropping the elements but keeping their children. *

* This is useful for e.g removing unwanted formatting elements but keeping their contents. *

* * E.g. with HTML:

{@code

One Two
}

*

{@code doc.select("font").unwrap();}

*

HTML = {@code

One Two
}

* * @return this (for chaining) * @see Node#unwrap */ @discardableResult open func unwrap()throws->Elements { for element: Element in this { try element.unwrap() } return self } /** * Empty (remove all child nodes from) each matched element. This is similar to setting the inner HTML of each * element to nothing. *

* E.g. HTML: {@code

Hello there

now

}
* doc.select("p").empty();
* HTML = {@code

} * @return this, for chaining * @see Element#empty() * @see #remove() */ @discardableResult open func empty() -> Elements { for element: Element in this { element.empty() } return self } /** * Remove each matched element from the DOM. This is similar to setting the outer HTML of each element to nothing. *

* E.g. HTML: {@code

Hello

there

}
* doc.select("p").remove();
* HTML = {@code
} *

* Note that this method should not be used to clean user-submitted HTML; rather, use {@link org.jsoup.safety.Cleaner} to clean HTML. * @return this, for chaining * @see Element#empty() * @see #empty() */ @discardableResult open func remove()throws->Elements { for element in this { try element.remove() } return self } // filters /** * Find matching elements within this element list. * @param query A {@link Selector} query * @return the filtered list of elements, or an empty list if none match. */ open func select(_ query: String)throws->Elements { return try Selector.select(query, this) } /** * Remove elements from this list that match the {@link Selector} query. *

* E.g. HTML: {@code

Two
}
* Elements divs = doc.select("div").not(".logo");
* Result: {@code divs: [
Two
]} *

* @param query the selector query whose results should be removed from these elements * @return a new elements list that contains only the filtered results */ open func not(_ query: String)throws->Elements { let out: Elements = try Selector.select(query, this) return Selector.filterOut(this, out.this) } /** * Get the nth matched element as an Elements object. *

* See also {@link #get(int)} to retrieve an Element. * @param index the (zero-based) index of the element in the list to retain * @return Elements containing only the specified element, or, if that element did not exist, an empty list. */ open func eq(_ index: Int) -> Elements { return size() > index ? Elements([get(index)]) : Elements() } /** * Test if any of the matched elements match the supplied query. * @param query A selector * @return true if at least one element in the list matches the query. */ open func iS(_ query: String)throws->Bool { let eval: Evaluator = try QueryParser.parse(query) for e: Element in this { if (try e.iS(eval)) { return true } } return false } /** * Get all of the parents and ancestor elements of the matched elements. * @return all of the parents and ancestor elements of the matched elements */ open func parents() -> Elements { let combo: OrderedSet = OrderedSet() for e: Element in this { combo.append(contentsOf: e.parents().array()) } return Elements(combo) } // list-like methods /** Get the first matched element. @return The first matched element, or null if contents is empty. */ open func first() -> Element? { return isEmpty() ? nil : get(0) } /// Check if no element stored open func isEmpty() -> Bool { return array().count == 0 } /// Count open func size() -> Int { return array().count } /** Get the last matched element. @return The last matched element, or null if contents is empty. */ open func last() -> Element? { return isEmpty() ? nil : get(size() - 1) } /** * Perform a depth-first traversal on each of the selected elements. * @param nodeVisitor the visitor callbacks to perform on each node * @return this, for chaining */ @discardableResult open func traverse(_ nodeVisitor: NodeVisitor)throws->Elements { let traversor: NodeTraversor = NodeTraversor(nodeVisitor) for el: Element in this { try traversor.traverse(el) } return self } /** * Get the {@link FormElement} forms from the selected elements, if any. * @return a list of {@link FormElement}s pulled from the matched elements. The list will be empty if the elements contain * no forms. */ open func forms()->Array { var forms: Array = Array() for el: Element in this { if let el = el as? FormElement { forms.append(el) } } return forms } /** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return true (as specified by {@link Collection#add}) */ open func add(_ e: Element) { this.append(e) } /** * Insert the specified element at index. */ open func add(_ index: Int, _ element: Element) { this.insert(element, at: index) } /// Return element at index open func get(_ i: Int) -> Element { return this[i] } /// Returns all elements open func array()->Array { return this } } /** * Elements extension Equatable. */ extension Elements: Equatable { /// 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: Elements, rhs: Elements) -> Bool { return lhs.this == rhs.this } } /** * Elements RandomAccessCollection */ extension Elements: RandomAccessCollection { public subscript(position: Int) -> Element { return this[position] } public var startIndex: Int { return this.startIndex } public var endIndex: Int { return this.endIndex } /// The number of Element objects in the collection. /// Equivalent to `size()` public var count: Int { return this.count } }