// // 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 true if the requirements are met or * false 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.. 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 :root selector * */ 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 = 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())" } } }