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.
 
 
 
 
 
 

720 lines
20 KiB

//
// Evaluator.swift
// SwiftSoup
//
// Created by Nabil Chatbi on 22/10/16.
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
//
import Foundation
/**
* Evaluates that an element matches the selector.
*/
public class Evaluator {
init () {}
/**
* Test if the element meets the evaluator's requirements.
*
* @param root Root of the matching subtree
* @param element tested element
* @return Returns <tt>true</tt> if the requirements are met or
* <tt>false</tt> otherwise
*/
open func matches(_ root: Element, _ element: Element)throws->Bool {
preconditionFailure("self method must be overridden")
}
open func toString() -> String {
preconditionFailure("self method must be overridden")
}
/**
* Evaluator for tag name
*/
public class Tag: Evaluator {
private let tagName: String
private let tagNameNormal: String
public init(_ tagName: String) {
self.tagName = tagName
self.tagNameNormal = tagName.lowercased()
}
open override func matches(_ root: Element, _ element: Element)throws->Bool {
return element.tagNameNormal() == tagNameNormal
}
open override func toString() -> String {
return String(tagName)
}
}
/**
* Evaluator for tag name that ends with
*/
public final class TagEndsWith: Evaluator {
private let tagName: String
public init(_ tagName: String) {
self.tagName = tagName
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return (element.tagName().hasSuffix(tagName))
}
public override func toString() -> String {
return String(tagName)
}
}
/**
* Evaluator for element id
*/
public final class Id: Evaluator {
private let id: String
public init(_ id: String) {
self.id = id
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return (id == element.id())
}
public override func toString() -> String {
return "#\(id)"
}
}
/**
* Evaluator for element class
*/
public final class Class: Evaluator {
private let className: String
public init(_ className: String) {
self.className = className
}
public override func matches(_ root: Element, _ element: Element) -> Bool {
return (element.hasClass(className))
}
public override func toString() -> String {
return ".\(className)"
}
}
/**
* Evaluator for attribute name matching
*/
public final class Attribute: Evaluator {
private let key: String
public init(_ key: String) {
self.key = key
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return element.hasAttr(key)
}
public override func toString() -> String {
return "[\(key)]"
}
}
/**
* Evaluator for attribute name prefix matching
*/
public final class AttributeStarting: Evaluator {
private let keyPrefix: String
public init(_ keyPrefix: String)throws {
try Validate.notEmpty(string: keyPrefix)
self.keyPrefix = keyPrefix.lowercased()
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
if let values = element.getAttributes() {
for attribute in values where attribute.getKey().lowercased().hasPrefix(keyPrefix) {
return true
}
}
return false
}
public override func toString() -> String {
return "[^\(keyPrefix)]"
}
}
/**
* Evaluator for attribute name/value matching
*/
public final class AttributeWithValue: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws {
try super.init(key, value)
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
if element.hasAttr(key) {
let string = try element.attr(key)
return value.equalsIgnoreCase(string: string.trim())
}
return false
}
public override func toString() -> String {
return "[\(key)=\(value)]"
}
}
/**
* Evaluator for attribute name != value matching
*/
public final class AttributeWithValueNot: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws {
try super.init(key, value)
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
let string = try element.attr(key)
return !value.equalsIgnoreCase(string: string)
}
public override func toString() -> String {
return "[\(key)!=\(value)]"
}
}
/**
* Evaluator for attribute name/value matching (value prefix)
*/
public final class AttributeWithValueStarting: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws {
try super.init(key, value)
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
if element.hasAttr(key) {
return try element.attr(key).lowercased().hasPrefix(value) // value is lower case already
}
return false
}
public override func toString() -> String {
return "[\(key)^=\(value)]"
}
}
/**
* Evaluator for attribute name/value matching (value ending)
*/
public final class AttributeWithValueEnding: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws {
try super.init(key, value)
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
if element.hasAttr(key) {
return try element.attr(key).lowercased().hasSuffix(value) // value is lower case
}
return false
}
public override func toString() -> String {
return "[\(key)$=\(value)]"
}
}
/**
* Evaluator for attribute name/value matching (value containing)
*/
public final class AttributeWithValueContaining: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws {
try super.init(key, value)
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
if element.hasAttr(key) {
return try element.attr(key).lowercased().contains(value) // value is lower case
}
return false
}
public override func toString() -> String {
return "[\(key)*=\(value)]"
}
}
/**
* Evaluator for attribute name/value matching (value regex matching)
*/
public final class AttributeWithValueMatching: Evaluator {
let key: String
let pattern: Pattern
public init(_ key: String, _ pattern: Pattern) {
self.key = key.trim().lowercased()
self.pattern = pattern
super.init()
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
if element.hasAttr(key) {
let string = try element.attr(key)
return pattern.matcher(in: string).find()
}
return false
}
public override func toString() -> String {
return "[\(key)~=\(pattern.toString())]"
}
}
/**
* Abstract evaluator for attribute name/value matching
*/
public class AttributeKeyPair: Evaluator {
let key: String
var value: String
public init(_ key: String, _ value2: String)throws {
var value2 = value2
try Validate.notEmpty(string: key)
try Validate.notEmpty(string: value2)
self.key = key.trim().lowercased()
if value2.startsWith("\"") && value2.hasSuffix("\"") || value2.startsWith("'") && value2.hasSuffix("'") {
value2 = value2.substring(1, value2.count-2)
}
self.value = value2.trim().lowercased()
}
open override func matches(_ root: Element, _ element: Element)throws->Bool {
preconditionFailure("self method must be overridden")
}
}
/**
* Evaluator for any / all element matching
*/
public final class AllElements: Evaluator {
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return true
}
public override func toString() -> String {
return "*"
}
}
/**
* Evaluator for matching by sibling index number (e {@literal <} idx)
*/
public final class IndexLessThan: IndexEvaluator {
public override init(_ index: Int) {
super.init(index)
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return try element.elementSiblingIndex() < index
}
public override func toString() -> String {
return ":lt(\(index))"
}
}
/**
* Evaluator for matching by sibling index number (e {@literal >} idx)
*/
public final class IndexGreaterThan: IndexEvaluator {
public override init(_ index: Int) {
super.init(index)
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return try element.elementSiblingIndex() > index
}
public override func toString() -> String {
return ":gt(\(index))"
}
}
/**
* Evaluator for matching by sibling index number (e = idx)
*/
public final class IndexEquals: IndexEvaluator {
public override init(_ index: Int) {
super.init(index)
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return try element.elementSiblingIndex() == index
}
public override func toString() -> String {
return ":eq(\(index))"
}
}
/**
* Evaluator for matching the last sibling (css :last-child)
*/
public final class IsLastChild: Evaluator {
public override func matches(_ root: Element, _ element: Element)throws->Bool {
if let parent = element.parent() {
let index = try element.elementSiblingIndex()
return !(parent is Document) && index == (parent.getChildNodes().count - 1)
}
return false
}
public override func toString() -> String {
return ":last-child"
}
}
public final class IsFirstOfType: IsNthOfType {
public init() {
super.init(0, 1)
}
public override func toString() -> String {
return ":first-of-type"
}
}
public final class IsLastOfType: IsNthLastOfType {
public init() {
super.init(0, 1)
}
public override func toString() -> String {
return ":last-of-type"
}
}
public class CssNthEvaluator: Evaluator {
public let a: Int
public let b: Int
public init(_ a: Int, _ b: Int) {
self.a = a
self.b = b
}
public init(_ b: Int) {
self.a = 0
self.b = b
}
open override func matches(_ root: Element, _ element: Element)throws->Bool {
let p: Element? = element.parent()
if (p == nil || (((p as? Document) != nil))) {return false}
let pos: Int = try calculatePosition(root, element)
if (a == 0) {return pos == b}
return (pos-b)*a >= 0 && (pos-b)%a==0
}
open override func toString() -> String {
if (a == 0) {
return ":\(getPseudoClass())(\(b))"
}
if (b == 0) {
return ":\(getPseudoClass())(\(a))"
}
return ":\(getPseudoClass())(\(a)\(b))"
}
open func getPseudoClass() -> String {
preconditionFailure("self method must be overridden")
}
open func calculatePosition(_ root: Element, _ element: Element)throws->Int {
preconditionFailure("self method must be overridden")
}
}
/**
* css-compatible Evaluator for :eq (css :nth-child)
*
* @see IndexEquals
*/
public final class IsNthChild: CssNthEvaluator {
public override init(_ a: Int, _ b: Int) {
super.init(a, b)
}
public override func calculatePosition(_ root: Element, _ element: Element)throws->Int {
return try element.elementSiblingIndex()+1
}
public override func getPseudoClass() -> String {
return "nth-child"
}
}
/**
* css pseudo class :nth-last-child)
*
* @see IndexEquals
*/
public final class IsNthLastChild: CssNthEvaluator {
public override init(_ a: Int, _ b: Int) {
super.init(a, b)
}
public override func calculatePosition(_ root: Element, _ element: Element)throws->Int {
var i = 0
if let l = element.parent() {
i = l.children().array().count
}
return i - (try element.elementSiblingIndex())
}
public override func getPseudoClass() -> String {
return "nth-last-child"
}
}
/**
* css pseudo class nth-of-type
*
*/
public class IsNthOfType: CssNthEvaluator {
public override init(_ a: Int, _ b: Int) {
super.init(a, b)
}
open override func calculatePosition(_ root: Element, _ element: Element) -> Int {
var pos = 0
let family: Elements? = element.parent()?.children()
if let array = family?.array() {
for el in array {
if (el.tag() == element.tag()) {pos+=1}
if (el === element) {break}
}
}
return pos
}
open override func getPseudoClass() -> String {
return "nth-of-type"
}
}
public class IsNthLastOfType: CssNthEvaluator {
public override init(_ a: Int, _ b: Int) {
super.init(a, b)
}
open override func calculatePosition(_ root: Element, _ element: Element)throws->Int {
var pos = 0
if let family = element.parent()?.children() {
let x = try element.elementSiblingIndex()
for i in x..<family.array().count {
if (family.get(i).tag() == element.tag()) {
pos+=1
}
}
}
return pos
}
open override func getPseudoClass() -> String {
return "nth-last-of-type"
}
}
/**
* Evaluator for matching the first sibling (css :first-child)
*/
public final class IsFirstChild: Evaluator {
public override func matches(_ root: Element, _ element: Element)throws->Bool {
let p = element.parent()
if(p != nil && !(((p as? Document) != nil))) {
return (try element.elementSiblingIndex()) == 0
}
return false
}
public override func toString() -> String {
return ":first-child"
}
}
/**
* css3 pseudo-class :root
* @see <a href="http://www.w3.org/TR/selectors/#root-pseudo">:root selector</a>
*
*/
public final class IsRoot: Evaluator {
public override func matches(_ root: Element, _ element: Element)throws->Bool {
let r: Element = ((root as? Document) != nil) ? root.child(0) : root
return element === r
}
public override func toString() -> String {
return ":root"
}
}
public final class IsOnlyChild: Evaluator {
public override func matches(_ root: Element, _ element: Element)throws->Bool {
let p = element.parent()
return p != nil && !((p as? Document) != nil) && element.siblingElements().array().count == 0
}
public override func toString() -> String {
return ":only-child"
}
}
public final class IsOnlyOfType: Evaluator {
public override func matches(_ root: Element, _ element: Element)throws->Bool {
let p = element.parent()
if (p == nil || (p as? Document) != nil) {return false}
var pos = 0
if let family = p?.children().array() {
for el in family {
if (el.tag() == element.tag()) {pos+=1}
}
}
return pos == 1
}
public override func toString() -> String {
return ":only-of-type"
}
}
public final class IsEmpty: Evaluator {
public override func matches(_ root: Element, _ element: Element)throws->Bool {
let family: Array<Node> = element.getChildNodes()
for n in family {
if (!((n as? Comment) != nil || (n as? XmlDeclaration) != nil || (n as? DocumentType) != nil)) {return false}
}
return true
}
public override func toString() -> String {
return ":empty"
}
}
/**
* Abstract evaluator for sibling index matching
*
* @author ant
*/
public class IndexEvaluator: Evaluator {
let index: Int
public init(_ index: Int) {
self.index = index
}
}
/**
* Evaluator for matching Element (and its descendants) text
*/
public final class ContainsText: Evaluator {
private let searchText: String
public init(_ searchText: String) {
self.searchText = searchText.lowercased()
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return (try element.text().lowercased().contains(searchText))
}
public override func toString() -> String {
return ":contains(\(searchText)"
}
}
/**
* Evaluator for matching Element's own text
*/
public final class ContainsOwnText: Evaluator {
private let searchText: String
public init(_ searchText: String) {
self.searchText = searchText.lowercased()
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
return (element.ownText().lowercased().contains(searchText))
}
public override func toString() -> String {
return ":containsOwn(\(searchText)"
}
}
/**
* Evaluator for matching Element (and its descendants) text with regex
*/
public final class Matches: Evaluator {
private let pattern: Pattern
public init(_ pattern: Pattern) {
self.pattern = pattern
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
let m = try pattern.matcher(in: element.text())
return m.find()
}
public override func toString() -> String {
return ":matches(\(pattern)"
}
}
/**
* Evaluator for matching Element's own text with regex
*/
public final class MatchesOwn: Evaluator {
private let pattern: Pattern
public init(_ pattern: Pattern) {
self.pattern = pattern
}
public override func matches(_ root: Element, _ element: Element)throws->Bool {
let m = pattern.matcher(in: element.ownText())
return m.find()
}
public override func toString() -> String {
return ":matchesOwn(\(pattern.toString())"
}
}
}