A small menubar app that allows you to switch between R versions quickly (if you have multiple versions of R framework installed). https://rud.is/rswitch
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

603 lines
15 KiB

5 years ago
//
// 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.
<p>
To get an {@code Elements} object, use the {@link Element#select(String)} method.
</p>
*/
import Foundation
//open typealias Elements = Array<Element>
//typealias E = Element
open class Elements: NSCopying {
fileprivate var this: Array<Element> = Array<Element>()
///base init
public init() {
}
///Initialized with an array
public init(_ a: Array<Element>) {
this = a
}
///Initialized with an order set
public init(_ a: OrderedSet<Element>) {
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.
* <p>
* 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 <i>} to a {@code <em>}, 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 <p><b>This</b> is <b>Jsoup</b></p>},
<code>doc.select("b").wrap("&lt;i&gt;&lt;/i&gt;");</code>
becomes {@code <p><i><b>This</b></i> is <i><b>jsoup</b></i></p>}
@param html HTML to wrap around each element, e.g. {@code <div class="head"></div>}. 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.
* <p>
* This is useful for e.g removing unwanted formatting elements but keeping their contents.
* </p>
*
* E.g. with HTML: <p>{@code <div><font>One</font> <font><a href="/">Two</a></font></div>}</p>
* <p>{@code doc.select("font").unwrap();}</p>
* <p>HTML = {@code <div>One <a href="/">Two</a></div>}</p>
*
* @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.
* <p>
* E.g. HTML: {@code <div><p>Hello <b>there</b></p> <p>now</p></div>}<br>
* <code>doc.select("p").empty();</code><br>
* HTML = {@code <div><p></p> <p></p></div>}
* @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.
* <p>
* E.g. HTML: {@code <div><p>Hello</p> <p>there</p> <img /></div>}<br>
* <code>doc.select("p").remove();</code><br>
* HTML = {@code <div> <img /></div>}
* <p>
* 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.
* <p>
* E.g. HTML: {@code <div class=logo>One</div> <div>Two</div>}<br>
* <code>Elements divs = doc.select("div").not(".logo");</code><br>
* Result: {@code divs: [<div>Two</div>]}
* <p>
* @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 <i>nth</i> matched element as an Elements object.
* <p>
* 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<Element> = OrderedSet<Element>()
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 <code>null</code> 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 <code>null</code> 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<FormElement> {
var forms: Array<FormElement> = Array<FormElement>()
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 <tt>true</tt> (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<Element> {
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
}
}