This commit is contained in:
Nabil Chatbi 2016-12-28 14:22:28 +01:00
parent 6272c01cd8
commit db2f0bafbc
82 changed files with 4557 additions and 5103 deletions

View File

@ -13,7 +13,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch. // Override point for customization after application launch.
return true return true
@ -41,6 +40,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
} }
} }

View File

@ -23,7 +23,6 @@ class ViewController: UIViewController {
// Dispose of any resources that can be recreated. // Dispose of any resources that can be recreated.
} }
func parseDocument()throws->Document { func parseDocument()throws->Document {
let html = "<html><head><title>First parse</title></head>" let html = "<html><head><title>First parse</title></head>"
+ "<body><p>Parsed HTML into a doc.</p></body></html>" + "<body><p>Parsed HTML into a doc.</p></body></html>"
@ -69,18 +68,16 @@ class ViewController: UIViewController {
" <foo />bar\n" + " <foo />bar\n" +
" </body>\n" + " </body>\n" +
"</html>" "</html>"
let doc: Document = try! SwiftSoup.parse(h); let doc: Document = try! SwiftSoup.parse(h)
do { do {
for _ in 0...100000{ for _ in 0...100000 {
_ = try doc.select("div"); _ = try doc.select("div")
} }
} } catch {
catch {
} }
} }
func testSite() func testSite() {
{
let myURLString = "http://apple.com" let myURLString = "http://apple.com"
guard let myURL = URL(string: myURLString) else { guard let myURL = URL(string: myURLString) else {
print("Error: \(myURLString) doesn't seem to be a valid URL") print("Error: \(myURLString) doesn't seem to be a valid URL")
@ -90,16 +87,12 @@ class ViewController: UIViewController {
let doc: Document = try! SwiftSoup.parse(html) let doc: Document = try! SwiftSoup.parse(html)
do { do {
for _ in 0...100{ for _ in 0...100 {
_ = try doc.text() _ = try doc.text()
} }
} } catch {
catch {
print("Error") print("Error")
} }
} }
} }

View File

@ -8,8 +8,7 @@
import Foundation import Foundation
extension Array extension Array {
{
func binarySearch<T: Comparable>(_ collection: [T], _ target: T) -> Int { func binarySearch<T: Comparable>(_ collection: [T], _ target: T) -> Int {
let min = 0 let min = 0
@ -19,7 +18,6 @@ extension Array
} }
func binaryMakeGuess<T: Comparable>(min: Int, max: Int, target: T, collection: [T]) -> Int { func binaryMakeGuess<T: Comparable>(min: Int, max: Int, target: T, collection: [T]) -> Int {
let guess = (min + max) / 2 let guess = (min + max) / 2
@ -49,12 +47,8 @@ extension Array
} }
} }
extension Array where Element : Equatable {
func lastIndexOf(_ e: Element) -> Int {
extension Array where Element : Equatable
{
func lastIndexOf(_ e: Element) -> Int
{
for pos in (0..<self.count).reversed() { for pos in (0..<self.count).reversed() {
let next = self[pos] let next = self[pos]
if (next == e) { if (next == e) {
@ -64,5 +58,3 @@ extension Array where Element : Equatable
return -1 return -1
} }
} }

View File

@ -22,7 +22,7 @@ open class Attribute {
var key: String var key: String
var value: String var value: String
public init(key: String,value :String) throws { public init(key: String, value: String) throws {
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
self.key = key.trim() self.key = key.trim()
self.value = value self.value = value
@ -32,7 +32,7 @@ open class Attribute {
Get the attribute key. Get the attribute key.
@return the attribute key @return the attribute key
*/ */
open func getKey() -> String{ open func getKey() -> String {
return key return key
} }
@ -68,7 +68,7 @@ open class Attribute {
Get the HTML representation of this attribute; e.g. {@code href="index.html"}. Get the HTML representation of this attribute; e.g. {@code href="index.html"}.
@return HTML @return HTML
*/ */
public func html()-> String { public func html() -> String {
let accum = StringBuilder() let accum = StringBuilder()
html(accum: accum, out: (Document("")).outputSettings()) html(accum: accum, out: (Document("")).outputSettings())
return accum.toString() return accum.toString()
@ -87,7 +87,7 @@ open class Attribute {
Get the string representation of this attribute, implemented as {@link #html()}. Get the string representation of this attribute, implemented as {@link #html()}.
@return string @return string
*/ */
open func toString()-> String { open func toString() -> String {
return html() return html()
} }
@ -118,38 +118,31 @@ open class Attribute {
&& isBooleanAttribute() && isBooleanAttribute()
} }
public func isBooleanAttribute() -> Bool public func isBooleanAttribute() -> Bool {
{ return (Attribute.booleanAttributes.binarySearch(Attribute.booleanAttributes, key) != -1)
return (Attribute.booleanAttributes.binarySearch(Attribute.booleanAttributes,key) != -1)
} }
public func hashCode() -> Int { public func hashCode() -> Int {
var result = key.hashValue var result = key.hashValue
result = 31 * result + value.hashValue result = 31 * result + value.hashValue
return result return result
} }
public func clone() -> Attribute {
public func clone() -> Attribute
{
do { do {
return try Attribute(key: key,value: value) return try Attribute(key: key, value: value)
} catch Exception.Error( _ , let msg){ } catch Exception.Error( _, let msg) {
print(msg) print(msg)
}catch{ } catch {
} }
return try! Attribute(key: "",value: "") return try! Attribute(key: "", value: "")
} }
} }
extension Attribute : Equatable extension Attribute : Equatable {
{ static public func == (lhs: Attribute, rhs: Attribute) -> Bool {
static public func == (lhs: Attribute, rhs: Attribute) -> Bool
{
return lhs.value == rhs.value && lhs.key == rhs.key return lhs.value == rhs.value && lhs.key == rhs.key
} }
} }

View File

@ -21,15 +21,15 @@ import Foundation
* *
* @author Jonathan Hedley, jonathan@hedley.net * @author Jonathan Hedley, jonathan@hedley.net
*/ */
open class Attributes : NSCopying { open class Attributes: NSCopying {
open static var dataPrefix : String = "data-" open static var dataPrefix: String = "data-"
fileprivate var attributes : OrderedDictionary<String, Attribute> = OrderedDictionary<String, Attribute>() fileprivate var attributes: OrderedDictionary<String, Attribute> = OrderedDictionary<String, Attribute>()
// linked hash map to preserve insertion order. // linked hash map to preserve insertion order.
// null be default as so many elements have no attributes -- saves a good chunk of memory // null be default as so many elements have no attributes -- saves a good chunk of memory
public init(){} public init() {}
/** /**
Get an attribute value by key. Get an attribute value by key.
@ -37,8 +37,8 @@ open class Attributes : NSCopying {
@return the attribute value if set; or empty string if not set. @return the attribute value if set; or empty string if not set.
@see #hasKey(String) @see #hasKey(String)
*/ */
open func get(key : String)-> String { open func get(key: String) -> String {
let attr : Attribute? = attributes.get(key:key) let attr: Attribute? = attributes.get(key:key)
return attr != nil ? attr!.getValue() : "" return attr != nil ? attr!.getValue() : ""
} }
@ -47,12 +47,11 @@ open class Attributes : NSCopying {
* @param key the attribute name * @param key the attribute name
* @return the first matching attribute value if set; or empty string if not set. * @return the first matching attribute value if set; or empty string if not set.
*/ */
open func getIgnoreCase(key : String )throws -> String { open func getIgnoreCase(key: String )throws -> String {
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
for attrKey in (attributes.keySet()) for attrKey in (attributes.keySet()) {
{ if attrKey.equalsIgnoreCase(string: key) {
if attrKey.equalsIgnoreCase(string: key){
return attributes.get(key: attrKey)!.getValue() return attributes.get(key: attrKey)!.getValue()
} }
} }
@ -64,7 +63,7 @@ open class Attributes : NSCopying {
@param key attribute key @param key attribute key
@param value attribute value @param value attribute value
*/ */
open func put(_ key : String , _ value : String) throws { open func put(_ key: String, _ value: String) throws {
let attr = try Attribute(key: key, value: value) let attr = try Attribute(key: key, value: value)
put(attribute: attr) put(attribute: attr)
} }
@ -74,10 +73,10 @@ open class Attributes : NSCopying {
@param key attribute key @param key attribute key
@param value attribute value @param value attribute value
*/ */
open func put(_ key : String , _ value : Bool) throws { open func put(_ key: String, _ value: Bool) throws {
if (value){ if (value) {
try put(attribute: BooleanAttribute(key: key)) try put(attribute: BooleanAttribute(key: key))
}else{ } else {
try remove(key: key) try remove(key: key)
} }
} }
@ -86,7 +85,7 @@ open class Attributes : NSCopying {
Set a new attribute, or replace an existing one by key. Set a new attribute, or replace an existing one by key.
@param attribute attribute @param attribute attribute
*/ */
open func put(attribute : Attribute) { open func put(attribute: Attribute) {
attributes.put(value: attribute, forKey:attribute.getKey()) attributes.put(value: attribute, forKey:attribute.getKey())
} }
@ -103,10 +102,10 @@ open class Attributes : NSCopying {
Remove an attribute by key. <b>Case insensitive.</b> Remove an attribute by key. <b>Case insensitive.</b>
@param key attribute key to remove @param key attribute key to remove
*/ */
open func removeIgnoreCase(key : String ) throws { open func removeIgnoreCase(key: String ) throws {
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
for attrKey in attributes.keySet(){ for attrKey in attributes.keySet() {
if (attrKey.equalsIgnoreCase(string: key)){ if (attrKey.equalsIgnoreCase(string: key)) {
attributes.remove(key: attrKey) attributes.remove(key: attrKey)
} }
} }
@ -117,7 +116,7 @@ open class Attributes : NSCopying {
@param key case-sensitive key to check for @param key case-sensitive key to check for
@return true if key exists, false otherwise @return true if key exists, false otherwise
*/ */
open func hasKey(key : String) -> Bool { open func hasKey(key: String) -> Bool {
return attributes.containsKey(key: key) return attributes.containsKey(key: key)
} }
@ -128,7 +127,7 @@ open class Attributes : NSCopying {
*/ */
open func hasKeyIgnoreCase(key: String) -> Bool { open func hasKeyIgnoreCase(key: String) -> Bool {
for attrKey in attributes.keySet() { for attrKey in attributes.keySet() {
if (attrKey.equalsIgnoreCase(string: key)){ if (attrKey.equalsIgnoreCase(string: key)) {
return true return true
} }
} }
@ -152,16 +151,15 @@ open class Attributes : NSCopying {
return return
} }
if (incoming.size() == 0){ if (incoming.size() == 0) {
return return
} }
attributes.putAll(all: incoming.attributes) attributes.putAll(all: incoming.attributes)
} }
open func iterator() -> IndexingIterator<Array<Attribute>> { open func iterator() -> IndexingIterator<Array<Attribute>> {
if (attributes.isEmpty) if (attributes.isEmpty) {
{ let args: [Attribute] = []
let args : [Attribute] = []
return args.makeIterator() return args.makeIterator()
} }
return attributes.orderedValues.makeIterator() return attributes.orderedValues.makeIterator()
@ -173,7 +171,7 @@ open class Attributes : NSCopying {
@return an view of the attributes as a List. @return an view of the attributes as a List.
*/ */
open func asList() -> Array<Attribute> { open func asList() -> Array<Attribute> {
var list : Array<Attribute> = Array(/*attributes.size()*/) var list: Array<Attribute> = Array(/*attributes.size()*/)
for entry in attributes.orderedValues { for entry in attributes.orderedValues {
list.append(entry) list.append(entry)
} }
@ -186,11 +184,11 @@ open class Attributes : NSCopying {
* @return map of custom data attributes. * @return map of custom data attributes.
*/ */
//Map<String, String> //Map<String, String>
open func dataset() -> Dictionary<String,String> { open func dataset() -> Dictionary<String, String> {
var dataset = Dictionary<String,String>() var dataset = Dictionary<String, String>()
for attribute in attributes{ for attribute in attributes {
let attr = attribute.1 let attr = attribute.1
if(attr.isDataAttribute()){ if(attr.isDataAttribute()) {
let key = attr.getKey().substring(Attributes.dataPrefix.characters.count) let key = attr.getKey().substring(Attributes.dataPrefix.characters.count)
dataset[key] = attribute.1.getValue() dataset[key] = attribute.1.getValue()
} }
@ -209,7 +207,7 @@ open class Attributes : NSCopying {
return accum.toString() return accum.toString()
} }
public func html(accum: StringBuilder,out: OutputSettings ) throws { public func html(accum: StringBuilder, out: OutputSettings ) throws {
for attribute in attributes.orderedValues { for attribute in attributes.orderedValues {
accum.append(" ") accum.append(" ")
attribute.html(accum: accum, out: out) attribute.html(accum: accum, out: out)
@ -226,9 +224,9 @@ open class Attributes : NSCopying {
* @return if both sets of attributes have the same content * @return if both sets of attributes have the same content
*/ */
open func equals(o: AnyObject?) -> Bool { open func equals(o: AnyObject?) -> Bool {
if(o == nil){return false} if(o == nil) {return false}
if (self === o.self) {return true} if (self === o.self) {return true}
guard let that : Attributes = o as? Attributes else {return false} guard let that: Attributes = o as? Attributes else {return false}
return (attributes == that.attributes) return (attributes == that.attributes)
} }
@ -240,8 +238,7 @@ open class Attributes : NSCopying {
return attributes.hashCode() return attributes.hashCode()
} }
public func copy(with zone: NSZone? = nil) -> Any public func copy(with zone: NSZone? = nil) -> Any {
{
let clone = Attributes() let clone = Attributes()
clone.attributes = attributes.clone() clone.attributes = attributes.clone()
return clone return clone
@ -260,9 +257,8 @@ open class Attributes : NSCopying {
extension Attributes : Sequence { extension Attributes : Sequence {
public func makeIterator() -> AnyIterator<Attribute> { public func makeIterator() -> AnyIterator<Attribute> {
var list = attributes.orderedValues var list = attributes.orderedValues
return AnyIterator{ return AnyIterator {
return list.count > 0 ? list.removeFirst() : nil return list.count > 0 ? list.removeFirst() : nil
} }
} }
} }

View File

@ -11,16 +11,15 @@ import Foundation
/** /**
* A boolean attribute that is written out without any value. * A boolean attribute that is written out without any value.
*/ */
open class BooleanAttribute : Attribute { open class BooleanAttribute: Attribute {
/** /**
* Create a new boolean attribute from unencoded (raw) key. * Create a new boolean attribute from unencoded (raw) key.
* @param key attribute key * @param key attribute key
*/ */
init(key : String) throws { init(key: String) throws {
try super.init(key: key, value: "") try super.init(key: key, value: "")
} }
override public func isBooleanAttribute() -> Bool { override public func isBooleanAttribute() -> Bool {
return true return true
} }

View File

@ -17,10 +17,10 @@ private let digitSet = CharacterSet.decimalDigits
extension Character { extension Character {
public static let BackslashF : Character = Character(UnicodeScalar(12)) public static let BackslashF: Character = Character(UnicodeScalar(12))
//http://www.unicode.org/glossary/#supplementary_code_point //http://www.unicode.org/glossary/#supplementary_code_point
public static let MIN_SUPPLEMENTARY_CODE_POINT : UInt32 = 0x010000 public static let MIN_SUPPLEMENTARY_CODE_POINT: UInt32 = 0x010000
/// The first `UnicodeScalar` of `self`. /// The first `UnicodeScalar` of `self`.
var unicodeScalar: UnicodeScalar { var unicodeScalar: UnicodeScalar {
@ -48,7 +48,7 @@ extension Character {
switch self { switch self {
case " ", "\t", "\n", "\r", "\r\n",Character.BackslashF: return true case " ", "\t", "\n", "\r", "\r\n", Character.BackslashF: return true
case "\u{000C}", "\u{000B}", "\u{0085}": return true // Form Feed, vertical tab, next line (nel) case "\u{000C}", "\u{000B}", "\u{0085}": return true // Form Feed, vertical tab, next line (nel)
@ -170,8 +170,7 @@ extension Character {
return Character(UnicodeScalar(value)!) return Character(UnicodeScalar(value)!)
} }
func unicodeScalarCodePoint() -> UInt32 func unicodeScalarCodePoint() -> UInt32 {
{
return unicodeScalar.value return unicodeScalar.value
} }
@ -179,22 +178,18 @@ extension Character {
return codePoint >= MIN_SUPPLEMENTARY_CODE_POINT ? 2 : 1 return codePoint >= MIN_SUPPLEMENTARY_CODE_POINT ? 2 : 1
} }
static func isLetter(_ char: Character)->Bool{ static func isLetter(_ char: Character) -> Bool {
return char.isLetter() return char.isLetter()
} }
func isLetter()->Bool{ func isLetter() -> Bool {
return self.isMemberOfCharacterSet(CharacterSet.letters) return self.isMemberOfCharacterSet(CharacterSet.letters)
} }
static func isLetterOrDigit(_ char: Character)->Bool static func isLetterOrDigit(_ char: Character) -> Bool {
{
return char.isLetterOrDigit() return char.isLetterOrDigit()
} }
func isLetterOrDigit()->Bool func isLetterOrDigit() -> Bool {
{
if(self.isLetter()) {return true} if(self.isLetter()) {return true}
return self.isDigit return self.isDigit
} }
} }

View File

@ -11,18 +11,16 @@ import Foundation
/** /**
CharacterReader consumes tokens off a string. To replace the old TokenQueue. CharacterReader consumes tokens off a string. To replace the old TokenQueue.
*/ */
public final class CharacterReader public final class CharacterReader {
{ public static let EOF: UnicodeScalar = "\u{FFFF}"//65535
public static let EOF : UnicodeScalar = "\u{FFFF}"//65535 private static let maxCacheLen: Int = 12
private static let maxCacheLen : Int = 12 private let input: [UnicodeScalar]
private let input : [UnicodeScalar] private let length: Int
private let length : Int private var pos: Int = 0
private var pos : Int = 0 private var mark: Int = 0
private var mark : Int = 0 private let stringCache: Array<String?> // holds reused strings in this doc, to lessen garbage
private let stringCache : Array<String?> // holds reused strings in this doc, to lessen garbage
public init(_ input: String) public init(_ input: String) {
{
self.input = Array(input.unicodeScalars) self.input = Array(input.unicodeScalars)
self.length = self.input.count self.length = self.input.count
stringCache = Array(repeating:nil, count:512) stringCache = Array(repeating:nil, count:512)
@ -75,10 +73,10 @@ public final class CharacterReader
* @param c scan target * @param c scan target
* @return offset between current position and next instance of target. -1 if not found. * @return offset between current position and next instance of target. -1 if not found.
*/ */
public func nextIndexOf(_ c : UnicodeScalar) -> Int { public func nextIndexOf(_ c: UnicodeScalar) -> Int {
// doesn't handle scanning for surrogates // doesn't handle scanning for surrogates
for i in pos..<length { for i in pos..<length {
if (c == input[i]){ if (c == input[i]) {
return i - pos return i - pos
} }
} }
@ -93,25 +91,24 @@ public final class CharacterReader
*/ */
public func nextIndexOf(_ seq: String) -> Int { public func nextIndexOf(_ seq: String) -> Int {
// doesn't handle scanning for surrogates // doesn't handle scanning for surrogates
if(seq.isEmpty){return -1} if(seq.isEmpty) {return -1}
let startChar : UnicodeScalar = seq.unicodeScalar(0) let startChar: UnicodeScalar = seq.unicodeScalar(0)
for var offset in pos..<length { for var offset in pos..<length {
// scan to first instance of startchar: // scan to first instance of startchar:
if (startChar != input[offset]){ if (startChar != input[offset]) {
offset+=1 offset+=1
while(offset < length && startChar != input[offset]) { offset+=1 } while(offset < length && startChar != input[offset]) { offset+=1 }
} }
var i = offset + 1 var i = offset + 1
let last = i + seq.unicodeScalars.count-1 let last = i + seq.unicodeScalars.count-1
if (offset < length && last <= length) if (offset < length && last <= length) {
{
var j = 1 var j = 1
while i < last && seq.unicodeScalar(j) == input[i] { while i < last && seq.unicodeScalar(j) == input[i] {
j+=1 j+=1
i+=1 i+=1
} }
// found full sequence // found full sequence
if (i == last){ if (i == last) {
return offset - pos return offset - pos
} }
} }
@ -119,7 +116,7 @@ public final class CharacterReader
return -1 return -1
} }
public func consumeTo(_ c : UnicodeScalar) -> String { public func consumeTo(_ c: UnicodeScalar) -> String {
let offset = nextIndexOf(c) let offset = nextIndexOf(c)
if (offset != -1) { if (offset != -1) {
let consumed = cacheString(pos, offset) let consumed = cacheString(pos, offset)
@ -141,24 +138,23 @@ public final class CharacterReader
} }
} }
public func consumeToAny(_ chars: UnicodeScalar...)->String { public func consumeToAny(_ chars: UnicodeScalar...) -> String {
return consumeToAny(chars) return consumeToAny(chars)
} }
public func consumeToAny(_ chars: [UnicodeScalar])->String { public func consumeToAny(_ chars: [UnicodeScalar]) -> String {
let start : Int = pos let start: Int = pos
let remaining : Int = length let remaining: Int = length
let val = input let val = input
if(start == 2528){ if(start == 2528) {
let d = 1 let d = 1
print(d) print(d)
} }
OUTER: while (pos < remaining) OUTER: while (pos < remaining) {
{ if(pos == 41708) {
if(pos == 41708){
let d = 1 let d = 1
print(d) print(d)
} }
if chars.contains(val[pos]){ if chars.contains(val[pos]) {
break OUTER break OUTER
} }
// for c in chars { // for c in chars {
@ -172,17 +168,16 @@ public final class CharacterReader
return pos > start ? cacheString(start, pos-start) : "" return pos > start ? cacheString(start, pos-start) : ""
} }
public func consumeToAnySorted(_ chars: UnicodeScalar...) -> String {
public func consumeToAnySorted(_ chars: UnicodeScalar...)->String {
return consumeToAnySorted(chars) return consumeToAnySorted(chars)
} }
public func consumeToAnySorted(_ chars: [UnicodeScalar])->String { public func consumeToAnySorted(_ chars: [UnicodeScalar]) -> String {
let start = pos let start = pos
let remaining = length let remaining = length
let val = input let val = input
while (pos < remaining) { while (pos < remaining) {
if (chars.binarySearch(chars, val[pos]) >= 0){ if (chars.binarySearch(chars, val[pos]) >= 0) {
break break
} }
pos += 1 pos += 1
@ -191,8 +186,6 @@ public final class CharacterReader
return pos > start ? cacheString(start, pos-start) : "" return pos > start ? cacheString(start, pos-start) : ""
} }
public func consumeData() -> String { public func consumeData() -> String {
// &, <, null // &, <, null
let start = pos let start = pos
@ -200,8 +193,8 @@ public final class CharacterReader
let val = input let val = input
while (pos < remaining) { while (pos < remaining) {
let c : UnicodeScalar = val[pos] let c: UnicodeScalar = val[pos]
if (c == "&" || c == "<" || c == TokeniserStateVars.nullScalr){ if (c == "&" || c == "<" || c == TokeniserStateVars.nullScalr) {
break break
} }
pos += 1 pos += 1
@ -210,15 +203,15 @@ public final class CharacterReader
return pos > start ? cacheString(start, pos-start) : "" return pos > start ? cacheString(start, pos-start) : ""
} }
public func consumeTagName()-> String { public func consumeTagName() -> String {
// '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar // '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar
let start = pos let start = pos
let remaining = length let remaining = length
let val = input let val = input
while (pos < remaining) { while (pos < remaining) {
let c : UnicodeScalar = val[pos] let c: UnicodeScalar = val[pos]
if (c == "\t" || c == "\n" || c == "\r" || c == UnicodeScalar.BackslashF || c == " " || c == "/" || c == ">" || c == TokeniserStateVars.nullScalr){ if (c == "\t" || c == "\n" || c == "\r" || c == UnicodeScalar.BackslashF || c == " " || c == "/" || c == ">" || c == TokeniserStateVars.nullScalr) {
break break
} }
pos += 1 pos += 1
@ -226,41 +219,40 @@ public final class CharacterReader
return pos > start ? cacheString(start, pos-start) : "" return pos > start ? cacheString(start, pos-start) : ""
} }
public func consumeToEnd() -> String {
public func consumeToEnd()-> String {
let data = cacheString(pos, length-pos) let data = cacheString(pos, length-pos)
pos = length pos = length
return data return data
} }
public func consumeLetterSequence()-> String { public func consumeLetterSequence() -> String {
let start = pos let start = pos
while (pos < length) { while (pos < length) {
let c : UnicodeScalar = input[pos] let c: UnicodeScalar = input[pos]
if ((c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)){ if ((c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)) {
pos += 1 pos += 1
}else{ } else {
break break
} }
} }
return cacheString(start, pos - start) return cacheString(start, pos - start)
} }
public func consumeLetterThenDigitSequence()-> String { public func consumeLetterThenDigitSequence() -> String {
let start = pos let start = pos
while (pos < length) { while (pos < length) {
let c = input[pos] let c = input[pos]
if ((c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)){ if ((c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)) {
pos += 1 pos += 1
}else{ } else {
break break
} }
} }
while (!isEmpty()) { while (!isEmpty()) {
let c = input[pos] let c = input[pos]
if (c >= "0" && c <= "9"){ if (c >= "0" && c <= "9") {
pos += 1 pos += 1
}else{ } else {
break break
} }
} }
@ -268,13 +260,13 @@ public final class CharacterReader
return cacheString(start, pos - start) return cacheString(start, pos - start)
} }
public func consumeHexSequence()-> String { public func consumeHexSequence() -> String {
let start = pos let start = pos
while (pos < length) { while (pos < length) {
let c = input[pos] let c = input[pos]
if ((c >= "0" && c <= "9") || (c >= "A" && c <= "F") || (c >= "a" && c <= "f")){ if ((c >= "0" && c <= "9") || (c >= "A" && c <= "F") || (c >= "a" && c <= "f")) {
pos+=1 pos+=1
}else{ } else {
break break
} }
} }
@ -285,9 +277,9 @@ public final class CharacterReader
let start = pos let start = pos
while (pos < length) { while (pos < length) {
let c = input[pos] let c = input[pos]
if (c >= "0" && c <= "9"){ if (c >= "0" && c <= "9") {
pos+=1 pos+=1
}else{ } else {
break break
} }
} }
@ -299,68 +291,68 @@ public final class CharacterReader
} }
public func matches(_ seq: String)-> Bool { public func matches(_ seq: String) -> Bool {
let scanLength = seq.unicodeScalars.count let scanLength = seq.unicodeScalars.count
if (scanLength > length - pos){ if (scanLength > length - pos) {
return false return false
} }
for offset in 0..<scanLength{ for offset in 0..<scanLength {
if (seq.unicodeScalar(offset) != input[pos+offset]){ if (seq.unicodeScalar(offset) != input[pos+offset]) {
return false return false
} }
} }
return true return true
} }
public func matchesIgnoreCase(_ seq: String )->Bool { public func matchesIgnoreCase(_ seq: String ) -> Bool {
let scanLength = seq.unicodeScalars.count let scanLength = seq.unicodeScalars.count
if(scanLength == 0){ if(scanLength == 0) {
return false return false
} }
if (scanLength > length - pos){ if (scanLength > length - pos) {
return false return false
} }
for offset in 0..<scanLength{ for offset in 0..<scanLength {
let upScan : UnicodeScalar = seq.unicodeScalar(offset).uppercase let upScan: UnicodeScalar = seq.unicodeScalar(offset).uppercase
let upTarget : UnicodeScalar = input[pos+offset].uppercase let upTarget: UnicodeScalar = input[pos+offset].uppercase
if (upScan != upTarget){ if (upScan != upTarget) {
return false return false
} }
} }
return true return true
} }
public func matchesAny(_ seq: UnicodeScalar...)->Bool { public func matchesAny(_ seq: UnicodeScalar...) -> Bool {
if (isEmpty()){ if (isEmpty()) {
return false return false
} }
let c : UnicodeScalar = input[pos] let c: UnicodeScalar = input[pos]
for seek in seq { for seek in seq {
if (seek == c){ if (seek == c) {
return true return true
} }
} }
return false return false
} }
public func matchesAnySorted(_ seq : [UnicodeScalar]) -> Bool { public func matchesAnySorted(_ seq: [UnicodeScalar]) -> Bool {
return !isEmpty() && seq.binarySearch(seq, input[pos]) >= 0 return !isEmpty() && seq.binarySearch(seq, input[pos]) >= 0
} }
public func matchesLetter()-> Bool { public func matchesLetter() -> Bool {
if (isEmpty()){ if (isEmpty()) {
return false return false
} }
let c = input[pos] let c = input[pos]
return (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters) return (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)
} }
public func matchesDigit()->Bool { public func matchesDigit() -> Bool {
if (isEmpty()){ if (isEmpty()) {
return false return false
} }
let c = input[pos] let c = input[pos]
@ -368,7 +360,7 @@ public final class CharacterReader
} }
@discardableResult @discardableResult
public func matchConsume(_ seq: String)->Bool { public func matchConsume(_ seq: String) -> Bool {
if (matches(seq)) { if (matches(seq)) {
pos += seq.unicodeScalars.count pos += seq.unicodeScalars.count
return true return true
@ -378,7 +370,7 @@ public final class CharacterReader
} }
@discardableResult @discardableResult
public func matchConsumeIgnoreCase(_ seq: String)->Bool { public func matchConsumeIgnoreCase(_ seq: String) -> Bool {
if (matchesIgnoreCase(seq)) { if (matchesIgnoreCase(seq)) {
pos += seq.unicodeScalars.count pos += seq.unicodeScalars.count
return true return true
@ -387,20 +379,18 @@ public final class CharacterReader
} }
} }
public func containsIgnoreCase(_ seq: String )->Bool { public func containsIgnoreCase(_ seq: String ) -> Bool {
// used to check presence of </title>, </style>. only finds consistent case. // used to check presence of </title>, </style>. only finds consistent case.
let loScan = seq.lowercased(with: Locale(identifier: "en")) let loScan = seq.lowercased(with: Locale(identifier: "en"))
let hiScan = seq.uppercased(with: Locale(identifier: "eng")) let hiScan = seq.uppercased(with: Locale(identifier: "eng"))
return (nextIndexOf(loScan) > -1) || (nextIndexOf(hiScan) > -1) return (nextIndexOf(loScan) > -1) || (nextIndexOf(hiScan) > -1)
} }
public func toString() -> String {
public func toString()->String {
return String.unicodescalars(Array(input[pos..<length])) return String.unicodescalars(Array(input[pos..<length]))
//return input.string(pos, length - pos) //return input.string(pos, length - pos)
} }
/** /**
* Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks. * Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks.
* <p /> * <p />
@ -410,17 +400,17 @@ public final class CharacterReader
*/ */
private func cacheString(_ start: Int, _ count: Int) -> String { private func cacheString(_ start: Int, _ count: Int) -> String {
let val = input let val = input
var cache : [String?] = stringCache var cache: [String?] = stringCache
// limit (no cache): // limit (no cache):
if (count > CharacterReader.maxCacheLen){ if (count > CharacterReader.maxCacheLen) {
return String.unicodescalars(Array(val[start..<start+count])) return String.unicodescalars(Array(val[start..<start+count]))
} }
// calculate hash: // calculate hash:
var hash : Int = 0 var hash: Int = 0
var offset = start var offset = start
for _ in 0..<count{ for _ in 0..<count {
let ch = val[offset].value let ch = val[offset].value
hash = Int.addWithOverflow(Int.multiplyWithOverflow(31, hash).0, Int(ch)).0 hash = Int.addWithOverflow(Int.multiplyWithOverflow(31, hash).0, Int(ch)).0
offset+=1 offset+=1
@ -429,11 +419,10 @@ public final class CharacterReader
// get from cache // get from cache
hash = abs(hash) hash = abs(hash)
let i = hash % cache.count let i = hash % cache.count
let index : Int = abs(i) //Int(hash & Int(cache.count) - 1) let index: Int = abs(i) //Int(hash & Int(cache.count) - 1)
var cached = cache[index] var cached = cache[index]
if (cached == nil) if (cached == nil) { // miss, add
{ // miss, add
cached = String.unicodescalars(Array(val[start..<start+count])) cached = String.unicodescalars(Array(val[start..<start+count]))
//cached = val.string(start, count) //cached = val.string(start, count)
cache[Int(index)] = cached cache[Int(index)] = cached
@ -453,16 +442,14 @@ public final class CharacterReader
* Check if the value of the provided range equals the string. * Check if the value of the provided range equals the string.
*/ */
public func rangeEquals(_ start: Int, _ count: Int, _ cached: String) -> Bool { public func rangeEquals(_ start: Int, _ count: Int, _ cached: String) -> Bool {
if (count == cached.unicodeScalars.count) if (count == cached.unicodeScalars.count) {
{
var count = count var count = count
let one = input let one = input
var i = start var i = start
var j = 0 var j = 0
while (count != 0) { while (count != 0) {
count -= 1 count -= 1
if (one[i] != cached.unicodeScalar(j) ) if (one[i] != cached.unicodeScalar(j) ) {
{
return false return false
} }
j += 1 j += 1

View File

@ -8,9 +8,8 @@
import Foundation import Foundation
open class Cleaner open class Cleaner {
{ fileprivate let whitelist: Whitelist
fileprivate let whitelist : Whitelist
/** /**
Create a new cleaner, that sanitizes documents using the supplied whitelist. Create a new cleaner, that sanitizes documents using the supplied whitelist.
@ -56,7 +55,7 @@ open class Cleaner
@discardableResult @discardableResult
fileprivate func copySafeNodes(_ source: Element, _ dest: Element)throws->Int { fileprivate func copySafeNodes(_ source: Element, _ dest: Element)throws->Int {
let cleaningVisitor: Cleaner.CleaningVisitor = Cleaner.CleaningVisitor(source, dest,self) let cleaningVisitor: Cleaner.CleaningVisitor = Cleaner.CleaningVisitor(source, dest, self)
let traversor: NodeTraversor = NodeTraversor(cleaningVisitor) let traversor: NodeTraversor = NodeTraversor(cleaningVisitor)
try traversor.traverse(source) try traversor.traverse(source)
return cleaningVisitor.numDiscarded return cleaningVisitor.numDiscarded
@ -68,13 +67,11 @@ open class Cleaner
let dest: Element = try Element(Tag.valueOf(sourceTag), sourceEl.getBaseUri(), destAttrs) let dest: Element = try Element(Tag.valueOf(sourceTag), sourceEl.getBaseUri(), destAttrs)
var numDiscarded: Int = 0 var numDiscarded: Int = 0
if let sourceAttrs = sourceEl.getAttributes() if let sourceAttrs = sourceEl.getAttributes() {
{ for sourceAttr: Attribute in sourceAttrs {
for sourceAttr: Attribute in sourceAttrs if (whitelist.isSafeAttribute(sourceTag, sourceEl, sourceAttr)) {
{
if (whitelist.isSafeAttribute(sourceTag, sourceEl, sourceAttr)){
destAttrs.put(attribute: sourceAttr) destAttrs.put(attribute: sourceAttr)
}else{ } else {
numDiscarded+=1 numDiscarded+=1
} }
} }
@ -87,18 +84,15 @@ open class Cleaner
} }
extension Cleaner {
extension Cleaner fileprivate final class CleaningVisitor: NodeVisitor {
{
fileprivate final class CleaningVisitor : NodeVisitor
{
var numDiscarded: Int = 0 var numDiscarded: Int = 0
let root: Element let root: Element
var destination: Element? // current element to append nodes to var destination: Element? // current element to append nodes to
private weak var cleaner : Cleaner? private weak var cleaner: Cleaner?
public init(_ root: Element, _ destination: Element, _ cleaner : Cleaner) { public init(_ root: Element, _ destination: Element, _ cleaner: Cleaner) {
self.root = root self.root = root
self.destination = destination self.destination = destination
} }
@ -118,11 +112,8 @@ extension Cleaner
} else if let sourceText = (source as? TextNode) { } else if let sourceText = (source as? TextNode) {
let destText: TextNode = TextNode(sourceText.getWholeText(), source.getBaseUri()) let destText: TextNode = TextNode(sourceText.getWholeText(), source.getBaseUri())
try destination?.appendChild(destText) try destination?.appendChild(destText)
} } else if let sourceData = (source as? DataNode) {
else if let sourceData = (source as? DataNode) if sourceData.parent() != nil && cleaner!.whitelist.isSafeTag(sourceData.parent()!.nodeName()) {
{
if sourceData.parent() != nil && cleaner!.whitelist.isSafeTag(sourceData.parent()!.nodeName())
{
//let sourceData: DataNode = (DataNode) source //let sourceData: DataNode = (DataNode) source
let destData: DataNode = DataNode(sourceData.getWholeData(), source.getBaseUri()) let destData: DataNode = DataNode(sourceData.getWholeData(), source.getBaseUri())
try destination?.appendChild(destData) try destination?.appendChild(destData)
@ -132,12 +123,9 @@ extension Cleaner
} }
} }
public func tail(_ source: Node, _ depth: Int)throws public func tail(_ source: Node, _ depth: Int)throws {
{ if let x = (source as? Element) {
if let x = (source as? Element) if cleaner!.whitelist.isSafeTag(x.nodeName()) {
{
if cleaner!.whitelist.isSafeTag(x.nodeName())
{
// would have descended, so pop destination stack // would have descended, so pop destination stack
destination = destination?.parent() destination = destination?.parent()
} }
@ -146,8 +134,7 @@ extension Cleaner
} }
} }
extension Cleaner extension Cleaner {
{
fileprivate struct ElementMeta { fileprivate struct ElementMeta {
let el: Element let el: Element
let numAttribsDiscarded: Int let numAttribsDiscarded: Int

View File

@ -25,14 +25,14 @@ open class Collector {
@return list of matches; empty if none @return list of matches; empty if none
*/ */
open static func collect (_ eval: Evaluator, _ root: Element)throws->Elements { open static func collect (_ eval: Evaluator, _ root: Element)throws->Elements {
let elements : Elements = Elements() let elements: Elements = Elements()
try NodeTraversor(Accumulator(root, elements, eval)).traverse(root) try NodeTraversor(Accumulator(root, elements, eval)).traverse(root)
return elements return elements
} }
} }
private final class Accumulator : NodeVisitor { private final class Accumulator: NodeVisitor {
private let root: Element private let root: Element
private let elements: Elements private let elements: Elements
private let eval: Evaluator private let eval: Evaluator
@ -44,13 +44,12 @@ private final class Accumulator : NodeVisitor {
} }
open func head(_ node: Node, _ depth: Int) { open func head(_ node: Node, _ depth: Int) {
if let el = node as? Element if let el = node as? Element {
{ do {
do{ if (try eval.matches(root, el)) {
if (try eval.matches(root, el)){
elements.add(el) elements.add(el)
} }
}catch{} } catch {}
} }
} }

View File

@ -11,10 +11,10 @@ import Foundation
/** /**
* Base combining (and, or) evaluator. * Base combining (and, or) evaluator.
*/ */
public class CombiningEvaluator : Evaluator { public class CombiningEvaluator: Evaluator {
public private(set) var evaluators: Array<Evaluator> public private(set) var evaluators: Array<Evaluator>
var num : Int = 0 var num: Int = 0
public override init() { public override init() {
evaluators = Array<Evaluator>() evaluators = Array<Evaluator>()
@ -27,7 +27,7 @@ public class CombiningEvaluator : Evaluator {
updateNumEvaluators() updateNumEvaluators()
} }
func rightMostEvaluator()->Evaluator? { func rightMostEvaluator() -> Evaluator? {
return num > 0 && evaluators.count > 0 ? evaluators[num - 1] : nil return num > 0 && evaluators.count > 0 ? evaluators[num - 1] : nil
} }
@ -40,7 +40,7 @@ public class CombiningEvaluator : Evaluator {
num = evaluators.count num = evaluators.count
} }
public final class And : CombiningEvaluator { public final class And: CombiningEvaluator {
public override init(_ evaluators: Array<Evaluator>) { public override init(_ evaluators: Array<Evaluator>) {
super.init(evaluators) super.init(evaluators)
} }
@ -49,36 +49,35 @@ public class CombiningEvaluator : Evaluator {
super.init(evaluators) super.init(evaluators)
} }
public override func matches(_ root: Element, _ node: Element)->Bool { public override func matches(_ root: Element, _ node: Element) -> Bool {
for i in 0..<num for i in 0..<num {
{
let s = evaluators[i] let s = evaluators[i]
do{ do {
if (try !s.matches(root, node)){ if (try !s.matches(root, node)) {
return false return false
} }
}catch{} } catch {}
} }
return true return true
} }
public override func toString()->String { public override func toString() -> String {
let ar : [String] = evaluators.map { String($0.toString()) } let ar: [String] = evaluators.map { String($0.toString()) }
return StringUtil.join(ar, sep: " ") return StringUtil.join(ar, sep: " ")
} }
} }
public final class Or : CombiningEvaluator { public final class Or: CombiningEvaluator {
/** /**
* Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR. * Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR.
* @param evaluators initial OR clause (these are wrapped into an AND evaluator). * @param evaluators initial OR clause (these are wrapped into an AND evaluator).
*/ */
public override init(_ evaluators: Array<Evaluator>) { public override init(_ evaluators: Array<Evaluator>) {
super.init() super.init()
if (num > 1){ if (num > 1) {
self.evaluators.append(And(evaluators)) self.evaluators.append(And(evaluators))
}else{ // 0 or 1 } else { // 0 or 1
self.evaluators.append(contentsOf: evaluators) self.evaluators.append(contentsOf: evaluators)
} }
updateNumEvaluators() updateNumEvaluators()
@ -86,9 +85,9 @@ public class CombiningEvaluator : Evaluator {
override init(_ evaluators: Evaluator...) { override init(_ evaluators: Evaluator...) {
super.init() super.init()
if (num > 1){ if (num > 1) {
self.evaluators.append(And(evaluators)) self.evaluators.append(And(evaluators))
}else{ // 0 or 1 } else { // 0 or 1
self.evaluators.append(contentsOf: evaluators) self.evaluators.append(contentsOf: evaluators)
} }
updateNumEvaluators() updateNumEvaluators()
@ -103,21 +102,20 @@ public class CombiningEvaluator : Evaluator {
updateNumEvaluators() updateNumEvaluators()
} }
public override func matches(_ root: Element, _ node: Element)->Bool { public override func matches(_ root: Element, _ node: Element) -> Bool {
for i in 0..<num for i in 0..<num {
{ let s: Evaluator = evaluators[i]
let s : Evaluator = evaluators[i] do {
do{ if (try s.matches(root, node)) {
if (try s.matches(root, node)){
return true return true
} }
}catch{} } catch {}
} }
return false return false
} }
public override func toString()->String { public override func toString() -> String {
return ":or\(evaluators.map{String($0.toString())})" return ":or\(evaluators.map {String($0.toString())})"
} }
} }
} }

View File

@ -11,8 +11,8 @@ import Foundation
/** /**
A comment node. A comment node.
*/ */
public class Comment : Node { public class Comment: Node {
private static let COMMENT_KEY: String = "comment"; private static let COMMENT_KEY: String = "comment"
/** /**
Create a new comment node. Create a new comment node.
@ -20,59 +20,56 @@ public class Comment : Node {
@param baseUri base URI @param baseUri base URI
*/ */
public init(_ data: String, _ baseUri: String) { public init(_ data: String, _ baseUri: String) {
super.init(baseUri); super.init(baseUri)
do{ do {
try attributes?.put(Comment.COMMENT_KEY, data); try attributes?.put(Comment.COMMENT_KEY, data)
}catch{} } catch {}
} }
public override func nodeName()->String { public override func nodeName() -> String {
return "#comment"; return "#comment"
} }
/** /**
Get the contents of the comment. Get the contents of the comment.
@return comment content @return comment content
*/ */
public func getData()->String { public func getData() -> String {
return attributes!.get(key: Comment.COMMENT_KEY); return attributes!.get(key: Comment.COMMENT_KEY)
} }
override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) { override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
if (out.prettyPrint()){ if (out.prettyPrint()) {
indent(accum, depth, out); indent(accum, depth, out)
} }
accum accum
.append("<!--") .append("<!--")
.append(getData()) .append(getData())
.append("-->"); .append("-->")
} }
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {} override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {}
public override func toString()->String { public override func toString() -> String {
do{ do {
return try return try
outerHtml(); outerHtml()
}catch{ } catch {
return "" return ""
} }
} }
public override func copy(with zone: NSZone? = nil) -> Any public override func copy(with zone: NSZone? = nil) -> Any {
{ let clone = Comment(attributes!.get(key: Comment.COMMENT_KEY), baseUri!)
let clone = Comment(attributes!.get(key: Comment.COMMENT_KEY),baseUri!)
return copy(clone: clone) return copy(clone: clone)
} }
public override func copy(parent: Node?)->Node public override func copy(parent: Node?) -> Node {
{ let clone = Comment(attributes!.get(key: Comment.COMMENT_KEY), baseUri!)
let clone = Comment(attributes!.get(key: Comment.COMMENT_KEY),baseUri!) return copy(clone: clone, parent: parent)
return copy(clone: clone,parent: parent)
} }
public override func copy(clone: Node, parent: Node?)->Node public override func copy(clone: Node, parent: Node?) -> Node {
{ return super.copy(clone: clone, parent: parent)
return super.copy(clone: clone,parent: parent)
} }
} }

View File

@ -8,6 +8,3 @@
import Foundation import Foundation
//TODO: //TODO:

View File

@ -11,8 +11,8 @@ import Foundation
/** /**
A data node, for contents of style, script tags etc, where contents should not show in text(). A data node, for contents of style, script tags etc, where contents should not show in text().
*/ */
open class DataNode : Node{ open class DataNode: Node {
private static let DATA_KEY : String = "data" private static let DATA_KEY: String = "data"
/** /**
Create a new DataNode. Create a new DataNode.
@ -21,13 +21,13 @@ open class DataNode : Node{
*/ */
public init(_ data: String, _ baseUri: String) { public init(_ data: String, _ baseUri: String) {
super.init(baseUri) super.init(baseUri)
do{ do {
try attributes?.put(DataNode.DATA_KEY, data) try attributes?.put(DataNode.DATA_KEY, data)
}catch{} } catch {}
} }
open override func nodeName()->String { open override func nodeName() -> String {
return "#data" return "#data"
} }
@ -35,7 +35,7 @@ open class DataNode : Node{
Get the data contents of this node. Will be unescaped and with original new lines, space etc. Get the data contents of this node. Will be unescaped and with original new lines, space etc.
@return data @return data
*/ */
open func getWholeData()->String { open func getWholeData() -> String {
return attributes!.get(key: DataNode.DATA_KEY) return attributes!.get(key: DataNode.DATA_KEY)
} }
@ -45,10 +45,10 @@ open class DataNode : Node{
* @return this node, for chaining * @return this node, for chaining
*/ */
@discardableResult @discardableResult
open func setWholeData(_ data: String)->DataNode { open func setWholeData(_ data: String) -> DataNode {
do{ do {
try attributes?.put(DataNode.DATA_KEY, data) try attributes?.put(DataNode.DATA_KEY, data)
}catch{} } catch {}
return self return self
} }
@ -58,7 +58,6 @@ open class DataNode : Node{
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {} override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {}
open override func toString()throws->String { open override func toString()throws->String {
return try outerHtml() return try outerHtml()
} }
@ -74,20 +73,17 @@ open class DataNode : Node{
return DataNode(data, baseUri) return DataNode(data, baseUri)
} }
public override func copy(with zone: NSZone? = nil) -> Any public override func copy(with zone: NSZone? = nil) -> Any {
{ let clone = DataNode(attributes!.get(key: DataNode.DATA_KEY), baseUri!)
let clone = DataNode(attributes!.get(key: DataNode.DATA_KEY),baseUri!)
return copy(clone: clone) return copy(clone: clone)
} }
public override func copy(parent: Node?)->Node public override func copy(parent: Node?) -> Node {
{ let clone = DataNode(attributes!.get(key: DataNode.DATA_KEY), baseUri!)
let clone = DataNode(attributes!.get(key: DataNode.DATA_KEY),baseUri!) return copy(clone: clone, parent: parent)
return copy(clone: clone,parent: parent)
} }
public override func copy(clone: Node, parent: Node?)->Node public override func copy(clone: Node, parent: Node?) -> Node {
{ return super.copy(clone: clone, parent: parent)
return super.copy(clone: clone,parent: parent)
} }
} }

View File

@ -21,5 +21,4 @@ class DataUtil {
static let mimeBoundaryChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".characters static let mimeBoundaryChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".characters
static let boundaryLength = 32 static let boundaryLength = 32
} }

View File

@ -8,8 +8,7 @@
import Foundation import Foundation
open class Document : Element open class Document: Element {
{
public enum QuirksMode { public enum QuirksMode {
case noQuirks, quirks, limitedQuirks case noQuirks, quirks, limitedQuirks
} }
@ -25,7 +24,7 @@ open class Document : Element
@see org.jsoup.Jsoup#parse @see org.jsoup.Jsoup#parse
@see #createShell @see #createShell
*/ */
public init(_ baseUri: String){ public init(_ baseUri: String) {
self._location = baseUri self._location = baseUri
super.init(try! Tag.valueOf("#root", ParseSettings.htmlDefault), baseUri) super.init(try! Tag.valueOf("#root", ParseSettings.htmlDefault), baseUri)
} }
@ -35,7 +34,7 @@ open class Document : Element
@param baseUri baseUri of document @param baseUri baseUri of document
@return document with html, head, and body elements. @return document with html, head, and body elements.
*/ */
static open func createShell(_ baseUri: String)->Document { static open func createShell(_ baseUri: String) -> Document {
let doc: Document = Document(baseUri) let doc: Document = Document(baseUri)
let html: Element = try! doc.appendElement("html") let html: Element = try! doc.appendElement("html")
try! html.appendElement("head") try! html.appendElement("head")
@ -49,7 +48,7 @@ open class Document : Element
* this will return the final URL from which the document was served from. * this will return the final URL from which the document was served from.
* @return location * @return location
*/ */
public func location()->String { public func location() -> String {
return _location return _location
} }
@ -57,7 +56,7 @@ open class Document : Element
Accessor to the document's {@code head} element. Accessor to the document's {@code head} element.
@return {@code head} @return {@code head}
*/ */
public func head()->Element? { public func head() -> Element? {
return findFirstElementByTagName("head", self) return findFirstElementByTagName("head", self)
} }
@ -65,7 +64,7 @@ open class Document : Element
Accessor to the document's {@code body} element. Accessor to the document's {@code body} element.
@return {@code body} @return {@code body}
*/ */
public func body()->Element? { public func body() -> Element? {
return findFirstElementByTagName("body", self) return findFirstElementByTagName("body", self)
} }
@ -110,15 +109,15 @@ open class Document : Element
@discardableResult @discardableResult
public func normalise()throws->Document { public func normalise()throws->Document {
var htmlE: Element? = findFirstElementByTagName("html", self) var htmlE: Element? = findFirstElementByTagName("html", self)
if (htmlE == nil){ if (htmlE == nil) {
htmlE = try appendElement("html") htmlE = try appendElement("html")
} }
let htmlEl: Element = htmlE! let htmlEl: Element = htmlE!
if (head() == nil){ if (head() == nil) {
try htmlEl.prependElement("head") try htmlEl.prependElement("head")
} }
if (body() == nil){ if (body() == nil) {
try htmlEl.appendElement("body") try htmlEl.appendElement("body")
} }
@ -139,17 +138,15 @@ open class Document : Element
// does not recurse. // does not recurse.
private func normaliseTextNodes(_ element: Element)throws { private func normaliseTextNodes(_ element: Element)throws {
var toMove: Array<Node> = Array<Node>() var toMove: Array<Node> = Array<Node>()
for node:Node in element.childNodes for node: Node in element.childNodes {
{
if let tn = (node as? TextNode) { if let tn = (node as? TextNode) {
if (!tn.isBlank()){ if (!tn.isBlank()) {
toMove.append(tn) toMove.append(tn)
} }
} }
} }
for i in toMove.count-1...0 for i in toMove.count-1...0 {
{
let node: Node = toMove[i] let node: Node = toMove[i]
try element.removeChild(node) try element.removeChild(node)
try body()?.prependChild(TextNode(" ", "")) try body()?.prependChild(TextNode(" ", ""))
@ -162,18 +159,16 @@ open class Document : Element
let elements: Elements = try self.getElementsByTag(tag) let elements: Elements = try self.getElementsByTag(tag)
let master: Element? = elements.first() // will always be available as created above if not existent let master: Element? = elements.first() // will always be available as created above if not existent
if (elements.size() > 1) { // dupes, move contents to master if (elements.size() > 1) { // dupes, move contents to master
var toMove:Array<Node> = Array<Node>() var toMove: Array<Node> = Array<Node>()
for i in 1..<elements.size() for i in 1..<elements.size() {
{
let dupe: Node = elements.get(i) let dupe: Node = elements.get(i)
for node:Node in dupe.childNodes for node: Node in dupe.childNodes {
{
toMove.append(node) toMove.append(node)
} }
try dupe.remove() try dupe.remove()
} }
for dupe:Node in toMove{ for dupe: Node in toMove {
try master?.appendChild(dupe) try master?.appendChild(dupe)
} }
} }
@ -183,15 +178,14 @@ open class Document : Element
} }
} }
// fast method to get first by tag name, used for html, head, body finders // fast method to get first by tag name, used for html, head, body finders
private func findFirstElementByTagName(_ tag: String, _ node: Node)->Element? { private func findFirstElementByTagName(_ tag: String, _ node: Node) -> Element? {
if (node.nodeName()==tag){ if (node.nodeName()==tag) {
return node as? Element return node as? Element
}else { } else {
for child:Node in node.childNodes { for child: Node in node.childNodes {
let found: Element? = findFirstElementByTagName(tag, child) let found: Element? = findFirstElementByTagName(tag, child)
if (found != nil){ if (found != nil) {
return found return found
} }
} }
@ -214,7 +208,7 @@ open class Document : Element
return self return self
} }
open override func nodeName()->String { open override func nodeName() -> String {
return "#document" return "#document"
} }
@ -285,7 +279,7 @@ open class Document : Element
* @return Returns <tt>true</tt> if the element is updated on charset * @return Returns <tt>true</tt> if the element is updated on charset
* changes, <tt>false</tt> if not * changes, <tt>false</tt> if not
*/ */
public func updateMetaCharsetElement()->Bool { public func updateMetaCharsetElement() -> Bool {
return updateMetaCharset return updateMetaCharset
} }
@ -363,7 +357,7 @@ open class Document : Element
* Get the document's current output settings. * Get the document's current output settings.
* @return the document's current output settings. * @return the document's current output settings.
*/ */
public func outputSettings()->OutputSettings { public func outputSettings() -> OutputSettings {
return _outputSettings return _outputSettings
} }
@ -373,7 +367,7 @@ open class Document : Element
* @return this document, for chaining. * @return this document, for chaining.
*/ */
@discardableResult @discardableResult
public func outputSettings(_ outputSettings: OutputSettings)->Document { public func outputSettings(_ outputSettings: OutputSettings) -> Document {
self._outputSettings = outputSettings self._outputSettings = outputSettings
return self return self
} }
@ -383,33 +377,29 @@ open class Document : Element
} }
@discardableResult @discardableResult
public func quirksMode(_ quirksMode: Document.QuirksMode)->Document { public func quirksMode(_ quirksMode: Document.QuirksMode) -> Document {
self._quirksMode = quirksMode self._quirksMode = quirksMode
return self return self
} }
public override func copy(with zone: NSZone? = nil) -> Any public override func copy(with zone: NSZone? = nil) -> Any {
{
let clone = Document(_location) let clone = Document(_location)
return copy(clone: clone) return copy(clone: clone)
} }
public override func copy(parent: Node?)->Node public override func copy(parent: Node?) -> Node {
{
let clone = Document(_location) let clone = Document(_location)
return copy(clone: clone,parent: parent) return copy(clone: clone, parent: parent)
} }
public override func copy(clone: Node, parent: Node?)->Node public override func copy(clone: Node, parent: Node?) -> Node {
{
let clone = clone as! Document let clone = clone as! Document
clone._outputSettings = _outputSettings.copy() as! OutputSettings clone._outputSettings = _outputSettings.copy() as! OutputSettings
clone._quirksMode = _quirksMode clone._quirksMode = _quirksMode
clone.updateMetaCharset = updateMetaCharset clone.updateMetaCharset = updateMetaCharset
return super.copy(clone: clone,parent: parent) return super.copy(clone: clone, parent: parent)
} }
} }
public class OutputSettings: NSCopying { public class OutputSettings: NSCopying {
@ -418,11 +408,11 @@ public class OutputSettings: NSCopying {
*/ */
public enum Syntax {case html, xml} public enum Syntax {case html, xml}
private var _escapeMode : Entities.EscapeMode = Entities.EscapeMode.base private var _escapeMode: Entities.EscapeMode = Entities.EscapeMode.base
private var _encoder : String.Encoding = String.Encoding.utf8 // Charset.forName("UTF-8") private var _encoder: String.Encoding = String.Encoding.utf8 // Charset.forName("UTF-8")
private var _prettyPrint : Bool = true private var _prettyPrint: Bool = true
private var _outline : Bool = false private var _outline: Bool = false
private var _indentAmount : UInt = 1 private var _indentAmount: UInt = 1
private var _syntax = Syntax.html private var _syntax = Syntax.html
public init() {} public init() {}
@ -482,13 +472,11 @@ public class OutputSettings: NSCopying {
return encoder(e) return encoder(e)
} }
/** /**
* Get the document's current output syntax. * Get the document's current output syntax.
* @return current syntax * @return current syntax
*/ */
public func syntax()-> Syntax { public func syntax() -> Syntax {
return _syntax return _syntax
} }
@ -499,7 +487,7 @@ public class OutputSettings: NSCopying {
* @return the document's output settings, for chaining * @return the document's output settings, for chaining
*/ */
@discardableResult @discardableResult
public func syntax(syntax: Syntax)->OutputSettings { public func syntax(syntax: Syntax) -> OutputSettings {
_syntax = syntax _syntax = syntax
return self return self
} }
@ -509,7 +497,7 @@ public class OutputSettings: NSCopying {
* the output, and the output will generally look like the input. * the output, and the output will generally look like the input.
* @return if pretty printing is enabled. * @return if pretty printing is enabled.
*/ */
public func prettyPrint()->Bool { public func prettyPrint() -> Bool {
return _prettyPrint return _prettyPrint
} }
@ -519,7 +507,7 @@ public class OutputSettings: NSCopying {
* @return this, for chaining * @return this, for chaining
*/ */
@discardableResult @discardableResult
public func prettyPrint(pretty: Bool)->OutputSettings { public func prettyPrint(pretty: Bool) -> OutputSettings {
_prettyPrint = pretty _prettyPrint = pretty
return self return self
} }
@ -529,7 +517,7 @@ public class OutputSettings: NSCopying {
* all tags as block. * all tags as block.
* @return if outline mode is enabled. * @return if outline mode is enabled.
*/ */
public func outline()->Bool { public func outline() -> Bool {
return _outline return _outline
} }
@ -539,7 +527,7 @@ public class OutputSettings: NSCopying {
* @return this, for chaining * @return this, for chaining
*/ */
@discardableResult @discardableResult
public func outline(outlineMode: Bool)->OutputSettings { public func outline(outlineMode: Bool) -> OutputSettings {
_outline = outlineMode _outline = outlineMode
return self return self
} }
@ -548,7 +536,7 @@ public class OutputSettings: NSCopying {
* Get the current tag indent amount, used when pretty printing. * Get the current tag indent amount, used when pretty printing.
* @return the current indent amount * @return the current indent amount
*/ */
public func indentAmount()-> UInt { public func indentAmount() -> UInt {
return _indentAmount return _indentAmount
} }
@ -558,13 +546,12 @@ public class OutputSettings: NSCopying {
* @return this, for chaining * @return this, for chaining
*/ */
@discardableResult @discardableResult
public func indentAmount(indentAmount: UInt)-> OutputSettings { public func indentAmount(indentAmount: UInt) -> OutputSettings {
_indentAmount = indentAmount _indentAmount = indentAmount
return self return self
} }
public func copy(with zone: NSZone? = nil) -> Any {
public func copy(with zone: NSZone? = nil) -> Any{
let clone: OutputSettings = OutputSettings() let clone: OutputSettings = OutputSettings()
clone.charset(_encoder) // new charset and charset encoder clone.charset(_encoder) // new charset and charset encoder
clone._escapeMode = _escapeMode//Entities.EscapeMode.valueOf(escapeMode.name()) clone._escapeMode = _escapeMode//Entities.EscapeMode.valueOf(escapeMode.name())
@ -572,7 +559,4 @@ public class OutputSettings: NSCopying {
return clone return clone
} }
} }

View File

@ -11,10 +11,10 @@ import Foundation
/** /**
* A {@code <!DOCTYPE>} node. * A {@code <!DOCTYPE>} node.
*/ */
public class DocumentType : Node { public class DocumentType: Node {
private static let NAME: String = "name"; private static let NAME: String = "name"
private static let PUBLIC_ID: String = "publicId"; private static let PUBLIC_ID: String = "publicId"
private static let SYSTEM_ID: String = "systemId"; private static let SYSTEM_ID: String = "systemId"
// todo: quirk mode from publicId and systemId // todo: quirk mode from publicId and systemId
/** /**
@ -25,57 +25,56 @@ public class DocumentType : Node {
* @param baseUri the doctype's base URI * @param baseUri the doctype's base URI
*/ */
public init(_ name: String, _ publicId: String, _ systemId: String, _ baseUri: String) { public init(_ name: String, _ publicId: String, _ systemId: String, _ baseUri: String) {
super.init(baseUri); super.init(baseUri)
do{ do {
try attr(DocumentType.NAME, name); try attr(DocumentType.NAME, name)
try attr(DocumentType.PUBLIC_ID, publicId); try attr(DocumentType.PUBLIC_ID, publicId)
try attr(DocumentType.SYSTEM_ID, systemId); try attr(DocumentType.SYSTEM_ID, systemId)
}catch{} } catch {}
} }
public override func nodeName()->String { public override func nodeName() -> String {
return "#doctype"; return "#doctype"
} }
override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) { override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
if (out.syntax() == OutputSettings.Syntax.html && !has(DocumentType.PUBLIC_ID) && !has(DocumentType.SYSTEM_ID)) { if (out.syntax() == OutputSettings.Syntax.html && !has(DocumentType.PUBLIC_ID) && !has(DocumentType.SYSTEM_ID)) {
// looks like a html5 doctype, go lowercase for aesthetics // looks like a html5 doctype, go lowercase for aesthetics
accum.append("<!doctype"); accum.append("<!doctype")
} else { } else {
accum.append("<!DOCTYPE"); accum.append("<!DOCTYPE")
} }
if (has(DocumentType.NAME)){ if (has(DocumentType.NAME)) {
do{ do {
accum.append(" ").append(try attr(DocumentType.NAME)); accum.append(" ").append(try attr(DocumentType.NAME))
}catch{} } catch {}
} }
if (has(DocumentType.PUBLIC_ID)){ if (has(DocumentType.PUBLIC_ID)) {
do{ do {
accum.append(" PUBLIC \"").append(try attr(DocumentType.PUBLIC_ID)).append("\""); accum.append(" PUBLIC \"").append(try attr(DocumentType.PUBLIC_ID)).append("\"")
}catch{} } catch {}
} }
if (has(DocumentType.SYSTEM_ID)){ if (has(DocumentType.SYSTEM_ID)) {
do{ do {
accum.append(" \"").append(try attr(DocumentType.SYSTEM_ID)).append("\""); accum.append(" \"").append(try attr(DocumentType.SYSTEM_ID)).append("\"")
}catch{} } catch {}
} }
accum.append(">"); accum.append(">")
} }
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) { override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
} }
private func has(_ attribute: String)->Bool { private func has(_ attribute: String) -> Bool {
do{ do {
return !StringUtil.isBlank(try attr(attribute)); return !StringUtil.isBlank(try attr(attribute))
}catch{return false} } catch {return false}
} }
public override func copy(with zone: NSZone? = nil) -> Any public override func copy(with zone: NSZone? = nil) -> Any {
{
let clone = DocumentType(attributes!.get(key: DocumentType.NAME), let clone = DocumentType(attributes!.get(key: DocumentType.NAME),
attributes!.get(key: DocumentType.PUBLIC_ID), attributes!.get(key: DocumentType.PUBLIC_ID),
attributes!.get(key: DocumentType.SYSTEM_ID), attributes!.get(key: DocumentType.SYSTEM_ID),
@ -83,18 +82,16 @@ public class DocumentType : Node {
return copy(clone: clone) return copy(clone: clone)
} }
public override func copy(parent: Node?)->Node public override func copy(parent: Node?) -> Node {
{
let clone = DocumentType(attributes!.get(key: DocumentType.NAME), let clone = DocumentType(attributes!.get(key: DocumentType.NAME),
attributes!.get(key: DocumentType.PUBLIC_ID), attributes!.get(key: DocumentType.PUBLIC_ID),
attributes!.get(key: DocumentType.SYSTEM_ID), attributes!.get(key: DocumentType.SYSTEM_ID),
baseUri!) baseUri!)
return copy(clone: clone,parent: parent) return copy(clone: clone, parent: parent)
} }
public override func copy(clone: Node, parent: Node?)->Node public override func copy(clone: Node, parent: Node?) -> Node {
{ return super.copy(clone: clone, parent: parent)
return super.copy(clone: clone,parent: parent)
} }
} }

View File

@ -8,9 +8,8 @@
import Foundation import Foundation
open class Element : Node open class Element: Node {
{ var _tag: Tag
var _tag : Tag
//private static let classSplit : Pattern = Pattern("\\s+") //private static let classSplit : Pattern = Pattern("\\s+")
private static let classSplit = "\\s+" private static let classSplit = "\\s+"
@ -41,7 +40,7 @@ open class Element : Node
super.init(baseUri, Attributes()) super.init(baseUri, Attributes())
} }
open override func nodeName()->String { open override func nodeName() -> String {
return _tag.getName() return _tag.getName()
} }
/** /**
@ -49,7 +48,7 @@ open class Element : Node
* *
* @return the tag name * @return the tag name
*/ */
open func tagName()->String { open func tagName() -> String {
return _tag.getName() return _tag.getName()
} }
@ -72,7 +71,7 @@ open class Element : Node
* *
* @return the tag object * @return the tag object
*/ */
open func tag()->Tag { open func tag() -> Tag {
return _tag return _tag
} }
@ -82,7 +81,7 @@ open class Element : Node
* *
* @return true if block, false if not (and thus inline) * @return true if block, false if not (and thus inline)
*/ */
open func isBlock()->Bool { open func isBlock() -> Bool {
return _tag.isBlock() return _tag.isBlock()
} }
@ -91,11 +90,11 @@ open class Element : Node
* *
* @return The id attribute, if present, or an empty string if not. * @return The id attribute, if present, or an empty string if not.
*/ */
open func id()->String { open func id() -> String {
guard let attributes = attributes else {return ""} guard let attributes = attributes else {return ""}
do{ do {
return try attributes.getIgnoreCase(key: "id") return try attributes.getIgnoreCase(key: "id")
}catch{} } catch {}
return "" return ""
} }
@ -140,11 +139,11 @@ open class Element : Node
* You can find elements that have data attributes using the {@code [^data-]} attribute key prefix selector. * You can find elements that have data attributes using the {@code [^data-]} attribute key prefix selector.
* @return a map of {@code key=value} custom data attributes. * @return a map of {@code key=value} custom data attributes.
*/ */
open func dataset()->Dictionary<String,String> { open func dataset()->Dictionary<String, String> {
return attributes!.dataset() return attributes!.dataset()
} }
open override func parent()->Element? { open override func parent() -> Element? {
return parentNode as? Element return parentNode as? Element
} }
@ -152,14 +151,14 @@ open class Element : Node
* Get this element's parent and ancestors, up to the document root. * Get this element's parent and ancestors, up to the document root.
* @return this element's stack of parents, closest first. * @return this element's stack of parents, closest first.
*/ */
open func parents()->Elements { open func parents() -> Elements {
let parents: Elements = Elements() let parents: Elements = Elements()
Element.accumulateParents(self, parents) Element.accumulateParents(self, parents)
return parents return parents
} }
private static func accumulateParents(_ el: Element, _ parents: Elements) { private static func accumulateParents(_ el: Element, _ parents: Elements) {
let parent : Element? = el.parent() let parent: Element? = el.parent()
if (parent != nil && !(parent!.tagName() == "#root")) { if (parent != nil && !(parent!.tagName() == "#root")) {
parents.add(parent!) parents.add(parent!)
accumulateParents(parent!, parents) accumulateParents(parent!, parents)
@ -177,7 +176,7 @@ open class Element : Node
* @return the child element, if it exists, otherwise throws an {@code IndexOutOfBoundsException} * @return the child element, if it exists, otherwise throws an {@code IndexOutOfBoundsException}
* @see #childNode(int) * @see #childNode(int)
*/ */
open func child(_ index: Int)->Element { open func child(_ index: Int) -> Element {
return children().get(index) return children().get(index)
} }
@ -190,13 +189,11 @@ open class Element : Node
* empty list. * empty list.
* @see #childNodes() * @see #childNodes()
*/ */
open func children()->Elements { open func children() -> Elements {
// create on the fly rather than maintaining two lists. if gets slow, memoize, and mark dirty on change // create on the fly rather than maintaining two lists. if gets slow, memoize, and mark dirty on change
var elements = Array<Element>() var elements = Array<Element>()
for node in childNodes for node in childNodes {
{ if let n = node as? Element {
if let n = node as? Element
{
elements.append(n) elements.append(n)
} }
} }
@ -222,7 +219,7 @@ open class Element : Node
open func textNodes()->Array<TextNode> { open func textNodes()->Array<TextNode> {
var textNodes = Array<TextNode>() var textNodes = Array<TextNode>()
for node in childNodes { for node in childNodes {
if let n = node as? TextNode{ if let n = node as? TextNode {
textNodes.append(n) textNodes.append(n)
} }
} }
@ -240,9 +237,8 @@ open class Element : Node
*/ */
open func dataNodes()->Array<DataNode> { open func dataNodes()->Array<DataNode> {
var dataNodes = Array<DataNode>() var dataNodes = Array<DataNode>()
for node in childNodes for node in childNodes {
{ if let n = node as? DataNode {
if let n = node as? DataNode{
dataNodes.append(n) dataNodes.append(n)
} }
} }
@ -315,7 +311,7 @@ open class Element : Node
//Validate.notNull(children, "Children collection to be inserted must not be null.") //Validate.notNull(children, "Children collection to be inserted must not be null.")
var index = index var index = index
let currentSize: Int = childNodeSize() let currentSize: Int = childNodeSize()
if (index < 0){ index += currentSize + 1} // roll around if (index < 0) { index += currentSize + 1} // roll around
try Validate.isTrue(val: index >= 0 && index <= currentSize, msg: "Insert position out of bounds.") try Validate.isTrue(val: index >= 0 && index <= currentSize, msg: "Insert position out of bounds.")
try addChildren(index, children) try addChildren(index, children)
@ -358,7 +354,7 @@ open class Element : Node
*/ */
@discardableResult @discardableResult
public func appendText(_ text: String)throws->Element { public func appendText(_ text: String)throws->Element {
let node:TextNode = TextNode(text, getBaseUri()) let node: TextNode = TextNode(text, getBaseUri())
try appendChild(node) try appendChild(node)
return self return self
} }
@ -398,7 +394,7 @@ open class Element : Node
@discardableResult @discardableResult
public func prepend(_ html: String)throws->Element { public func prepend(_ html: String)throws->Element {
let nodes: Array<Node> = try Parser.parseFragment(html, self, getBaseUri()) let nodes: Array<Node> = try Parser.parseFragment(html, self, getBaseUri())
try addChildren(0,nodes) try addChildren(0, nodes)
return self return self
} }
@ -452,7 +448,7 @@ open class Element : Node
* @return this element * @return this element
*/ */
@discardableResult @discardableResult
public func empty()->Element { public func empty() -> Element {
childNodes.removeAll() childNodes.removeAll()
return self return self
} }
@ -479,7 +475,7 @@ open class Element : Node
* @return the CSS Path that can be used to retrieve the element in a selector. * @return the CSS Path that can be used to retrieve the element in a selector.
*/ */
public func cssSelector()throws->String { public func cssSelector()throws->String {
if (id().characters.count > 0){ if (id().characters.count > 0) {
return "#" + id() return "#" + id()
} }
@ -488,7 +484,7 @@ open class Element : Node
let selector: StringBuilder = StringBuilder(string: tagName) let selector: StringBuilder = StringBuilder(string: tagName)
let cl = try classNames() let cl = try classNames()
let classes: String = cl.joined(separator: ".") let classes: String = cl.joined(separator: ".")
if (classes.characters.count > 0){ if (classes.characters.count > 0) {
selector.append(".").append(classes) selector.append(".").append(classes)
} }
@ -498,7 +494,7 @@ open class Element : Node
} }
selector.insert(0, " > ") selector.insert(0, " > ")
if (try parent()!.select(selector.toString()).array().count > 1){ if (try parent()!.select(selector.toString()).array().count > 1) {
selector.append(":nth-child(\(try elementSiblingIndex() + 1))") selector.append(":nth-child(\(try elementSiblingIndex() + 1))")
} }
@ -510,14 +506,14 @@ open class Element : Node
* of itself, so will not be included in the returned list. * of itself, so will not be included in the returned list.
* @return sibling elements * @return sibling elements
*/ */
public func siblingElements()->Elements { public func siblingElements() -> Elements {
if (parentNode == nil){return Elements()} if (parentNode == nil) {return Elements()}
let elements: Array<Element>? = parent()?.children().array() let elements: Array<Element>? = parent()?.children().array()
let siblings: Elements = Elements() let siblings: Elements = Elements()
if let elements = elements{ if let elements = elements {
for el:Element in elements{ for el: Element in elements {
if (el != self){ if (el != self) {
siblings.add(el) siblings.add(el)
} }
} }
@ -539,10 +535,10 @@ open class Element : Node
let siblings: Array<Element>? = parent()?.children().array() let siblings: Array<Element>? = parent()?.children().array()
let index: Int? = try Element.indexInList(self, siblings) let index: Int? = try Element.indexInList(self, siblings)
try Validate.notNull(obj: index) try Validate.notNull(obj: index)
if let siblings = siblings{ if let siblings = siblings {
if (siblings.count > index!+1){ if (siblings.count > index!+1) {
return siblings[index!+1] return siblings[index!+1]
}else{ } else {
return nil} return nil}
} }
return nil return nil
@ -558,9 +554,9 @@ open class Element : Node
let siblings: Array<Element>? = parent()?.children().array() let siblings: Array<Element>? = parent()?.children().array()
let index: Int? = try Element.indexInList(self, siblings) let index: Int? = try Element.indexInList(self, siblings)
try Validate.notNull(obj: index) try Validate.notNull(obj: index)
if (index! > 0){ if (index! > 0) {
return siblings?[index!-1] return siblings?[index!-1]
}else{ } else {
return nil return nil
} }
} }
@ -569,7 +565,7 @@ open class Element : Node
* Gets the first element sibling of this element. * Gets the first element sibling of this element.
* @return the first sibling that is an element (aka the parent's first element child) * @return the first sibling that is an element (aka the parent's first element child)
*/ */
public func firstElementSibling()->Element? { public func firstElementSibling() -> Element? {
// todo: should firstSibling() exclude this? // todo: should firstSibling() exclude this?
let siblings: Array<Element>? = parent()?.children().array() let siblings: Array<Element>? = parent()?.children().array()
return (siblings != nil && siblings!.count > 1) ? siblings![0] : nil return (siblings != nil && siblings!.count > 1) ? siblings![0] : nil
@ -590,18 +586,17 @@ open class Element : Node
* Gets the last element sibling of this element * Gets the last element sibling of this element
* @return the last sibling that is an element (aka the parent's last element child) * @return the last sibling that is an element (aka the parent's last element child)
*/ */
public func lastElementSibling()->Element? { public func lastElementSibling() -> Element? {
let siblings: Array<Element>? = parent()?.children().array() let siblings: Array<Element>? = parent()?.children().array()
return (siblings != nil && siblings!.count > 1) ? siblings![siblings!.count - 1] : nil return (siblings != nil && siblings!.count > 1) ? siblings![siblings!.count - 1] : nil
} }
private static func indexInList(_ search: Element, _ elements: Array<Element>?)throws->Int? { private static func indexInList(_ search: Element, _ elements: Array<Element>?)throws->Int? {
try Validate.notNull(obj: elements) try Validate.notNull(obj: elements)
if let elements = elements if let elements = elements {
{
for i in 0..<elements.count { for i in 0..<elements.count {
let element: Element = elements[i] let element: Element = elements[i]
if (element == search){ if (element == search) {
return i return i
} }
} }
@ -636,9 +631,9 @@ open class Element : Node
try Validate.notEmpty(string: id) try Validate.notEmpty(string: id)
let elements: Elements = try Collector.collect(Evaluator.Id(id), self) let elements: Elements = try Collector.collect(Evaluator.Id(id), self)
if (elements.array().count > 0){ if (elements.array().count > 0) {
return elements.get(0) return elements.get(0)
}else{ } else {
return nil return nil
} }
} }
@ -660,7 +655,6 @@ open class Element : Node
return try Collector.collect(Evaluator.Class(className), self) return try Collector.collect(Evaluator.Class(className), self)
} }
/** /**
* Find elements that have a named attribute set. Case insensitive. * Find elements that have a named attribute set. Case insensitive.
* *
@ -760,11 +754,11 @@ open class Element : Node
* @return elements that have attributes matching this regular expression * @return elements that have attributes matching this regular expression
*/ */
public func getElementsByAttributeValueMatching(_ key: String, _ regex: String)throws->Elements { public func getElementsByAttributeValueMatching(_ key: String, _ regex: String)throws->Elements {
var pattern : Pattern var pattern: Pattern
do { do {
pattern = Pattern.compile(regex) pattern = Pattern.compile(regex)
try pattern.validate() try pattern.validate()
} catch{ } catch {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Pattern syntax error: \(regex)") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Pattern syntax error: \(regex)")
} }
return try getElementsByAttributeValueMatching(key, pattern) return try getElementsByAttributeValueMatching(key, pattern)
@ -840,13 +834,12 @@ open class Element : Node
do { do {
pattern = Pattern.compile(regex) pattern = Pattern.compile(regex)
try pattern.validate() try pattern.validate()
} catch{ } catch {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Pattern syntax error: \(regex)") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Pattern syntax error: \(regex)")
} }
return try getElementsMatchingText(pattern) return try getElementsMatchingText(pattern)
} }
/** /**
* Find elements whose own text matches the supplied regular expression. * Find elements whose own text matches the supplied regular expression.
* @param pattern regular expression to match text against * @param pattern regular expression to match text against
@ -868,7 +861,7 @@ open class Element : Node
do { do {
pattern = Pattern.compile(regex) pattern = Pattern.compile(regex)
try pattern.validate() try pattern.validate()
} catch{ } catch {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Pattern syntax error: \(regex)") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Pattern syntax error: \(regex)")
} }
return try getElementsMatchingOwnText(pattern) return try getElementsMatchingOwnText(pattern)
@ -883,7 +876,6 @@ open class Element : Node
return try Collector.collect(Evaluator.AllElements(), self) return try Collector.collect(Evaluator.AllElements(), self)
} }
/** /**
* Gets the combined text of this element and all its children. Whitespace is normalized and trimmed. * Gets the combined text of this element and all its children. Whitespace is normalized and trimmed.
* <p> * <p>
@ -893,20 +885,18 @@ open class Element : Node
* @see #ownText() * @see #ownText()
* @see #textNodes() * @see #textNodes()
*/ */
class textNodeVisitor: NodeVisitor class textNodeVisitor: NodeVisitor {
{
let accum: StringBuilder let accum: StringBuilder
init(_ accum: StringBuilder) { init(_ accum: StringBuilder) {
self.accum = accum self.accum = accum
} }
public func head(_ node: Node, _ depth: Int) { public func head(_ node: Node, _ depth: Int) {
if let textNode = (node as? TextNode) if let textNode = (node as? TextNode) {
{
Element.appendNormalisedText(accum, textNode) Element.appendNormalisedText(accum, textNode)
} else if let element = (node as? Element) { } else if let element = (node as? Element) {
if (accum.length > 0 && if (accum.length > 0 &&
(element.isBlock() || element._tag.getName() == "br") && (element.isBlock() || element._tag.getName() == "br") &&
!TextNode.lastCharIsWhitespace(accum)){ !TextNode.lastCharIsWhitespace(accum)) {
accum.append(" ") accum.append(" ")
} }
} }
@ -932,7 +922,7 @@ open class Element : Node
* @see #text() * @see #text()
* @see #textNodes() * @see #textNodes()
*/ */
public func ownText()->String { public func ownText() -> String {
let sb: StringBuilder = StringBuilder() let sb: StringBuilder = StringBuilder()
ownText(sb) ownText(sb)
return sb.toString().trim() return sb.toString().trim()
@ -951,20 +941,20 @@ open class Element : Node
private static func appendNormalisedText(_ accum: StringBuilder, _ textNode: TextNode) { private static func appendNormalisedText(_ accum: StringBuilder, _ textNode: TextNode) {
let text: String = textNode.getWholeText() let text: String = textNode.getWholeText()
if (Element.preserveWhitespace(textNode.parentNode)){ if (Element.preserveWhitespace(textNode.parentNode)) {
accum.append(text) accum.append(text)
}else{ } else {
StringUtil.appendNormalisedWhitespace(accum, string: text, stripLeading: TextNode.lastCharIsWhitespace(accum)) StringUtil.appendNormalisedWhitespace(accum, string: text, stripLeading: TextNode.lastCharIsWhitespace(accum))
} }
} }
private static func appendWhitespaceIfBr(_ element: Element, _ accum: StringBuilder) { private static func appendWhitespaceIfBr(_ element: Element, _ accum: StringBuilder) {
if (element._tag.getName() == "br" && !TextNode.lastCharIsWhitespace(accum)){ if (element._tag.getName() == "br" && !TextNode.lastCharIsWhitespace(accum)) {
accum.append(" ") accum.append(" ")
} }
} }
static func preserveWhitespace(_ node: Node?)->Bool { static func preserveWhitespace(_ node: Node?) -> Bool {
// looks only at this element and one level up, to prevent recursion & needless stack searches // looks only at this element and one level up, to prevent recursion & needless stack searches
if let element = (node as? Element) { if let element = (node as? Element) {
return element._tag.preserveWhitespace() || element.parent() != nil && element.parent()!._tag.preserveWhitespace() return element._tag.preserveWhitespace() || element.parent() != nil && element.parent()!._tag.preserveWhitespace()
@ -972,7 +962,6 @@ open class Element : Node
return false return false
} }
/** /**
* Set the text of this element. Any existing contents (text or elements) will be cleared * Set the text of this element. Any existing contents (text or elements) will be cleared
* @param text unencoded text * @param text unencoded text
@ -990,14 +979,14 @@ open class Element : Node
Test if this element has any text content (that is not just whitespace). Test if this element has any text content (that is not just whitespace).
@return true if element has non-blank text content. @return true if element has non-blank text content.
*/ */
public func hasText()->Bool { public func hasText() -> Bool {
for child:Node in childNodes { for child: Node in childNodes {
if let textNode = (child as? TextNode) { if let textNode = (child as? TextNode) {
if (!textNode.isBlank()){ if (!textNode.isBlank()) {
return true return true
} }
} else if let el = (child as? Element) { } else if let el = (child as? Element) {
if (el.hasText()){ if (el.hasText()) {
return true return true
} }
} }
@ -1011,7 +1000,7 @@ open class Element : Node
* *
* @see #dataNodes() * @see #dataNodes()
*/ */
public func data()->String { public func data() -> String {
let sb: StringBuilder = StringBuilder() let sb: StringBuilder = StringBuilder()
for childNode: Node in childNodes { for childNode: Node in childNodes {
@ -1042,7 +1031,7 @@ open class Element : Node
*/ */
public func classNames()throws->OrderedSet<String> { public func classNames()throws->OrderedSet<String> {
let fitted = try className().replaceAll(of: Element.classSplit, with: " ", options:.caseInsensitive) let fitted = try className().replaceAll(of: Element.classSplit, with: " ", options:.caseInsensitive)
let names:[String] = fitted.components(separatedBy: " ") let names: [String] = fitted.components(separatedBy: " ")
let classNames: OrderedSet<String> = OrderedSet(sequence:names) let classNames: OrderedSet<String> = OrderedSet(sequence:names)
classNames.remove("") // if classNames() was empty, would include an empty class classNames.remove("") // if classNames() was empty, would include an empty class
return classNames return classNames
@ -1065,7 +1054,7 @@ open class Element : Node
* @return true if it does, false if not * @return true if it does, false if not
*/ */
// performance sensitive // performance sensitive
public func hasClass(_ className: String)->Bool { public func hasClass(_ className: String) -> Bool {
let classAtt: String? = attributes?.get(key: "class") let classAtt: String? = attributes?.get(key: "class")
let len: Int = (classAtt != nil) ? classAtt!.characters.count : 0 let len: Int = (classAtt != nil) ? classAtt!.characters.count : 0
let wantLen: Int = className.characters.count let wantLen: Int = className.characters.count
@ -1143,8 +1132,8 @@ open class Element : Node
@discardableResult @discardableResult
public func toggleClass(_ className: String)throws->Element { public func toggleClass(_ className: String)throws->Element {
let classes: OrderedSet<String> = try classNames() let classes: OrderedSet<String> = try classNames()
if (classes.contains(className)){classes.remove(className) if (classes.contains(className)) {classes.remove(className)
}else{ } else {
classes.append(className) classes.append(className)
} }
try classNames(classes) try classNames(classes)
@ -1157,9 +1146,9 @@ open class Element : Node
* @return the value of the form element, or empty string if not set. * @return the value of the form element, or empty string if not set.
*/ */
public func val()throws->String { public func val()throws->String {
if (tagName()=="textarea"){ if (tagName()=="textarea") {
return try text() return try text()
}else{ } else {
return try attr("value") return try attr("value")
} }
} }
@ -1170,19 +1159,17 @@ open class Element : Node
* @return this element (for chaining) * @return this element (for chaining)
*/ */
@discardableResult @discardableResult
public func val(_ value:String)throws->Element { public func val(_ value: String)throws->Element {
if (tagName() == "textarea"){ if (tagName() == "textarea") {
try text(value) try text(value)
}else{ } else {
try attr("value", value) try attr("value", value)
} }
return self return self
} }
override func outerHtmlHead(_ accum:StringBuilder, _ depth: Int, _ out: OutputSettings)throws override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings)throws {
{ if (out.prettyPrint() && (_tag.formatAsBlock() || (parent() != nil && parent()!.tag().formatAsBlock()) || out.outline())) {
if (out.prettyPrint() && (_tag.formatAsBlock() || (parent() != nil && parent()!.tag().formatAsBlock()) || out.outline()))
{
if (accum.length > 0) { if (accum.length > 0) {
indent(accum, depth, out) indent(accum, depth, out)
} }
@ -1194,22 +1181,21 @@ open class Element : Node
// selfclosing includes unknown tags, isEmpty defines tags that are always empty // selfclosing includes unknown tags, isEmpty defines tags that are always empty
if (childNodes.isEmpty && _tag.isSelfClosing()) { if (childNodes.isEmpty && _tag.isSelfClosing()) {
if (out.syntax() == OutputSettings.Syntax.html && _tag.isEmpty()){ if (out.syntax() == OutputSettings.Syntax.html && _tag.isEmpty()) {
accum.append(">") accum.append(">")
}else{ } else {
accum.append(" />") // <img> in html, <img /> in xml accum.append(" />") // <img> in html, <img /> in xml
} }
} } else {
else{
accum.append(">") accum.append(">")
} }
} }
override func outerHtmlTail(_ accum:StringBuilder, _ depth: Int, _ out: OutputSettings){ override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
if (!(childNodes.isEmpty && _tag.isSelfClosing())) { if (!(childNodes.isEmpty && _tag.isSelfClosing())) {
if (out.prettyPrint() && (!childNodes.isEmpty && ( if (out.prettyPrint() && (!childNodes.isEmpty && (
_tag.formatAsBlock() || (out.outline() && (childNodes.count>1 || (childNodes.count==1 && !(((childNodes[0] as? TextNode) != nil))))) _tag.formatAsBlock() || (out.outline() && (childNodes.count>1 || (childNodes.count==1 && !(((childNodes[0] as? TextNode) != nil)))))
))){ ))) {
indent(accum, depth, out) indent(accum, depth, out)
} }
accum.append("</").append(tagName()).append(">") accum.append("</").append(tagName()).append(">")
@ -1230,7 +1216,7 @@ open class Element : Node
} }
private func html2(_ accum: StringBuilder)throws { private func html2(_ accum: StringBuilder)throws {
for node in childNodes{ for node in childNodes {
try node.outerHtml(accum) try node.outerHtml(accum)
} }
} }
@ -1239,7 +1225,7 @@ open class Element : Node
* {@inheritDoc} * {@inheritDoc}
*/ */
open override func html(_ appendable: StringBuilder)throws->StringBuilder { open override func html(_ appendable: StringBuilder)throws->StringBuilder {
for node in childNodes{ for node in childNodes {
try node.outerHtml(appendable) try node.outerHtml(appendable)
} }
return appendable return appendable
@ -1258,32 +1244,26 @@ open class Element : Node
return self return self
} }
open override func toString()throws->String { open override func toString()throws->String {
return try outerHtml() return try outerHtml()
} }
public override func copy(with zone: NSZone? = nil) -> Any public override func copy(with zone: NSZone? = nil) -> Any {
{ let clone = Element(_tag, baseUri!, attributes!)
let clone = Element(_tag,baseUri!,attributes!)
return copy(clone: clone) return copy(clone: clone)
} }
public override func copy(parent: Node?)->Node public override func copy(parent: Node?) -> Node {
{ let clone = Element(_tag, baseUri!, attributes!)
let clone = Element(_tag,baseUri!,attributes!) return copy(clone: clone, parent: parent)
return copy(clone: clone,parent: parent)
} }
public override func copy(clone: Node, parent: Node?)->Node public override func copy(clone: Node, parent: Node?) -> Node {
{ return super.copy(clone: clone, parent: parent)
return super.copy(clone: clone,parent: parent)
} }
override public var hashValue: Int override public var hashValue: Int {
{
var h = super.hashValue var h = super.hashValue
h = Int.addWithOverflow(Int.multiplyWithOverflow(31, h).0,_tag.hashValue).0 h = Int.addWithOverflow(Int.multiplyWithOverflow(31, h).0, _tag.hashValue).0
return h return h
} }

View File

@ -16,16 +16,15 @@ import Foundation
//open typealias Elements = Array<Element> //open typealias Elements = Array<Element>
//typealias E = Element //typealias E = Element
open class Elements: NSCopying open class Elements: NSCopying {
{ fileprivate var this: Array<Element> = Array<Element>()
fileprivate var this : Array<Element> = Array<Element>()
public init() { public init() {
} }
public init(_ a: Array<Element>){ public init(_ a: Array<Element>) {
this = a this = a
} }
public init(_ a: OrderedSet<Element>){ public init(_ a: OrderedSet<Element>) {
this.append(contentsOf: a) this.append(contentsOf: a)
} }
@ -33,13 +32,12 @@ open class Elements: NSCopying
* Creates a deep copy of these elements. * Creates a deep copy of these elements.
* @return a deep copy * @return a deep copy
*/ */
public func copy(with zone: NSZone? = nil) -> Any public func copy(with zone: NSZone? = nil) -> Any {
{
let clone: Elements = Elements() let clone: Elements = Elements()
for e: Element in this{ for e: Element in this {
clone.add(e.copy() as! Element) clone.add(e.copy() as! Element)
} }
return clone; return clone
} }
// attribute methods // attribute methods
@ -52,11 +50,11 @@ open class Elements: NSCopying
*/ */
open func attr(_ attributeKey: String)throws->String { open func attr(_ attributeKey: String)throws->String {
for element in this { for element in this {
if (element.hasAttr(attributeKey)){ if (element.hasAttr(attributeKey)) {
return try element.attr(attributeKey) return try element.attr(attributeKey)
} }
} }
return ""; return ""
} }
/** /**
@ -64,11 +62,11 @@ open class Elements: NSCopying
@param attributeKey attribute key @param attributeKey attribute key
@return true if any of the elements have the attribute; false if none do. @return true if any of the elements have the attribute; false if none do.
*/ */
open func hasAttr(_ attributeKey: String)->Bool { open func hasAttr(_ attributeKey: String) -> Bool {
for element in this { for element in this {
if element.hasAttr(attributeKey) {return true} if element.hasAttr(attributeKey) {return true}
} }
return false; return false
} }
/** /**
@ -80,9 +78,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func attr(_ attributeKey: String, _ attributeValue: String)throws->Elements { open func attr(_ attributeKey: String, _ attributeValue: String)throws->Elements {
for element in this { for element in this {
try element.attr(attributeKey, attributeValue); try element.attr(attributeKey, attributeValue)
} }
return self; return self
} }
/** /**
@ -93,9 +91,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func removeAttr(_ attributeKey: String)throws->Elements { open func removeAttr(_ attributeKey: String)throws->Elements {
for element in this { for element in this {
try element.removeAttr(attributeKey); try element.removeAttr(attributeKey)
} }
return self; return self
} }
/** /**
@ -106,9 +104,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func addClass(_ className: String)throws->Elements { open func addClass(_ className: String)throws->Elements {
for element in this { for element in this {
try element.addClass(className); try element.addClass(className)
} }
return self; return self
} }
/** /**
@ -119,9 +117,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func removeClass(_ className: String)throws->Elements { open func removeClass(_ className: String)throws->Elements {
for element: Element in this { for element: Element in this {
try element.removeClass(className); try element.removeClass(className)
} }
return self; return self
} }
/** /**
@ -132,9 +130,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func toggleClass(_ className: String)throws->Elements { open func toggleClass(_ className: String)throws->Elements {
for element: Element in this { for element: Element in this {
try element.toggleClass(className); try element.toggleClass(className)
} }
return self; return self
} }
/** /**
@ -143,13 +141,13 @@ open class Elements: NSCopying
@return true if any do, false if none do @return true if any do, false if none do
*/ */
open func hasClass(_ className: String)->Bool { open func hasClass(_ className: String) -> Bool {
for element: Element in this { for element: Element in this {
if (element.hasClass(className)){ if (element.hasClass(className)) {
return true; return true
} }
} }
return false; return false
} }
/** /**
@ -158,10 +156,10 @@ open class Elements: NSCopying
* @see Element#val() * @see Element#val()
*/ */
open func val()throws->String { open func val()throws->String {
if (size() > 0){ if (size() > 0) {
return try first()!.val(); return try first()!.val()
} }
return ""; return ""
} }
/** /**
@ -171,10 +169,10 @@ open class Elements: NSCopying
*/ */
@discardableResult @discardableResult
open func val(_ value: String)throws->Elements { open func val(_ value: String)throws->Elements {
for element: Element in this{ for element: Element in this {
try element.val(value); try element.val(value)
} }
return self; return self
} }
/** /**
@ -186,25 +184,23 @@ open class Elements: NSCopying
* @see Element#text() * @see Element#text()
*/ */
open func text()throws->String { open func text()throws->String {
let sb: StringBuilder = StringBuilder(); let sb: StringBuilder = StringBuilder()
for element: Element in this for element: Element in this {
{ if (sb.length != 0) {
if (sb.length != 0){
sb.append(" ") sb.append(" ")
} }
sb.append(try element.text()); sb.append(try element.text())
} }
return sb.toString(); return sb.toString()
} }
open func hasText()->Bool { open func hasText() -> Bool {
for element:Element in this for element: Element in this {
{ if (element.hasText()) {
if (element.hasText()){ return true
return true;
} }
} }
return false; return false
} }
/** /**
@ -214,14 +210,14 @@ open class Elements: NSCopying
* @see #outerHtml() * @see #outerHtml()
*/ */
open func html()throws->String { open func html()throws->String {
let sb: StringBuilder = StringBuilder(); let sb: StringBuilder = StringBuilder()
for element: Element in this { for element: Element in this {
if (sb.length != 0){ if (sb.length != 0) {
sb.append("\n") sb.append("\n")
} }
sb.append(try element.html()); sb.append(try element.html())
} }
return sb.toString(); return sb.toString()
} }
/** /**
@ -231,15 +227,14 @@ open class Elements: NSCopying
* @see #html() * @see #html()
*/ */
open func outerHtml()throws->String { open func outerHtml()throws->String {
let sb: StringBuilder = StringBuilder(); let sb: StringBuilder = StringBuilder()
for element in this for element in this {
{ if (sb.length != 0) {
if (sb.length != 0){
sb.append("\n") sb.append("\n")
} }
sb.append(try element.outerHtml()); sb.append(try element.outerHtml())
} }
return sb.toString(); return sb.toString()
} }
/** /**
@ -250,7 +245,7 @@ open class Elements: NSCopying
*/ */
open func toString()throws->String { open func toString()throws->String {
return try outerHtml(); return try outerHtml()
} }
/** /**
@ -263,9 +258,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func tagName(_ tagName: String)throws->Elements { open func tagName(_ tagName: String)throws->Elements {
for element: Element in this { for element: Element in this {
try element.tagName(tagName); try element.tagName(tagName)
} }
return self; return self
} }
/** /**
@ -277,9 +272,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func html(_ html: String)throws->Elements { open func html(_ html: String)throws->Elements {
for element: Element in this { for element: Element in this {
try element.html(html); try element.html(html)
} }
return self; return self
} }
/** /**
@ -291,9 +286,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func prepend(_ html: String)throws->Elements { open func prepend(_ html: String)throws->Elements {
for element: Element in this { for element: Element in this {
try element.prepend(html); try element.prepend(html)
} }
return self; return self
} }
/** /**
@ -305,9 +300,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func append(_ html: String)throws->Elements { open func append(_ html: String)throws->Elements {
for element: Element in this { for element: Element in this {
try element.append(html); try element.append(html)
} }
return self; return self
} }
/** /**
@ -319,9 +314,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func before(_ html: String)throws->Elements { open func before(_ html: String)throws->Elements {
for element: Element in this { for element: Element in this {
try element.before(html); try element.before(html)
} }
return self; return self
} }
/** /**
@ -333,9 +328,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func after(_ html: String)throws->Elements { open func after(_ html: String)throws->Elements {
for element: Element in this { for element: Element in this {
try element.after(html); try element.after(html)
} }
return self; return self
} }
/** /**
@ -349,11 +344,11 @@ open class Elements: NSCopying
*/ */
@discardableResult @discardableResult
open func wrap(_ html: String)throws->Elements { open func wrap(_ html: String)throws->Elements {
try Validate.notEmpty(string: html); try Validate.notEmpty(string: html)
for element: Element in this { for element: Element in this {
try element.wrap(html); try element.wrap(html)
} }
return self; return self
} }
/** /**
@ -373,9 +368,9 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func unwrap()throws->Elements { open func unwrap()throws->Elements {
for element: Element in this { for element: Element in this {
try element.unwrap(); try element.unwrap()
} }
return self; return self
} }
/** /**
@ -390,11 +385,11 @@ open class Elements: NSCopying
* @see #remove() * @see #remove()
*/ */
@discardableResult @discardableResult
open func empty()->Elements { open func empty() -> Elements {
for element: Element in this { for element: Element in this {
element.empty(); element.empty()
} }
return self; return self
} }
/** /**
@ -411,11 +406,10 @@ open class Elements: NSCopying
*/ */
@discardableResult @discardableResult
open func remove()throws->Elements { open func remove()throws->Elements {
for element in this for element in this {
{ try element.remove()
try element.remove();
} }
return self; return self
} }
// filters // filters
@ -426,7 +420,7 @@ open class Elements: NSCopying
* @return the filtered list of elements, or an empty list if none match. * @return the filtered list of elements, or an empty list if none match.
*/ */
open func select(_ query: String)throws->Elements { open func select(_ query: String)throws->Elements {
return try Selector.select(query, this); return try Selector.select(query, this)
} }
/** /**
@ -440,8 +434,8 @@ open class Elements: NSCopying
* @return a new elements list that contains only the filtered results * @return a new elements list that contains only the filtered results
*/ */
open func not(_ query: String)throws->Elements { open func not(_ query: String)throws->Elements {
let out: Elements = try Selector.select(query, this); let out: Elements = try Selector.select(query, this)
return Selector.filterOut(this, out.this); return Selector.filterOut(this, out.this)
} }
/** /**
@ -451,8 +445,8 @@ open class Elements: NSCopying
* @param index the (zero-based) index of the element in the list to retain * @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. * @return Elements containing only the specified element, or, if that element did not exist, an empty list.
*/ */
open func eq(_ index: Int)->Elements { open func eq(_ index: Int) -> Elements {
return size() > index ? Elements([get(index)]) : Elements(); return size() > index ? Elements([get(index)]) : Elements()
} }
/** /**
@ -461,8 +455,8 @@ open class Elements: NSCopying
* @return true if at least one element in the list matches the query. * @return true if at least one element in the list matches the query.
*/ */
open func `is`(_ query: String)throws->Bool { open func `is`(_ query: String)throws->Bool {
let children: Elements = try select(query); let children: Elements = try select(query)
return !children.isEmpty(); return !children.isEmpty()
} }
/** /**
@ -470,13 +464,12 @@ open class Elements: NSCopying
* @return 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 { open func parents() -> Elements {
let combo: OrderedSet<Element> = OrderedSet<Element>(); let combo: OrderedSet<Element> = OrderedSet<Element>()
for e:Element in this for e: Element in this {
{ combo.append(contentsOf: e.parents().array())
combo.append(contentsOf: e.parents().array());
} }
return Elements(combo); return Elements(combo)
} }
// list-like methods // list-like methods
@ -484,25 +477,24 @@ open class Elements: NSCopying
Get the first matched element. Get the first matched element.
@return The first matched element, or <code>null</code> if contents is empty. @return The first matched element, or <code>null</code> if contents is empty.
*/ */
open func first()->Element? { open func first() -> Element? {
return isEmpty() ? nil : get(0); return isEmpty() ? nil : get(0)
} }
open func isEmpty()->Bool{ open func isEmpty() -> Bool {
return array().count == 0 return array().count == 0
} }
open func size()->Int{ open func size() -> Int {
return array().count return array().count
} }
/** /**
Get the last matched element. Get the last matched element.
@return The last matched element, or <code>null</code> if contents is empty. @return The last matched element, or <code>null</code> if contents is empty.
*/ */
open func last()->Element? { open func last() -> Element? {
return isEmpty() ? nil : get(size() - 1); return isEmpty() ? nil : get(size() - 1)
} }
/** /**
@ -513,10 +505,10 @@ open class Elements: NSCopying
@discardableResult @discardableResult
open func traverse(_ nodeVisitor: NodeVisitor)throws->Elements { open func traverse(_ nodeVisitor: NodeVisitor)throws->Elements {
let traversor: NodeTraversor = NodeTraversor(nodeVisitor) let traversor: NodeTraversor = NodeTraversor(nodeVisitor)
for el:Element in this { for el: Element in this {
try traversor.traverse(el); try traversor.traverse(el)
} }
return self; return self
} }
/** /**
@ -525,15 +517,13 @@ open class Elements: NSCopying
* no forms. * no forms.
*/ */
open func forms()->Array<FormElement> { open func forms()->Array<FormElement> {
var forms: Array<FormElement> = Array<FormElement>(); var forms: Array<FormElement> = Array<FormElement>()
for el:Element in this for el: Element in this {
{ if let el = el as? FormElement {
if let el = el as? FormElement
{
forms.append(el) forms.append(el)
} }
} }
return forms; return forms
} }
/** /**
@ -550,18 +540,16 @@ open class Elements: NSCopying
this.insert(element, at: index) this.insert(element, at: index)
} }
open func get(_ i :Int)->Element{ open func get(_ i: Int) -> Element {
return this[i] return this[i]
} }
open func array()->Array<Element>{ open func array()->Array<Element> {
return this return this
} }
} }
extension Elements: Equatable {
extension Elements: Equatable
{
/// Returns a Boolean value indicating whether two values are equal. /// Returns a Boolean value indicating whether two values are equal.
/// ///
/// Equality is the inverse of inequality. For any values `a` and `b`, /// Equality is the inverse of inequality. For any values `a` and `b`,
@ -570,9 +558,7 @@ extension Elements: Equatable
/// - Parameters: /// - Parameters:
/// - lhs: A value to compare. /// - lhs: A value to compare.
/// - rhs: Another value to compare. /// - rhs: Another value to compare.
public static func ==(lhs: Elements, rhs: Elements) -> Bool public static func ==(lhs: Elements, rhs: Elements) -> Bool {
{
return lhs.this == rhs.this return lhs.this == rhs.this
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -22,43 +22,38 @@ public class Evaluator {
* @return Returns <tt>true</tt> if the requirements are met or * @return Returns <tt>true</tt> if the requirements are met or
* <tt>false</tt> otherwise * <tt>false</tt> otherwise
*/ */
open func matches(_ root: Element, _ element: Element)throws->Bool{ open func matches(_ root: Element, _ element: Element)throws->Bool {
preconditionFailure("self method must be overridden") preconditionFailure("self method must be overridden")
} }
open func toString()->String open func toString() -> String {
{
preconditionFailure("self method must be overridden") preconditionFailure("self method must be overridden")
} }
/** /**
* Evaluator for tag name * Evaluator for tag name
*/ */
public class Tag : Evaluator { public class Tag: Evaluator {
private let tagName : String private let tagName: String
public init(_ tagName: String) { public init(_ tagName: String) {
self.tagName = tagName self.tagName = tagName
} }
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
return (element.tagName().equalsIgnoreCase(string: tagName)) return (element.tagName().equalsIgnoreCase(string: tagName))
} }
open override func toString() -> String {
open override func toString()->String {
return String(tagName) return String(tagName)
} }
} }
/** /**
* Evaluator for tag name that ends with * Evaluator for tag name that ends with
*/ */
public final class TagEndsWith : Evaluator { public final class TagEndsWith: Evaluator {
private let tagName : String private let tagName: String
public init(_ tagName: String) { public init(_ tagName: String) {
self.tagName = tagName self.tagName = tagName
@ -68,7 +63,7 @@ public class Evaluator {
return (element.tagName().hasSuffix(tagName)) return (element.tagName().hasSuffix(tagName))
} }
open override func toString()->String { open override func toString() -> String {
return String(tagName) return String(tagName)
} }
} }
@ -76,8 +71,8 @@ public class Evaluator {
/** /**
* Evaluator for element id * Evaluator for element id
*/ */
public final class Id : Evaluator { public final class Id: Evaluator {
private let id : String private let id: String
public init(_ id: String) { public init(_ id: String) {
self.id = id self.id = id
@ -87,7 +82,7 @@ public class Evaluator {
return (id == element.id()) return (id == element.id())
} }
open override func toString()->String { open override func toString() -> String {
return "#\(id)" return "#\(id)"
} }
@ -96,18 +91,18 @@ public class Evaluator {
/** /**
* Evaluator for element class * Evaluator for element class
*/ */
public final class Class : Evaluator { public final class Class: Evaluator {
private let className : String private let className: String
public init(_ className: String) { public init(_ className: String) {
self.className = className self.className = className
} }
open override func matches(_ root: Element, _ element: Element)->Bool { open override func matches(_ root: Element, _ element: Element) -> Bool {
return (element.hasClass(className)) return (element.hasClass(className))
} }
open override func toString()->String { open override func toString() -> String {
return ".\(className)" return ".\(className)"
} }
@ -116,8 +111,8 @@ public class Evaluator {
/** /**
* Evaluator for attribute name matching * Evaluator for attribute name matching
*/ */
public final class Attribute : Evaluator { public final class Attribute: Evaluator {
private let key : String private let key: String
public init(_ key: String) { public init(_ key: String) {
self.key = key self.key = key
@ -127,7 +122,7 @@ public class Evaluator {
return element.hasAttr(key) return element.hasAttr(key)
} }
open override func toString()->String { open override func toString() -> String {
return "[\(key)]" return "[\(key)]"
} }
@ -136,8 +131,8 @@ public class Evaluator {
/** /**
* Evaluator for attribute name prefix matching * Evaluator for attribute name prefix matching
*/ */
public final class AttributeStarting : Evaluator { public final class AttributeStarting: Evaluator {
private let keyPrefix : String private let keyPrefix: String
public init(_ keyPrefix: String)throws { public init(_ keyPrefix: String)throws {
try Validate.notEmpty(string: keyPrefix) try Validate.notEmpty(string: keyPrefix)
@ -145,9 +140,9 @@ public class Evaluator {
} }
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
if let values = element.getAttributes(){ if let values = element.getAttributes() {
for attribute in values.iterator() { for attribute in values.iterator() {
if (attribute.getKey().lowercased().hasPrefix(keyPrefix)){ if (attribute.getKey().lowercased().hasPrefix(keyPrefix)) {
return true return true
} }
} }
@ -155,7 +150,7 @@ public class Evaluator {
return false return false
} }
open override func toString()->String { open override func toString() -> String {
return "[^\(keyPrefix)]" return "[^\(keyPrefix)]"
} }
@ -164,22 +159,20 @@ public class Evaluator {
/** /**
* Evaluator for attribute name/value matching * Evaluator for attribute name/value matching
*/ */
public final class AttributeWithValue : AttributeKeyPair { public final class AttributeWithValue: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws { public override init(_ key: String, _ value: String)throws {
try super.init(key, value) try super.init(key, value)
} }
open override func matches(_ root: Element, _ element: Element)throws->Bool {
open override func matches(_ root: Element, _ element: Element)throws->Bool if(element.hasAttr(key)) {
{
if(element.hasAttr(key)){
let s = try element.attr(key) let s = try element.attr(key)
return value.equalsIgnoreCase(string: s.trim()) return value.equalsIgnoreCase(string: s.trim())
} }
return false return false
} }
open override func toString()->String { open override func toString() -> String {
return "[\(key)=\(value)]" return "[\(key)=\(value)]"
} }
@ -188,7 +181,7 @@ public class Evaluator {
/** /**
* Evaluator for attribute name != value matching * Evaluator for attribute name != value matching
*/ */
public final class AttributeWithValueNot : AttributeKeyPair { public final class AttributeWithValueNot: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws { public override init(_ key: String, _ value: String)throws {
try super.init(key, value) try super.init(key, value)
} }
@ -198,7 +191,7 @@ public class Evaluator {
return !value.equalsIgnoreCase(string: s) return !value.equalsIgnoreCase(string: s)
} }
open override func toString()->String { open override func toString() -> String {
return "[\(key)!=\(value)]" return "[\(key)!=\(value)]"
} }
@ -207,19 +200,19 @@ public class Evaluator {
/** /**
* Evaluator for attribute name/value matching (value prefix) * Evaluator for attribute name/value matching (value prefix)
*/ */
public final class AttributeWithValueStarting : AttributeKeyPair { public final class AttributeWithValueStarting: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws { public override init(_ key: String, _ value: String)throws {
try super.init(key, value) try super.init(key, value)
} }
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
if(element.hasAttr(key)){ if(element.hasAttr(key)) {
return try element.attr(key).lowercased().hasPrefix(value) // value is lower case already return try element.attr(key).lowercased().hasPrefix(value) // value is lower case already
} }
return false return false
} }
open override func toString()->String { open override func toString() -> String {
return "[\(key)^=\(value)]" return "[\(key)^=\(value)]"
} }
@ -228,19 +221,19 @@ public class Evaluator {
/** /**
* Evaluator for attribute name/value matching (value ending) * Evaluator for attribute name/value matching (value ending)
*/ */
public final class AttributeWithValueEnding : AttributeKeyPair { public final class AttributeWithValueEnding: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws { public override init(_ key: String, _ value: String)throws {
try super.init(key, value) try super.init(key, value)
} }
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
if(element.hasAttr(key)){ if(element.hasAttr(key)) {
return try element.attr(key).lowercased().hasSuffix(value) // value is lower case return try element.attr(key).lowercased().hasSuffix(value) // value is lower case
} }
return false return false
} }
open override func toString()->String { open override func toString() -> String {
return "[\(key)$=\(value)]" return "[\(key)$=\(value)]"
} }
@ -249,19 +242,19 @@ public class Evaluator {
/** /**
* Evaluator for attribute name/value matching (value containing) * Evaluator for attribute name/value matching (value containing)
*/ */
public final class AttributeWithValueContaining : AttributeKeyPair { public final class AttributeWithValueContaining: AttributeKeyPair {
public override init(_ key: String, _ value: String)throws { public override init(_ key: String, _ value: String)throws {
try super.init(key, value) try super.init(key, value)
} }
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
if(element.hasAttr(key)){ if(element.hasAttr(key)) {
return try element.attr(key).lowercased().contains(value) // value is lower case return try element.attr(key).lowercased().contains(value) // value is lower case
} }
return false return false
} }
open override func toString()->String { open override func toString() -> String {
return "[\(key)*=\(value)]" return "[\(key)*=\(value)]"
} }
@ -270,9 +263,9 @@ public class Evaluator {
/** /**
* Evaluator for attribute name/value matching (value regex matching) * Evaluator for attribute name/value matching (value regex matching)
*/ */
public final class AttributeWithValueMatching : Evaluator { public final class AttributeWithValueMatching: Evaluator {
let key : String let key: String
let pattern : Pattern let pattern: Pattern
public init(_ key: String, _ pattern: Pattern) { public init(_ key: String, _ pattern: Pattern) {
self.key = key.trim().lowercased() self.key = key.trim().lowercased()
@ -281,14 +274,14 @@ public class Evaluator {
} }
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
if(element.hasAttr(key)){ if(element.hasAttr(key)) {
let s = try element.attr(key) let s = try element.attr(key)
return pattern.matcher(in:s).find() return pattern.matcher(in:s).find()
} }
return false return false
} }
open override func toString()->String { open override func toString() -> String {
return "[\(key)~=\(pattern.toString())]" return "[\(key)~=\(pattern.toString())]"
} }
@ -297,9 +290,9 @@ public class Evaluator {
/** /**
* Abstract evaluator for attribute name/value matching * Abstract evaluator for attribute name/value matching
*/ */
public class AttributeKeyPair : Evaluator { public class AttributeKeyPair: Evaluator {
let key : String let key: String
var value : String var value: String
public init(_ key: String, _ value2: String)throws { public init(_ key: String, _ value2: String)throws {
var value2 = value2 var value2 = value2
@ -307,14 +300,13 @@ public class Evaluator {
try Validate.notEmpty(string: value2) try Validate.notEmpty(string: value2)
self.key = key.trim().lowercased() self.key = key.trim().lowercased()
if (value2.startsWith("\"") && value2.hasSuffix("\"") || value2.startsWith("'") && value2.hasSuffix("'")) if (value2.startsWith("\"") && value2.hasSuffix("\"") || value2.startsWith("'") && value2.hasSuffix("'")) {
{
value2 = value2.substring(1, value2.characters.count-2) value2 = value2.substring(1, value2.characters.count-2)
} }
self.value = value2.trim().lowercased() self.value = value2.trim().lowercased()
} }
open override func matches(_ root: Element, _ element: Element)throws->Bool{ open override func matches(_ root: Element, _ element: Element)throws->Bool {
preconditionFailure("self method must be overridden") preconditionFailure("self method must be overridden")
} }
} }
@ -322,13 +314,13 @@ public class Evaluator {
/** /**
* Evaluator for any / all element matching * Evaluator for any / all element matching
*/ */
public final class AllElements : Evaluator { public final class AllElements: Evaluator {
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
return true return true
} }
open override func toString()->String { open override func toString() -> String {
return "*" return "*"
} }
} }
@ -336,7 +328,7 @@ public class Evaluator {
/** /**
* Evaluator for matching by sibling index number (e {@literal <} idx) * Evaluator for matching by sibling index number (e {@literal <} idx)
*/ */
public final class IndexLessThan : IndexEvaluator { public final class IndexLessThan: IndexEvaluator {
public override init(_ index: Int) { public override init(_ index: Int) {
super.init(index) super.init(index)
} }
@ -345,7 +337,7 @@ public class Evaluator {
return try element.elementSiblingIndex() < index return try element.elementSiblingIndex() < index
} }
open override func toString()->String { open override func toString() -> String {
return ":lt(\(index))" return ":lt(\(index))"
} }
@ -354,7 +346,7 @@ public class Evaluator {
/** /**
* Evaluator for matching by sibling index number (e {@literal >} idx) * Evaluator for matching by sibling index number (e {@literal >} idx)
*/ */
public final class IndexGreaterThan : IndexEvaluator { public final class IndexGreaterThan: IndexEvaluator {
public override init(_ index: Int) { public override init(_ index: Int) {
super.init(index) super.init(index)
} }
@ -363,7 +355,7 @@ public class Evaluator {
return try element.elementSiblingIndex() > index return try element.elementSiblingIndex() > index
} }
open override func toString()->String { open override func toString() -> String {
return ":gt(\(index))" return ":gt(\(index))"
} }
@ -372,7 +364,7 @@ public class Evaluator {
/** /**
* Evaluator for matching by sibling index number (e = idx) * Evaluator for matching by sibling index number (e = idx)
*/ */
public final class IndexEquals : IndexEvaluator { public final class IndexEquals: IndexEvaluator {
public override init(_ index: Int) { public override init(_ index: Int) {
super.init(index) super.init(index)
} }
@ -381,7 +373,7 @@ public class Evaluator {
return try element.elementSiblingIndex() == index return try element.elementSiblingIndex() == index
} }
open override func toString()->String { open override func toString() -> String {
return ":eq(\(index))" return ":eq(\(index))"
} }
@ -390,43 +382,42 @@ public class Evaluator {
/** /**
* Evaluator for matching the last sibling (css :last-child) * Evaluator for matching the last sibling (css :last-child)
*/ */
public final class IsLastChild : Evaluator { public final class IsLastChild: Evaluator {
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
if let p = element.parent(){ if let p = element.parent() {
let i = try element.elementSiblingIndex() let i = try element.elementSiblingIndex()
return !((p as? Document) != nil) && i == (p.getChildNodes().count - 1) return !((p as? Document) != nil) && i == (p.getChildNodes().count - 1)
} }
return false return false
} }
open override func toString()->String { open override func toString() -> String {
return ":last-child" return ":last-child"
} }
} }
public final class IsFirstOfType : IsNthOfType { public final class IsFirstOfType: IsNthOfType {
public init() { public init() {
super.init(0,1) super.init(0, 1)
} }
open override func toString()->String { open override func toString() -> String {
return ":first-of-type" return ":first-of-type"
} }
} }
public final class IsLastOfType : IsNthLastOfType { public final class IsLastOfType: IsNthLastOfType {
public init() { public init() {
super.init(0,1) super.init(0, 1)
} }
open override func toString()->String { open override func toString() -> String {
return ":last-of-type" return ":last-of-type"
} }
} }
public class CssNthEvaluator: Evaluator {
public class CssNthEvaluator : Evaluator { public let a: Int
public let a : Int public let b: Int
public let b : Int
public init(_ a: Int, _ b: Int) { public init(_ a: Int, _ b: Int) {
self.a = a self.a = a
@ -447,42 +438,40 @@ public class Evaluator {
return (pos-b)*a >= 0 && (pos-b)%a==0 return (pos-b)*a >= 0 && (pos-b)%a==0
} }
open override func toString()->String { open override func toString() -> String {
if (a == 0){ if (a == 0) {
return ":\(getPseudoClass)(\(b))" return ":\(getPseudoClass)(\(b))"
} }
if (b == 0){ if (b == 0) {
return ":\(getPseudoClass)(\(a))" return ":\(getPseudoClass)(\(a))"
} }
return ":\(getPseudoClass)(\(a)\(b))" return ":\(getPseudoClass)(\(a)\(b))"
} }
open func getPseudoClass()->String{ open func getPseudoClass() -> String {
preconditionFailure("self method must be overridden") preconditionFailure("self method must be overridden")
} }
open func calculatePosition(_ root: Element, _ element: Element)throws->Int{ open func calculatePosition(_ root: Element, _ element: Element)throws->Int {
preconditionFailure("self method must be overridden") preconditionFailure("self method must be overridden")
} }
} }
/** /**
* css-compatible Evaluator for :eq (css :nth-child) * css-compatible Evaluator for :eq (css :nth-child)
* *
* @see IndexEquals * @see IndexEquals
*/ */
public final class IsNthChild : CssNthEvaluator { public final class IsNthChild: CssNthEvaluator {
public override init(_ a: Int, _ b: Int) { public override init(_ a: Int, _ b: Int) {
super.init(a,b) super.init(a, b)
} }
open override func calculatePosition(_ root: Element, _ element: Element)throws->Int { open override func calculatePosition(_ root: Element, _ element: Element)throws->Int {
return try element.elementSiblingIndex()+1 return try element.elementSiblingIndex()+1
} }
open override func getPseudoClass() -> String {
open override func getPseudoClass()->String {
return "nth-child" return "nth-child"
} }
} }
@ -492,22 +481,21 @@ public class Evaluator {
* *
* @see IndexEquals * @see IndexEquals
*/ */
public final class IsNthLastChild : CssNthEvaluator { public final class IsNthLastChild: CssNthEvaluator {
public override init(_ a: Int, _ b: Int) { public override init(_ a: Int, _ b: Int) {
super.init(a,b) super.init(a, b)
} }
open override func calculatePosition(_ root: Element, _ element: Element)throws->Int open override func calculatePosition(_ root: Element, _ element: Element)throws->Int {
{
var i = 0 var i = 0
if let l = element.parent(){ if let l = element.parent() {
i = l.children().array().count i = l.children().array().count
} }
return i - (try element.elementSiblingIndex()) return i - (try element.elementSiblingIndex())
} }
open override func getPseudoClass()->String { open override func getPseudoClass() -> String {
return "nth-last-child" return "nth-last-child"
} }
} }
@ -516,32 +504,30 @@ public class Evaluator {
* css pseudo class nth-of-type * css pseudo class nth-of-type
* *
*/ */
public class IsNthOfType : CssNthEvaluator { public class IsNthOfType: CssNthEvaluator {
public override init(_ a: Int, _ b: Int) { public override init(_ a: Int, _ b: Int) {
super.init(a,b) super.init(a, b)
} }
open override func calculatePosition(_ root: Element, _ element: Element)->Int { open override func calculatePosition(_ root: Element, _ element: Element) -> Int {
var pos = 0 var pos = 0
let family: Elements? = element.parent()?.children() let family: Elements? = element.parent()?.children()
if let array = family?.array(){ if let array = family?.array() {
for el in array for el in array {
{
if (el.tag() == element.tag()) {pos+=1} if (el.tag() == element.tag()) {pos+=1}
if (el === element) {break} if (el === element) {break}
} }
} }
return pos return pos
} }
open override func getPseudoClass()->String { open override func getPseudoClass() -> String {
return "nth-of-type" return "nth-of-type"
} }
} }
public class IsNthLastOfType : CssNthEvaluator { public class IsNthLastOfType: CssNthEvaluator {
public override init(_ a: Int, _ b: Int) { public override init(_ a: Int, _ b: Int) {
super.init(a, b) super.init(a, b)
@ -549,10 +535,10 @@ public class Evaluator {
open override func calculatePosition(_ root: Element, _ element: Element)throws->Int { open override func calculatePosition(_ root: Element, _ element: Element)throws->Int {
var pos = 0 var pos = 0
if let family = element.parent()?.children(){ if let family = element.parent()?.children() {
let x = try element.elementSiblingIndex() let x = try element.elementSiblingIndex()
for i in x..<family.array().count { for i in x..<family.array().count {
if (family.get(i).tag() == element.tag()){ if (family.get(i).tag() == element.tag()) {
pos+=1 pos+=1
} }
} }
@ -561,7 +547,7 @@ public class Evaluator {
return pos return pos
} }
open override func getPseudoClass()->String { open override func getPseudoClass() -> String {
return "nth-last-of-type" return "nth-last-of-type"
} }
} }
@ -569,16 +555,16 @@ public class Evaluator {
/** /**
* Evaluator for matching the first sibling (css :first-child) * Evaluator for matching the first sibling (css :first-child)
*/ */
public final class IsFirstChild : Evaluator { public final class IsFirstChild: Evaluator {
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
let p = element.parent() let p = element.parent()
if(p != nil && !(((p as? Document) != nil))){ if(p != nil && !(((p as? Document) != nil))) {
return (try element.elementSiblingIndex()) == 0 return (try element.elementSiblingIndex()) == 0
} }
return false return false
} }
open override func toString()->String { open override func toString() -> String {
return ":first-child" return ":first-child"
} }
} }
@ -588,54 +574,53 @@ public class Evaluator {
* @see <a href="http://www.w3.org/TR/selectors/#root-pseudo">:root selector</a> * @see <a href="http://www.w3.org/TR/selectors/#root-pseudo">:root selector</a>
* *
*/ */
public final class IsRoot : Evaluator { public final class IsRoot: Evaluator {
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
let r: Element = ((root as? Document) != nil) ? root.child(0) : root let r: Element = ((root as? Document) != nil) ? root.child(0) : root
return element === r return element === r
} }
open override func toString()->String { open override func toString() -> String {
return ":root" return ":root"
} }
} }
public final class IsOnlyChild : Evaluator { public final class IsOnlyChild: Evaluator {
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
let p = element.parent() let p = element.parent()
return p != nil && !((p as? Document) != nil) && element.siblingElements().array().count == 0 return p != nil && !((p as? Document) != nil) && element.siblingElements().array().count == 0
} }
open override func toString()->String { open override func toString() -> String {
return ":only-child" return ":only-child"
} }
} }
public final class IsOnlyOfType : Evaluator { public final class IsOnlyOfType: Evaluator {
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
let p = element.parent() let p = element.parent()
if (p == nil || (p as? Document) != nil) {return false} if (p == nil || (p as? Document) != nil) {return false}
var pos = 0 var pos = 0
if let family = p?.children().array(){ if let family = p?.children().array() {
for el in family { for el in family {
if (el.tag() == element.tag()) {pos+=1} if (el.tag() == element.tag()) {pos+=1}
} }
} }
return pos == 1 return pos == 1
} }
open override func toString()->String { open override func toString() -> String {
return ":only-of-type" return ":only-of-type"
} }
} }
public final class IsEmpty : Evaluator { public final class IsEmpty: Evaluator {
open override func matches(_ root: Element, _ element: Element)throws->Bool { open override func matches(_ root: Element, _ element: Element)throws->Bool {
let family: Array<Node> = element.getChildNodes() let family: Array<Node> = element.getChildNodes()
for n in family for n in family {
{
if (!((n as? Comment) != nil || (n as? XmlDeclaration) != nil || (n as? DocumentType) != nil)) {return false} if (!((n as? Comment) != nil || (n as? XmlDeclaration) != nil || (n as? DocumentType) != nil)) {return false}
} }
return true return true
} }
open override func toString()->String { open override func toString() -> String {
return ":empty" return ":empty"
} }
} }
@ -645,7 +630,7 @@ public class Evaluator {
* *
* @author ant * @author ant
*/ */
public class IndexEvaluator : Evaluator { public class IndexEvaluator: Evaluator {
let index: Int let index: Int
public init(_ index: Int) { public init(_ index: Int) {
@ -656,7 +641,7 @@ public class Evaluator {
/** /**
* Evaluator for matching Element (and its descendants) text * Evaluator for matching Element (and its descendants) text
*/ */
public final class ContainsText : Evaluator { public final class ContainsText: Evaluator {
private let searchText: String private let searchText: String
public init(_ searchText: String) { public init(_ searchText: String) {
@ -667,7 +652,7 @@ public class Evaluator {
return (try element.text().lowercased().contains(searchText)) return (try element.text().lowercased().contains(searchText))
} }
open override func toString()->String { open override func toString() -> String {
return ":contains(\(searchText)" return ":contains(\(searchText)"
} }
} }
@ -675,8 +660,8 @@ public class Evaluator {
/** /**
* Evaluator for matching Element's own text * Evaluator for matching Element's own text
*/ */
public final class ContainsOwnText : Evaluator { public final class ContainsOwnText: Evaluator {
private let searchText : String private let searchText: String
public init(_ searchText: String) { public init(_ searchText: String) {
self.searchText = searchText.lowercased() self.searchText = searchText.lowercased()
@ -686,7 +671,7 @@ public class Evaluator {
return (element.ownText().lowercased().contains(searchText)) return (element.ownText().lowercased().contains(searchText))
} }
open override func toString()->String { open override func toString() -> String {
return ":containsOwn(\(searchText)" return ":containsOwn(\(searchText)"
} }
} }
@ -694,7 +679,7 @@ public class Evaluator {
/** /**
* Evaluator for matching Element (and its descendants) text with regex * Evaluator for matching Element (and its descendants) text with regex
*/ */
public final class Matches : Evaluator { public final class Matches: Evaluator {
private let pattern: Pattern private let pattern: Pattern
public init(_ pattern: Pattern) { public init(_ pattern: Pattern) {
@ -706,7 +691,7 @@ public class Evaluator {
return m.find() return m.find()
} }
open override func toString()->String { open override func toString() -> String {
return ":matches(\(pattern)" return ":matches(\(pattern)"
} }
} }
@ -714,7 +699,7 @@ public class Evaluator {
/** /**
* Evaluator for matching Element's own text with regex * Evaluator for matching Element's own text with regex
*/ */
public final class MatchesOwn : Evaluator { public final class MatchesOwn: Evaluator {
private let pattern: Pattern private let pattern: Pattern
public init(_ pattern: Pattern) { public init(_ pattern: Pattern) {
@ -726,7 +711,7 @@ public class Evaluator {
return m.find() return m.find()
} }
open override func toString()->String { open override func toString() -> String {
return ":matchesOwn(\(pattern.toString())" return ":matchesOwn(\(pattern.toString())"
} }
} }

View File

@ -17,8 +17,6 @@ public enum ExceptionType {
case SelectorParseException case SelectorParseException
} }
public enum Exception : Error { public enum Exception: Error {
case Error(type:ExceptionType ,Message: String) case Error(type:ExceptionType, Message: String)
} }

View File

@ -12,7 +12,7 @@ import Foundation
* A HTML Form Element provides ready access to the form fields/controls that are associated with it. It also allows a * A HTML Form Element provides ready access to the form fields/controls that are associated with it. It also allows a
* form to easily be submitted. * form to easily be submitted.
*/ */
public class FormElement : Element { public class FormElement: Element {
private let _elements: Elements = Elements() private let _elements: Elements = Elements()
/** /**
@ -30,7 +30,7 @@ public class FormElement : Element {
* Get the list of form control elements associated with this form. * Get the list of form control elements associated with this form.
* @return form controls associated with this element. * @return form controls associated with this element.
*/ */
public func elements()->Elements { public func elements() -> Elements {
return _elements return _elements
} }
@ -40,7 +40,7 @@ public class FormElement : Element {
* @return this form element, for chaining * @return this form element, for chaining
*/ */
@discardableResult @discardableResult
public func addElement(_ element: Element)->FormElement { public func addElement(_ element: Element) -> FormElement {
_elements.add(element) _elements.add(element)
return self return self
} }
@ -106,23 +106,20 @@ public class FormElement : Element {
// return data; // return data;
// } // }
public override func copy(with zone: NSZone? = nil) -> Any public override func copy(with zone: NSZone? = nil) -> Any {
{ let clone = FormElement(_tag, baseUri!, attributes!)
let clone = FormElement(_tag,baseUri!,attributes!)
return copy(clone: clone) return copy(clone: clone)
} }
public override func copy(parent: Node?)->Node public override func copy(parent: Node?) -> Node {
{ let clone = FormElement(_tag, baseUri!, attributes!)
let clone = FormElement(_tag,baseUri!,attributes!) return copy(clone: clone, parent: parent)
return copy(clone: clone,parent: parent)
} }
public override func copy(clone: Node, parent: Node?)->Node public override func copy(clone: Node, parent: Node?) -> Node {
{
let clone = clone as! FormElement let clone = clone as! FormElement
for att in _elements.array(){ for att in _elements.array() {
clone._elements.add(att) clone._elements.add(att)
} }
return super.copy(clone: clone,parent: parent) return super.copy(clone: clone, parent: parent)
} }
} }

View File

@ -43,13 +43,11 @@ class HtmlTreeBuilder: TreeBuilder {
private var fosterInserts: Bool = false // if next inserts should be fostered private var fosterInserts: Bool = false // if next inserts should be fostered
private var fragmentParsing: Bool = false // if parsing a fragment of html private var fragmentParsing: Bool = false // if parsing a fragment of html
public override init() { public override init() {
super.init() super.init()
} }
public override func defaultSettings() -> ParseSettings {
public override func defaultSettings()->ParseSettings {
return ParseSettings.htmlDefault return ParseSettings.htmlDefault
} }
@ -74,17 +72,17 @@ class HtmlTreeBuilder: TreeBuilder {
// initialise the tokeniser state: // initialise the tokeniser state:
let contextTag: String = context.tagName() let contextTag: String = context.tagName()
if (StringUtil.inString(contextTag, haystack: "title", "textarea")){ if (StringUtil.inString(contextTag, haystack: "title", "textarea")) {
tokeniser.transition(TokeniserState.Rcdata) tokeniser.transition(TokeniserState.Rcdata)
}else if (StringUtil.inString(contextTag, haystack: "iframe", "noembed", "noframes", "style", "xmp")){ } else if (StringUtil.inString(contextTag, haystack: "iframe", "noembed", "noframes", "style", "xmp")) {
tokeniser.transition(TokeniserState.Rawtext) tokeniser.transition(TokeniserState.Rawtext)
}else if (contextTag=="script"){ } else if (contextTag=="script") {
tokeniser.transition(TokeniserState.ScriptData) tokeniser.transition(TokeniserState.ScriptData)
}else if (contextTag==("noscript")){ } else if (contextTag==("noscript")) {
tokeniser.transition(TokeniserState.Data) // if scripting enabled, rawtext tokeniser.transition(TokeniserState.Data) // if scripting enabled, rawtext
}else if (contextTag=="plaintext"){ } else if (contextTag=="plaintext") {
tokeniser.transition(TokeniserState.Data) tokeniser.transition(TokeniserState.Data)
}else{ } else {
tokeniser.transition(TokeniserState.Data) // default tokeniser.transition(TokeniserState.Data) // default
} }
@ -98,7 +96,7 @@ class HtmlTreeBuilder: TreeBuilder {
// with form correctly // with form correctly
let contextChain: Elements = context.parents() let contextChain: Elements = context.parents()
contextChain.add(0, context) contextChain.add(0, context)
for parent:Element in contextChain.array() { for parent: Element in contextChain.array() {
if let x = (parent as? FormElement) { if let x = (parent as? FormElement) {
formElement = x formElement = x
break break
@ -107,15 +105,15 @@ class HtmlTreeBuilder: TreeBuilder {
} }
try runParser() try runParser()
if (context != nil && root != nil){ if (context != nil && root != nil) {
return root!.getChildNodes() return root!.getChildNodes()
}else{ } else {
return doc.getChildNodes() return doc.getChildNodes()
} }
} }
@discardableResult @discardableResult
public override func process(_ token: Token)throws->Bool{ public override func process(_ token: Token)throws->Bool {
currentToken = token currentToken = token
return try self._state.process(token, self) return try self._state.process(token, self)
} }
@ -130,7 +128,7 @@ class HtmlTreeBuilder: TreeBuilder {
self._state = state self._state = state
} }
func state()->HtmlTreeBuilderState { func state() -> HtmlTreeBuilderState {
return _state return _state
} }
@ -138,7 +136,7 @@ class HtmlTreeBuilder: TreeBuilder {
_originalState = _state _originalState = _state
} }
func originalState()->HtmlTreeBuilderState { func originalState() -> HtmlTreeBuilderState {
return _originalState return _originalState
} }
@ -146,20 +144,20 @@ class HtmlTreeBuilder: TreeBuilder {
self._framesetOk = framesetOk self._framesetOk = framesetOk
} }
func framesetOk()->Bool { func framesetOk() -> Bool {
return _framesetOk return _framesetOk
} }
func getDocument()->Document { func getDocument() -> Document {
return doc return doc
} }
func getBaseUri()->String { func getBaseUri() -> String {
return baseUri return baseUri
} }
func maybeSetBaseUri(_ base: Element)throws { func maybeSetBaseUri(_ base: Element)throws {
if (baseUriSetFromDoc){ // only listen to the first <base href> in parse if (baseUriSetFromDoc) { // only listen to the first <base href> in parse
return return
} }
@ -171,12 +169,12 @@ class HtmlTreeBuilder: TreeBuilder {
} }
} }
func isFragmentParsing()->Bool { func isFragmentParsing() -> Bool {
return fragmentParsing return fragmentParsing
} }
func error(_ state: HtmlTreeBuilderState) { func error(_ state: HtmlTreeBuilderState) {
if (errors.canAddError() && currentToken != nil){ if (errors.canAddError() && currentToken != nil) {
errors.add(ParseError(reader.getPos(), "Unexpected token [\(currentToken!.tokenType())] when in state [\(state.rawValue)]")) errors.add(ParseError(reader.getPos(), "Unexpected token [\(currentToken!.tokenType())] when in state [\(state.rawValue)]"))
} }
} }
@ -235,7 +233,7 @@ class HtmlTreeBuilder: TreeBuilder {
let el: FormElement = FormElement(tag, baseUri, startTag._attributes) let el: FormElement = FormElement(tag, baseUri, startTag._attributes)
setFormElement(el) setFormElement(el)
try insertNode(el) try insertNode(el)
if (onStack){ if (onStack) {
stack.append(el) stack.append(el)
} }
return el return el
@ -250,10 +248,10 @@ class HtmlTreeBuilder: TreeBuilder {
var node: Node var node: Node
// characters in script and style go in as datanodes, not text nodes // characters in script and style go in as datanodes, not text nodes
let tagName: String? = currentElement()?.tagName() let tagName: String? = currentElement()?.tagName()
if (tagName=="script" || tagName=="style"){ if (tagName=="script" || tagName=="style") {
try Validate.notNull(obj: characterToken.getData()) try Validate.notNull(obj: characterToken.getData())
node = DataNode(characterToken.getData()!, baseUri) node = DataNode(characterToken.getData()!, baseUri)
}else{ } else {
try Validate.notNull(obj: characterToken.getData()) try Validate.notNull(obj: characterToken.getData())
node = TextNode(characterToken.getData()!, baseUri) node = TextNode(characterToken.getData()!, baseUri)
} }
@ -262,18 +260,18 @@ class HtmlTreeBuilder: TreeBuilder {
private func insertNode(_ node: Node)throws { private func insertNode(_ node: Node)throws {
// if the stack hasn't been set up yet, elements (doctype, comments) go into the doc // if the stack hasn't been set up yet, elements (doctype, comments) go into the doc
if (stack.count == 0){ if (stack.count == 0) {
try doc.appendChild(node) try doc.appendChild(node)
}else if (isFosterInserts()){ } else if (isFosterInserts()) {
try insertInFosterParent(node) try insertInFosterParent(node)
}else{ } else {
try currentElement()?.appendChild(node) try currentElement()?.appendChild(node)
} }
// connect form controls to their form element // connect form controls to their form element
if let n = (node as? Element) { if let n = (node as? Element) {
if(n.tag().isFormListed()){ if(n.tag().isFormListed()) {
if ( formElement != nil){ if ( formElement != nil) {
formElement!.addElement(n) formElement!.addElement(n)
} }
} }
@ -281,7 +279,7 @@ class HtmlTreeBuilder: TreeBuilder {
} }
@discardableResult @discardableResult
func pop()->Element { func pop() -> Element {
let size: Int = stack.count let size: Int = stack.count
return stack.remove(at: size-1) return stack.remove(at: size-1)
} }
@ -295,11 +293,11 @@ class HtmlTreeBuilder: TreeBuilder {
} }
@discardableResult @discardableResult
func onStack(_ el: Element)->Bool { func onStack(_ el: Element) -> Bool {
return isElementInQueue(stack, el) return isElementInQueue(stack, el)
} }
private func isElementInQueue(_ queue: Array<Element?>, _ element: Element?)->Bool { private func isElementInQueue(_ queue: Array<Element?>, _ element: Element?) -> Bool {
for pos in (0..<queue.count).reversed() { for pos in (0..<queue.count).reversed() {
let next: Element? = queue[pos] let next: Element? = queue[pos]
if (next == element) { if (next == element) {
@ -309,7 +307,7 @@ class HtmlTreeBuilder: TreeBuilder {
return false return false
} }
func getFromStack(_ elName: String)->Element? { func getFromStack(_ elName: String) -> Element? {
for pos in (0..<stack.count).reversed() { for pos in (0..<stack.count).reversed() {
let next: Element = stack[pos] let next: Element = stack[pos]
if next.nodeName() == elName { if next.nodeName() == elName {
@ -320,7 +318,7 @@ class HtmlTreeBuilder: TreeBuilder {
} }
@discardableResult @discardableResult
func removeFromStack(_ el: Element)->Bool { func removeFromStack(_ el: Element) -> Bool {
for pos in (0..<stack.count).reversed() { for pos in (0..<stack.count).reversed() {
let next: Element = stack[pos] let next: Element = stack[pos]
if (next == el) { if (next == el) {
@ -335,7 +333,7 @@ class HtmlTreeBuilder: TreeBuilder {
for pos in (0..<stack.count).reversed() { for pos in (0..<stack.count).reversed() {
let next: Element = stack[pos] let next: Element = stack[pos]
stack.remove(at: pos) stack.remove(at: pos)
if (next.nodeName() == elName){ if (next.nodeName() == elName) {
break break
} }
} }
@ -348,7 +346,7 @@ class HtmlTreeBuilder: TreeBuilder {
for pos in (0..<stack.count).reversed() { for pos in (0..<stack.count).reversed() {
let next: Element = stack[pos] let next: Element = stack[pos]
stack.remove(at: pos) stack.remove(at: pos)
if (StringUtil.inString(next.nodeName(),elNames)){ if (StringUtil.inString(next.nodeName(), elNames)) {
break break
} }
} }
@ -383,15 +381,15 @@ class HtmlTreeBuilder: TreeBuilder {
private func clearStackToContext(_ nodeNames: [String]) { private func clearStackToContext(_ nodeNames: [String]) {
for pos in (0..<stack.count).reversed() { for pos in (0..<stack.count).reversed() {
let next: Element = stack[pos] let next: Element = stack[pos]
if (StringUtil.inString(next.nodeName(), nodeNames) || next.nodeName()=="html"){ if (StringUtil.inString(next.nodeName(), nodeNames) || next.nodeName()=="html") {
break break
}else{ } else {
stack.remove(at: pos) stack.remove(at: pos)
} }
} }
} }
func aboveOnStack(_ el: Element)->Element? { func aboveOnStack(_ el: Element) -> Element? {
//assert(onStack(el), "Invalid parameter") //assert(onStack(el), "Invalid parameter")
onStack(el) onStack(el)
for pos in (0..<stack.count).reversed() { for pos in (0..<stack.count).reversed() {
@ -479,17 +477,17 @@ class HtmlTreeBuilder: TreeBuilder {
return try inSpecificScope(specificScopeTarget, baseTypes, extraTypes) return try inSpecificScope(specificScopeTarget, baseTypes, extraTypes)
} }
private func inSpecificScope(_ targetNames: [String?], _ baseTypes: [String] , _ extraTypes: [String]?)throws->Bool { private func inSpecificScope(_ targetNames: [String?], _ baseTypes: [String], _ extraTypes: [String]?)throws->Bool {
for pos in (0..<stack.count).reversed() { for pos in (0..<stack.count).reversed() {
let el: Element = stack[pos] let el: Element = stack[pos]
let elName: String = el.nodeName() let elName: String = el.nodeName()
if (StringUtil.inString(elName, targetNames)){ if (StringUtil.inString(elName, targetNames)) {
return true return true
} }
if (StringUtil.inString(elName, baseTypes)){ if (StringUtil.inString(elName, baseTypes)) {
return false return false
} }
if (extraTypes != nil && StringUtil.inString(elName, extraTypes!)){ if (extraTypes != nil && StringUtil.inString(elName, extraTypes!)) {
return false return false
} }
} }
@ -527,10 +525,10 @@ class HtmlTreeBuilder: TreeBuilder {
for pos in (0..<stack.count).reversed() { for pos in (0..<stack.count).reversed() {
let el: Element = stack[pos] let el: Element = stack[pos]
let elName: String = el.nodeName() let elName: String = el.nodeName()
if (elName.equals(targetName)){ if (elName.equals(targetName)) {
return true return true
} }
if (!StringUtil.inString(elName, HtmlTreeBuilder.TagSearchSelectScope)){ // all elements except if (!StringUtil.inString(elName, HtmlTreeBuilder.TagSearchSelectScope)) { // all elements except
return false return false
} }
} }
@ -542,11 +540,11 @@ class HtmlTreeBuilder: TreeBuilder {
self.headElement = headElement self.headElement = headElement
} }
func getHeadElement()->Element? { func getHeadElement() -> Element? {
return headElement return headElement
} }
func isFosterInserts()->Bool { func isFosterInserts() -> Bool {
return fosterInserts return fosterInserts
} }
@ -554,7 +552,7 @@ class HtmlTreeBuilder: TreeBuilder {
self.fosterInserts = fosterInserts self.fosterInserts = fosterInserts
} }
func getFormElement()->FormElement? { func getFormElement() -> FormElement? {
return formElement return formElement
} }
@ -587,7 +585,7 @@ class HtmlTreeBuilder: TreeBuilder {
func generateImpliedEndTags(_ excludeTag: String?) { func generateImpliedEndTags(_ excludeTag: String?) {
while ((excludeTag != nil && !currentElement()!.nodeName().equals(excludeTag!)) && while ((excludeTag != nil && !currentElement()!.nodeName().equals(excludeTag!)) &&
StringUtil.inString(currentElement()!.nodeName(), HtmlTreeBuilder.TagSearchEndTags)){ StringUtil.inString(currentElement()!.nodeName(), HtmlTreeBuilder.TagSearchEndTags)) {
pop() pop()
} }
} }
@ -596,22 +594,22 @@ class HtmlTreeBuilder: TreeBuilder {
generateImpliedEndTags(nil) generateImpliedEndTags(nil)
} }
func isSpecial(_ el: Element)->Bool { func isSpecial(_ el: Element) -> Bool {
// todo: mathml's mi, mo, mn // todo: mathml's mi, mo, mn
// todo: svg's foreigObject, desc, title // todo: svg's foreigObject, desc, title
let name: String = el.nodeName() let name: String = el.nodeName()
return StringUtil.inString(name, HtmlTreeBuilder.TagSearchSpecial) return StringUtil.inString(name, HtmlTreeBuilder.TagSearchSpecial)
} }
func lastFormattingElement()->Element? { func lastFormattingElement() -> Element? {
return formattingElements.count > 0 ? formattingElements[formattingElements.count-1] : nil return formattingElements.count > 0 ? formattingElements[formattingElements.count-1] : nil
} }
func removeLastFormattingElement()->Element? { func removeLastFormattingElement() -> Element? {
let size: Int = formattingElements.count let size: Int = formattingElements.count
if (size > 0){ if (size > 0) {
return formattingElements.remove(at: size-1) return formattingElements.remove(at: size-1)
}else{ } else {
return nil return nil
} }
} }
@ -621,11 +619,11 @@ class HtmlTreeBuilder: TreeBuilder {
var numSeen: Int = 0 var numSeen: Int = 0
for pos in (0..<formattingElements.count).reversed() { for pos in (0..<formattingElements.count).reversed() {
let el: Element? = formattingElements[pos] let el: Element? = formattingElements[pos]
if (el == nil){ // marker if (el == nil) { // marker
break break
} }
if (isSameFormattingElement(input, el!)){ if (isSameFormattingElement(input, el!)) {
numSeen += 1 numSeen += 1
} }
@ -637,9 +635,9 @@ class HtmlTreeBuilder: TreeBuilder {
formattingElements.append(input) formattingElements.append(input)
} }
private func isSameFormattingElement(_ a: Element, _ b: Element)->Bool { private func isSameFormattingElement(_ a: Element, _ b: Element) -> Bool {
// same if: same namespace, tag, and attributes. Element.equals only checks tag, might in future check children // same if: same namespace, tag, and attributes. Element.equals only checks tag, might in future check children
if(a.attributes == nil){ if(a.attributes == nil) {
return false return false
} }
@ -651,14 +649,13 @@ class HtmlTreeBuilder: TreeBuilder {
func reconstructFormattingElements()throws { func reconstructFormattingElements()throws {
let last: Element? = lastFormattingElement() let last: Element? = lastFormattingElement()
if (last == nil || onStack(last!)){ if (last == nil || onStack(last!)) {
return return
} }
var entry: Element? = last
var entry:Element? = last let size: Int = formattingElements.count
let size:Int = formattingElements.count var pos: Int = size - 1
var pos:Int = size - 1
var skip: Bool = false var skip: Bool = false
while (true) { while (true) {
if (pos == 0) { // step 4. if none before, skip to 8 if (pos == 0) { // step 4. if none before, skip to 8
@ -696,7 +693,7 @@ class HtmlTreeBuilder: TreeBuilder {
func clearFormattingElementsToLastMarker() { func clearFormattingElementsToLastMarker() {
while (!formattingElements.isEmpty) { while (!formattingElements.isEmpty) {
let el: Element? = removeLastFormattingElement() let el: Element? = removeLastFormattingElement()
if (el == nil){ if (el == nil) {
break break
} }
} }
@ -712,27 +709,22 @@ class HtmlTreeBuilder: TreeBuilder {
} }
} }
func isInActiveFormattingElements(_ el: Element)->Bool { func isInActiveFormattingElements(_ el: Element) -> Bool {
return isElementInQueue(formattingElements, el) return isElementInQueue(formattingElements, el)
} }
func getActiveFormattingElement(_ nodeName: String) -> Element? {
func getActiveFormattingElement(_ nodeName: String)->Element? {
for pos in (0..<formattingElements.count).reversed() { for pos in (0..<formattingElements.count).reversed() {
let next: Element? = formattingElements[pos] let next: Element? = formattingElements[pos]
if (next == nil){ // scope marker if (next == nil) { // scope marker
break break
}else if (next!.nodeName().equals(nodeName)){ } else if (next!.nodeName().equals(nodeName)) {
return next return next
} }
} }
return nil return nil
} }
func replaceActiveFormattingElement(_ out: Element, _ input: Element)throws { func replaceActiveFormattingElement(_ out: Element, _ input: Element)throws {
try formattingElements = replaceInQueue(formattingElements as! Array<Element>, out, input)//todo: testare as! non è bello try formattingElements = replaceInQueue(formattingElements as! Array<Element>, out, input)//todo: testare as! non è bello
} }
@ -749,7 +741,7 @@ class HtmlTreeBuilder: TreeBuilder {
if (lastTable.parent() != nil) { if (lastTable.parent() != nil) {
fosterParent = lastTable.parent()! fosterParent = lastTable.parent()!
isLastTableParent = true isLastTableParent = true
} else{ } else {
fosterParent = aboveOnStack(lastTable) fosterParent = aboveOnStack(lastTable)
} }
} else { // no table == frag } else { // no table == frag
@ -759,8 +751,7 @@ class HtmlTreeBuilder: TreeBuilder {
if (isLastTableParent) { if (isLastTableParent) {
try Validate.notNull(obj: lastTable) // last table cannot be null by this point. try Validate.notNull(obj: lastTable) // last table cannot be null by this point.
try lastTable!.before(input) try lastTable!.before(input)
} } else {
else{
try fosterParent?.appendChild(input) try fosterParent?.appendChild(input)
} }
} }

View File

@ -12,8 +12,7 @@ protocol HtmlTreeBuilderStateProtocol {
func process(_ t: Token, _ tb: HtmlTreeBuilder)throws->Bool func process(_ t: Token, _ tb: HtmlTreeBuilder)throws->Bool
} }
enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol {
{
case Initial case Initial
case BeforeHtml case BeforeHtml
case BeforeHead case BeforeHead
@ -40,13 +39,11 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
private static let nullString: String = "\u{0000}" private static let nullString: String = "\u{0000}"
public func equals(_ s: HtmlTreeBuilderState)->Bool public func equals(_ s: HtmlTreeBuilderState) -> Bool {
{
return self.hashValue == s.hashValue return self.hashValue == s.hashValue
} }
func process(_ t: Token, _ tb: HtmlTreeBuilder)throws->Bool func process(_ t: Token, _ tb: HtmlTreeBuilder)throws->Bool {
{
switch self { switch self {
case .Initial: case .Initial:
if (HtmlTreeBuilderState.isWhitespace(t)) { if (HtmlTreeBuilderState.isWhitespace(t)) {
@ -60,7 +57,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
let doctype: DocumentType = DocumentType( let doctype: DocumentType = DocumentType(
tb.settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier(), tb.getBaseUri()) tb.settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier(), tb.getBaseUri())
try tb.getDocument().appendChild(doctype) try tb.getDocument().appendChild(doctype)
if (d.isForceQuirks()){ if (d.isForceQuirks()) {
tb.getDocument().quirksMode(Document.QuirksMode.quirks) tb.getDocument().quirksMode(Document.QuirksMode.quirks)
} }
tb.transition(.BeforeHtml) tb.transition(.BeforeHtml)
@ -147,7 +144,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} else if (StringUtil.inString(name, haystack: "base", "basefont", "bgsound", "command", "link")) { } else if (StringUtil.inString(name, haystack: "base", "basefont", "bgsound", "command", "link")) {
let el: Element = try tb.insertEmpty(start) let el: Element = try tb.insertEmpty(start)
// jsoup special: update base the frist time it is seen // jsoup special: update base the frist time it is seen
if (name.equals("base") && el.hasAttr("href")){ if (name.equals("base") && el.hasAttr("href")) {
try tb.maybeSetBaseUri(el) try tb.maybeSetBaseUri(el)
} }
} else if (name.equals("meta")) { } else if (name.equals("meta")) {
@ -267,15 +264,14 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} }
return true return true
case .InBody: case .InBody:
func anyOtherEndTag(_ t: Token, _ tb: HtmlTreeBuilder)->Bool func anyOtherEndTag(_ t: Token, _ tb: HtmlTreeBuilder) -> Bool {
{
let name: String? = t.asEndTag().normalName() let name: String? = t.asEndTag().normalName()
let stack: Array<Element> = tb.getStack() let stack: Array<Element> = tb.getStack()
for pos in (0..<stack.count).reversed(){ for pos in (0..<stack.count).reversed() {
let node: Element = stack[pos] let node: Element = stack[pos]
if (name != nil && node.nodeName().equals(name!)) { if (name != nil && node.nodeName().equals(name!)) {
tb.generateImpliedEndTags(name) tb.generateImpliedEndTags(name)
if (!name!.equals((tb.currentElement()?.nodeName())!)){ if (!name!.equals((tb.currentElement()?.nodeName())!)) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose(name!) tb.popStackToClose(name!)
@ -290,8 +286,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return true return true
} }
switch (t.type) switch (t.type) {
{
case Token.TokenType.Char: case Token.TokenType.Char:
let c: Token.Char = t.asCharacter() let c: Token.Char = t.asCharacter()
if (c.getData() != nil && c.getData()!.equals(HtmlTreeBuilderState.nullString)) { if (c.getData() != nil && c.getData()!.equals(HtmlTreeBuilderState.nullString)) {
@ -315,8 +310,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
case Token.TokenType.StartTag: case Token.TokenType.StartTag:
let startTag: Token.StartTag = t.asStartTag() let startTag: Token.StartTag = t.asStartTag()
if let name: String = startTag.normalName() if let name: String = startTag.normalName() {
{
if (name.equals("a")) { if (name.equals("a")) {
if (tb.getActiveFormattingElement("a") != nil) { if (tb.getActiveFormattingElement("a") != nil) {
tb.error(self) tb.error(self)
@ -354,7 +348,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
try tb.processEndTag("li") try tb.processEndTag("li")
break break
} }
if (tb.isSpecial(el) && !StringUtil.inSorted(el.nodeName(), haystack: Constants.InBodyStartLiBreakers)){ if (tb.isSpecial(el) && !StringUtil.inSorted(el.nodeName(), haystack: Constants.InBodyStartLiBreakers)) {
break break
} }
} }
@ -367,7 +361,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
// merge attributes onto real html // merge attributes onto real html
let html: Element = tb.getStack()[0] let html: Element = tb.getStack()[0]
for attribute in startTag.getAttributes().iterator() { for attribute in startTag.getAttributes().iterator() {
if (!html.hasAttr(attribute.getKey())){ if (!html.hasAttr(attribute.getKey())) {
html.getAttributes()?.put(attribute: attribute) html.getAttributes()?.put(attribute: attribute)
} }
} }
@ -375,7 +369,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return try tb.process(t, .InHead) return try tb.process(t, .InHead)
} else if (name.equals("body")) { } else if (name.equals("body")) {
tb.error(self) tb.error(self)
let stack : Array<Element> = tb.getStack() let stack: Array<Element> = tb.getStack()
if (stack.count == 1 || (stack.count > 2 && !stack[1].nodeName().equals("body"))) { if (stack.count == 1 || (stack.count > 2 && !stack[1].nodeName().equals("body"))) {
// only in fragment case // only in fragment case
return false // ignore return false // ignore
@ -383,7 +377,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
tb.framesetOk(false) tb.framesetOk(false)
let body: Element = stack[1] let body: Element = stack[1]
for attribute: Attribute in startTag.getAttributes().iterator() { for attribute: Attribute in startTag.getAttributes().iterator() {
if (!body.hasAttr(attribute.getKey())){ if (!body.hasAttr(attribute.getKey())) {
body.getAttributes()?.put(attribute: attribute) body.getAttributes()?.put(attribute: attribute)
} }
} }
@ -398,11 +392,11 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false // ignore frameset return false // ignore frameset
} else { } else {
let second: Element = stack[1] let second: Element = stack[1]
if (second.parent() != nil){ if (second.parent() != nil) {
try second.remove() try second.remove()
} }
// pop up to html element // pop up to html element
while (stack.count > 1){ while (stack.count > 1) {
stack.remove(at: stack.count-1) stack.remove(at: stack.count-1)
} }
try tb.insert(startTag) try tb.insert(startTag)
@ -435,14 +429,14 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
try tb.insertForm(startTag, true) try tb.insertForm(startTag, true)
} else if (StringUtil.inSorted(name, haystack: Constants.DdDt)) { } else if (StringUtil.inSorted(name, haystack: Constants.DdDt)) {
tb.framesetOk(false) tb.framesetOk(false)
let stack:Array<Element> = tb.getStack() let stack: Array<Element> = tb.getStack()
for i in (1..<stack.count).reversed() { for i in (1..<stack.count).reversed() {
let el: Element = stack[i] let el: Element = stack[i]
if (StringUtil.inSorted(el.nodeName(), haystack: Constants.DdDt)) { if (StringUtil.inSorted(el.nodeName(), haystack: Constants.DdDt)) {
try tb.processEndTag(el.nodeName()) try tb.processEndTag(el.nodeName())
break break
} }
if (tb.isSpecial(el) && !StringUtil.inSorted(el.nodeName(), haystack: Constants.InBodyStartLiBreakers)){ if (tb.isSpecial(el) && !StringUtil.inSorted(el.nodeName(), haystack: Constants.InBodyStartLiBreakers)) {
break break
} }
} }
@ -495,7 +489,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} else if (name.equals("input")) { } else if (name.equals("input")) {
try tb.reconstructFormattingElements() try tb.reconstructFormattingElements()
let el: Element = try tb.insertEmpty(startTag) let el: Element = try tb.insertEmpty(startTag)
if (try !el.attr("type").equalsIgnoreCase(string: "hidden")){ if (try !el.attr("type").equalsIgnoreCase(string: "hidden")) {
tb.framesetOk(false) tb.framesetOk(false)
} }
} else if (StringUtil.inSorted(name, haystack: Constants.InBodyStartMedia)) { } else if (StringUtil.inSorted(name, haystack: Constants.InBodyStartMedia)) {
@ -507,22 +501,22 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
try tb.insertEmpty(startTag) try tb.insertEmpty(startTag)
tb.framesetOk(false) tb.framesetOk(false)
} else if (name.equals("image")) { } else if (name.equals("image")) {
if (tb.getFromStack("svg") == nil){ if (tb.getFromStack("svg") == nil) {
return try tb.process(startTag.name("img")) // change <image> to <img>, unless in svg return try tb.process(startTag.name("img")) // change <image> to <img>, unless in svg
}else{ } else {
try tb.insert(startTag) try tb.insert(startTag)
} }
} else if (name.equals("isindex")) { } else if (name.equals("isindex")) {
// how much do we care about the early 90s? // how much do we care about the early 90s?
tb.error(self) tb.error(self)
if (tb.getFormElement() != nil){ if (tb.getFormElement() != nil) {
return false return false
} }
tb.tokeniser.acknowledgeSelfClosingFlag() tb.tokeniser.acknowledgeSelfClosingFlag()
try tb.processStartTag("form") try tb.processStartTag("form")
if (startTag._attributes.hasKey(key: "action")) { if (startTag._attributes.hasKey(key: "action")) {
if let form: Element = tb.getFormElement(){ if let form: Element = tb.getFormElement() {
try form.attr("action", startTag._attributes.get(key: "action")) try form.attr("action", startTag._attributes.get(key: "action"))
} }
} }
@ -538,7 +532,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
// input // input
let inputAttribs: Attributes = Attributes() let inputAttribs: Attributes = Attributes()
for attr: Attribute in startTag._attributes.iterator() { for attr: Attribute in startTag._attributes.iterator() {
if (!StringUtil.inSorted(attr.getKey(), haystack: Constants.InBodyStartInputAttribs)){ if (!StringUtil.inSorted(attr.getKey(), haystack: Constants.InBodyStartInputAttribs)) {
inputAttribs.put(attribute: attr) inputAttribs.put(attribute: attr)
} }
} }
@ -573,13 +567,13 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
tb.framesetOk(false) tb.framesetOk(false)
let state: HtmlTreeBuilderState = tb.state() let state: HtmlTreeBuilderState = tb.state()
if (state.equals(.InTable) || state.equals(.InCaption) || state.equals(.InTableBody) || state.equals(.InRow) || state.equals(.InCell)){ if (state.equals(.InTable) || state.equals(.InCaption) || state.equals(.InTableBody) || state.equals(.InRow) || state.equals(.InCell)) {
tb.transition(.InSelectInTable) tb.transition(.InSelectInTable)
}else{ } else {
tb.transition(.InSelect) tb.transition(.InSelect)
} }
} else if (StringUtil.inSorted(name, haystack: Constants.InBodyStartOptions)) { } else if (StringUtil.inSorted(name, haystack: Constants.InBodyStartOptions)) {
if (tb.currentElement() != nil && tb.currentElement()!.nodeName().equals("option")){ if (tb.currentElement() != nil && tb.currentElement()!.nodeName().equals("option")) {
try tb.processEndTag("option") try tb.processEndTag("option")
} }
try tb.reconstructFormattingElements() try tb.reconstructFormattingElements()
@ -610,7 +604,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
try tb.reconstructFormattingElements() try tb.reconstructFormattingElements()
try tb.insert(startTag) try tb.insert(startTag)
} }
}else{ } else {
try tb.reconstructFormattingElements() try tb.reconstructFormattingElements()
try tb.insert(startTag) try tb.insert(startTag)
} }
@ -618,22 +612,21 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
case .EndTag: case .EndTag:
let endTag: Token.EndTag = t.asEndTag() let endTag: Token.EndTag = t.asEndTag()
if let name = endTag.normalName(){ if let name = endTag.normalName() {
if (StringUtil.inSorted(name, haystack: Constants.InBodyEndAdoptionFormatters)) { if (StringUtil.inSorted(name, haystack: Constants.InBodyEndAdoptionFormatters)) {
// Adoption Agency Algorithm. // Adoption Agency Algorithm.
for i in 0..<8 for i in 0..<8 {
{
let formatEl: Element? = tb.getActiveFormattingElement(name) let formatEl: Element? = tb.getActiveFormattingElement(name)
if (formatEl == nil){ if (formatEl == nil) {
return anyOtherEndTag(t, tb) return anyOtherEndTag(t, tb)
}else if (!tb.onStack(formatEl!)) { } else if (!tb.onStack(formatEl!)) {
tb.error(self) tb.error(self)
tb.removeFromActiveFormattingElements(formatEl!) tb.removeFromActiveFormattingElements(formatEl!)
return true return true
} else if (try !tb.inScope(formatEl!.nodeName())) { } else if (try !tb.inScope(formatEl!.nodeName())) {
tb.error(self) tb.error(self)
return false return false
} else if (tb.currentElement() != formatEl!){ } else if (tb.currentElement() != formatEl!) {
tb.error(self) tb.error(self)
} }
@ -644,9 +637,8 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
// the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) self prevents // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) self prevents
// run-aways // run-aways
var stackSize = stack.count var stackSize = stack.count
if(stackSize > 64){stackSize = 64} if(stackSize > 64) {stackSize = 64}
for si in 0..<stackSize for si in 0..<stackSize {
{
let el: Element = stack[si] let el: Element = stack[si]
if (el == formatEl) { if (el == formatEl) {
commonAncestor = stack[si - 1] commonAncestor = stack[si - 1]
@ -666,17 +658,15 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
// does that mean: int pos of format el in list? // does that mean: int pos of format el in list?
var node: Element? = furthestBlock var node: Element? = furthestBlock
var lastNode: Element? = furthestBlock var lastNode: Element? = furthestBlock
for j in 0..<3 for j in 0..<3 {
{ if (node != nil && tb.onStack(node!)) {
if (node != nil && tb.onStack(node!)){
node = tb.aboveOnStack(node!) node = tb.aboveOnStack(node!)
} }
// note no bookmark check // note no bookmark check
if (node != nil && !tb.isInActiveFormattingElements(node!)) if (node != nil && !tb.isInActiveFormattingElements(node!)) {
{
tb.removeFromStack(node!) tb.removeFromStack(node!)
continue continue
} else if (node == formatEl){ } else if (node == formatEl) {
break break
} }
@ -690,7 +680,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
// todo: move the aforementioned bookmark to be immediately after the node in the list of active formatting elements. // todo: move the aforementioned bookmark to be immediately after the node in the list of active formatting elements.
// not getting how self bookmark both straddles the element above, but is inbetween here... // not getting how self bookmark both straddles the element above, but is inbetween here...
} }
if (lastNode!.parent() != nil){ if (lastNode!.parent() != nil) {
try lastNode?.remove() try lastNode?.remove()
} }
try node!.appendChild(lastNode!) try node!.appendChild(lastNode!)
@ -699,12 +689,12 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} }
if (StringUtil.inSorted(commonAncestor!.nodeName(), haystack: Constants.InBodyEndTableFosters)) { if (StringUtil.inSorted(commonAncestor!.nodeName(), haystack: Constants.InBodyEndTableFosters)) {
if (lastNode!.parent() != nil){ if (lastNode!.parent() != nil) {
try lastNode!.remove() try lastNode!.remove()
} }
try tb.insertInFosterParent(lastNode!) try tb.insertInFosterParent(lastNode!)
} else { } else {
if (lastNode!.parent() != nil){ if (lastNode!.parent() != nil) {
try lastNode!.remove() try lastNode!.remove()
} }
try commonAncestor!.appendChild(lastNode!) try commonAncestor!.appendChild(lastNode!)
@ -713,8 +703,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
let adopter: Element = Element(formatEl!.tag(), tb.getBaseUri()) let adopter: Element = Element(formatEl!.tag(), tb.getBaseUri())
adopter.getAttributes()?.addAll(incoming: formatEl!.getAttributes()) adopter.getAttributes()?.addAll(incoming: formatEl!.getAttributes())
var childNodes: [Node] = furthestBlock!.getChildNodes() var childNodes: [Node] = furthestBlock!.getChildNodes()
for childNode: Node in childNodes for childNode: Node in childNodes {
{
try adopter.appendChild(childNode) // append will reparent. thus the clone to avoid concurrent mod. try adopter.appendChild(childNode) // append will reparent. thus the clone to avoid concurrent mod.
} }
try furthestBlock?.appendChild(adopter) try furthestBlock?.appendChild(adopter)
@ -730,7 +719,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} else { } else {
tb.generateImpliedEndTags() tb.generateImpliedEndTags()
if (!tb.currentElement()!.nodeName().equals(name)){ if (!tb.currentElement()!.nodeName().equals(name)) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose(name) tb.popStackToClose(name)
@ -744,7 +733,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} else { } else {
tb.generateImpliedEndTags(name) tb.generateImpliedEndTags(name)
if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)){ if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose(name) tb.popStackToClose(name)
@ -759,7 +748,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} }
} else if (name.equals("html")) { } else if (name.equals("html")) {
let notIgnored: Bool = try tb.processEndTag("body") let notIgnored: Bool = try tb.processEndTag("body")
if (notIgnored){ if (notIgnored) {
return try tb.process(endTag) return try tb.process(endTag)
} }
} else if (name.equals("form")) { } else if (name.equals("form")) {
@ -770,7 +759,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} else { } else {
tb.generateImpliedEndTags() tb.generateImpliedEndTags()
if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)){ if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)) {
tb.error(self) tb.error(self)
} }
// remove currentForm from stack. will shift anything under up. // remove currentForm from stack. will shift anything under up.
@ -783,7 +772,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return try tb.process(endTag) return try tb.process(endTag)
} else { } else {
tb.generateImpliedEndTags(name) tb.generateImpliedEndTags(name)
if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)){ if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose(name) tb.popStackToClose(name)
@ -794,7 +783,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} else { } else {
tb.generateImpliedEndTags(name) tb.generateImpliedEndTags(name)
if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)){ if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose(name) tb.popStackToClose(name)
@ -805,7 +794,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} else { } else {
tb.generateImpliedEndTags(name) tb.generateImpliedEndTags(name)
if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)){ if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose(Constants.Headings) tb.popStackToClose(Constants.Headings)
@ -820,7 +809,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} }
tb.generateImpliedEndTags() tb.generateImpliedEndTags()
if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)){ if (tb.currentElement() != nil && !tb.currentElement()!.nodeName().equals(name)) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose(name) tb.popStackToClose(name)
@ -833,7 +822,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} else { } else {
return anyOtherEndTag(t, tb) return anyOtherEndTag(t, tb)
} }
}else{ } else {
return anyOtherEndTag(t, tb) return anyOtherEndTag(t, tb)
} }
@ -886,8 +875,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} else if (t.isStartTag()) { } else if (t.isStartTag()) {
let startTag: Token.StartTag = t.asStartTag() let startTag: Token.StartTag = t.asStartTag()
if let name: String = startTag.normalName() if let name: String = startTag.normalName() {
{
if (name.equals("caption")) { if (name.equals("caption")) {
tb.clearStackToTableContext() tb.clearStackToTableContext()
tb.insertMarkerToFormattingElements() tb.insertMarkerToFormattingElements()
@ -922,9 +910,9 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} }
} else if (name.equals("form")) { } else if (name.equals("form")) {
tb.error(self) tb.error(self)
if (tb.getFormElement() != nil){ if (tb.getFormElement() != nil) {
return false return false
}else { } else {
try tb.insertForm(startTag, false) try tb.insertForm(startTag, false)
} }
} else { } else {
@ -934,8 +922,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return true // todo: check if should return processed http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-intable return true // todo: check if should return processed http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-intable
} else if (t.isEndTag()) { } else if (t.isEndTag()) {
let endTag: Token.EndTag = t.asEndTag() let endTag: Token.EndTag = t.asEndTag()
if let name: String = endTag.normalName() if let name: String = endTag.normalName() {
{
if (name.equals("table")) { if (name.equals("table")) {
if (try !tb.inTableScope(name)) { if (try !tb.inTableScope(name)) {
tb.error(self) tb.error(self)
@ -951,12 +938,12 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} else { } else {
return try anythingElse(t, tb) return try anythingElse(t, tb)
} }
}else{ } else {
return try anythingElse(t, tb) return try anythingElse(t, tb)
} }
return true // todo: as above todo return true // todo: as above todo
} else if (t.isEOF()) { } else if (t.isEOF()) {
if (tb.currentElement() != nil && tb.currentElement()!.nodeName().equals("html")){ if (tb.currentElement() != nil && tb.currentElement()!.nodeName().equals("html")) {
tb.error(self) tb.error(self)
} }
return true // stops parsing return true // stops parsing
@ -978,8 +965,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
default: default:
// todo - don't really like the way these table character data lists are built // todo - don't really like the way these table character data lists are built
if (tb.getPendingTableCharacters().count > 0) { if (tb.getPendingTableCharacters().count > 0) {
for character:String in tb.getPendingTableCharacters() for character: String in tb.getPendingTableCharacters() {
{
if (!HtmlTreeBuilderState.isWhitespace(character)) { if (!HtmlTreeBuilderState.isWhitespace(character)) {
// InTable anything else section: // InTable anything else section:
tb.error(self) tb.error(self)
@ -990,7 +976,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} else { } else {
try tb.process(Token.Char().data(character), .InBody) try tb.process(Token.Char().data(character), .InBody)
} }
} else{ } else {
try tb.insert(Token.Char().data(character)) try tb.insert(Token.Char().data(character))
} }
} }
@ -1001,8 +987,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} }
return true return true
case .InCaption: case .InCaption:
if (t.isEndTag() && t.asEndTag().normalName()!.equals("caption")) if (t.isEndTag() && t.asEndTag().normalName()!.equals("caption")) {
{
let endTag: Token.EndTag = t.asEndTag() let endTag: Token.EndTag = t.asEndTag()
let name: String? = endTag.normalName() let name: String? = endTag.normalName()
if (try name != nil && !tb.inTableScope(name!)) { if (try name != nil && !tb.inTableScope(name!)) {
@ -1010,7 +995,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} else { } else {
tb.generateImpliedEndTags() tb.generateImpliedEndTags()
if (!tb.currentElement()!.nodeName().equals("caption")){ if (!tb.currentElement()!.nodeName().equals("caption")) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose("caption") tb.popStackToClose("caption")
@ -1024,7 +1009,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
) { ) {
tb.error(self) tb.error(self)
let processed: Bool = try tb.processEndTag("caption") let processed: Bool = try tb.processEndTag("caption")
if (processed){ if (processed) {
return try tb.process(t) return try tb.process(t)
} }
} else if (t.isEndTag() && StringUtil.inString(t.asEndTag().normalName()!, } else if (t.isEndTag() && StringUtil.inString(t.asEndTag().normalName()!,
@ -1038,14 +1023,12 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
case .InColumnGroup: case .InColumnGroup:
func anythingElse(_ t: Token, _ tb: TreeBuilder)throws->Bool { func anythingElse(_ t: Token, _ tb: TreeBuilder)throws->Bool {
let processed: Bool = try tb.processEndTag("colgroup") let processed: Bool = try tb.processEndTag("colgroup")
if (processed){ // only ignored in frag case if (processed) { // only ignored in frag case
return try tb.process(t) return try tb.process(t)
} }
return true return true
} }
if (HtmlTreeBuilderState.isWhitespace(t)) { if (HtmlTreeBuilderState.isWhitespace(t)) {
try tb.insert(t.asCharacter()) try tb.insert(t.asCharacter())
return true return true
@ -1060,11 +1043,11 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
case .StartTag: case .StartTag:
let startTag: Token.StartTag = t.asStartTag() let startTag: Token.StartTag = t.asStartTag()
let name: String? = startTag.normalName() let name: String? = startTag.normalName()
if ("html".equals(name)){ if ("html".equals(name)) {
return try tb.process(t, .InBody) return try tb.process(t, .InBody)
}else if ("col".equals(name)){ } else if ("col".equals(name)) {
try tb.insertEmpty(startTag) try tb.insertEmpty(startTag)
}else{ } else {
return try anythingElse(t, tb) return try anythingElse(t, tb)
} }
break break
@ -1079,14 +1062,14 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
tb.pop() tb.pop()
tb.transition(.InTable) tb.transition(.InTable)
} }
} else{ } else {
return try anythingElse(t, tb) return try anythingElse(t, tb)
} }
break break
case .EOF: case .EOF:
if ("html".equals(tb.currentElement()?.nodeName())){ if ("html".equals(tb.currentElement()?.nodeName())) {
return true // stop parsing; frag case return true // stop parsing; frag case
}else{ } else {
return try anythingElse(t, tb) return try anythingElse(t, tb)
} }
default: default:
@ -1095,7 +1078,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return true return true
case .InTableBody: case .InTableBody:
@discardableResult @discardableResult
func exitTableBody(_ t:Token, _ tb: HtmlTreeBuilder)throws->Bool { func exitTableBody(_ t: Token, _ tb: HtmlTreeBuilder)throws->Bool {
if (try !(tb.inTableScope("tbody") || tb.inTableScope("thead") || tb.inScope("tfoot"))) { if (try !(tb.inTableScope("tbody") || tb.inTableScope("thead") || tb.inScope("tfoot"))) {
// frag case // frag case
tb.error(self) tb.error(self)
@ -1110,7 +1093,6 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return try tb.process(t, .InTable) return try tb.process(t, .InTable)
} }
switch (t.type) { switch (t.type) {
case .StartTag: case .StartTag:
let startTag: Token.StartTag = t.asStartTag() let startTag: Token.StartTag = t.asStartTag()
@ -1125,7 +1107,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return try tb.process(startTag) return try tb.process(startTag)
} else if (StringUtil.inString(name, haystack: "caption", "col", "colgroup", "tbody", "tfoot", "thead")) { } else if (StringUtil.inString(name, haystack: "caption", "col", "colgroup", "tbody", "tfoot", "thead")) {
return try exitTableBody(t, tb) return try exitTableBody(t, tb)
} else{ } else {
return try anythingElse(t, tb) return try anythingElse(t, tb)
} }
break break
@ -1146,7 +1128,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} else if (StringUtil.inString(name, haystack: "body", "caption", "col", "colgroup", "html", "td", "th", "tr")) { } else if (StringUtil.inString(name, haystack: "body", "caption", "col", "colgroup", "html", "td", "th", "tr")) {
tb.error(self) tb.error(self)
return false return false
} else{ } else {
return try anythingElse(t, tb) return try anythingElse(t, tb)
} }
break break
@ -1161,9 +1143,9 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
func handleMissingTr(_ t: Token, _ tb: TreeBuilder)throws->Bool { func handleMissingTr(_ t: Token, _ tb: TreeBuilder)throws->Bool {
let processed: Bool = try tb.processEndTag("tr") let processed: Bool = try tb.processEndTag("tr")
if (processed){ if (processed) {
return try tb.process(t) return try tb.process(t)
}else{ } else {
return false return false
} }
} }
@ -1219,14 +1201,13 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} }
func closeCell(_ tb: HtmlTreeBuilder)throws { func closeCell(_ tb: HtmlTreeBuilder)throws {
if (try tb.inTableScope("td")){ if (try tb.inTableScope("td")) {
try tb.processEndTag("td") try tb.processEndTag("td")
}else{ } else {
try tb.processEndTag("th") // only here if th or td in scope try tb.processEndTag("th") // only here if th or td in scope
} }
} }
if (t.isEndTag()) { if (t.isEndTag()) {
let endTag: Token.EndTag = t.asEndTag() let endTag: Token.EndTag = t.asEndTag()
let name: String? = endTag.normalName() let name: String? = endTag.normalName()
@ -1238,7 +1219,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} }
tb.generateImpliedEndTags() tb.generateImpliedEndTags()
if (!name!.equals(tb.currentElement()?.nodeName())){ if (!name!.equals(tb.currentElement()?.nodeName())) {
tb.error(self) tb.error(self)
} }
tb.popStackToClose(name!) tb.popStackToClose(name!)
@ -1272,12 +1253,11 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return true return true
case .InSelect: case .InSelect:
func anythingElse(_ t: Token, _ tb: HtmlTreeBuilder)->Bool { func anythingElse(_ t: Token, _ tb: HtmlTreeBuilder) -> Bool {
tb.error(self) tb.error(self)
return false return false
} }
switch (t.type) { switch (t.type) {
case .Char: case .Char:
let c: Token.Char = t.asCharacter() let c: Token.Char = t.asCharacter()
@ -1297,15 +1277,15 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
case .StartTag: case .StartTag:
let start: Token.StartTag = t.asStartTag() let start: Token.StartTag = t.asStartTag()
let name: String? = start.normalName() let name: String? = start.normalName()
if ("html".equals(name)){ if ("html".equals(name)) {
return try tb.process(start, .InBody) return try tb.process(start, .InBody)
}else if ("option".equals(name)) { } else if ("option".equals(name)) {
try tb.processEndTag("option") try tb.processEndTag("option")
try tb.insert(start) try tb.insert(start)
} else if ("optgroup".equals(name)) { } else if ("optgroup".equals(name)) {
if ("option".equals(tb.currentElement()?.nodeName())){ if ("option".equals(tb.currentElement()?.nodeName())) {
try tb.processEndTag("option") try tb.processEndTag("option")
}else if ("optgroup".equals(tb.currentElement()?.nodeName())){ } else if ("optgroup".equals(tb.currentElement()?.nodeName())) {
try tb.processEndTag("optgroup") try tb.processEndTag("optgroup")
} }
try tb.insert(start) try tb.insert(start)
@ -1314,7 +1294,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return try tb.processEndTag("select") return try tb.processEndTag("select")
} else if (StringUtil.inString(name, haystack: "input", "keygen", "textarea")) { } else if (StringUtil.inString(name, haystack: "input", "keygen", "textarea")) {
tb.error(self) tb.error(self)
if (try !tb.inSelectScope("select")){ if (try !tb.inSelectScope("select")) {
return false // frag return false // frag
} }
try tb.processEndTag("select") try tb.processEndTag("select")
@ -1329,18 +1309,18 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
let end: Token.EndTag = t.asEndTag() let end: Token.EndTag = t.asEndTag()
let name = end.normalName() let name = end.normalName()
if ("optgroup".equals(name)) { if ("optgroup".equals(name)) {
if ("option".equals(tb.currentElement()?.nodeName()) && tb.currentElement() != nil && tb.aboveOnStack(tb.currentElement()!) != nil && "optgroup".equals(tb.aboveOnStack(tb.currentElement()!)?.nodeName())){ if ("option".equals(tb.currentElement()?.nodeName()) && tb.currentElement() != nil && tb.aboveOnStack(tb.currentElement()!) != nil && "optgroup".equals(tb.aboveOnStack(tb.currentElement()!)?.nodeName())) {
try tb.processEndTag("option") try tb.processEndTag("option")
} }
if ("optgroup".equals(tb.currentElement()?.nodeName())){ if ("optgroup".equals(tb.currentElement()?.nodeName())) {
tb.pop() tb.pop()
}else{ } else {
tb.error(self) tb.error(self)
} }
} else if ("option".equals(name)) { } else if ("option".equals(name)) {
if ("option".equals(tb.currentElement()?.nodeName())){ if ("option".equals(tb.currentElement()?.nodeName())) {
tb.pop() tb.pop()
}else{ } else {
tb.error(self) tb.error(self)
} }
} else if ("select".equals(name)) { } else if ("select".equals(name)) {
@ -1351,12 +1331,12 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
tb.popStackToClose(name!) tb.popStackToClose(name!)
tb.resetInsertionMode() tb.resetInsertionMode()
} }
} else{ } else {
return anythingElse(t, tb) return anythingElse(t, tb)
} }
break break
case .EOF: case .EOF:
if (!"html".equals(tb.currentElement()?.nodeName())){ if (!"html".equals(tb.currentElement()?.nodeName())) {
tb.error(self) tb.error(self)
} }
break break
@ -1374,7 +1354,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
if (try t.asEndTag().normalName() != nil && tb.inTableScope(t.asEndTag().normalName()!)) { if (try t.asEndTag().normalName() != nil && tb.inTableScope(t.asEndTag().normalName()!)) {
try tb.processEndTag("select") try tb.processEndTag("select")
return try (tb.process(t)) return try (tb.process(t))
} else{ } else {
return false return false
} }
} else { } else {
@ -1507,7 +1487,7 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
} }
private static func isWhitespace(_ t: Token)->Bool { private static func isWhitespace(_ t: Token) -> Bool {
if (t.isCharacter()) { if (t.isCharacter()) {
let data: String? = t.asCharacter().getData() let data: String? = t.asCharacter().getData()
return isWhitespace(data) return isWhitespace(data)
@ -1515,11 +1495,11 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
return false return false
} }
private static func isWhitespace(_ data: String?)->Bool { private static func isWhitespace(_ data: String?) -> Bool {
// todo: self checks more than spec - "\t", "\n", "\f", "\r", " " // todo: self checks more than spec - "\t", "\n", "\f", "\r", " "
if let data = data{ if let data = data {
for c in data.characters { for c in data.characters {
if (!StringUtil.isWhitespace(c)){ if (!StringUtil.isWhitespace(c)) {
return false} return false}
} }
} }
@ -1540,13 +1520,6 @@ enum HtmlTreeBuilderState: String, HtmlTreeBuilderStateProtocol
tb.transition(.Text) tb.transition(.Text)
} }
// lists of tags to search through. A little harder to read here, but causes less GC than dynamic varargs. // lists of tags to search through. A little harder to read here, but causes less GC than dynamic varargs.
// was contributing around 10% of parse GC load. // was contributing around 10% of parse GC load.
fileprivate final class Constants { fileprivate final class Constants {

View File

@ -8,13 +8,12 @@
import Foundation import Foundation
open class Node : Equatable, Hashable open class Node: Equatable, Hashable {
{ private static let EMPTY_NODES: Array<Node> = Array<Node>()
private static let EMPTY_NODES : Array<Node> = Array<Node>() var parentNode: Node?
var parentNode : Node? var childNodes: Array <Node>
var childNodes : Array <Node> var attributes: Attributes?
var attributes : Attributes? var baseUri: String?
var baseUri : String?
/** /**
* Get the list index of this node in its node sibling list. I.e. if this is the first node * Get the list index of this node in its node sibling list. I.e. if this is the first node
@ -22,7 +21,7 @@ open class Node : Equatable, Hashable
* @return position in node sibling list * @return position in node sibling list
* @see org.jsoup.nodes.Element#elementSiblingIndex() * @see org.jsoup.nodes.Element#elementSiblingIndex()
*/ */
public private(set) var siblingIndex : Int = 0 public private(set) var siblingIndex: Int = 0
/** /**
Create a new Node. Create a new Node.
@ -54,7 +53,7 @@ open class Node : Equatable, Hashable
Get the node name of this node. Use for debugging purposes and not logic switching (for that, use instanceof). Get the node name of this node. Use for debugging purposes and not logic switching (for that, use instanceof).
@return node name @return node name
*/ */
public func nodeName()->String { public func nodeName() -> String {
preconditionFailure("This method must be overridden") preconditionFailure("This method must be overridden")
} }
@ -74,19 +73,19 @@ open class Node : Equatable, Hashable
* @see #absUrl(String) * @see #absUrl(String)
*/ */
open func attr(_ attributeKey: String)throws ->String { open func attr(_ attributeKey: String)throws ->String {
let val : String = try attributes!.getIgnoreCase(key: attributeKey) let val: String = try attributes!.getIgnoreCase(key: attributeKey)
if (val.characters.count > 0){ if (val.characters.count > 0) {
return val return val
}else if (attributeKey.lowercased().startsWith("abs:")){ } else if (attributeKey.lowercased().startsWith("abs:")) {
return try absUrl(attributeKey.substring("abs:".characters.count)) return try absUrl(attributeKey.substring("abs:".characters.count))
}else {return ""} } else {return ""}
} }
/** /**
* Get all of the element's attributes. * Get all of the element's attributes.
* @return attributes (which implements iterable, in same order as presented in original HTML). * @return attributes (which implements iterable, in same order as presented in original HTML).
*/ */
open func getAttributes()->Attributes? { open func getAttributes() -> Attributes? {
return attributes return attributes
} }
@ -98,7 +97,7 @@ open class Node : Equatable, Hashable
*/ */
@discardableResult @discardableResult
open func attr(_ attributeKey: String, _ attributeValue: String)throws->Node { open func attr(_ attributeKey: String, _ attributeValue: String)throws->Node {
try attributes?.put(attributeKey , attributeValue) try attributes?.put(attributeKey, attributeValue)
return self return self
} }
@ -107,18 +106,18 @@ open class Node : Equatable, Hashable
* @param attributeKey The attribute key to check. * @param attributeKey The attribute key to check.
* @return true if the attribute exists, false if not. * @return true if the attribute exists, false if not.
*/ */
open func hasAttr(_ attributeKey: String)->Bool { open func hasAttr(_ attributeKey: String) -> Bool {
guard let attributes = attributes else { guard let attributes = attributes else {
return false return false
} }
if (attributeKey.startsWith("abs:")) { if (attributeKey.startsWith("abs:")) {
let key : String = attributeKey.substring("abs:".characters.count) let key: String = attributeKey.substring("abs:".characters.count)
do{ do {
let abs = try absUrl(key) let abs = try absUrl(key)
if (attributes.hasKeyIgnoreCase(key: key) && !"".equals(abs)){ if (attributes.hasKeyIgnoreCase(key: key) && !"".equals(abs)) {
return true return true
} }
}catch{ } catch {
return false return false
} }
@ -141,7 +140,7 @@ open class Node : Equatable, Hashable
Get the base URI of this node. Get the base URI of this node.
@return base URI @return base URI
*/ */
open func getBaseUri()->String { open func getBaseUri() -> String {
return baseUri! return baseUri!
} }
@ -149,13 +148,10 @@ open class Node : Equatable, Hashable
Update the base URI of this node and all of its descendants. Update the base URI of this node and all of its descendants.
@param baseUri base URI to set @param baseUri base URI to set
*/ */
open func setBaseUri(_ baseUri: String)throws open func setBaseUri(_ baseUri: String)throws {
{ class nodeVisitor: NodeVisitor {
class nodeVisitor : NodeVisitor private let baseUri: String
{ init(_ baseUri: String) {
private let baseUri : String
init(_ baseUri: String)
{
self.baseUri = baseUri self.baseUri = baseUri
} }
@ -207,7 +203,7 @@ open class Node : Equatable, Hashable
@param index index of child node @param index index of child node
@return the child node at this index. Throws a {@code IndexOutOfBoundsException} if the index is out of bounds. @return the child node at this index. Throws a {@code IndexOutOfBoundsException} if the index is out of bounds.
*/ */
open func childNode(_ index: Int)->Node { open func childNode(_ index: Int) -> Node {
return childNodes[index] return childNodes[index]
} }
@ -225,10 +221,9 @@ open class Node : Equatable, Hashable
* nodes * nodes
* @return a deep copy of this node's children * @return a deep copy of this node's children
*/ */
open func childNodesCopy()->Array<Node>{ open func childNodesCopy()->Array<Node> {
var children: Array<Node> = Array<Node>() var children: Array<Node> = Array<Node>()
for node: Node in childNodes for node: Node in childNodes {
{
children.append(node.copy() as! Node) children.append(node.copy() as! Node)
} }
return children return children
@ -238,11 +233,11 @@ open class Node : Equatable, Hashable
* Get the number of child nodes that this node holds. * Get the number of child nodes that this node holds.
* @return the number of child nodes that this node holds. * @return the number of child nodes that this node holds.
*/ */
public func childNodeSize()->Int { public func childNodeSize() -> Int {
return childNodes.count return childNodes.count
} }
final func childNodesAsArray()->[Node] { final func childNodesAsArray() -> [Node] {
return childNodes as Array return childNodes as Array
} }
@ -258,7 +253,7 @@ open class Node : Equatable, Hashable
Gets this node's parent node. Node overridable by extending classes, so useful if you really just need the Node type. 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. @return parent node or null if no parent.
*/ */
final func getParentNode()->Node? { final func getParentNode() -> Node? {
return parentNode return parentNode
} }
@ -266,12 +261,12 @@ open class Node : Equatable, Hashable
* Gets the Document associated with this Node. * Gets the Document associated with this Node.
* @return the Document associated with this Node, or null if there is no such Document. * @return the Document associated with this Node, or null if there is no such Document.
*/ */
open func ownerDocument()-> Document? { open func ownerDocument() -> Document? {
if let this = self as? Document{ if let this = self as? Document {
return this return this
}else if (parentNode == nil){ } else if (parentNode == nil) {
return nil return nil
}else{ } else {
return parentNode!.ownerDocument() return parentNode!.ownerDocument()
} }
} }
@ -306,7 +301,7 @@ open class Node : Equatable, Hashable
try Validate.notNull(obj: node) try Validate.notNull(obj: node)
try Validate.notNull(obj: parentNode) try Validate.notNull(obj: parentNode)
try parentNode?.addChildren(siblingIndex,node) try parentNode?.addChildren(siblingIndex, node)
return self return self
} }
@ -333,20 +328,19 @@ open class Node : Equatable, Hashable
try Validate.notNull(obj: node) try Validate.notNull(obj: node)
try Validate.notNull(obj: parentNode) try Validate.notNull(obj: parentNode)
try parentNode?.addChildren(siblingIndex+1,node) try parentNode?.addChildren(siblingIndex+1, node)
return self return self
} }
private func addSiblingHtml(_ index: Int, _ html: String)throws { private func addSiblingHtml(_ index: Int, _ html: String)throws {
try Validate.notNull(obj: parentNode) try Validate.notNull(obj: parentNode)
let context : Element? = parent() as? Element let context: Element? = parent() as? Element
let nodes : Array<Node> = try Parser.parseFragment(html, context, getBaseUri()) let nodes: Array<Node> = try Parser.parseFragment(html, context, getBaseUri())
try parentNode?.addChildren(index,nodes) try parentNode?.addChildren(index, nodes)
} }
/** /**
* Insert the specified HTML into the DOM after this node (i.e. as a following sibling). * Insert the specified HTML into the DOM after this node (i.e. as a following sibling).
* @param html HTML to add after this node * @param html HTML to add after this node
@ -378,8 +372,8 @@ open class Node : Equatable, Hashable
try Validate.notNull(obj: html) try Validate.notNull(obj: html)
try Validate.notNull(obj: parentNode) try Validate.notNull(obj: parentNode)
let context : Element? = parent() as? Element let context: Element? = parent() as? Element
let nodes : Array<Node> = try Parser.parseFragment(html, context, getBaseUri()) let nodes: Array<Node> = try Parser.parseFragment(html, context, getBaseUri())
try parentNode?.addChildren(index, nodes) try parentNode?.addChildren(index, nodes)
} }
@ -392,25 +386,23 @@ open class Node : Equatable, Hashable
open func wrap(_ html: String)throws->Node? { open func wrap(_ html: String)throws->Node? {
try Validate.notEmpty(string: html) try Validate.notEmpty(string: html)
let context : Element? = parent() as? Element let context: Element? = parent() as? Element
var wrapChildren : Array<Node> = try Parser.parseFragment(html, context, getBaseUri()) var wrapChildren: Array<Node> = try Parser.parseFragment(html, context, getBaseUri())
let wrapNode : Node? = wrapChildren.count > 0 ? wrapChildren[0] : nil let wrapNode: Node? = wrapChildren.count > 0 ? wrapChildren[0] : nil
if (wrapNode == nil || !(((wrapNode as? Element) != nil))){ // nothing to wrap with; noop if (wrapNode == nil || !(((wrapNode as? Element) != nil))) { // nothing to wrap with; noop
return nil return nil
} }
let wrap : Element = wrapNode as! Element let wrap: Element = wrapNode as! Element
let deepest : Element = getDeepChild(el: wrap) let deepest: Element = getDeepChild(el: wrap)
try parentNode?.replaceChild(self, wrap) try parentNode?.replaceChild(self, wrap)
wrapChildren = wrapChildren.filter { $0 != wrap} wrapChildren = wrapChildren.filter { $0 != wrap}
try deepest.addChildren(self) try deepest.addChildren(self)
// remainder (unbalanced wrap, like <div></div><p></p> -- The <p> is remainder // remainder (unbalanced wrap, like <div></div><p></p> -- The <p> is remainder
if (wrapChildren.count > 0) if (wrapChildren.count > 0) {
{ for i in 0..<wrapChildren.count {
for i in 0..<wrapChildren.count let remainder: Node = wrapChildren[i]
{
let remainder : Node = wrapChildren[i]
try remainder.parentNode?.removeChild(remainder) try remainder.parentNode?.removeChild(remainder)
try wrap.appendChild(remainder) try wrap.appendChild(remainder)
} }
@ -437,18 +429,18 @@ open class Node : Equatable, Hashable
open func unwrap()throws ->Node? { open func unwrap()throws ->Node? {
try Validate.notNull(obj: parentNode) try Validate.notNull(obj: parentNode)
let firstChild : Node? = childNodes.count > 0 ? childNodes[0] : nil let firstChild: Node? = childNodes.count > 0 ? childNodes[0] : nil
try parentNode?.addChildren(siblingIndex, self.childNodesAsArray()) try parentNode?.addChildren(siblingIndex, self.childNodesAsArray())
try self.remove() try self.remove()
return firstChild return firstChild
} }
private func getDeepChild(el: Element)->Element { private func getDeepChild(el: Element) -> Element {
let children = el.children() let children = el.children()
if (children.size() > 0){ if (children.size() > 0) {
return getDeepChild(el: children.get(0)) return getDeepChild(el: children.get(0))
}else{ } else {
return el return el
} }
} }
@ -464,7 +456,7 @@ open class Node : Equatable, Hashable
} }
public func setParentNode(_ parentNode: Node)throws { public func setParentNode(_ parentNode: Node)throws {
if (self.parentNode != nil){ if (self.parentNode != nil) {
try self.parentNode?.removeChild(self) try self.parentNode?.removeChild(self)
} }
self.parentNode = parentNode self.parentNode = parentNode
@ -473,11 +465,11 @@ open class Node : Equatable, Hashable
public func replaceChild(_ out: Node, _ input: Node)throws { public func replaceChild(_ out: Node, _ input: Node)throws {
try Validate.isTrue(val: out.parentNode === self) try Validate.isTrue(val: out.parentNode === self)
try Validate.notNull(obj: input) try Validate.notNull(obj: input)
if (input.parentNode != nil){ if (input.parentNode != nil) {
try input.parentNode?.removeChild(input) try input.parentNode?.removeChild(input)
} }
let index : Int = out.siblingIndex let index: Int = out.siblingIndex
childNodes[index] = input childNodes[index] = input
input.parentNode = self input.parentNode = self
input.setSiblingIndex(index) input.setSiblingIndex(index)
@ -486,7 +478,7 @@ open class Node : Equatable, Hashable
public func removeChild(_ out: Node)throws { public func removeChild(_ out: Node)throws {
try Validate.isTrue(val: out.parentNode === self) try Validate.isTrue(val: out.parentNode === self)
let index : Int = out.siblingIndex let index: Int = out.siblingIndex
childNodes.remove(at: index) childNodes.remove(at: index)
reindexChildren(index) reindexChildren(index)
out.parentNode = nil out.parentNode = nil
@ -499,8 +491,7 @@ open class Node : Equatable, Hashable
public func addChildren(_ children: [Node])throws { public func addChildren(_ children: [Node])throws {
//most used. short circuit addChildren(int), which hits reindex children and array copy //most used. short circuit addChildren(int), which hits reindex children and array copy
for child in children for child in children {
{
try reparentChild(child) try reparentChild(child)
ensureChildNodes() ensureChildNodes()
childNodes.append(child) childNodes.append(child)
@ -508,38 +499,35 @@ open class Node : Equatable, Hashable
} }
} }
public func addChildren(_ index: Int,_ children: Node...)throws { public func addChildren(_ index: Int, _ children: Node...)throws {
try addChildren(index,children) try addChildren(index, children)
} }
public func addChildren(_ index: Int,_ children: [Node])throws { public func addChildren(_ index: Int, _ children: [Node])throws {
ensureChildNodes() ensureChildNodes()
for i in (0..<children.count).reversed() for i in (0..<children.count).reversed() {
{ let input: Node = children[i]
let input : Node = children[i]
try reparentChild(input) try reparentChild(input)
childNodes.insert(input, at: index) childNodes.insert(input, at: index)
reindexChildren(index) reindexChildren(index)
} }
} }
public func ensureChildNodes() public func ensureChildNodes() {
{
// if (childNodes === Node.EMPTY_NODES) { // if (childNodes === Node.EMPTY_NODES) {
// childNodes = Array<Node>() // childNodes = Array<Node>()
// } // }
} }
public func reparentChild(_ child: Node)throws { public func reparentChild(_ child: Node)throws {
if (child.parentNode != nil){ if (child.parentNode != nil) {
try child.parentNode?.removeChild(child) try child.parentNode?.removeChild(child)
} }
try child.setParentNode(self) try child.setParentNode(self)
} }
private func reindexChildren(_ start: Int) { private func reindexChildren(_ start: Int) {
for i in start..<childNodes.count for i in start..<childNodes.count {
{
childNodes[i].setSiblingIndex(i) childNodes[i].setSiblingIndex(i)
} }
} }
@ -550,15 +538,14 @@ open class Node : Equatable, Hashable
@return node siblings. If the node has no parent, returns an empty list. @return node siblings. If the node has no parent, returns an empty list.
*/ */
open func siblingNodes()->Array<Node> { open func siblingNodes()->Array<Node> {
if (parentNode == nil){ if (parentNode == nil) {
return Array<Node>() return Array<Node>()
} }
let nodes : Array<Node> = parentNode!.childNodes let nodes: Array<Node> = parentNode!.childNodes
var siblings : Array<Node> = Array<Node>() var siblings: Array<Node> = Array<Node>()
for node in nodes for node in nodes {
{ if (node !== self) {
if (node !== self){
siblings.append(node) siblings.append(node)
} }
} }
@ -566,21 +553,20 @@ open class Node : Equatable, Hashable
return siblings return siblings
} }
/** /**
Get this node's next sibling. Get this node's next sibling.
@return next sibling, or null if this is the last sibling @return next sibling, or null if this is the last sibling
*/ */
open func nextSibling()->Node? { open func nextSibling() -> Node? {
if (parentNode == nil){ if (parentNode == nil) {
return nil // root return nil // root
} }
let siblings : Array<Node> = parentNode!.childNodes let siblings: Array<Node> = parentNode!.childNodes
let index : Int = siblingIndex+1 let index: Int = siblingIndex+1
if (siblings.count > index){ if (siblings.count > index) {
return siblings[index] return siblings[index]
}else{ } else {
return nil return nil
} }
} }
@ -589,20 +575,18 @@ open class Node : Equatable, Hashable
Get this node's previous sibling. Get this node's previous sibling.
@return the previous sibling, or null if this is the first sibling @return the previous sibling, or null if this is the first sibling
*/ */
open func previousSibling()->Node? { open func previousSibling() -> Node? {
if (parentNode == nil){ if (parentNode == nil) {
return nil // root return nil // root
} }
if (siblingIndex > 0){ if (siblingIndex > 0) {
return parentNode?.childNodes[siblingIndex-1] return parentNode?.childNodes[siblingIndex-1]
}else{ } else {
return nil return nil
} }
} }
public func setSiblingIndex(_ siblingIndex: Int) { public func setSiblingIndex(_ siblingIndex: Int) {
self.siblingIndex = siblingIndex self.siblingIndex = siblingIndex
} }
@ -614,7 +598,7 @@ open class Node : Equatable, Hashable
*/ */
@discardableResult @discardableResult
open func traverse(_ nodeVisitor: NodeVisitor)throws->Node { open func traverse(_ nodeVisitor: NodeVisitor)throws->Node {
let traversor : NodeTraversor = NodeTraversor(nodeVisitor) let traversor: NodeTraversor = NodeTraversor(nodeVisitor)
try traversor.traverse(self) try traversor.traverse(self)
return self return self
} }
@ -624,7 +608,7 @@ open class Node : Equatable, Hashable
@return HTML @return HTML
*/ */
open func outerHtml()throws->String { open func outerHtml()throws->String {
let accum : StringBuilder = StringBuilder(128) let accum: StringBuilder = StringBuilder(128)
try outerHtml(accum) try outerHtml(accum)
return accum.toString() return accum.toString()
} }
@ -634,7 +618,7 @@ open class Node : Equatable, Hashable
} }
// if this node has no document (or parent), retrieve the default output settings // if this node has no document (or parent), retrieve the default output settings
func getOutputSettings()-> OutputSettings { func getOutputSettings() -> OutputSettings {
return ownerDocument() != nil ? ownerDocument()!.outputSettings() : (Document("")).outputSettings() return ownerDocument() != nil ? ownerDocument()!.outputSettings() : (Document("")).outputSettings()
} }
@ -643,17 +627,14 @@ open class Node : Equatable, Hashable
@param accum accumulator to place HTML into @param accum accumulator to place HTML into
@throws IOException if appending to the given accumulator fails. @throws IOException if appending to the given accumulator fails.
*/ */
func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) throws func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) throws {
{
preconditionFailure("This method must be overridden") preconditionFailure("This method must be overridden")
} }
func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) throws func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) throws {
{
preconditionFailure("This method must be overridden") preconditionFailure("This method must be overridden")
} }
/** /**
* Write this node and its children to the given {@link Appendable}. * Write this node and its children to the given {@link Appendable}.
* *
@ -680,7 +661,7 @@ open class Node : Equatable, Hashable
* @see Node#hasSameValue(Object) to compare nodes by their value * @see Node#hasSameValue(Object) to compare nodes by their value
*/ */
open func equals(_ o: Node)->Bool { open func equals(_ o: Node) -> Bool {
// implemented just so that javadoc is clear this is an identity test // implemented just so that javadoc is clear this is an identity test
return self === o return self === o
} }
@ -692,8 +673,8 @@ open class Node : Equatable, Hashable
* @return true if the content of this node is the same as the other * @return true if the content of this node is the same as the other
*/ */
open func hasSameValue(_ o : Node)throws->Bool { open func hasSameValue(_ o: Node)throws->Bool {
if (self === o){return true} if (self === o) {return true}
// if (type(of:self) != type(of: o)) // if (type(of:self) != type(of: o))
// { // {
// return false // return false
@ -710,31 +691,27 @@ open class Node : Equatable, Hashable
* The cloned node may be adopted into another Document or node structure using {@link Element#appendChild(Node)}. * The cloned node may be adopted into another Document or node structure using {@link Element#appendChild(Node)}.
* @return stand-alone cloned node * @return stand-alone cloned node
*/ */
public func copy(with zone: NSZone? = nil) -> Any public func copy(with zone: NSZone? = nil) -> Any {
{
return copy(clone: Node()) return copy(clone: Node())
} }
public func copy(parent: Node?)->Node public func copy(parent: Node?) -> Node {
{
let clone = Node() let clone = Node()
return copy(clone: clone,parent: parent) return copy(clone: clone, parent: parent)
} }
public func copy(clone: Node)->Node public func copy(clone: Node) -> Node {
{ let thisClone: Node = copy(clone: clone, parent: nil) // splits for orphan
let thisClone : Node = copy(clone: clone, parent: nil) // splits for orphan
// Queue up nodes that need their children cloned (BFS). // Queue up nodes that need their children cloned (BFS).
var nodesToProcess : Array<Node> = Array<Node>() var nodesToProcess: Array<Node> = Array<Node>()
nodesToProcess.append(thisClone) nodesToProcess.append(thisClone)
while (!nodesToProcess.isEmpty) { while (!nodesToProcess.isEmpty) {
let currParent : Node = nodesToProcess.removeFirst() let currParent: Node = nodesToProcess.removeFirst()
for i in 0..<currParent.childNodes.count for i in 0..<currParent.childNodes.count {
{ let childClone: Node = currParent.childNodes[i].copy(parent:currParent)
let childClone : Node = currParent.childNodes[i].copy(parent:currParent)
currParent.childNodes[i] = childClone currParent.childNodes[i] = childClone
nodesToProcess.append(childClone) nodesToProcess.append(childClone)
} }
@ -746,48 +723,41 @@ open class Node : Equatable, Hashable
* Return a clone of the node using the given parent (which can be null). * Return a clone of the node using the given parent (which can be null).
* Not a deep copy of children. * Not a deep copy of children.
*/ */
public func copy(clone: Node, parent: Node?)->Node public func copy(clone: Node, parent: Node?) -> Node {
{
clone.parentNode = parent // can be null, to create an orphan split clone.parentNode = parent // can be null, to create an orphan split
clone.siblingIndex = parent == nil ? 0 : siblingIndex clone.siblingIndex = parent == nil ? 0 : siblingIndex
clone.attributes = attributes != nil ? attributes?.clone() : nil clone.attributes = attributes != nil ? attributes?.clone() : nil
clone.baseUri = baseUri clone.baseUri = baseUri
clone.childNodes = Array<Node>() clone.childNodes = Array<Node>()
for child in childNodes{ for child in childNodes {
clone.childNodes.append(child) clone.childNodes.append(child)
} }
return clone return clone
} }
private class OuterHtmlVisitor : NodeVisitor { private class OuterHtmlVisitor: NodeVisitor {
private var accum : StringBuilder private var accum: StringBuilder
private var out : OutputSettings private var out: OutputSettings
init(_ accum: StringBuilder, _ out: OutputSettings) { init(_ accum: StringBuilder, _ out: OutputSettings) {
self.accum = accum self.accum = accum
self.out = out self.out = out
} }
open func head(_ node: Node, _ depth: Int)throws{ open func head(_ node: Node, _ depth: Int)throws {
try node.outerHtmlHead(accum, depth, out) try node.outerHtmlHead(accum, depth, out)
} }
open func tail(_ node: Node, _ depth: Int)throws { open func tail(_ node: Node, _ depth: Int)throws {
if (!(node.nodeName() == "#text")) if (!(node.nodeName() == "#text")) { // saves a void hit.
{ // saves a void hit.
try node.outerHtmlTail(accum, depth, out) try node.outerHtmlTail(accum, depth, out)
} }
} }
} }
/// Returns a Boolean value indicating whether two values are equal. /// Returns a Boolean value indicating whether two values are equal.
/// ///
/// Equality is the inverse of inequality. For any values `a` and `b`, /// Equality is the inverse of inequality. For any values `a` and `b`,
@ -796,46 +766,40 @@ open class Node : Equatable, Hashable
/// - Parameters: /// - Parameters:
/// - lhs: A value to compare. /// - lhs: A value to compare.
/// - rhs: Another value to compare. /// - rhs: Another value to compare.
public static func ==(lhs: Node, rhs: Node) -> Bool{ public static func ==(lhs: Node, rhs: Node) -> Bool {
return lhs === rhs return lhs === rhs
} }
/// The hash value. /// The hash value.
/// ///
/// Hash values are not guaranteed to be equal across different executions of /// 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. /// your program. Do not save hash values to use during a future execution.
public var hashValue: Int { public var hashValue: Int {
var result : Int = description.hashValue var result: Int = description.hashValue
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, baseUri != nil ? baseUri!.hashValue : 31).0 result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, baseUri != nil ? baseUri!.hashValue : 31).0
return result return result
} }
} }
extension Node : CustomStringConvertible extension Node : CustomStringConvertible {
{
public var description: String { public var description: String {
do{ do {
return try toString() return try toString()
}catch{ } catch {
} }
return "" return ""
} }
} }
extension Node : CustomDebugStringConvertible extension Node : CustomDebugStringConvertible {
{
public var debugDescription: String { public var debugDescription: String {
do{ do {
return try String(describing: type(of: self)) + " " + toString() return try String(describing: type(of: self)) + " " + toString()
}catch{ } catch {
} }
return String(describing: type(of: self)) return String(describing: type(of: self))
} }
} }

View File

@ -8,9 +8,8 @@
import Foundation import Foundation
class NodeTraversor class NodeTraversor {
{ private let visitor: NodeVisitor
private let visitor : NodeVisitor
/** /**
* Create a new traversor. * Create a new traversor.
@ -25,8 +24,8 @@ class NodeTraversor
* @param root the root node point to traverse. * @param root the root node point to traverse.
*/ */
open func traverse(_ root: Node?)throws { open func traverse(_ root: Node?)throws {
var node : Node? = root var node: Node? = root
var depth : Int = 0 var depth: Int = 0
while (node != nil) { while (node != nil) {
try visitor.head(node!, depth) try visitor.head(node!, depth)
@ -40,7 +39,7 @@ class NodeTraversor
depth-=1 depth-=1
} }
try visitor.tail(node!, depth) try visitor.tail(node!, depth)
if (node === root){ if (node === root) {
break break
} }
node = node!.nextSibling() node = node!.nextSibling()
@ -48,5 +47,4 @@ class NodeTraversor
} }
} }
} }

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
public class OrderedDictionary<Key: Hashable, Value: Equatable>: MutableCollection ,Hashable { public class OrderedDictionary<Key: Hashable, Value: Equatable>: MutableCollection, Hashable {
/// Returns the position immediately after the given index. /// Returns the position immediately after the given index.
/// ///
@ -32,7 +32,7 @@ public class OrderedDictionary<Key: Hashable, Value: Equatable>: MutableCollecti
// ======================================================= // // ======================================================= //
public init() {} public init() {}
public init(count:Int) {} public init(count: Int) {}
public init(elements: [Element]) { public init(elements: [Element]) {
for (key, value) in elements { for (key, value) in elements {
@ -44,16 +44,14 @@ public class OrderedDictionary<Key: Hashable, Value: Equatable>: MutableCollecti
return copy(with: nil) return copy(with: nil)
} }
public func mutableCopy(with zone: NSZone? = nil) -> Any public func mutableCopy(with zone: NSZone? = nil) -> Any {
{
return copy() return copy()
} }
public func copy(with zone: NSZone? = nil) -> Any { public func copy(with zone: NSZone? = nil) -> Any {
let copy = OrderedDictionary<Key,Value>() let copy = OrderedDictionary<Key, Value>()
//let copy = type(of:self).init() //let copy = type(of:self).init()
for element in orderedKeys for element in orderedKeys {
{
copy.put(value: valueForKey(key: element)!, forKey: element) copy.put(value: valueForKey(key: element)!, forKey: element)
} }
return copy return copy
@ -113,7 +111,6 @@ public class OrderedDictionary<Key: Hashable, Value: Equatable>: MutableCollecti
return 0 return 0
} }
public func hashCode() -> Int { public func hashCode() -> Int {
return hashValue return hashValue
} }
@ -136,15 +133,12 @@ public class OrderedDictionary<Key: Hashable, Value: Equatable>: MutableCollecti
} }
} }
public func put(value: Value, forKey key: Key) { public func put(value: Value, forKey key: Key) {
self[key] = value self[key] = value
} }
public func putAll(all:OrderedDictionary<Key, Value>) public func putAll(all: OrderedDictionary<Key, Value>) {
{ for i in all.orderedKeys {
for i in all.orderedKeys
{
put(value:all[i]!, forKey: i) put(value:all[i]!, forKey: i)
} }
} }
@ -175,7 +169,6 @@ public class OrderedDictionary<Key: Hashable, Value: Equatable>: MutableCollecti
_keysToValues.removeAll(keepingCapacity: keepCapacity) _keysToValues.removeAll(keepingCapacity: keepCapacity)
} }
// ======================================================= // // ======================================================= //
// MARK: - Managing Content Using Indexes // MARK: - Managing Content Using Indexes
// ======================================================= // // ======================================================= //
@ -370,8 +363,7 @@ extension OrderedDictionary: CustomStringConvertible, CustomDebugStringConvertib
} }
extension OrderedDictionary: Equatable extension OrderedDictionary: Equatable {
{
/// Returns a Boolean value indicating whether two values are equal. /// Returns a Boolean value indicating whether two values are equal.
/// ///
/// Equality is the inverse of inequality. For any values `a` and `b`, /// Equality is the inverse of inequality. For any values `a` and `b`,
@ -381,12 +373,11 @@ extension OrderedDictionary: Equatable
/// - lhs: A value to compare. /// - lhs: A value to compare.
/// - rhs: Another value to compare. /// - rhs: Another value to compare.
public static func ==(lhs: OrderedDictionary<Key, Value>, rhs: OrderedDictionary<Key, Value>) -> Bool { public static func ==(lhs: OrderedDictionary<Key, Value>, rhs: OrderedDictionary<Key, Value>) -> Bool {
if(lhs.count != rhs.count){return false} if(lhs.count != rhs.count) {return false}
return (lhs._orderedKeys == rhs._orderedKeys) && (lhs._keysToValues == rhs._keysToValues) return (lhs._orderedKeys == rhs._orderedKeys) && (lhs._keysToValues == rhs._keysToValues)
} }
} }
//public func == <Key: Equatable, Value: Equatable>(lhs: OrderedDictionary<Key, Value>, rhs: OrderedDictionary<Key, Value>) -> Bool { //public func == <Key: Equatable, Value: Equatable>(lhs: OrderedDictionary<Key, Value>, rhs: OrderedDictionary<Key, Value>) -> Bool {
// return lhs._orderedKeys == rhs._orderedKeys && lhs._keysToValues == rhs._keysToValues // return lhs._orderedKeys == rhs._orderedKeys && lhs._keysToValues == rhs._keysToValues
//} //}

View File

@ -13,14 +13,13 @@ public class OrderedSet<T: Hashable> {
fileprivate var contents = [T: Index]() // Needs to have a value of Index instead of Void for fast removals fileprivate var contents = [T: Index]() // Needs to have a value of Index instead of Void for fast removals
fileprivate var sequencedContents = Array<UnsafeMutablePointer<T>>() fileprivate var sequencedContents = Array<UnsafeMutablePointer<T>>()
/** /**
Inititalizes an empty ordered set. Inititalizes an empty ordered set.
- returns: An empty ordered set. - returns: An empty ordered set.
*/ */
public init() { } public init() { }
deinit{ deinit {
removeAllObjects() removeAllObjects()
} }

View File

@ -12,8 +12,8 @@ import Foundation
* A Parse Error records an error in the input HTML that occurs in either the tokenisation or the tree building phase. * A Parse Error records an error in the input HTML that occurs in either the tokenisation or the tree building phase.
*/ */
open class ParseError { open class ParseError {
private let pos : Int private let pos: Int
private let errorMsg : String private let errorMsg: String
init(_ pos: Int, _ errorMsg: String) { init(_ pos: Int, _ errorMsg: String) {
self.pos = pos self.pos = pos
@ -24,7 +24,7 @@ open class ParseError {
* Retrieve the error message. * Retrieve the error message.
* @return the error message. * @return the error message.
*/ */
open func getErrorMessage()->String { open func getErrorMessage() -> String {
return errorMsg return errorMsg
} }
@ -32,12 +32,11 @@ open class ParseError {
* Retrieves the offset of the error. * Retrieves the offset of the error.
* @return error offset within input * @return error offset within input
*/ */
open func getPosition()->Int { open func getPosition() -> Int {
return pos return pos
} }
open func toString() -> String {
open func toString()->String {
return "\(pos): " + errorMsg return "\(pos): " + errorMsg
} }
} }

View File

@ -8,12 +8,11 @@
import Foundation import Foundation
public class ParseErrorList public class ParseErrorList {
{ private static let INITIAL_CAPACITY: Int = 16
private static let INITIAL_CAPACITY : Int = 16 private let maxSize: Int
private let maxSize : Int private let initialCapacity: Int
private let initialCapacity : Int private var array: Array<ParseError?> = Array<ParseError>()
private var array : Array<ParseError?> = Array<ParseError>()
init(_ initialCapacity: Int, _ maxSize: Int) { init(_ initialCapacity: Int, _ maxSize: Int) {
self.maxSize = maxSize self.maxSize = maxSize
@ -21,19 +20,19 @@ public class ParseErrorList
array = Array(repeating: nil, count: maxSize) array = Array(repeating: nil, count: maxSize)
} }
func canAddError()->Bool { func canAddError() -> Bool {
return array.count < maxSize return array.count < maxSize
} }
func getMaxSize()->Int { func getMaxSize() -> Int {
return maxSize return maxSize
} }
static func noTracking()->ParseErrorList { static func noTracking() -> ParseErrorList {
return ParseErrorList(0, 0) return ParseErrorList(0, 0)
} }
static func tracking(_ maxSize: Int)->ParseErrorList { static func tracking(_ maxSize: Int) -> ParseErrorList {
return ParseErrorList(INITIAL_CAPACITY, maxSize) return ParseErrorList(INITIAL_CAPACITY, maxSize)
} }
@ -46,7 +45,7 @@ public class ParseErrorList
array.append(e) array.append(e)
} }
open func add(_ index: Int, _ element: ParseError){ open func add(_ index: Int, _ element: ParseError) {
array.insert(element, at: index) array.insert(element, at: index)
} }

View File

@ -8,21 +8,18 @@
import Foundation import Foundation
open class ParseSettings open class ParseSettings {
{
/** /**
* HTML default settings: both tag and attribute names are lower-cased during parsing. * HTML default settings: both tag and attribute names are lower-cased during parsing.
*/ */
public static let htmlDefault : ParseSettings = ParseSettings(false, false) public static let htmlDefault: ParseSettings = ParseSettings(false, false)
/** /**
* Preserve both tag and attribute case. * Preserve both tag and attribute case.
*/ */
public static let preserveCase : ParseSettings = ParseSettings(true, true) public static let preserveCase: ParseSettings = ParseSettings(true, true)
private let preserveTagCase : Bool
private let preserveAttributeCase : Bool
private let preserveTagCase: Bool
private let preserveAttributeCase: Bool
/** /**
* Define parse settings. * Define parse settings.
@ -34,17 +31,17 @@ open class ParseSettings
preserveAttributeCase = attribute preserveAttributeCase = attribute
} }
open func normalizeTag(_ name: String)->String { open func normalizeTag(_ name: String) -> String {
var name = name.trim() var name = name.trim()
if (!preserveTagCase){ if (!preserveTagCase) {
name = name.lowercased() name = name.lowercased()
} }
return name return name
} }
open func normalizeAttribute(_ name: String)->String { open func normalizeAttribute(_ name: String) -> String {
var name = name.trim() var name = name.trim()
if (!preserveAttributeCase){ if (!preserveAttributeCase) {
name = name.lowercased() name = name.lowercased()
} }
return name return name
@ -52,13 +49,11 @@ open class ParseSettings
open func normalizeAttributes(_ attributes: Attributes)throws ->Attributes { open func normalizeAttributes(_ attributes: Attributes)throws ->Attributes {
if (!preserveAttributeCase) { if (!preserveAttributeCase) {
for attr in attributes.iterator() for attr in attributes.iterator() {
{
try attr.setKey(key: attr.getKey().lowercased()) try attr.setKey(key: attr.getKey().lowercased())
} }
} }
return attributes return attributes
} }
} }

View File

@ -8,18 +8,16 @@
import Foundation import Foundation
/** /**
* Parses HTML into a {@link org.jsoup.nodes.Document}. Generally best to use one of the more convenient parse methods * Parses HTML into a {@link org.jsoup.nodes.Document}. Generally best to use one of the more convenient parse methods
* in {@link org.jsoup.Jsoup}. * in {@link org.jsoup.Jsoup}.
*/ */
public class Parser public class Parser {
{
private static let DEFAULT_MAX_ERRORS: Int = 0 // by default, error tracking is disabled. private static let DEFAULT_MAX_ERRORS: Int = 0 // by default, error tracking is disabled.
private var _treeBuilder: TreeBuilder private var _treeBuilder: TreeBuilder
private var _maxErrors: Int = DEFAULT_MAX_ERRORS private var _maxErrors: Int = DEFAULT_MAX_ERRORS
private var _errors: ParseErrorList = ParseErrorList(16,16) private var _errors: ParseErrorList = ParseErrorList(16, 16)
private var _settings: ParseSettings private var _settings: ParseSettings
/** /**
@ -41,7 +39,7 @@ public class Parser
* Get the TreeBuilder currently in use. * Get the TreeBuilder currently in use.
* @return current TreeBuilder. * @return current TreeBuilder.
*/ */
public func getTreeBuilder()->TreeBuilder { public func getTreeBuilder() -> TreeBuilder {
return _treeBuilder return _treeBuilder
} }
@ -51,7 +49,7 @@ public class Parser
* @return this, for chaining * @return this, for chaining
*/ */
@discardableResult @discardableResult
public func setTreeBuilder(_ treeBuilder: TreeBuilder)->Parser { public func setTreeBuilder(_ treeBuilder: TreeBuilder) -> Parser {
self._treeBuilder = treeBuilder self._treeBuilder = treeBuilder
return self return self
} }
@ -60,7 +58,7 @@ public class Parser
* Check if parse error tracking is enabled. * Check if parse error tracking is enabled.
* @return current track error state. * @return current track error state.
*/ */
public func isTrackErrors()->Bool { public func isTrackErrors() -> Bool {
return _maxErrors > 0 return _maxErrors > 0
} }
@ -70,7 +68,7 @@ public class Parser
* @return this, for chaining * @return this, for chaining
*/ */
@discardableResult @discardableResult
public func setTrackErrors(_ maxErrors: Int)->Parser { public func setTrackErrors(_ maxErrors: Int) -> Parser {
self._maxErrors = maxErrors self._maxErrors = maxErrors
return self return self
} }
@ -79,17 +77,17 @@ public class Parser
* Retrieve the parse errors, if any, from the last parse. * Retrieve the parse errors, if any, from the last parse.
* @return list of parse errors, up to the size of the maximum errors tracked. * @return list of parse errors, up to the size of the maximum errors tracked.
*/ */
public func getErrors()->ParseErrorList { public func getErrors() -> ParseErrorList {
return _errors return _errors
} }
@discardableResult @discardableResult
public func settings(_ settings: ParseSettings)->Parser { public func settings(_ settings: ParseSettings) -> Parser {
self._settings = settings self._settings = settings
return self return self
} }
public func settings()->ParseSettings { public func settings() -> ParseSettings {
return _settings return _settings
} }
@ -144,15 +142,13 @@ public class Parser
*/ */
public static func parseBodyFragment(_ bodyHtml: String, _ baseUri: String)throws->Document { public static func parseBodyFragment(_ bodyHtml: String, _ baseUri: String)throws->Document {
let doc: Document = Document.createShell(baseUri) let doc: Document = Document.createShell(baseUri)
if let body: Element = doc.body() if let body: Element = doc.body() {
{
let nodeList: Array<Node> = try parseFragment(bodyHtml, body, baseUri) let nodeList: Array<Node> = try parseFragment(bodyHtml, body, baseUri)
//var nodes: [Node] = nodeList.toArray(Node[nodeList.size()]) // the node list gets modified when re-parented //var nodes: [Node] = nodeList.toArray(Node[nodeList.size()]) // the node list gets modified when re-parented
for i in (nodeList.count - 1)...1 for i in (nodeList.count - 1)...1 {
{
try nodeList[i].remove() try nodeList[i].remove()
} }
for node:Node in nodeList { for node: Node in nodeList {
try body.appendChild(node) try body.appendChild(node)
} }
} }
@ -188,7 +184,7 @@ public class Parser
* based on a knowledge of the semantics of the incoming tags. * based on a knowledge of the semantics of the incoming tags.
* @return a new HTML parser. * @return a new HTML parser.
*/ */
public static func htmlParser()->Parser { public static func htmlParser() -> Parser {
return Parser(HtmlTreeBuilder()) return Parser(HtmlTreeBuilder())
} }
@ -197,7 +193,7 @@ public class Parser
* rather creates a simple tree directly from the input. * rather creates a simple tree directly from the input.
* @return a new simple XML parser. * @return a new simple XML parser.
*/ */
public static func xmlParser()->Parser { public static func xmlParser() -> Parser {
return Parser(XmlTreeBuilder()) return Parser(XmlTreeBuilder())
} }
} }

View File

@ -24,17 +24,14 @@ public struct Pattern {
self.pattern = pattern self.pattern = pattern
} }
static public func compile(_ s: String)->Pattern static public func compile(_ s: String) -> Pattern {
{
return Pattern(s) return Pattern(s)
} }
static public func compile(_ s: String, _ op: Int)->Pattern static public func compile(_ s: String, _ op: Int) -> Pattern {
{
return Pattern(s) return Pattern(s)
} }
func validate()throws func validate()throws {
{
_ = try NCRegularExpression(pattern: self.pattern, options:[]) _ = try NCRegularExpression(pattern: self.pattern, options:[])
} }
@ -42,55 +39,49 @@ public struct Pattern {
do { do {
let regex = try NCRegularExpression(pattern: self.pattern, options:[]) let regex = try NCRegularExpression(pattern: self.pattern, options:[])
let nsString = NSString(string: text) let nsString = NSString(string: text)
let results = regex.matches(in: text,options:[], range: NSRange(location: 0, length: nsString.length)) let results = regex.matches(in: text, options:[], range: NSRange(location: 0, length: nsString.length))
return Matcher(results,text) return Matcher(results, text)
} catch let error { } catch let error {
print("invalid regex: \(error.localizedDescription)") print("invalid regex: \(error.localizedDescription)")
return Matcher([],text) return Matcher([], text)
} }
} }
public func toString()->String{ public func toString() -> String {
return pattern return pattern
} }
} }
public class Matcher public class Matcher {
{ let matches: [NCTextCheckingResult]
let matches :[NCTextCheckingResult] let string: String
let string : String var index: Int = -1
var index : Int = -1
public var count : Int { return matches.count} public var count: Int { return matches.count}
init(_ m:[NCTextCheckingResult],_ s: String) init(_ m: [NCTextCheckingResult], _ s: String) {
{
matches = m matches = m
string = s string = s
} }
@discardableResult @discardableResult
public func find() -> Bool public func find() -> Bool {
{
index += 1 index += 1
if(index < matches.count) if(index < matches.count) {
{
return true return true
} }
return false return false
} }
public func group(_ i: Int) -> String? public func group(_ i: Int) -> String? {
{
let b = matches[index] let b = matches[index]
let c = b.range(at:i) let c = b.range(at:i)
if(c.location == NSNotFound) {return nil} if(c.location == NSNotFound) {return nil}
let result = string.substring(c.location,c.length) let result = string.substring(c.location, c.length)
return result return result
} }
public func group() -> String? public func group() -> String? {
{
return group(0) return group(0)
} }
} }

View File

@ -12,8 +12,8 @@ import Foundation
* Parses a CSS selector into an Evaluator tree. * Parses a CSS selector into an Evaluator tree.
*/ */
public class QueryParser { public class QueryParser {
private static let combinators : [String] = [",", ">", "+", "~", " "] private static let combinators: [String] = [",", ">", "+", "~", " "]
private static let AttributeEvals : [String] = ["=", "!=", "^=", "$=", "*=", "~="] private static let AttributeEvals: [String] = ["=", "!=", "^=", "$=", "*=", "~="]
private var tq: TokenQueue private var tq: TokenQueue
private var query: String private var query: String
@ -65,7 +65,7 @@ public class QueryParser {
} }
} }
if (evals.count == 1){ if (evals.count == 1) {
return evals[0] return evals[0]
} }
return CombiningEvaluator.And(evals) return CombiningEvaluator.And(evals)
@ -88,24 +88,15 @@ public class QueryParser {
currentEval = (currentEval as! CombiningEvaluator.Or).rightMostEvaluator() currentEval = (currentEval as! CombiningEvaluator.Or).rightMostEvaluator()
replaceRightMost = true replaceRightMost = true
} }
} } else {
else {
currentEval = CombiningEvaluator.And(evals) currentEval = CombiningEvaluator.And(evals)
rootEval = currentEval rootEval = currentEval
} }
evals.removeAll() evals.removeAll()
// for most combinators: change the current eval into an AND of the current eval and the new eval // for most combinators: change the current eval into an AND of the current eval and the new eval
if (combinator == ">") if (combinator == ">") {currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.ImmediateParent(currentEval!))} else if (combinator == " ") {currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.Parent(currentEval!))} else if (combinator == "+") {currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.ImmediatePreviousSibling(currentEval!))} else if (combinator == "~") {currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.PreviousSibling(currentEval!))} else if (combinator == ",") { // group or.
{currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.ImmediateParent(currentEval!))} let or: CombiningEvaluator.Or
else if (combinator == " ")
{currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.Parent(currentEval!))}
else if (combinator == "+")
{currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.ImmediatePreviousSibling(currentEval!))}
else if (combinator == "~")
{currentEval = CombiningEvaluator.And(newEval, StructuralEvaluator.PreviousSibling(currentEval!))}
else if (combinator == ",") { // group or.
let or : CombiningEvaluator.Or
if ((currentEval as? CombiningEvaluator.Or) != nil) { if ((currentEval as? CombiningEvaluator.Or) != nil) {
or = currentEval as! CombiningEvaluator.Or or = currentEval as! CombiningEvaluator.Or
or.add(newEval) or.add(newEval)
@ -115,33 +106,28 @@ public class QueryParser {
or.add(newEval) or.add(newEval)
} }
currentEval = or currentEval = or
} } else {
else{
throw Exception.Error(type: ExceptionType.SelectorParseException, Message: "Unknown combinator: \(String(combinator))") throw Exception.Error(type: ExceptionType.SelectorParseException, Message: "Unknown combinator: \(String(combinator))")
} }
if (replaceRightMost) {
if (replaceRightMost)
{
(rootEval as! CombiningEvaluator.Or).replaceRightMostEvaluator(currentEval!) (rootEval as! CombiningEvaluator.Or).replaceRightMostEvaluator(currentEval!)
} } else {
else {
rootEval = currentEval rootEval = currentEval
} }
evals.append(rootEval!) evals.append(rootEval!)
} }
private func consumeSubQuery() -> String {
private func consumeSubQuery()->String { let sq: StringBuilder = StringBuilder()
let sq : StringBuilder = StringBuilder()
while (!tq.isEmpty()) { while (!tq.isEmpty()) {
if (tq.matches("(")){ if (tq.matches("(")) {
sq.append("(").append(tq.chompBalanced("(", ")")).append(")") sq.append("(").append(tq.chompBalanced("(", ")")).append(")")
}else if (tq.matches("[")){ } else if (tq.matches("[")) {
sq.append("[").append(tq.chompBalanced("[", "]")).append("]") sq.append("[").append(tq.chompBalanced("[", "]")).append("]")
}else if (tq.matchesAny(QueryParser.combinators)){ } else if (tq.matchesAny(QueryParser.combinators)) {
break break
}else{ } else {
sq.append(tq.consume()) sq.append(tq.consume())
} }
} }
@ -149,61 +135,10 @@ public class QueryParser {
} }
private func findElements()throws { private func findElements()throws {
if (tq.matchChomp("#")) if (tq.matchChomp("#")) {
{
try byId() try byId()
}else if (tq.matchChomp(".")) } else if (tq.matchChomp(".")) {
{ try byClass()} else if (tq.matchesWord() || tq.matches("*|")) {try byTag()} else if (tq.matches("[")) {try byAttribute()} else if (tq.matchChomp("*")) { allElements()} else if (tq.matchChomp(":lt(")) {try indexLessThan()} else if (tq.matchChomp(":gt(")) {try indexGreaterThan()} else if (tq.matchChomp(":eq(")) {try indexEquals()} else if (tq.matches(":has(")) {try has()} else if (tq.matches(":contains(")) {try contains(false)} else if (tq.matches(":containsOwn(")) {try contains(true)} else if (tq.matches(":matches(")) {try matches(false)} else if (tq.matches(":matchesOwn(")) {try matches(true)} else if (tq.matches(":not(")) {try not()} else if (tq.matchChomp(":nth-child(")) {try cssNthChild(false, false)} else if (tq.matchChomp(":nth-last-child(")) {try cssNthChild(true, false)} else if (tq.matchChomp(":nth-of-type(")) {try cssNthChild(false, true)} else if (tq.matchChomp(":nth-last-of-type(")) {try cssNthChild(true, true)} else if (tq.matchChomp(":first-child")) {evals.append(Evaluator.IsFirstChild())} else if (tq.matchChomp(":last-child")) {evals.append(Evaluator.IsLastChild())} else if (tq.matchChomp(":first-of-type")) {evals.append(Evaluator.IsFirstOfType())} else if (tq.matchChomp(":last-of-type")) {evals.append(Evaluator.IsLastOfType())} else if (tq.matchChomp(":only-child")) {evals.append(Evaluator.IsOnlyChild())} else if (tq.matchChomp(":only-of-type")) {evals.append(Evaluator.IsOnlyOfType())} else if (tq.matchChomp(":empty")) {evals.append(Evaluator.IsEmpty())} else if (tq.matchChomp(":root")) {evals.append(Evaluator.IsRoot())} else // unhandled
try byClass()}
else if (tq.matchesWord() || tq.matches("*|"))
{try byTag()}
else if (tq.matches("["))
{try byAttribute()}
else if (tq.matchChomp("*"))
{ allElements()}
else if (tq.matchChomp(":lt("))
{try indexLessThan()}
else if (tq.matchChomp(":gt("))
{try indexGreaterThan()}
else if (tq.matchChomp(":eq("))
{try indexEquals()}
else if (tq.matches(":has("))
{try has()}
else if (tq.matches(":contains("))
{try contains(false)}
else if (tq.matches(":containsOwn("))
{try contains(true)}
else if (tq.matches(":matches("))
{try matches(false)}
else if (tq.matches(":matchesOwn("))
{try matches(true)}
else if (tq.matches(":not("))
{try not()}
else if (tq.matchChomp(":nth-child("))
{try cssNthChild(false, false)}
else if (tq.matchChomp(":nth-last-child("))
{try cssNthChild(true, false)}
else if (tq.matchChomp(":nth-of-type("))
{try cssNthChild(false, true)}
else if (tq.matchChomp(":nth-last-of-type("))
{try cssNthChild(true, true)}
else if (tq.matchChomp(":first-child"))
{evals.append(Evaluator.IsFirstChild())}
else if (tq.matchChomp(":last-child"))
{evals.append(Evaluator.IsLastChild())}
else if (tq.matchChomp(":first-of-type"))
{evals.append(Evaluator.IsFirstOfType())}
else if (tq.matchChomp(":last-of-type"))
{evals.append(Evaluator.IsLastOfType())}
else if (tq.matchChomp(":only-child"))
{evals.append(Evaluator.IsOnlyChild())}
else if (tq.matchChomp(":only-of-type"))
{evals.append(Evaluator.IsOnlyOfType())}
else if (tq.matchChomp(":empty"))
{evals.append(Evaluator.IsEmpty())}
else if (tq.matchChomp(":root"))
{evals.append(Evaluator.IsRoot())}
else // unhandled
{ {
throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse query \(query): unexpected token at \(tq.remainder())") throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse query \(query): unexpected token at \(tq.remainder())")
} }
@ -234,7 +169,7 @@ public class QueryParser {
Evaluator.TagEndsWith(tagName.replacingOccurrences(of: "*|", with: ":").trim().lowercased()))) Evaluator.TagEndsWith(tagName.replacingOccurrences(of: "*|", with: ":").trim().lowercased())))
} else { } else {
// namespaces: if element name is "abc:def", selector must be "abc|def", so flip: // namespaces: if element name is "abc:def", selector must be "abc|def", so flip:
if (tagName.contains("|")){ if (tagName.contains("|")) {
tagName = tagName.replacingOccurrences(of: "|", with: ":") tagName = tagName.replacingOccurrences(of: "|", with: ":")
} }
@ -249,35 +184,25 @@ public class QueryParser {
cq.consumeWhitespace() cq.consumeWhitespace()
if (cq.isEmpty()) { if (cq.isEmpty()) {
if (key.startsWith("^")){ if (key.startsWith("^")) {
evals.append(try Evaluator.AttributeStarting(key.substring(1))) evals.append(try Evaluator.AttributeStarting(key.substring(1)))
}else{ } else {
evals.append(Evaluator.Attribute(key)) evals.append(Evaluator.Attribute(key))
} }
} else { } else {
if (cq.matchChomp("=")){ if (cq.matchChomp("=")) {
evals.append(try Evaluator.AttributeWithValue(key, cq.remainder())) evals.append(try Evaluator.AttributeWithValue(key, cq.remainder()))
} } else if (cq.matchChomp("!=")) {
else if (cq.matchChomp("!=")){
evals.append(try Evaluator.AttributeWithValueNot(key, cq.remainder())) evals.append(try Evaluator.AttributeWithValueNot(key, cq.remainder()))
} } else if (cq.matchChomp("^=")) {
else if (cq.matchChomp("^=")){
evals.append(try Evaluator.AttributeWithValueStarting(key, cq.remainder())) evals.append(try Evaluator.AttributeWithValueStarting(key, cq.remainder()))
} } else if (cq.matchChomp("$=")) {
else if (cq.matchChomp("$=")){
evals.append(try Evaluator.AttributeWithValueEnding(key, cq.remainder())) evals.append(try Evaluator.AttributeWithValueEnding(key, cq.remainder()))
} } else if (cq.matchChomp("*=")) {
else if (cq.matchChomp("*=")){
evals.append(try Evaluator.AttributeWithValueContaining(key, cq.remainder())) evals.append(try Evaluator.AttributeWithValueContaining(key, cq.remainder()))
} } else if (cq.matchChomp("~=")) {
else if (cq.matchChomp("~=")){
evals.append( Evaluator.AttributeWithValueMatching(key, Pattern.compile(cq.remainder()))) evals.append( Evaluator.AttributeWithValueMatching(key, Pattern.compile(cq.remainder())))
}else{ } else {
throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse attribute query '\(query)': unexpected token at '\(cq.remainder())'") throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse attribute query '\(query)': unexpected token at '\(cq.remainder())'")
} }
} }
@ -327,26 +252,21 @@ public class QueryParser {
} else { } else {
throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse nth-index '\(argS)': unexpected format") throw Exception.Error(type: ExceptionType.SelectorParseException, Message:"Could not parse nth-index '\(argS)': unexpected format")
} }
if (ofType){ if (ofType) {
if (backwards){ if (backwards) {
evals.append(Evaluator.IsNthLastOfType(a, b)) evals.append(Evaluator.IsNthLastOfType(a, b))
}else{ } else {
evals.append(Evaluator.IsNthOfType(a, b)) evals.append(Evaluator.IsNthOfType(a, b))
} }
}else { } else {
if (backwards){ if (backwards) {
evals.append(Evaluator.IsNthLastChild(a, b)) evals.append(Evaluator.IsNthLastChild(a, b))
}else{ } else {
evals.append(Evaluator.IsNthChild(a, b)) evals.append(Evaluator.IsNthChild(a, b))
} }
} }
} }
private func consumeIndex()throws->Int { private func consumeIndex()throws->Int {
let indexS: String = tq.chompTo(")").trim() let indexS: String = tq.chompTo(")").trim()
try Validate.isTrue(val: StringUtil.isNumeric(indexS), msg: "Index must be numeric") try Validate.isTrue(val: StringUtil.isNumeric(indexS), msg: "Index must be numeric")
@ -366,9 +286,9 @@ public class QueryParser {
try tq.consume(own ? ":containsOwn" : ":contains") try tq.consume(own ? ":containsOwn" : ":contains")
let searchText: String = TokenQueue.unescape(tq.chompBalanced("(", ")")) let searchText: String = TokenQueue.unescape(tq.chompBalanced("(", ")"))
try Validate.notEmpty(string: searchText, msg: ":contains(text) query must not be empty") try Validate.notEmpty(string: searchText, msg: ":contains(text) query must not be empty")
if (own){ if (own) {
evals.append(Evaluator.ContainsOwnText(searchText)) evals.append(Evaluator.ContainsOwnText(searchText))
}else{ } else {
evals.append(Evaluator.ContainsText(searchText)) evals.append(Evaluator.ContainsText(searchText))
} }
} }
@ -379,9 +299,9 @@ public class QueryParser {
let regex: String = tq.chompBalanced("(", ")") // don't unescape, as regex bits will be escaped let regex: String = tq.chompBalanced("(", ")") // don't unescape, as regex bits will be escaped
try Validate.notEmpty(string: regex, msg: ":matches(regex) query must not be empty") try Validate.notEmpty(string: regex, msg: ":matches(regex) query must not be empty")
if (own){ if (own) {
evals.append(Evaluator.MatchesOwn(Pattern.compile(regex))) evals.append(Evaluator.MatchesOwn(Pattern.compile(regex)))
}else{ } else {
evals.append(Evaluator.Matches(Pattern.compile(regex))) evals.append(Evaluator.Matches(Pattern.compile(regex)))
} }
} }

View File

@ -73,8 +73,8 @@ import Foundation
* @see Element#select(String) * @see Element#select(String)
*/ */
open class Selector { open class Selector {
private let evaluator : Evaluator private let evaluator: Evaluator
private let root : Element private let root: Element
private init(_ query: String, _ root: Element)throws { private init(_ query: String, _ root: Element)throws {
let query = query.trim() let query = query.trim()
@ -127,11 +127,9 @@ open class Selector {
var seenElements: Array<Element> = Array<Element>() var seenElements: Array<Element> = Array<Element>()
// dedupe elements by identity, not equality // dedupe elements by identity, not equality
for root: Element in roots for root: Element in roots {
{
let found: Elements = try select(evaluator, root) let found: Elements = try select(evaluator, root)
for el: Element in found.array() for el: Element in found.array() {
{
if (!seenElements.contains(el)) { if (!seenElements.contains(el)) {
elements.append(el) elements.append(el)
seenElements.append(el) seenElements.append(el)
@ -146,7 +144,7 @@ open class Selector {
} }
// exclude set. package open so that Elements can implement .not() selector. // exclude set. package open so that Elements can implement .not() selector.
static func filterOut(_ elements: Array<Element>, _ outs: Array<Element>)->Elements { static func filterOut(_ elements: Array<Element>, _ outs: Array<Element>) -> Elements {
let output: Elements = Elements() let output: Elements = Elements()
for el: Element in elements { for el: Element in elements {
var found: Bool = false var found: Bool = false
@ -156,7 +154,7 @@ open class Selector {
break break
} }
} }
if (!found){ if (!found) {
output.add(el) output.add(el)
} }
} }

View File

@ -8,7 +8,6 @@
import Foundation import Foundation
public class SimpleDictionary<KeyType: Hashable, ValueType> { public class SimpleDictionary<KeyType: Hashable, ValueType> {
public typealias DictionaryType = [KeyType: ValueType] public typealias DictionaryType = [KeyType: ValueType]
@ -25,17 +24,15 @@ public class SimpleDictionary<KeyType: Hashable, ValueType> {
values.removeValue(forKey: key) values.removeValue(forKey: key)
} }
public func contains(_ key: KeyType) -> Bool { public func contains(_ key: KeyType) -> Bool {
return self.values[key] != nil return self.values[key] != nil
} }
public func put(_ value: ValueType, forKey key: KeyType) { public func put(_ value: ValueType, forKey key: KeyType) {
self.values[key] = value self.values[key] = value
} }
public func get(_ key: KeyType)->ValueType?{ public func get(_ key: KeyType) -> ValueType? {
return self.values[key] return self.values[key]
} }

View File

@ -10,12 +10,12 @@ import Foundation
class StreamReader { class StreamReader {
let encoding : String.Encoding let encoding: String.Encoding
let chunkSize : Int let chunkSize: Int
var fileHandle : FileHandle! var fileHandle: FileHandle!
let delimData : Data let delimData: Data
var buffer : Data var buffer: Data
var atEof : Bool var atEof: Bool
init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8, init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8,
chunkSize: Int = 4096) { chunkSize: Int = 4096) {

View File

@ -18,20 +18,16 @@ extension String {
return String(self[i] as Character) return String(self[i] as Character)
} }
func unicodeScalar(_ i: Int) -> UnicodeScalar {
func unicodeScalar(_ i: Int)->UnicodeScalar{
return self.unicodeScalars.prefix(i+1).last! return self.unicodeScalars.prefix(i+1).last!
} }
func string(_ offset: Int, _ count: Int) -> String {
func string(_ offset: Int, _ count: Int)->String{
let truncStart = self.unicodeScalars.count-offset let truncStart = self.unicodeScalars.count-offset
return String(self.unicodeScalars.suffix(truncStart).prefix(count)) return String(self.unicodeScalars.suffix(truncStart).prefix(count))
} }
static func split(_ value: String, _ offset: Int, _ count: Int) -> String {
static func split(_ value: String,_ offset : Int, _ count: Int) -> String
{
let start = value.index(value.startIndex, offsetBy: offset) let start = value.index(value.startIndex, offsetBy: offset)
let end = value.index(value.startIndex, offsetBy: count+offset) let end = value.index(value.startIndex, offsetBy: count+offset)
let range = start..<end let range = start..<end
@ -46,17 +42,15 @@ extension String {
return (self.trimmingCharacters(in: CharacterSet.whitespaces) == "") return (self.trimmingCharacters(in: CharacterSet.whitespaces) == "")
} }
func startsWith(_ string:String) -> Bool func startsWith(_ string: String) -> Bool {
{
return self.hasPrefix(string) return self.hasPrefix(string)
} }
func indexOf(_ substring: String, _ offset: Int ) -> Int { func indexOf(_ substring: String, _ offset: Int ) -> Int {
if(offset > characters.count){return -1} if(offset > characters.count) {return -1}
let maxIndex = self.characters.count - substring.characters.count let maxIndex = self.characters.count - substring.characters.count
if(maxIndex >= 0) if(maxIndex >= 0) {
{
for index in offset...maxIndex { for index in offset...maxIndex {
let rangeSubstring = self.characters.index(self.startIndex, offsetBy: index)..<self.characters.index(self.startIndex, offsetBy: index + substring.characters.count) let rangeSubstring = self.characters.index(self.startIndex, offsetBy: index)..<self.characters.index(self.startIndex, offsetBy: index + substring.characters.count)
if self.substring(with: rangeSubstring) == substring { if self.substring(with: rangeSubstring) == substring {
@ -71,31 +65,28 @@ extension String {
return self.indexOf(substring, 0) return self.indexOf(substring, 0)
} }
func trim() -> String { func trim() -> String {
return trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) return trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
} }
func equalsIgnoreCase(string:String?)->Bool func equalsIgnoreCase(string: String?) -> Bool {
{ if(string == nil) {return false}
if(string == nil){return false}
return caseInsensitiveCompare(string!) == ComparisonResult.orderedSame return caseInsensitiveCompare(string!) == ComparisonResult.orderedSame
} }
static func toHexString(n:Int)->String{ static func toHexString(n: Int) -> String {
return String(format:"%2x", n) return String(format:"%2x", n)
} }
func toCharArray() -> [Int] { func toCharArray() -> [Int] {
return characters.flatMap{Int($0.unicodeScalar.value)} return characters.flatMap {Int($0.unicodeScalar.value)}
} }
func insert(string:String,ind:Int) -> String { func insert(string: String, ind: Int) -> String {
return String(self.characters.prefix(ind)) + string + String(self.characters.suffix(self.characters.count-ind)) return String(self.characters.prefix(ind)) + string + String(self.characters.suffix(self.characters.count-ind))
} }
func charAt(_ i:Int) -> Character { func charAt(_ i: Int) -> Character {
return self[i] as Character return self[i] as Character
} }
@ -107,23 +98,22 @@ extension String {
return String.split(self, beginIndex, count) return String.split(self, beginIndex, count)
} }
func regionMatches(_ ignoreCase: Bool, _ selfOffset: Int, _ other: String, _ otherOffset: Int, _ length: Int )->Bool{ func regionMatches(_ ignoreCase: Bool, _ selfOffset: Int, _ other: String, _ otherOffset: Int, _ length: Int ) -> Bool {
if ((otherOffset < 0) || (selfOffset < 0) if ((otherOffset < 0) || (selfOffset < 0)
|| (selfOffset > self.characters.count - length) || (selfOffset > self.characters.count - length)
|| (otherOffset > other.characters.count - length)) { || (otherOffset > other.characters.count - length)) {
return false return false
} }
for i in 0..<length for i in 0..<length {
{ let charSelf: Character = self[i+selfOffset]
let charSelf : Character = self[i+selfOffset] let charOther: Character = other[i+otherOffset]
let charOther : Character = other[i+otherOffset] if(ignoreCase) {
if(ignoreCase){ if(charSelf.lowercase != charOther.lowercase) {
if(charSelf.lowercase != charOther.lowercase){
return false return false
} }
}else{ } else {
if(charSelf != charOther){ if(charSelf != charOther) {
return false return false
} }
} }
@ -131,42 +121,40 @@ extension String {
return true return true
} }
func startsWith(_ input: String , _ offset: Int)->Bool func startsWith(_ input: String, _ offset: Int) -> Bool {
{
if ((offset < 0) || (offset > characters.count - input.characters.count)) { if ((offset < 0) || (offset > characters.count - input.characters.count)) {
return false return false
} }
for i in 0..<input.characters.count for i in 0..<input.characters.count {
{ let charSelf: Character = self[i+offset]
let charSelf : Character = self[i+offset] let charOther: Character = input[i]
let charOther : Character = input[i] if(charSelf != charOther) {return false}
if(charSelf != charOther){return false}
} }
return true return true
} }
func replaceFirst(of pattern:String,with replacement:String) -> String { func replaceFirst(of pattern: String, with replacement: String) -> String {
if let range = self.range(of: pattern){ if let range = self.range(of: pattern) {
return self.replacingCharacters(in: range, with: replacement) return self.replacingCharacters(in: range, with: replacement)
}else{ } else {
return self return self
} }
} }
func replaceAll(of pattern:String,with replacement:String,options: NCRegularExpression.Options = []) -> String{ func replaceAll(of pattern: String, with replacement: String, options: NCRegularExpression.Options = []) -> String {
do{ do {
let regex = try NCRegularExpression(pattern: pattern, options: []) let regex = try NCRegularExpression(pattern: pattern, options: [])
let range = NSRange(0..<self.utf16.count) let range = NSRange(0..<self.utf16.count)
return regex.stringByReplacingMatches(in: self, options: [], return regex.stringByReplacingMatches(in: self, options: [],
range: range, withTemplate: replacement) range: range, withTemplate: replacement)
}catch{ } catch {
NSLog("replaceAll error: \(error)") NSLog("replaceAll error: \(error)")
return self return self
} }
} }
func equals(_ s: String?)->Bool{ func equals(_ s: String?) -> Bool {
if(s == nil){return false} if(s == nil) {return false}
return self == s! return self == s!
} }
@ -176,15 +164,12 @@ extension String {
} }
extension String.Encoding {
extension String.Encoding func canEncode(_ string: String) -> Bool {
{
func canEncode(_ string: String) -> Bool
{
return string.cString(using: self) != nil return string.cString(using: self) != nil
} }
public func displayName()->String{ public func displayName() -> String {
switch self { switch self {
case String.Encoding.ascii: return "US-ASCII" case String.Encoding.ascii: return "US-ASCII"
case String.Encoding.nextstep: return "nextstep" case String.Encoding.nextstep: return "nextstep"
@ -214,9 +199,3 @@ extension String.Encoding
} }
} }
} }

View File

@ -46,13 +46,12 @@ open class StringBuilder {
stringValue += string stringValue += string
} }
open func appendCodePoint(_ chr: Character) { open func appendCodePoint(_ chr: Character) {
stringValue = stringValue + String(chr) stringValue = stringValue + String(chr)
} }
open func appendCodePoints(_ chr: [Character]) { open func appendCodePoints(_ chr: [Character]) {
for c in chr{ for c in chr {
stringValue = stringValue + String(c) stringValue = stringValue + String(c)
} }
} }
@ -66,12 +65,11 @@ open class StringBuilder {
} }
open func appendCodePoints(_ chr: [UnicodeScalar]) { open func appendCodePoints(_ chr: [UnicodeScalar]) {
for c in chr{ for c in chr {
stringValue = stringValue + String(c) stringValue = stringValue + String(c)
} }
} }
/** /**
Append a Printable to the object Append a Printable to the object
@ -86,7 +84,7 @@ open class StringBuilder {
} }
@discardableResult @discardableResult
open func insert<T: CustomStringConvertible>(_ offset: Int ,_ value: T) -> StringBuilder { open func insert<T: CustomStringConvertible>(_ offset: Int, _ value: T) -> StringBuilder {
stringValue = stringValue.insert(string: value.description, ind: offset) stringValue = stringValue.insert(string: value.description, ind: offset)
return self return self
} }
@ -160,4 +158,3 @@ public func += <T: CustomStringConvertible>(lhs: StringBuilder, rhs: T) {
public func +(lhs: StringBuilder, rhs: StringBuilder) -> StringBuilder { public func +(lhs: StringBuilder, rhs: StringBuilder) -> StringBuilder {
return StringBuilder(string: lhs.toString() + rhs.toString()) return StringBuilder(string: lhs.toString() + rhs.toString())
} }

View File

@ -11,8 +11,7 @@ import Foundation
/** /**
* A minimal String utility class. Designed for internal jsoup use only. * A minimal String utility class. Designed for internal jsoup use only.
*/ */
open class StringUtil open class StringUtil {
{
enum StringError: Error { enum StringError: Error {
case empty case empty
case short case short
@ -20,7 +19,7 @@ open class StringUtil
} }
// memoised padding up to 10 // memoised padding up to 10
fileprivate static var padding : [String] = ["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "] fileprivate static var padding: [String] = ["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
/** /**
* Join a collection of strings by a seperator * Join a collection of strings by a seperator
@ -28,18 +27,17 @@ open class StringUtil
* @param sep string to place between strings * @param sep string to place between strings
* @return joined string * @return joined string
*/ */
open static func join(_ strings:[String], sep:String) -> String { open static func join(_ strings: [String], sep: String) -> String {
return strings.joined(separator: sep) return strings.joined(separator: sep)
} }
open static func join(_ strings:Set<String>, sep:String) -> String { open static func join(_ strings: Set<String>, sep: String) -> String {
return strings.joined(separator: sep) return strings.joined(separator: sep)
} }
open static func join(_ strings:OrderedSet<String>, sep:String) -> String { open static func join(_ strings: OrderedSet<String>, sep: String) -> String {
return strings.joined(separator: sep) return strings.joined(separator: sep)
} }
// /** // /**
// * Join a collection of strings by a seperator // * Join a collection of strings by a seperator
// * @param strings iterator of string objects // * @param strings iterator of string objects
@ -66,20 +64,19 @@ open class StringUtil
* @param width amount of padding desired * @param width amount of padding desired
* @return string of spaces * width * @return string of spaces * width
*/ */
open static func padding(_ width:Int) -> String{ open static func padding(_ width: Int) -> String {
if(width <= 0){ if(width <= 0) {
return "" return ""
} }
if (width < padding.count){ if (width < padding.count) {
return padding[width] return padding[width]
} }
var out: [Character] = [Character]() var out: [Character] = [Character]()
for _ in 0..<width for _ in 0..<width {
{
out.append(" ") out.append(" ")
} }
return String(out) return String(out)
@ -90,14 +87,13 @@ open class StringUtil
* @param string string to test * @param string string to test
* @return if string is blank * @return if string is blank
*/ */
open static func isBlank(_ string:String) -> Bool { open static func isBlank(_ string: String) -> Bool {
if (string.characters.count == 0){ if (string.characters.count == 0) {
return true return true
} }
for chr in string.characters for chr in string.characters {
{ if (!StringUtil.isWhitespace(chr)) {
if (!StringUtil.isWhitespace(chr)){
return false return false
} }
} }
@ -109,15 +105,13 @@ open class StringUtil
* @param string string to test * @param string string to test
* @return true if only digit chars, false if empty or null or contains non-digit chrs * @return true if only digit chars, false if empty or null or contains non-digit chrs
*/ */
open static func isNumeric(_ string:String) -> Bool { open static func isNumeric(_ string: String) -> Bool {
if (string.characters.count == 0){ if (string.characters.count == 0) {
return false return false
} }
for chr in string.characters for chr in string.characters {
{ if !("0"..."9" ~= chr) {
if !("0"..."9" ~= chr)
{
return false return false
} }
} }
@ -129,8 +123,7 @@ open class StringUtil
* @param c code point to test * @param c code point to test
* @return true if code point is whitespace, false otherwise * @return true if code point is whitespace, false otherwise
*/ */
open static func isWhitespace(_ c:Character) -> Bool open static func isWhitespace(_ c: Character) -> Bool {
{
//(c == " " || c == "\t" || c == "\n" || (c == "\f" ) || c == "\r") //(c == " " || c == "\t" || c == "\n" || (c == "\f" ) || c == "\r")
return c.isWhitespace return c.isWhitespace
} }
@ -141,8 +134,8 @@ open class StringUtil
* @param string content to normalise * @param string content to normalise
* @return normalised string * @return normalised string
*/ */
open static func normaliseWhitespace(_ string:String) -> String { open static func normaliseWhitespace(_ string: String) -> String {
let sb : StringBuilder = StringBuilder.init() let sb: StringBuilder = StringBuilder.init()
appendNormalisedWhitespace(sb, string: string, stripLeading: false) appendNormalisedWhitespace(sb, string: string, stripLeading: false)
return sb.toString() return sb.toString()
} }
@ -153,22 +146,18 @@ open class StringUtil
* @param string string to normalize whitespace within * @param string string to normalize whitespace within
* @param stripLeading set to true if you wish to remove any leading whitespace * @param stripLeading set to true if you wish to remove any leading whitespace
*/ */
open static func appendNormalisedWhitespace(_ accum:StringBuilder, string: String , stripLeading:Bool ) { open static func appendNormalisedWhitespace(_ accum: StringBuilder, string: String, stripLeading: Bool ) {
var lastWasWhite : Bool = false var lastWasWhite: Bool = false
var reachedNonWhite: Bool = false var reachedNonWhite: Bool = false
for c in string.characters for c in string.characters {
{ if (isWhitespace(c)) {
if (isWhitespace(c)) if ((stripLeading && !reachedNonWhite) || lastWasWhite) {
{
if ((stripLeading && !reachedNonWhite) || lastWasWhite){
continue continue
} }
accum.append(" ") accum.append(" ")
lastWasWhite = true lastWasWhite = true
} } else {
else
{
accum.appendCodePoint(c) accum.appendCodePoint(c)
lastWasWhite = false lastWasWhite = false
reachedNonWhite = true reachedNonWhite = true
@ -176,25 +165,24 @@ open class StringUtil
} }
} }
open static func inString(_ needle:String? , haystack:String...) -> Bool { open static func inString(_ needle: String?, haystack: String...) -> Bool {
return inString(needle,haystack) return inString(needle, haystack)
} }
open static func inString(_ needle:String? , _ haystack:[String?]) -> Bool { open static func inString(_ needle: String?, _ haystack: [String?]) -> Bool {
if(needle == nil){return false} if(needle == nil) {return false}
for hay in haystack for hay in haystack {
{ if(hay != nil && hay!.compare(needle!) == ComparisonResult.orderedSame) {
if(hay != nil && hay!.compare(needle!) == ComparisonResult.orderedSame){
return true return true
} }
} }
return false return false
} }
open static func inSorted(_ needle:String, haystack:[String]) -> Bool { open static func inSorted(_ needle: String, haystack: [String]) -> Bool {
return binarySearch(haystack, searchItem: needle) >= 0 return binarySearch(haystack, searchItem: needle) >= 0
} }
open static func binarySearch<T:Comparable>(_ inputArr:Array<T>, searchItem: T)->Int{ open static func binarySearch<T: Comparable>(_ inputArr: Array<T>, searchItem: T) -> Int {
var lowerIndex = 0 var lowerIndex = 0
var upperIndex = inputArr.count - 1 var upperIndex = inputArr.count - 1
@ -222,10 +210,9 @@ open class StringUtil
* @throws MalformedURLException if an error occurred generating the URL * @throws MalformedURLException if an error occurred generating the URL
*/ */
//NOTE: Not sure it work //NOTE: Not sure it work
open static func resolve(_ base:URL, relUrl: String ) -> URL? { open static func resolve(_ base: URL, relUrl: String ) -> URL? {
var base = base var base = base
if(base.pathComponents.count == 0 && base.absoluteString.characters.last != "/" && !base.isFileURL) if(base.pathComponents.count == 0 && base.absoluteString.characters.last != "/" && !base.isFileURL) {
{
base = base.appendingPathComponent("/", isDirectory: false) base = base.appendingPathComponent("/", isDirectory: false)
} }
let u = URL(string: relUrl, relativeTo : base) let u = URL(string: relUrl, relativeTo : base)
@ -238,22 +225,21 @@ open class StringUtil
* @param relUrl the relative URL to resolve. (If it's already absolute, it will be returned) * @param relUrl the relative URL to resolve. (If it's already absolute, it will be returned)
* @return an absolute URL if one was able to be generated, or the empty string if not * @return an absolute URL if one was able to be generated, or the empty string if not
*/ */
open static func resolve(_ baseUrl : String , relUrl : String ) -> String { open static func resolve(_ baseUrl: String, relUrl: String ) -> String {
let base = URL(string: baseUrl) let base = URL(string: baseUrl)
if(base == nil || base?.scheme == nil) if(base == nil || base?.scheme == nil) {
{
let abs = URL(string: relUrl) let abs = URL(string: relUrl)
return abs != nil && abs?.scheme != nil ? abs!.absoluteURL.absoluteString : "" return abs != nil && abs?.scheme != nil ? abs!.absoluteURL.absoluteString : ""
}else{ } else {
let url = resolve(base!, relUrl: relUrl) let url = resolve(base!, relUrl: relUrl)
if(url != nil){ if(url != nil) {
let ext = url!.absoluteURL.absoluteString let ext = url!.absoluteURL.absoluteString
return ext return ext
} }
if(base != nil && base?.scheme != nil){ if(base != nil && base?.scheme != nil) {
let ext = base!.absoluteString let ext = base!.absoluteString
return ext return ext
} }
@ -277,15 +263,3 @@ open class StringUtil
} }
} }

View File

@ -11,83 +11,81 @@ import Foundation
/** /**
* Base structural evaluator. * Base structural evaluator.
*/ */
public class StructuralEvaluator : Evaluator { public class StructuralEvaluator: Evaluator {
let evaluator: Evaluator let evaluator: Evaluator
public init(_ evaluator: Evaluator) { public init(_ evaluator: Evaluator) {
self.evaluator = evaluator self.evaluator = evaluator
} }
public class Root : Evaluator { public class Root: Evaluator {
public override func matches(_ root: Element, _ element: Element)->Bool { public override func matches(_ root: Element, _ element: Element) -> Bool {
return root === element return root === element
} }
} }
public class Has : StructuralEvaluator { public class Has: StructuralEvaluator {
public override init(_ evaluator: Evaluator) { public override init(_ evaluator: Evaluator) {
super.init(evaluator) super.init(evaluator)
} }
public override func matches(_ root: Element, _ element: Element)throws->Bool { public override func matches(_ root: Element, _ element: Element)throws->Bool {
for e in try element.getAllElements().array() { for e in try element.getAllElements().array() {
do{ do {
if(e != element){ if(e != element) {
if ((try evaluator.matches(root, e))) if ((try evaluator.matches(root, e))) {
{
return true return true
} }
} }
}catch{} } catch {}
} }
return false return false
} }
public override func toString() -> String {
public override func toString()->String {
return ":has(\(evaluator.toString()))" return ":has(\(evaluator.toString()))"
} }
} }
public class Not : StructuralEvaluator { public class Not: StructuralEvaluator {
public override init(_ evaluator: Evaluator) { public override init(_ evaluator: Evaluator) {
super.init(evaluator) super.init(evaluator)
} }
public override func matches(_ root: Element, _ node: Element)->Bool { public override func matches(_ root: Element, _ node: Element) -> Bool {
do{ do {
return try !evaluator.matches(root, node) return try !evaluator.matches(root, node)
}catch{} } catch {}
return false return false
} }
public override func toString()->String { public override func toString() -> String {
return ":not\(evaluator.toString())" return ":not\(evaluator.toString())"
} }
} }
public class Parent : StructuralEvaluator { public class Parent: StructuralEvaluator {
public override init(_ evaluator: Evaluator) { public override init(_ evaluator: Evaluator) {
super.init(evaluator) super.init(evaluator)
} }
public override func matches(_ root: Element, _ element: Element)->Bool { public override func matches(_ root: Element, _ element: Element) -> Bool {
if (root == element){ if (root == element) {
return false return false
} }
var parent = element.parent() var parent = element.parent()
while (true) { while (true) {
do{ do {
if parent != nil{ if parent != nil {
if (try evaluator.matches(root, parent!)){ if (try evaluator.matches(root, parent!)) {
return true return true
} }
} }
}catch{} } catch {}
if (parent == root){ if (parent == root) {
break break
} }
parent = parent?.parent() parent = parent?.parent()
@ -95,83 +93,83 @@ public class StructuralEvaluator : Evaluator {
return false return false
} }
public override func toString()->String { public override func toString() -> String {
return ":parent\(evaluator.toString())" return ":parent\(evaluator.toString())"
} }
} }
public class ImmediateParent : StructuralEvaluator { public class ImmediateParent: StructuralEvaluator {
public override init(_ evaluator: Evaluator) { public override init(_ evaluator: Evaluator) {
super.init(evaluator) super.init(evaluator)
} }
public override func matches(_ root: Element, _ element: Element)->Bool { public override func matches(_ root: Element, _ element: Element) -> Bool {
if (root == element){ if (root == element) {
return false return false
} }
if let parent = element.parent(){ if let parent = element.parent() {
do{ do {
return try evaluator.matches(root, parent) return try evaluator.matches(root, parent)
}catch{} } catch {}
} }
return false return false
} }
public override func toString()->String { public override func toString() -> String {
return ":ImmediateParent\(evaluator.toString())" return ":ImmediateParent\(evaluator.toString())"
} }
} }
public class PreviousSibling : StructuralEvaluator { public class PreviousSibling: StructuralEvaluator {
public override init(_ evaluator: Evaluator) { public override init(_ evaluator: Evaluator) {
super.init(evaluator) super.init(evaluator)
} }
public override func matches(_ root: Element, _ element: Element)throws->Bool { public override func matches(_ root: Element, _ element: Element)throws->Bool {
if (root == element){ if (root == element) {
return false return false
} }
var prev = try element.previousElementSibling() var prev = try element.previousElementSibling()
while (prev != nil) { while (prev != nil) {
do{ do {
if (try evaluator.matches(root, prev!)){ if (try evaluator.matches(root, prev!)) {
return true return true
} }
}catch{} } catch {}
prev = try prev!.previousElementSibling() prev = try prev!.previousElementSibling()
} }
return false return false
} }
public override func toString()->String { public override func toString() -> String {
return ":prev*\(evaluator.toString())" return ":prev*\(evaluator.toString())"
} }
} }
class ImmediatePreviousSibling : StructuralEvaluator { class ImmediatePreviousSibling: StructuralEvaluator {
public override init(_ evaluator: Evaluator) { public override init(_ evaluator: Evaluator) {
super.init(evaluator) super.init(evaluator)
} }
public override func matches(_ root: Element, _ element: Element)throws->Bool { public override func matches(_ root: Element, _ element: Element)throws->Bool {
if (root == element){ if (root == element) {
return false return false
} }
if let prev = try element.previousElementSibling(){ if let prev = try element.previousElementSibling() {
do{ do {
return try evaluator.matches(root, prev) return try evaluator.matches(root, prev)
}catch{} } catch {}
} }
return false return false
} }
public override func toString()->String { public override func toString() -> String {
return ":prev\(evaluator.toString())" return ":prev\(evaluator.toString())"
} }
} }

View File

@ -8,12 +8,11 @@
import Foundation import Foundation
/** /**
The core public access point to the jsoup functionality. The core public access point to the jsoup functionality.
*/ */
open class SwiftSoup { open class SwiftSoup {
private init(){} private init() {}
/** /**
Parse HTML into a Document. The parser will make a sensible, balanced document tree out of any HTML. Parse HTML into a Document. The parser will make a sensible, balanced document tree out of any HTML.
@ -234,5 +233,4 @@ open class SwiftSoup {
return try clean.body()?.html() return try clean.body()?.html()
} }
} }

View File

@ -8,28 +8,27 @@
import Foundation import Foundation
open class Tag : Hashable open class Tag: Hashable {
{
// map of known tags // map of known tags
static var tags: Dictionary<String, Tag> = { static var tags: Dictionary<String, Tag> = {
do{ do {
return try Tag.initializeMaps() return try Tag.initializeMaps()
}catch{ } catch {
preconditionFailure("This method must be overridden") preconditionFailure("This method must be overridden")
} }
return Dictionary<String, Tag>() return Dictionary<String, Tag>()
}() }()
fileprivate var _tagName : String fileprivate var _tagName: String
fileprivate var _isBlock : Bool = true // block or inline fileprivate var _isBlock: Bool = true // block or inline
fileprivate var _formatAsBlock : Bool = true // should be formatted as a block fileprivate var _formatAsBlock: Bool = true // should be formatted as a block
fileprivate var _canContainBlock : Bool = true // Can this tag hold block level tags? fileprivate var _canContainBlock: Bool = true // Can this tag hold block level tags?
fileprivate var _canContainInline : Bool = true // only pcdata if not fileprivate var _canContainInline: Bool = true // only pcdata if not
fileprivate var _empty : Bool = false // can hold nothing e.g. img fileprivate var _empty: Bool = false // can hold nothing e.g. img
fileprivate var _selfClosing : Bool = false // can self close (<foo />). used for unknown tags that self close, without forcing them as empty. fileprivate var _selfClosing: Bool = false // can self close (<foo />). used for unknown tags that self close, without forcing them as empty.
fileprivate var _preserveWhitespace : Bool = false // for pre, textarea, script etc fileprivate var _preserveWhitespace: Bool = false // for pre, textarea, script etc
fileprivate var _formList : Bool = false // a control that appears in forms: input, textarea, output etc fileprivate var _formList: Bool = false // a control that appears in forms: input, textarea, output etc
fileprivate var _formSubmit : Bool = false // a control that can be submitted in a form: input etc fileprivate var _formSubmit: Bool = false // a control that can be submitted in a form: input etc
public init(_ tagName: String) { public init(_ tagName: String) {
self._tagName = tagName self._tagName = tagName
@ -40,7 +39,7 @@ open class Tag : Hashable
* *
* @return the tag's name * @return the tag's name
*/ */
open func getName()->String { open func getName() -> String {
return self._tagName return self._tagName
} }
@ -56,7 +55,7 @@ open class Tag : Hashable
*/ */
open static func valueOf(_ tagName: String, _ settings: ParseSettings)throws->Tag { open static func valueOf(_ tagName: String, _ settings: ParseSettings)throws->Tag {
var tagName = tagName var tagName = tagName
var tag : Tag? = Tag.tags[tagName] var tag: Tag? = Tag.tags[tagName]
if (tag == nil) { if (tag == nil) {
tagName = settings.normalizeTag(tagName) tagName = settings.normalizeTag(tagName)
@ -91,7 +90,7 @@ open class Tag : Hashable
* *
* @return if block tag * @return if block tag
*/ */
open func isBlock()->Bool { open func isBlock() -> Bool {
return _isBlock return _isBlock
} }
@ -100,7 +99,7 @@ open class Tag : Hashable
* *
* @return if should be formatted as block or inline * @return if should be formatted as block or inline
*/ */
open func formatAsBlock()->Bool { open func formatAsBlock() -> Bool {
return _formatAsBlock return _formatAsBlock
} }
@ -109,7 +108,7 @@ open class Tag : Hashable
* *
* @return if tag can contain block tags * @return if tag can contain block tags
*/ */
open func canContainBlock()->Bool { open func canContainBlock() -> Bool {
return _canContainBlock return _canContainBlock
} }
@ -118,7 +117,7 @@ open class Tag : Hashable
* *
* @return if this tag is an inline tag. * @return if this tag is an inline tag.
*/ */
open func isInline()->Bool { open func isInline() -> Bool {
return !_isBlock return !_isBlock
} }
@ -127,7 +126,7 @@ open class Tag : Hashable
* *
* @return if this tag is a data only tag * @return if this tag is a data only tag
*/ */
open func isData()->Bool { open func isData() -> Bool {
return !_canContainInline && !isEmpty() return !_canContainInline && !isEmpty()
} }
@ -136,7 +135,7 @@ open class Tag : Hashable
* *
* @return if this is an empty tag * @return if this is an empty tag
*/ */
open func isEmpty()->Bool { open func isEmpty() -> Bool {
return _empty return _empty
} }
@ -145,7 +144,7 @@ open class Tag : Hashable
* *
* @return if this tag should be output as self closing. * @return if this tag should be output as self closing.
*/ */
open func isSelfClosing()->Bool { open func isSelfClosing() -> Bool {
return _empty || _selfClosing return _empty || _selfClosing
} }
@ -154,7 +153,7 @@ open class Tag : Hashable
* *
* @return if a known tag * @return if a known tag
*/ */
open func isKnownTag()->Bool { open func isKnownTag() -> Bool {
return Tag.tags[_tagName] != nil return Tag.tags[_tagName] != nil
} }
@ -164,7 +163,7 @@ open class Tag : Hashable
* @param tagName name of tag * @param tagName name of tag
* @return if known HTML tag * @return if known HTML tag
*/ */
open static func isKnownTag(_ tagName: String)->Bool { open static func isKnownTag(_ tagName: String) -> Bool {
return Tag.tags[tagName] != nil return Tag.tags[tagName] != nil
} }
@ -173,7 +172,7 @@ open class Tag : Hashable
* *
* @return if preserve whitepace * @return if preserve whitepace
*/ */
public func preserveWhitespace()->Bool { public func preserveWhitespace() -> Bool {
return _preserveWhitespace return _preserveWhitespace
} }
@ -181,7 +180,7 @@ open class Tag : Hashable
* Get if this tag represents a control associated with a form. E.g. input, textarea, output * Get if this tag represents a control associated with a form. E.g. input, textarea, output
* @return if associated with a form * @return if associated with a form
*/ */
public func isFormListed()->Bool { public func isFormListed() -> Bool {
return _formList return _formList
} }
@ -189,12 +188,12 @@ open class Tag : Hashable
* Get if this tag represents an element that should be submitted with a form. E.g. input, option * Get if this tag represents an element that should be submitted with a form. E.g. input, option
* @return if submittable with a form * @return if submittable with a form
*/ */
public func isFormSubmittable()->Bool { public func isFormSubmittable() -> Bool {
return _formSubmit return _formSubmit
} }
@discardableResult @discardableResult
func setSelfClosing() ->Tag{ func setSelfClosing() -> Tag {
_selfClosing = true _selfClosing = true
return self return self
} }
@ -207,14 +206,13 @@ open class Tag : Hashable
/// - Parameters: /// - Parameters:
/// - lhs: A value to compare. /// - lhs: A value to compare.
/// - rhs: Another value to compare. /// - rhs: Another value to compare.
static public func ==(lhs: Tag, rhs: Tag) -> Bool static public func ==(lhs: Tag, rhs: Tag) -> Bool {
{
let this = lhs let this = lhs
let o = rhs let o = rhs
if (this === o) {return true} if (this === o) {return true}
if (type(of:this) != type(of:o)) {return false} if (type(of:this) != type(of:o)) {return false}
let tag : Tag = o let tag: Tag = o
if (lhs._tagName != tag._tagName) {return false} if (lhs._tagName != tag._tagName) {return false}
if (lhs._canContainBlock != tag._canContainBlock) {return false} if (lhs._canContainBlock != tag._canContainBlock) {return false}
@ -228,8 +226,7 @@ open class Tag : Hashable
return lhs._formSubmit == tag._formSubmit return lhs._formSubmit == tag._formSubmit
} }
public func equals(_ tag : Tag)->Bool public func equals(_ tag: Tag) -> Bool {
{
return self == tag return self == tag
} }
@ -237,9 +234,8 @@ open class Tag : Hashable
/// ///
/// Hash values are not guaranteed to be equal across different executions of /// 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. /// your program. Do not save hash values to use during a future execution.
public var hashValue: Int public var hashValue: Int {
{ var result: Int = _tagName.hashValue
var result : Int = _tagName.hashValue
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _isBlock ? 1 : 0).0 result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _isBlock ? 1 : 0).0
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _formatAsBlock ? 1 : 0).0 result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _formatAsBlock ? 1 : 0).0
result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _canContainBlock ? 1 : 0).0 result = Int.addWithOverflow(Int.multiplyWithOverflow(31, result).0, _canContainBlock ? 1 : 0).0
@ -252,14 +248,13 @@ open class Tag : Hashable
return result return result
} }
open func toString() -> String {
open func toString()->String {
return _tagName return _tagName
} }
// internal static initialisers: // internal static initialisers:
// prepped from http://www.w3.org/TR/REC-html40/sgml/dtd.html and other sources // prepped from http://www.w3.org/TR/REC-html40/sgml/dtd.html and other sources
private static let blockTags : [String] = [ private static let blockTags: [String] = [
"html", "head", "body", "frameset", "script", "noscript", "style", "meta", "link", "title", "frame", "html", "head", "body", "frameset", "script", "noscript", "style", "meta", "link", "title", "frame",
"noframes", "section", "nav", "aside", "hgroup", "header", "footer", "p", "h1", "h2", "h3", "h4", "h5", "h6", "noframes", "section", "nav", "aside", "hgroup", "header", "footer", "p", "h1", "h2", "h3", "h4", "h5", "h6",
"ul", "ol", "pre", "div", "blockquote", "hr", "address", "figure", "figcaption", "form", "fieldset", "ins", "ul", "ol", "pre", "div", "blockquote", "hr", "address", "figure", "figcaption", "form", "fieldset", "ins",
@ -267,7 +262,7 @@ open class Tag : Hashable
"td", "video", "audio", "canvas", "details", "menu", "plaintext", "template", "article", "main", "td", "video", "audio", "canvas", "details", "menu", "plaintext", "template", "article", "main",
"svg", "math" "svg", "math"
] ]
private static let inlineTags : [String] = [ private static let inlineTags: [String] = [
"object", "base", "font", "tt", "i", "b", "u", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd", "object", "base", "font", "tt", "i", "b", "u", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd",
"var", "cite", "abbr", "time", "acronym", "mark", "ruby", "rt", "rp", "a", "img", "br", "wbr", "map", "q", "var", "cite", "abbr", "time", "acronym", "mark", "ruby", "rt", "rp", "a", "img", "br", "wbr", "map", "q",
"sub", "sup", "bdo", "iframe", "embed", "span", "input", "select", "textarea", "label", "button", "optgroup", "sub", "sup", "bdo", "iframe", "embed", "span", "input", "select", "textarea", "label", "button", "optgroup",
@ -275,29 +270,27 @@ open class Tag : Hashable
"summary", "command", "device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track", "summary", "command", "device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track",
"data", "bdi" "data", "bdi"
] ]
private static let emptyTags : [String] = [ private static let emptyTags: [String] = [
"meta", "link", "base", "frame", "img", "br", "wbr", "embed", "hr", "input", "keygen", "col", "command", "meta", "link", "base", "frame", "img", "br", "wbr", "embed", "hr", "input", "keygen", "col", "command",
"device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track" "device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track"
] ]
private static let formatAsInlineTags : [String] = [ private static let formatAsInlineTags: [String] = [
"title", "a", "p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "address", "li", "th", "td", "script", "style", "title", "a", "p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "address", "li", "th", "td", "script", "style",
"ins", "del", "s" "ins", "del", "s"
] ]
private static let preserveWhitespaceTags : [String] = [ private static let preserveWhitespaceTags: [String] = [
"pre", "plaintext", "title", "textarea" "pre", "plaintext", "title", "textarea"
// script is not here as it is a data node, which always preserve whitespace // script is not here as it is a data node, which always preserve whitespace
] ]
// todo: I think we just need submit tags, and can scrub listed // todo: I think we just need submit tags, and can scrub listed
private static let formListedTags : [String] = [ private static let formListedTags: [String] = [
"button", "fieldset", "input", "keygen", "object", "output", "select", "textarea" "button", "fieldset", "input", "keygen", "object", "output", "select", "textarea"
] ]
private static let formSubmitTags : [String] = [ private static let formSubmitTags: [String] = [
"input", "keygen", "object", "select", "textarea" "input", "keygen", "object", "select", "textarea"
] ]
static private func initializeMaps()throws->Dictionary<String, Tag> {
static private func initializeMaps()throws->Dictionary<String, Tag>
{
var dict = Dictionary<String, Tag>() var dict = Dictionary<String, Tag>()
// creates // creates
@ -348,9 +341,3 @@ open class Tag : Hashable
return dict return dict
} }
} }

View File

@ -11,14 +11,14 @@ import Foundation
/** /**
A text node. A text node.
*/ */
open class TextNode : Node { open class TextNode: Node {
/* /*
TextNode is a node, and so by default comes with attributes and children. The attributes are seldom used, but use TextNode is a node, and so by default comes with attributes and children. The attributes are seldom used, but use
memory, and the child nodes are never used. So we don't have them, and override accessors to attributes to create memory, and the child nodes are never used. So we don't have them, and override accessors to attributes to create
them as needed on the fly. them as needed on the fly.
*/ */
private static let TEXT_KEY : String = "text" private static let TEXT_KEY: String = "text"
var _text : String var _text: String
/** /**
Create a new TextNode representing the supplied (unencoded) text). Create a new TextNode representing the supplied (unencoded) text).
@ -34,7 +34,7 @@ open class TextNode : Node {
} }
open override func nodeName()->String { open override func nodeName() -> String {
return "#text" return "#text"
} }
@ -43,7 +43,7 @@ open class TextNode : Node {
* @return Unencoded, normalised text. * @return Unencoded, normalised text.
* @see TextNode#getWholeText() * @see TextNode#getWholeText()
*/ */
open func text()->String { open func text() -> String {
return TextNode.normaliseWhitespace(getWholeText()) return TextNode.normaliseWhitespace(getWholeText())
} }
@ -53,14 +53,14 @@ open class TextNode : Node {
* @return this, for chaining * @return this, for chaining
*/ */
@discardableResult @discardableResult
public func text(_ text: String)->TextNode { public func text(_ text: String) -> TextNode {
self._text = text self._text = text
guard let attributes = attributes else { guard let attributes = attributes else {
return self return self
} }
do{ do {
try attributes.put(TextNode.TEXT_KEY, text) try attributes.put(TextNode.TEXT_KEY, text)
}catch{ } catch {
} }
return self return self
@ -70,7 +70,7 @@ open class TextNode : Node {
Get the (unencoded) text of this text node, including any newlines and spaces present in the original. Get the (unencoded) text of this text node, including any newlines and spaces present in the original.
@return text @return text
*/ */
open func getWholeText()->String { open func getWholeText() -> String {
return attributes == nil ? _text : attributes!.get(key: TextNode.TEXT_KEY) return attributes == nil ? _text : attributes!.get(key: TextNode.TEXT_KEY)
} }
@ -78,7 +78,7 @@ open class TextNode : Node {
Test if this text node is blank -- that is, empty or only whitespace (including newlines). Test if this text node is blank -- that is, empty or only whitespace (including newlines).
@return true if this document is empty or only whitespace, false if it contains any text content. @return true if this document is empty or only whitespace, false if it contains any text content.
*/ */
open func isBlank()->Bool { open func isBlank() -> Bool {
return StringUtil.isBlank(getWholeText()) return StringUtil.isBlank(getWholeText())
} }
@ -92,25 +92,24 @@ open class TextNode : Node {
try Validate.isTrue(val: offset >= 0, msg: "Split offset must be not be negative") try Validate.isTrue(val: offset >= 0, msg: "Split offset must be not be negative")
try Validate.isTrue(val: offset < _text.characters.count, msg: "Split offset must not be greater than current text length") try Validate.isTrue(val: offset < _text.characters.count, msg: "Split offset must not be greater than current text length")
let head : String = getWholeText().substring(0, offset) let head: String = getWholeText().substring(0, offset)
let tail : String = getWholeText().substring(offset) let tail: String = getWholeText().substring(offset)
text(head) text(head)
let tailNode : TextNode = TextNode(tail, self.getBaseUri()) let tailNode: TextNode = TextNode(tail, self.getBaseUri())
if (parent() != nil){ if (parent() != nil) {
try parent()?.addChildren(siblingIndex+1, tailNode) try parent()?.addChildren(siblingIndex+1, tailNode)
} }
return tailNode return tailNode
} }
override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings)throws override func outerHtmlHead(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings)throws {
{
if (out.prettyPrint() && if (out.prettyPrint() &&
((siblingIndex == 0 && (parentNode as? Element) != nil && (parentNode as! Element).tag().formatAsBlock() && !isBlank()) || ((siblingIndex == 0 && (parentNode as? Element) != nil && (parentNode as! Element).tag().formatAsBlock() && !isBlank()) ||
(out.outline() && siblingNodes().count > 0 && !isBlank()) )){ (out.outline() && siblingNodes().count > 0 && !isBlank()) )) {
indent(accum, depth, out) indent(accum, depth, out)
} }
let par : Element? = parent() as? Element let par: Element? = parent() as? Element
let normaliseWhite = out.prettyPrint() && par != nil && !Element.preserveWhitespace(par!) let normaliseWhite = out.prettyPrint() && par != nil && !Element.preserveWhitespace(par!)
Entities.escape(accum, getWholeText(), out, false, normaliseWhite, false) Entities.escape(accum, getWholeText(), out, false, normaliseWhite, false)
@ -119,7 +118,6 @@ open class TextNode : Node {
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) { override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {
} }
open override func toString()throws->String { open override func toString()throws->String {
return try outerHtml() return try outerHtml()
} }
@ -131,21 +129,21 @@ open class TextNode : Node {
* @return TextNode containing unencoded data (e.g. &lt;) * @return TextNode containing unencoded data (e.g. &lt;)
*/ */
open static func createFromEncoded(_ encodedText: String, _ baseUri: String)throws->TextNode { open static func createFromEncoded(_ encodedText: String, _ baseUri: String)throws->TextNode {
let text : String = try Entities.unescape(encodedText) let text: String = try Entities.unescape(encodedText)
return TextNode(text, baseUri) return TextNode(text, baseUri)
} }
static open func normaliseWhitespace(_ text: String)->String { static open func normaliseWhitespace(_ text: String) -> String {
let _text = StringUtil.normaliseWhitespace(text) let _text = StringUtil.normaliseWhitespace(text)
return _text return _text
} }
static open func stripLeadingWhitespace(_ text: String)->String { static open func stripLeadingWhitespace(_ text: String) -> String {
return text.replaceFirst(of: "^\\s+", with: "") return text.replaceFirst(of: "^\\s+", with: "")
//return text.replaceFirst("^\\s+", "") //return text.replaceFirst("^\\s+", "")
} }
static open func lastCharIsWhitespace(_ sb: StringBuilder)->Bool { static open func lastCharIsWhitespace(_ sb: StringBuilder) -> Bool {
return sb.toString().characters.last == " " return sb.toString().characters.last == " "
} }
@ -153,19 +151,18 @@ open class TextNode : Node {
private func ensureAttributes() { private func ensureAttributes() {
if (attributes == nil) { if (attributes == nil) {
attributes = Attributes() attributes = Attributes()
do{ do {
try attributes?.put(TextNode.TEXT_KEY, _text) try attributes?.put(TextNode.TEXT_KEY, _text)
}catch{} } catch {}
} }
} }
open override func attr(_ attributeKey: String)throws->String { open override func attr(_ attributeKey: String)throws->String {
ensureAttributes() ensureAttributes()
return try super.attr(attributeKey) return try super.attr(attributeKey)
} }
open override func getAttributes()->Attributes { open override func getAttributes() -> Attributes {
ensureAttributes() ensureAttributes()
return super.getAttributes()! return super.getAttributes()!
} }
@ -175,38 +172,32 @@ open class TextNode : Node {
return try super.attr(attributeKey, attributeValue) return try super.attr(attributeKey, attributeValue)
} }
open override func hasAttr(_ attributeKey: String) -> Bool {
open override func hasAttr(_ attributeKey: String)->Bool {
ensureAttributes() ensureAttributes()
return super.hasAttr(attributeKey) return super.hasAttr(attributeKey)
} }
open override func removeAttr(_ attributeKey: String)throws->Node { open override func removeAttr(_ attributeKey: String)throws->Node {
ensureAttributes() ensureAttributes()
return try super.removeAttr(attributeKey) return try super.removeAttr(attributeKey)
} }
open override func absUrl(_ attributeKey: String)throws->String { open override func absUrl(_ attributeKey: String)throws->String {
ensureAttributes() ensureAttributes()
return try super.absUrl(attributeKey) return try super.absUrl(attributeKey)
} }
public override func copy(with zone: NSZone? = nil) -> Any public override func copy(with zone: NSZone? = nil) -> Any {
{ let clone = TextNode(_text, baseUri)
let clone = TextNode(_text,baseUri)
return super.copy(clone: clone) return super.copy(clone: clone)
} }
public override func copy(parent: Node?)->Node public override func copy(parent: Node?) -> Node {
{ let clone = TextNode(_text, baseUri)
let clone = TextNode(_text,baseUri) return super.copy(clone: clone, parent: parent)
return super.copy(clone: clone,parent: parent)
} }
public override func copy(clone: Node, parent: Node?)->Node public override func copy(clone: Node, parent: Node?) -> Node {
{ return super.copy(clone: clone, parent: parent)
return super.copy(clone: clone,parent: parent)
} }
} }

View File

@ -8,15 +8,13 @@
import Foundation import Foundation
open class Token open class Token {
{ var type: TokenType = TokenType.Doctype
var type : TokenType = TokenType.Doctype
private init() { private init() {
} }
func tokenType()->String func tokenType() -> String {
{
return String(describing: type(of: self)) return String(describing: type(of: self))
} }
@ -25,8 +23,7 @@ open class Token
* piece of data, which immediately get GCed. * piece of data, which immediately get GCed.
*/ */
@discardableResult @discardableResult
public func reset()->Token public func reset() -> Token {
{
preconditionFailure("This method must be overridden") preconditionFailure("This method must be overridden")
} }
@ -38,11 +35,11 @@ open class Token
return String(describing: type(of: self)) return String(describing: type(of: self))
} }
final class Doctype : Token { final class Doctype: Token {
let name: StringBuilder = StringBuilder() let name: StringBuilder = StringBuilder()
let publicIdentifier: StringBuilder = StringBuilder() let publicIdentifier: StringBuilder = StringBuilder()
let systemIdentifier : StringBuilder = StringBuilder() let systemIdentifier: StringBuilder = StringBuilder()
var forceQuirks : Bool = false var forceQuirks: Bool = false
override init() { override init() {
super.init() super.init()
@ -50,7 +47,7 @@ open class Token
} }
@discardableResult @discardableResult
override func reset()->Token { override func reset() -> Token {
Token.reset(name) Token.reset(name)
Token.reset(publicIdentifier) Token.reset(publicIdentifier)
Token.reset(systemIdentifier) Token.reset(systemIdentifier)
@ -58,41 +55,41 @@ open class Token
return self return self
} }
func getName()->String { func getName() -> String {
return name.toString() return name.toString()
} }
func getPublicIdentifier()->String { func getPublicIdentifier() -> String {
return publicIdentifier.toString() return publicIdentifier.toString()
} }
open func getSystemIdentifier()->String { open func getSystemIdentifier() -> String {
return systemIdentifier.toString() return systemIdentifier.toString()
} }
open func isForceQuirks()->Bool { open func isForceQuirks() -> Bool {
return forceQuirks return forceQuirks
} }
} }
class Tag : Token { class Tag: Token {
public var _tagName: String? public var _tagName: String?
public var _normalName: String? // lc version of tag name, for case insensitive tree build public var _normalName: String? // lc version of tag name, for case insensitive tree build
private var _pendingAttributeName: String? // attribute names are generally caught in one hop, not accumulated private var _pendingAttributeName: String? // attribute names are generally caught in one hop, not accumulated
private let _pendingAttributeValue : StringBuilder = StringBuilder() // but values are accumulated, from e.g. & in hrefs private let _pendingAttributeValue: StringBuilder = StringBuilder() // but values are accumulated, from e.g. & in hrefs
private var _pendingAttributeValueS: String? // try to get attr vals in one shot, vs Builder private var _pendingAttributeValueS: String? // try to get attr vals in one shot, vs Builder
private var _hasEmptyAttributeValue : Bool = false // distinguish boolean attribute from empty string value private var _hasEmptyAttributeValue: Bool = false // distinguish boolean attribute from empty string value
private var _hasPendingAttributeValue: Bool = false private var _hasPendingAttributeValue: Bool = false
public var _selfClosing : Bool = false public var _selfClosing: Bool = false
// start tags get attributes on construction. End tags get attributes on first new attribute (but only for parser convenience, not used). // start tags get attributes on construction. End tags get attributes on first new attribute (but only for parser convenience, not used).
public var _attributes : Attributes = Attributes() public var _attributes: Attributes = Attributes()
override init() { override init() {
super.init() super.init()
} }
@discardableResult @discardableResult
override func reset()->Tag { override func reset() -> Tag {
_tagName = nil _tagName = nil
_normalName = nil _normalName = nil
_pendingAttributeName = nil _pendingAttributeName = nil
@ -111,12 +108,12 @@ open class Token
// } // }
if (_pendingAttributeName != nil) { if (_pendingAttributeName != nil) {
var attribute : Attribute var attribute: Attribute
if (_hasPendingAttributeValue){ if (_hasPendingAttributeValue) {
attribute = try Attribute(key: _pendingAttributeName!,value: _pendingAttributeValue.length > 0 ? _pendingAttributeValue.toString() : _pendingAttributeValueS!) attribute = try Attribute(key: _pendingAttributeName!, value: _pendingAttributeValue.length > 0 ? _pendingAttributeValue.toString() : _pendingAttributeValueS!)
}else if (_hasEmptyAttributeValue){ } else if (_hasEmptyAttributeValue) {
attribute = try Attribute(key: _pendingAttributeName!, value: "") attribute = try Attribute(key: _pendingAttributeName!, value: "")
}else{ } else {
attribute = try BooleanAttribute(key: _pendingAttributeName!) attribute = try BooleanAttribute(key: _pendingAttributeName!)
} }
_attributes.put(attribute: attribute) _attributes.put(attribute: attribute)
@ -141,22 +138,22 @@ open class Token
return _tagName! return _tagName!
} }
func normalName()->String? { // loses case, used in tree building for working out where in tree it should go func normalName() -> String? { // loses case, used in tree building for working out where in tree it should go
return _normalName return _normalName
} }
@discardableResult @discardableResult
func name(_ name: String)->Tag { func name(_ name: String) -> Tag {
_tagName = name _tagName = name
_normalName = name.lowercased() _normalName = name.lowercased()
return self return self
} }
func isSelfClosing()->Bool { func isSelfClosing() -> Bool {
return _selfClosing return _selfClosing
} }
func getAttributes()->Attributes { func getAttributes() -> Attributes {
return _attributes return _attributes
} }
@ -166,7 +163,7 @@ open class Token
_normalName = _tagName?.lowercased() _normalName = _tagName?.lowercased()
} }
func appendTagName(_ append : UnicodeScalar) { func appendTagName(_ append: UnicodeScalar) {
appendTagName("\(append)") appendTagName("\(append)")
} }
@ -218,7 +215,7 @@ open class Token
} }
} }
final class StartTag : Tag { final class StartTag: Tag {
override init() { override init() {
super.init() super.init()
_attributes = Attributes() _attributes = Attributes()
@ -226,7 +223,7 @@ open class Token
} }
@discardableResult @discardableResult
override func reset()->Tag { override func reset() -> Tag {
super.reset() super.reset()
_attributes = Attributes() _attributes = Attributes()
// todo - would prefer these to be null, but need to check Element assertions // todo - would prefer these to be null, but need to check Element assertions
@ -234,7 +231,7 @@ open class Token
} }
@discardableResult @discardableResult
func nameAttr(_ name: String, _ attributes: Attributes)->StartTag { func nameAttr(_ name: String, _ attributes: Attributes) -> StartTag {
self._tagName = name self._tagName = name
self._attributes = attributes self._attributes = attributes
_normalName = _tagName?.lowercased() _normalName = _tagName?.lowercased()
@ -242,32 +239,31 @@ open class Token
} }
open override func toString()throws->String { open override func toString()throws->String {
if (_attributes.size() > 0){ if (_attributes.size() > 0) {
return try "<" + (name()) + " " + (_attributes.toString()) + ">" return try "<" + (name()) + " " + (_attributes.toString()) + ">"
}else{ } else {
return try "<" + name() + ">" return try "<" + name() + ">"
} }
} }
} }
final class EndTag : Tag{ final class EndTag: Tag {
override init() { override init() {
super.init() super.init()
type = TokenType.EndTag type = TokenType.EndTag
} }
open override func toString()throws->String { open override func toString()throws->String {
return "</" + (try name()) + ">" return "</" + (try name()) + ">"
} }
} }
final class Comment : Token { final class Comment: Token {
let data : StringBuilder = StringBuilder() let data: StringBuilder = StringBuilder()
var bogus : Bool = false var bogus: Bool = false
@discardableResult @discardableResult
override func reset()->Token { override func reset() -> Token {
Token.reset(data) Token.reset(data)
bogus = false bogus = false
return self return self
@ -278,18 +274,17 @@ open class Token
type = TokenType.Comment type = TokenType.Comment
} }
func getData()->String { func getData() -> String {
return data.toString() return data.toString()
} }
open override func toString()throws->String { open override func toString()throws->String {
return "<!--" + getData() + "-->" return "<!--" + getData() + "-->"
} }
} }
final class Char : Token { final class Char: Token {
public var data : String? public var data: String?
override init() { override init() {
super.init() super.init()
@ -297,85 +292,83 @@ open class Token
} }
@discardableResult @discardableResult
override func reset()->Token { override func reset() -> Token {
data = nil data = nil
return self return self
} }
@discardableResult @discardableResult
func data(_ data: String)->Char { func data(_ data: String) -> Char {
self.data = data self.data = data
return self return self
} }
func getData()->String? { func getData() -> String? {
return data return data
} }
open override func toString()throws->String { open override func toString()throws->String {
try Validate.notNull(obj: data) try Validate.notNull(obj: data)
return getData()! return getData()!
} }
} }
final class EOF : Token { final class EOF: Token {
override init() { override init() {
super.init() super.init()
type = Token.TokenType.EOF type = Token.TokenType.EOF
} }
@discardableResult @discardableResult
override func reset()->Token { override func reset() -> Token {
return self return self
} }
} }
func isDoctype()->Bool { func isDoctype() -> Bool {
return type == TokenType.Doctype return type == TokenType.Doctype
} }
func asDoctype()->Doctype { func asDoctype() -> Doctype {
return self as! Doctype return self as! Doctype
} }
func isStartTag()->Bool { func isStartTag() -> Bool {
return type == TokenType.StartTag return type == TokenType.StartTag
} }
func asStartTag()->StartTag { func asStartTag() -> StartTag {
return self as! StartTag return self as! StartTag
} }
func isEndTag()->Bool { func isEndTag() -> Bool {
return type == TokenType.EndTag return type == TokenType.EndTag
} }
func asEndTag()->EndTag { func asEndTag() -> EndTag {
return self as! EndTag return self as! EndTag
} }
func isComment()->Bool { func isComment() -> Bool {
return type == TokenType.Comment return type == TokenType.Comment
} }
func asComment()->Comment { func asComment() -> Comment {
return self as! Comment return self as! Comment
} }
func isCharacter()->Bool { func isCharacter() -> Bool {
return type == TokenType.Char return type == TokenType.Char
} }
func asCharacter()->Char { func asCharacter() -> Char {
return self as! Char return self as! Char
} }
func isEOF()->Bool { func isEOF() -> Bool {
return type == TokenType.EOF return type == TokenType.EOF
} }
public enum TokenType { public enum TokenType {
case Doctype case Doctype
case StartTag case StartTag

View File

@ -8,12 +8,11 @@
import Foundation import Foundation
open class TokenQueue open class TokenQueue {
{ private var queue: String
private var queue : String private var pos: Int = 0
private var pos : Int = 0
private static let ESC : Character = "\\" // escape char for chomp balanced. private static let ESC: Character = "\\" // escape char for chomp balanced.
/** /**
Create a new TokenQueue. Create a new TokenQueue.
@ -27,11 +26,11 @@ open class TokenQueue
* Is the queue empty? * Is the queue empty?
* @return true if no data left in queue. * @return true if no data left in queue.
*/ */
open func isEmpty()->Bool { open func isEmpty() -> Bool {
return remainingLength() == 0 return remainingLength() == 0
} }
private func remainingLength()->Int { private func remainingLength() -> Int {
return queue.characters.count - pos return queue.characters.count - pos
} }
@ -39,7 +38,7 @@ open class TokenQueue
* Retrieves but does not remove the first character from the queue. * Retrieves but does not remove the first character from the queue.
* @return First character, or 0 if empty. * @return First character, or 0 if empty.
*/ */
open func peek()-> Character { open func peek() -> Character {
return isEmpty() ? Character(UnicodeScalar(0)) : queue[pos] return isEmpty() ? Character(UnicodeScalar(0)) : queue[pos]
} }
@ -66,7 +65,7 @@ open class TokenQueue
* @param seq String to check queue for. * @param seq String to check queue for.
* @return true if the next characters match. * @return true if the next characters match.
*/ */
open func matches(_ seq: String)->Bool { open func matches(_ seq: String) -> Bool {
return queue.regionMatches(true, pos, seq, 0, seq.characters.count) return queue.regionMatches(true, pos, seq, 0, seq.characters.count)
} }
@ -75,7 +74,7 @@ open class TokenQueue
* @param seq string to case sensitively check for * @param seq string to case sensitively check for
* @return true if matched, false if not * @return true if matched, false if not
*/ */
open func matchesCS(_ seq: String)->Bool { open func matchesCS(_ seq: String) -> Bool {
return queue.startsWith(seq, pos) return queue.startsWith(seq, pos)
} }
@ -84,32 +83,32 @@ open class TokenQueue
@param seq list of strings to case insensitively check for @param seq list of strings to case insensitively check for
@return true of any matched, false if none did @return true of any matched, false if none did
*/ */
open func matchesAny(_ seq:[String])->Bool { open func matchesAny(_ seq: [String]) -> Bool {
for s in seq { for s in seq {
if (matches(s)){ if (matches(s)) {
return true return true
} }
} }
return false return false
} }
open func matchesAny(_ seq: String...)->Bool { open func matchesAny(_ seq: String...) -> Bool {
return matchesAny(seq) return matchesAny(seq)
} }
open func matchesAny(_ seq: Character...)->Bool { open func matchesAny(_ seq: Character...) -> Bool {
if (isEmpty()){ if (isEmpty()) {
return false return false
} }
for c in seq { for c in seq {
if (queue[pos] as Character == c){ if (queue[pos] as Character == c) {
return true return true
} }
} }
return false return false
} }
open func matchesStartTag()->Bool { open func matchesStartTag() -> Bool {
// micro opt for matching "<x" // micro opt for matching "<x"
return (remainingLength() >= 2 && queue[pos] as Character == "<" && Character.isLetter(queue.charAt(pos+1))) return (remainingLength() >= 2 && queue[pos] as Character == "<" && Character.isLetter(queue.charAt(pos+1)))
} }
@ -121,7 +120,7 @@ open class TokenQueue
* @return true if found and removed, false if not found. * @return true if found and removed, false if not found.
*/ */
@discardableResult @discardableResult
open func matchChomp(_ seq: String)->Bool { open func matchChomp(_ seq: String) -> Bool {
if (matches(seq)) { if (matches(seq)) {
pos += seq.characters.count pos += seq.characters.count
return true return true
@ -134,7 +133,7 @@ open class TokenQueue
Tests if queue starts with a whitespace character. Tests if queue starts with a whitespace character.
@return if starts with whitespace @return if starts with whitespace
*/ */
open func matchesWhitespace()->Bool { open func matchesWhitespace() -> Bool {
return !isEmpty() && StringUtil.isWhitespace(queue.charAt(pos)) return !isEmpty() && StringUtil.isWhitespace(queue.charAt(pos))
} }
@ -142,7 +141,7 @@ open class TokenQueue
Test if the queue matches a word character (letter or digit). Test if the queue matches a word character (letter or digit).
@return if matches a word character @return if matches a word character
*/ */
open func matchesWord()->Bool { open func matchesWord() -> Bool {
return !isEmpty() && (Character.isLetterOrDigit(queue.charAt(pos))) return !isEmpty() && (Character.isLetterOrDigit(queue.charAt(pos)))
} }
@ -158,7 +157,7 @@ open class TokenQueue
* Consume one character off queue. * Consume one character off queue.
* @return first character on queue. * @return first character on queue.
*/ */
open func consume()->Character { open func consume() -> Character {
let i = pos let i = pos
pos+=1 pos+=1
return queue.charAt(i) return queue.charAt(i)
@ -172,12 +171,12 @@ open class TokenQueue
* @param seq sequence to remove from head of queue. * @param seq sequence to remove from head of queue.
*/ */
open func consume(_ seq: String)throws { open func consume(_ seq: String)throws {
if (!matches(seq)){ if (!matches(seq)) {
//throw new IllegalStateException("Queue did not match expected sequence") //throw new IllegalStateException("Queue did not match expected sequence")
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Queue did not match expected sequence") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Queue did not match expected sequence")
} }
let len = seq.characters.count let len = seq.characters.count
if (len > remainingLength()){ if (len > remainingLength()) {
//throw new IllegalStateException("Queue not long enough to consume sequence") //throw new IllegalStateException("Queue not long enough to consume sequence")
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Queue not long enough to consume sequence") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Queue not long enough to consume sequence")
} }
@ -191,7 +190,7 @@ open class TokenQueue
* @return The matched data consumed from queue. * @return The matched data consumed from queue.
*/ */
@discardableResult @discardableResult
open func consumeTo(_ seq: String)->String { open func consumeTo(_ seq: String) -> String {
let offset = queue.indexOf(seq, pos) let offset = queue.indexOf(seq, pos)
if (offset != -1) { if (offset != -1) {
let consumed = queue.substring(pos, offset-pos) let consumed = queue.substring(pos, offset-pos)
@ -203,25 +202,24 @@ open class TokenQueue
return "" return ""
} }
open func consumeToIgnoreCase(_ seq: String)->String { open func consumeToIgnoreCase(_ seq: String) -> String {
let start = pos let start = pos
let first = seq.substring(0, 1) let first = seq.substring(0, 1)
let canScan = first.lowercased() == first.uppercased() // if first is not cased, use index of let canScan = first.lowercased() == first.uppercased() // if first is not cased, use index of
while (!isEmpty()) { while (!isEmpty()) {
if (matches(seq)){ if (matches(seq)) {
break break
} }
if (canScan) if (canScan) {
{
let skip = queue.indexOf(first, pos) - pos let skip = queue.indexOf(first, pos) - pos
if (skip == 0){ // this char is the skip char, but not match, so force advance of pos if (skip == 0) { // this char is the skip char, but not match, so force advance of pos
pos+=1 pos+=1
}else if (skip < 0){ // no chance of finding, grab to end } else if (skip < 0) { // no chance of finding, grab to end
pos = queue.characters.count pos = queue.characters.count
}else{ } else {
pos += skip pos += skip
} }
} else{ } else {
pos+=1 pos+=1
} }
} }
@ -236,10 +234,10 @@ open class TokenQueue
*/ */
// todo: method name. not good that consumeTo cares for case, and consume to any doesn't. And the only use for this // todo: method name. not good that consumeTo cares for case, and consume to any doesn't. And the only use for this
// is is a case sensitive time... // is is a case sensitive time...
open func consumeToAny(_ seq: String...)->String { open func consumeToAny(_ seq: String...) -> String {
return consumeToAny(seq) return consumeToAny(seq)
} }
open func consumeToAny(_ seq: [String])->String { open func consumeToAny(_ seq: [String]) -> String {
let start = pos let start = pos
while (!isEmpty() && !matchesAny(seq)) { while (!isEmpty() && !matchesAny(seq)) {
pos+=1 pos+=1
@ -255,13 +253,13 @@ open class TokenQueue
* @param seq String to match up to, and not include in return, and to pull off queue. <b>Case sensitive.</b> * @param seq String to match up to, and not include in return, and to pull off queue. <b>Case sensitive.</b>
* @return Data matched from queue. * @return Data matched from queue.
*/ */
open func chompTo(_ seq: String)->String { open func chompTo(_ seq: String) -> String {
let data = consumeTo(seq) let data = consumeTo(seq)
matchChomp(seq) matchChomp(seq)
return data return data
} }
open func chompToIgnoreCase(_ seq: String)->String { open func chompToIgnoreCase(_ seq: String) -> String {
let data = consumeToIgnoreCase(seq) // case insensitive scan let data = consumeToIgnoreCase(seq) // case insensitive scan
matchChomp(seq) matchChomp(seq)
return data return data
@ -276,35 +274,34 @@ open class TokenQueue
* @param close closer * @param close closer
* @return data matched from the queue * @return data matched from the queue
*/ */
open func chompBalanced(_ open:Character, _ close: Character)->String { open func chompBalanced(_ open: Character, _ close: Character) -> String {
var start = -1 var start = -1
var end = -1 var end = -1
var depth = 0 var depth = 0
var last : Character = Character(UnicodeScalar(0)) var last: Character = Character(UnicodeScalar(0))
var inQuote = false var inQuote = false
repeat { repeat {
if (isEmpty()){break} if (isEmpty()) {break}
let c = consume() let c = consume()
if (last.unicodeScalar.value == 0 || last != TokenQueue.ESC) { if (last.unicodeScalar.value == 0 || last != TokenQueue.ESC) {
if ((c=="'" || c=="\"") && c != open){ if ((c=="'" || c=="\"") && c != open) {
inQuote = !inQuote inQuote = !inQuote
} }
if (inQuote){ if (inQuote) {
continue continue
} }
if (c==open) { if (c==open) {
depth+=1 depth+=1
if (start == -1){ if (start == -1) {
start = pos start = pos
} }
} } else if (c==close) {
else if (c==close){
depth-=1 depth-=1
} }
} }
if (depth > 0 && last.unicodeScalar.value != 0){ if (depth > 0 && last.unicodeScalar.value != 0) {
end = pos // don't include the outer match pair in the return end = pos // don't include the outer match pair in the return
} }
last = c last = c
@ -317,17 +314,15 @@ open class TokenQueue
* @param in backslash escaped string * @param in backslash escaped string
* @return unescaped string * @return unescaped string
*/ */
open static func unescape(_ input: String)->String { open static func unescape(_ input: String) -> String {
let out = StringBuilder() let out = StringBuilder()
var last = Character(UnicodeScalar(0)) var last = Character(UnicodeScalar(0))
for c in input.characters for c in input.characters {
{
if (c == ESC) { if (c == ESC) {
if (last.unicodeScalar.value != 0 && last == TokenQueue.ESC){ if (last.unicodeScalar.value != 0 && last == TokenQueue.ESC) {
out.append(c) out.append(c)
} }
} } else {
else{
out.append(c) out.append(c)
} }
last = c last = c
@ -340,7 +335,7 @@ open class TokenQueue
* @return Whether consuming whitespace or not * @return Whether consuming whitespace or not
*/ */
@discardableResult @discardableResult
open func consumeWhitespace()->Bool { open func consumeWhitespace() -> Bool {
var seen = false var seen = false
while (matchesWhitespace()) { while (matchesWhitespace()) {
pos+=1 pos+=1
@ -354,9 +349,9 @@ open class TokenQueue
* @return String of word characters from queue, or empty string if none. * @return String of word characters from queue, or empty string if none.
*/ */
@discardableResult @discardableResult
open func consumeWord()->String { open func consumeWord() -> String {
let start = pos let start = pos
while (matchesWord()){ while (matchesWord()) {
pos+=1 pos+=1
} }
return queue.substring(start, pos-start) return queue.substring(start, pos-start)
@ -367,9 +362,9 @@ open class TokenQueue
* *
* @return tag name * @return tag name
*/ */
open func consumeTagName()->String { open func consumeTagName() -> String {
let start = pos let start = pos
while (!isEmpty() && (matchesWord() || matchesAny(":", "_", "-"))){ while (!isEmpty() && (matchesWord() || matchesAny(":", "_", "-"))) {
pos+=1 pos+=1
} }
@ -381,9 +376,9 @@ open class TokenQueue
* *
* @return tag name * @return tag name
*/ */
open func consumeElementSelector()->String { open func consumeElementSelector() -> String {
let start = pos let start = pos
while (!isEmpty() && (matchesWord() || matchesAny("*|","|", "_", "-"))){ while (!isEmpty() && (matchesWord() || matchesAny("*|", "|", "_", "-"))) {
pos+=1 pos+=1
} }
@ -395,9 +390,9 @@ open class TokenQueue
http://www.w3.org/TR/CSS2/syndata.html#value-def-identifier http://www.w3.org/TR/CSS2/syndata.html#value-def-identifier
@return identifier @return identifier
*/ */
open func consumeCssIdentifier()->String { open func consumeCssIdentifier() -> String {
let start = pos let start = pos
while (!isEmpty() && (matchesWord() || matchesAny("-", "_"))){ while (!isEmpty() && (matchesWord() || matchesAny("-", "_"))) {
pos+=1 pos+=1
} }
@ -408,9 +403,9 @@ open class TokenQueue
Consume an attribute key off the queue (letter, digit, -, _, :") Consume an attribute key off the queue (letter, digit, -, _, :")
@return attribute key @return attribute key
*/ */
open func consumeAttributeKey()->String { open func consumeAttributeKey() -> String {
let start = pos let start = pos
while (!isEmpty() && (matchesWord() || matchesAny("-", "_", ":"))){ while (!isEmpty() && (matchesWord() || matchesAny("-", "_", ":"))) {
pos+=1 pos+=1
} }
@ -421,14 +416,13 @@ open class TokenQueue
Consume and return whatever is left on the queue. Consume and return whatever is left on the queue.
@return remained of queue. @return remained of queue.
*/ */
open func remainder()->String { open func remainder() -> String {
let remainder = queue.substring(pos, queue.characters.count-pos) let remainder = queue.substring(pos, queue.characters.count-pos)
pos = queue.characters.count pos = queue.characters.count
return remainder return remainder
} }
open func toString() -> String {
open func toString()->String {
return queue.substring(pos) return queue.substring(pos)
} }
} }

View File

@ -8,23 +8,22 @@
import Foundation import Foundation
final class Tokeniser final class Tokeniser {
{ static let replacementChar: UnicodeScalar = "\u{FFFD}" // replaces null character
static let replacementChar : UnicodeScalar = "\u{FFFD}" // replaces null character private static let notCharRefCharsSorted: [UnicodeScalar] = ["\t", "\n", "\r", UnicodeScalar.BackslashF, " ", "<", "&"].sorted()
private static let notCharRefCharsSorted : [UnicodeScalar] = ["\t", "\n", "\r",UnicodeScalar.BackslashF, " ", "<", "&"].sorted()
private let reader : CharacterReader // html input private let reader: CharacterReader // html input
private let errors : ParseErrorList? // errors found while tokenising private let errors: ParseErrorList? // errors found while tokenising
private var state: TokeniserState = TokeniserState.Data // current tokenisation state private var state: TokeniserState = TokeniserState.Data // current tokenisation state
private var emitPending: Token? // the token we are about to emit on next read private var emitPending: Token? // the token we are about to emit on next read
private var isEmitPending : Bool = false private var isEmitPending: Bool = false
private var charsString : String? = nil // characters pending an emit. Will fall to charsBuilder if more than one private var charsString: String? = nil // characters pending an emit. Will fall to charsBuilder if more than one
private let charsBuilder : StringBuilder = StringBuilder(1024) // buffers characters to output as one token, if more than one emit per read private let charsBuilder: StringBuilder = StringBuilder(1024) // buffers characters to output as one token, if more than one emit per read
let dataBuffer : StringBuilder = StringBuilder(1024) // buffers data looking for </script> let dataBuffer: StringBuilder = StringBuilder(1024) // buffers data looking for </script>
var tagPending : Token.Tag = Token.Tag() // tag we are building up var tagPending: Token.Tag = Token.Tag() // tag we are building up
let startPending : Token.StartTag = Token.StartTag() let startPending: Token.StartTag = Token.StartTag()
let endPending: Token.EndTag = Token.EndTag() let endPending: Token.EndTag = Token.EndTag()
let charPending: Token.Char = Token.Char() let charPending: Token.Char = Token.Char()
let doctypePending: Token.Doctype = Token.Doctype() // doctype building up let doctypePending: Token.Doctype = Token.Doctype() // doctype building up
@ -32,7 +31,6 @@ final class Tokeniser
private var lastStartTag: String? // the last start tag emitted, to test appropriate end tag private var lastStartTag: String? // the last start tag emitted, to test appropriate end tag
private var selfClosingFlagAcknowledged: Bool = true private var selfClosingFlagAcknowledged: Bool = true
init(_ reader: CharacterReader, _ errors: ParseErrorList?) { init(_ reader: CharacterReader, _ errors: ParseErrorList?) {
self.reader = reader self.reader = reader
self.errors = errors self.errors = errors
@ -44,7 +42,7 @@ final class Tokeniser
selfClosingFlagAcknowledged = true selfClosingFlagAcknowledged = true
} }
while (!isEmitPending){ while (!isEmitPending) {
try state.read(self, reader) try state.read(self, reader)
} }
@ -55,7 +53,7 @@ final class Tokeniser
charsString = nil charsString = nil
return charPending.data(str) return charPending.data(str)
} else if (charsString != nil) { } else if (charsString != nil) {
let token : Token = charPending.data(charsString!) let token: Token = charPending.data(charsString!)
charsString = nil charsString = nil
return token return token
} else { } else {
@ -71,14 +69,14 @@ final class Tokeniser
isEmitPending = true isEmitPending = true
if (token.type == Token.TokenType.StartTag) { if (token.type == Token.TokenType.StartTag) {
let startTag : Token.StartTag = token as! Token.StartTag let startTag: Token.StartTag = token as! Token.StartTag
lastStartTag = startTag._tagName! lastStartTag = startTag._tagName!
if (startTag._selfClosing){ if (startTag._selfClosing) {
selfClosingFlagAcknowledged = false selfClosingFlagAcknowledged = false
} }
} else if (token.type == Token.TokenType.EndTag) { } else if (token.type == Token.TokenType.EndTag) {
let endTag : Token.EndTag = token as! Token.EndTag let endTag: Token.EndTag = token as! Token.EndTag
if (endTag._attributes.size() != 0){ if (endTag._attributes.size() != 0) {
error("Attributes incorrectly present on end tag") error("Attributes incorrectly present on end tag")
} }
} }
@ -89,8 +87,7 @@ final class Tokeniser
// does not set isEmitPending; read checks that // does not set isEmitPending; read checks that
if (charsString == nil) { if (charsString == nil) {
charsString = str charsString = str
} } else {
else {
if (charsBuilder.length == 0) { // switching to string builder as more than one emit before read if (charsBuilder.length == 0) { // switching to string builder as more than one emit before read
charsBuilder.append(charsString!) charsBuilder.append(charsString!)
} }
@ -99,7 +96,7 @@ final class Tokeniser
} }
func emit(_ chars: [UnicodeScalar]) { func emit(_ chars: [UnicodeScalar]) {
emit(String(chars.map{Character($0)})) emit(String(chars.map {Character($0)}))
} }
// func emit(_ codepoints: [Int]) { // func emit(_ codepoints: [Int]) {
@ -110,7 +107,7 @@ final class Tokeniser
emit(String(c)) emit(String(c))
} }
func getState()->TokeniserState { func getState() -> TokeniserState {
return state return state
} }
@ -128,16 +125,16 @@ final class Tokeniser
} }
private var codepointHolder: [UnicodeScalar] = [UnicodeScalar(0)!] // holder to not have to keep creating arrays private var codepointHolder: [UnicodeScalar] = [UnicodeScalar(0)!] // holder to not have to keep creating arrays
private var multipointHolder: [UnicodeScalar] = [UnicodeScalar(0)!,UnicodeScalar(0)!] private var multipointHolder: [UnicodeScalar] = [UnicodeScalar(0)!, UnicodeScalar(0)!]
func consumeCharacterReference(_ additionalAllowedCharacter: UnicodeScalar?, _ inAttribute: Bool)throws->[UnicodeScalar]? { func consumeCharacterReference(_ additionalAllowedCharacter: UnicodeScalar?, _ inAttribute: Bool)throws->[UnicodeScalar]? {
if (reader.isEmpty()){ if (reader.isEmpty()) {
return nil return nil
} }
if (additionalAllowedCharacter != nil && additionalAllowedCharacter == reader.current()){ if (additionalAllowedCharacter != nil && additionalAllowedCharacter == reader.current()) {
return nil return nil
} }
if (reader.matchesAnySorted(Tokeniser.notCharRefCharsSorted)){ if (reader.matchesAnySorted(Tokeniser.notCharRefCharsSorted)) {
return nil return nil
} }
@ -151,18 +148,16 @@ final class Tokeniser
reader.rewindToMark() reader.rewindToMark()
return nil return nil
} }
if (!reader.matchConsume(";")){ if (!reader.matchConsume(";")) {
characterReferenceError("missing semicolon") // missing semi characterReferenceError("missing semicolon") // missing semi
} }
var charval : Int = -1 var charval: Int = -1
let base: Int = isHexMode ? 16 : 10 let base: Int = isHexMode ? 16 : 10
if let num = Int(numRef,radix: base) if let num = Int(numRef, radix: base) {
{
charval = num charval = num
} }
if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) { if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) {
characterReferenceError("character outside of valid range") characterReferenceError("character outside of valid range")
codeRef[0] = Tokeniser.replacementChar codeRef[0] = Tokeniser.replacementChar
@ -175,14 +170,14 @@ final class Tokeniser
} }
} else { // named } else { // named
// get as many letters as possible, and look for matching entities. // get as many letters as possible, and look for matching entities.
let nameRef : String = reader.consumeLetterThenDigitSequence() let nameRef: String = reader.consumeLetterThenDigitSequence()
let looksLegit: Bool = reader.matches(";") let looksLegit: Bool = reader.matches(";")
// found if a base named entity without a ;, or an extended entity with the ;. // found if a base named entity without a ;, or an extended entity with the ;.
let found: Bool = (Entities.isBaseNamedEntity(nameRef) || (Entities.isNamedEntity(nameRef) && looksLegit)) let found: Bool = (Entities.isBaseNamedEntity(nameRef) || (Entities.isNamedEntity(nameRef) && looksLegit))
if (!found) { if (!found) {
reader.rewindToMark() reader.rewindToMark()
if (looksLegit){ // named with semicolon if (looksLegit) { // named with semicolon
characterReferenceError("invalid named referenece '\(nameRef)'") characterReferenceError("invalid named referenece '\(nameRef)'")
} }
return nil return nil
@ -192,7 +187,7 @@ final class Tokeniser
reader.rewindToMark() reader.rewindToMark()
return nil return nil
} }
if (!reader.matchConsume(";")){ if (!reader.matchConsume(";")) {
characterReferenceError("missing semicolon") // missing semi characterReferenceError("missing semicolon") // missing semi
} }
let numChars: Int = Entities.codepointsForName(nameRef, codepoints: &multipointHolder) let numChars: Int = Entities.codepointsForName(nameRef, codepoints: &multipointHolder)
@ -240,45 +235,45 @@ final class Tokeniser
} }
func isAppropriateEndTagToken()throws->Bool { func isAppropriateEndTagToken()throws->Bool {
if(lastStartTag != nil){ if(lastStartTag != nil) {
let s = try tagPending.name() let s = try tagPending.name()
return s.equalsIgnoreCase(string: lastStartTag!) return s.equalsIgnoreCase(string: lastStartTag!)
} }
return false return false
} }
func appropriateEndTagName()->String? { func appropriateEndTagName() -> String? {
if (lastStartTag == nil){ if (lastStartTag == nil) {
return nil return nil
} }
return lastStartTag return lastStartTag
} }
func error(_ state: TokeniserState) { func error(_ state: TokeniserState) {
if (errors != nil && errors!.canAddError()){ if (errors != nil && errors!.canAddError()) {
errors?.add(ParseError(reader.getPos(), "Unexpected character '\(String(reader.current()))' in input state [\(state.description)]")) errors?.add(ParseError(reader.getPos(), "Unexpected character '\(String(reader.current()))' in input state [\(state.description)]"))
} }
} }
func eofError(_ state: TokeniserState) { func eofError(_ state: TokeniserState) {
if (errors != nil && errors!.canAddError()){ if (errors != nil && errors!.canAddError()) {
errors?.add(ParseError(reader.getPos(), "Unexpectedly reached end of file (EOF) in input state [\(state.description)]")) errors?.add(ParseError(reader.getPos(), "Unexpectedly reached end of file (EOF) in input state [\(state.description)]"))
} }
} }
private func characterReferenceError(_ message: String) { private func characterReferenceError(_ message: String) {
if (errors != nil && errors!.canAddError()){ if (errors != nil && errors!.canAddError()) {
errors?.add(ParseError(reader.getPos(), "Invalid character reference: \(message)")) errors?.add(ParseError(reader.getPos(), "Invalid character reference: \(message)"))
} }
} }
private func error(_ errorMsg: String) { private func error(_ errorMsg: String) {
if (errors != nil && errors!.canAddError()){ if (errors != nil && errors!.canAddError()) {
errors?.add(ParseError(reader.getPos(), errorMsg)) errors?.add(ParseError(reader.getPos(), errorMsg))
} }
} }
func currentNodeInHtmlNS()->Bool { func currentNodeInHtmlNS() -> Bool {
// todo: implement namespaces correctly // todo: implement namespaces correctly
return true return true
// Element currentNode = currentNode() // Element currentNode = currentNode()
@ -291,22 +286,21 @@ final class Tokeniser
* @return unescaped string from reader * @return unescaped string from reader
*/ */
func unescapeEntities(_ inAttribute: Bool)throws->String { func unescapeEntities(_ inAttribute: Bool)throws->String {
let builder : StringBuilder = StringBuilder() let builder: StringBuilder = StringBuilder()
while (!reader.isEmpty()) { while (!reader.isEmpty()) {
builder.append(reader.consumeTo("&")) builder.append(reader.consumeTo("&"))
if (reader.matches("&")) { if (reader.matches("&")) {
reader.consume() reader.consume()
if let c = try consumeCharacterReference(nil, inAttribute) if let c = try consumeCharacterReference(nil, inAttribute) {
{ if (c.count==0) {
if (c.count==0){
builder.append("&") builder.append("&")
}else { } else {
builder.appendCodePoint(c[0]) builder.appendCodePoint(c[0])
if (c.count == 2){ if (c.count == 2) {
builder.appendCodePoint(c[1]) builder.appendCodePoint(c[1])
} }
} }
}else { } else {
builder.append("&") builder.append("&")
} }
} }
@ -314,7 +308,4 @@ final class Tokeniser
return builder.toString() return builder.toString()
} }
} }

View File

@ -12,22 +12,20 @@ protocol TokeniserStateProtocol {
func read(_ t: Tokeniser, _ r: CharacterReader)throws func read(_ t: Tokeniser, _ r: CharacterReader)throws
} }
public class TokeniserStateVars public class TokeniserStateVars {
{ public static let nullScalr: UnicodeScalar = "\u{0000}"
public static let nullScalr : UnicodeScalar = "\u{0000}"
static let attributeSingleValueCharsSorted = ["'", "&", nullScalr].sorted() static let attributeSingleValueCharsSorted = ["'", "&", nullScalr].sorted()
static let attributeDoubleValueCharsSorted = ["\"", "&", nullScalr].sorted() static let attributeDoubleValueCharsSorted = ["\"", "&", nullScalr].sorted()
static let attributeNameCharsSorted = ["\t", "\n", "\r", UnicodeScalar.BackslashF, " ", "/", "=", ">", nullScalr, "\"", "'", "<"].sorted() static let attributeNameCharsSorted = ["\t", "\n", "\r", UnicodeScalar.BackslashF, " ", "/", "=", ">", nullScalr, "\"", "'", "<"].sorted()
static let attributeValueUnquoted = ["\t", "\n", "\r", UnicodeScalar.BackslashF, " ", "&", ">", nullScalr, "\"", "'", "<", "=", "`"].sorted() static let attributeValueUnquoted = ["\t", "\n", "\r", UnicodeScalar.BackslashF, " ", "&", ">", nullScalr, "\"", "'", "<", "=", "`"].sorted()
static let replacementChar : UnicodeScalar = Tokeniser.replacementChar static let replacementChar: UnicodeScalar = Tokeniser.replacementChar
static let replacementStr : String = String(Tokeniser.replacementChar) static let replacementStr: String = String(Tokeniser.replacementChar)
static let eof : UnicodeScalar = CharacterReader.EOF static let eof: UnicodeScalar = CharacterReader.EOF
} }
enum TokeniserState: TokeniserStateProtocol enum TokeniserState: TokeniserStateProtocol {
{
case Data case Data
case CharacterReferenceInData case CharacterReferenceInData
case Rcdata case Rcdata
@ -96,10 +94,8 @@ enum TokeniserState: TokeniserStateProtocol
case BogusDoctype case BogusDoctype
case CdataSection case CdataSection
internal func read(_ t: Tokeniser, _ r: CharacterReader)throws internal func read(_ t: Tokeniser, _ r: CharacterReader)throws {
{ switch self {
switch self
{
case .Data: case .Data:
switch (r.current()) { switch (r.current()) {
case "&": case "&":
@ -286,8 +282,7 @@ enum TokeniserState: TokeniserStateProtocol
return return
} }
func anythingElse(_ t: Tokeniser, _ r: CharacterReader) func anythingElse(_ t: Tokeniser, _ r: CharacterReader) {
{
t.emit("</" + t.dataBuffer.toString()) t.emit("</" + t.dataBuffer.toString())
r.unconsume() r.unconsume()
t.transition(.Rcdata) t.transition(.Rcdata)
@ -296,44 +291,44 @@ enum TokeniserState: TokeniserStateProtocol
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t": case "\t":
if (try t.isAppropriateEndTagToken()){ if (try t.isAppropriateEndTagToken()) {
t.transition(.BeforeAttributeName) t.transition(.BeforeAttributeName)
}else{ } else {
anythingElse(t, r) anythingElse(t, r)
} }
break break
case "\n": case "\n":
if (try t.isAppropriateEndTagToken()){ if (try t.isAppropriateEndTagToken()) {
t.transition(.BeforeAttributeName) t.transition(.BeforeAttributeName)
}else{ } else {
anythingElse(t, r) anythingElse(t, r)
} }
break break
case "\r": case "\r":
if (try t.isAppropriateEndTagToken()){ if (try t.isAppropriateEndTagToken()) {
t.transition(.BeforeAttributeName) t.transition(.BeforeAttributeName)
}else{ } else {
anythingElse(t, r) anythingElse(t, r)
} }
break break
case UnicodeScalar.BackslashF: case UnicodeScalar.BackslashF:
if (try t.isAppropriateEndTagToken()){ if (try t.isAppropriateEndTagToken()) {
t.transition(.BeforeAttributeName) t.transition(.BeforeAttributeName)
}else{ } else {
anythingElse(t, r) anythingElse(t, r)
} }
break break
case " ": case " ":
if (try t.isAppropriateEndTagToken()){ if (try t.isAppropriateEndTagToken()) {
t.transition(.BeforeAttributeName) t.transition(.BeforeAttributeName)
}else{ } else {
anythingElse(t, r) anythingElse(t, r)
} }
break break
case "/": case "/":
if (try t.isAppropriateEndTagToken()){ if (try t.isAppropriateEndTagToken()) {
t.transition(.SelfClosingStartTag) t.transition(.SelfClosingStartTag)
}else{ } else {
anythingElse(t, r) anythingElse(t, r)
} }
break break
@ -341,8 +336,7 @@ enum TokeniserState: TokeniserStateProtocol
if (try t.isAppropriateEndTagToken()) { if (try t.isAppropriateEndTagToken()) {
try t.emitTagPending() try t.emitTagPending()
t.transition(.Data) t.transition(.Data)
} } else {anythingElse(t, r)}
else{anythingElse(t, r)}
break break
default: default:
anythingElse(t, r) anythingElse(t, r)
@ -601,7 +595,7 @@ enum TokeniserState: TokeniserStateProtocol
} }
break break
case .ScriptDataDoubleEscapeEnd: case .ScriptDataDoubleEscapeEnd:
TokeniserState.handleDataDoubleEscapeTag(t,r, .ScriptDataEscaped, .ScriptDataDoubleEscaped) TokeniserState.handleDataDoubleEscapeTag(t, r, .ScriptDataEscaped, .ScriptDataDoubleEscaped)
break break
case .BeforeAttributeName: case .BeforeAttributeName:
// from tagname <xxx // from tagname <xxx
@ -724,7 +718,7 @@ enum TokeniserState: TokeniserStateProtocol
case .AfterAttributeName: case .AfterAttributeName:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
// ignore // ignore
break break
case "/": case "/":
@ -746,7 +740,7 @@ enum TokeniserState: TokeniserStateProtocol
t.eofError(self) t.eofError(self)
t.transition(.Data) t.transition(.Data)
break break
case "\"","'","<": case "\"", "'", "<":
t.error(self) t.error(self)
try t.tagPending.newAttribute() try t.tagPending.newAttribute()
t.tagPending.appendAttributeName(c) t.tagPending.appendAttributeName(c)
@ -761,7 +755,7 @@ enum TokeniserState: TokeniserStateProtocol
case .BeforeAttributeValue: case .BeforeAttributeValue:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
// ignore // ignore
break break
case "\"": case "\"":
@ -789,7 +783,7 @@ enum TokeniserState: TokeniserStateProtocol
try t.emitTagPending() try t.emitTagPending()
t.transition(.Data) t.transition(.Data)
break break
case "<","=","`": case "<", "=", "`":
t.error(self) t.error(self)
t.tagPending.appendAttributeValue(c) t.tagPending.appendAttributeValue(c)
t.transition(.AttributeValue_unquoted) t.transition(.AttributeValue_unquoted)
@ -801,9 +795,9 @@ enum TokeniserState: TokeniserStateProtocol
break break
case .AttributeValue_doubleQuoted: case .AttributeValue_doubleQuoted:
let value = r.consumeToAny(TokeniserStateVars.attributeDoubleValueCharsSorted) let value = r.consumeToAny(TokeniserStateVars.attributeDoubleValueCharsSorted)
if (value.characters.count > 0){ if (value.characters.count > 0) {
t.tagPending.appendAttributeValue(value) t.tagPending.appendAttributeValue(value)
}else{ } else {
t.tagPending.setEmptyAttributeValue() t.tagPending.setEmptyAttributeValue()
} }
@ -814,9 +808,9 @@ enum TokeniserState: TokeniserStateProtocol
break break
case "&": case "&":
if let ref = try t.consumeCharacterReference("\"", true){ if let ref = try t.consumeCharacterReference("\"", true) {
t.tagPending.appendAttributeValue(ref) t.tagPending.appendAttributeValue(ref)
}else{ } else {
t.tagPending.appendAttributeValue("&") t.tagPending.appendAttributeValue("&")
} }
break break
@ -835,9 +829,9 @@ enum TokeniserState: TokeniserStateProtocol
break break
case .AttributeValue_singleQuoted: case .AttributeValue_singleQuoted:
let value = r.consumeToAny(TokeniserStateVars.attributeSingleValueCharsSorted) let value = r.consumeToAny(TokeniserStateVars.attributeSingleValueCharsSorted)
if (value.characters.count > 0){ if (value.characters.count > 0) {
t.tagPending.appendAttributeValue(value) t.tagPending.appendAttributeValue(value)
}else{ } else {
t.tagPending.setEmptyAttributeValue() t.tagPending.setEmptyAttributeValue()
} }
@ -848,9 +842,9 @@ enum TokeniserState: TokeniserStateProtocol
break break
case "&": case "&":
if let ref = try t.consumeCharacterReference("'", true){ if let ref = try t.consumeCharacterReference("'", true) {
t.tagPending.appendAttributeValue(ref) t.tagPending.appendAttributeValue(ref)
}else{ } else {
t.tagPending.appendAttributeValue("&") t.tagPending.appendAttributeValue("&")
} }
break break
@ -869,19 +863,19 @@ enum TokeniserState: TokeniserStateProtocol
break break
case .AttributeValue_unquoted: case .AttributeValue_unquoted:
let value = r.consumeToAnySorted(TokeniserStateVars.attributeValueUnquoted) let value = r.consumeToAnySorted(TokeniserStateVars.attributeValueUnquoted)
if (value.characters.count > 0){ if (value.characters.count > 0) {
t.tagPending.appendAttributeValue(value) t.tagPending.appendAttributeValue(value)
} }
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
t.transition(.BeforeAttributeName) t.transition(.BeforeAttributeName)
break break
case "&": case "&":
if let ref = try t.consumeCharacterReference(">", true){ if let ref = try t.consumeCharacterReference(">", true) {
t.tagPending.appendAttributeValue(ref) t.tagPending.appendAttributeValue(ref)
}else{ } else {
t.tagPending.appendAttributeValue("&") t.tagPending.appendAttributeValue("&")
} }
break break
@ -897,7 +891,7 @@ enum TokeniserState: TokeniserStateProtocol
t.eofError(self) t.eofError(self)
t.transition(.Data) t.transition(.Data)
break break
case "\"","'","<","=","`": case "\"", "'", "<", "=", "`":
t.error(self) t.error(self)
t.tagPending.appendAttributeValue(c) t.tagPending.appendAttributeValue(c)
break break
@ -910,7 +904,7 @@ enum TokeniserState: TokeniserStateProtocol
// CharacterReferenceInAttributeValue state handled inline // CharacterReferenceInAttributeValue state handled inline
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
t.transition(.BeforeAttributeName) t.transition(.BeforeAttributeName)
break break
case "/": case "/":
@ -952,7 +946,7 @@ enum TokeniserState: TokeniserStateProtocol
// todo: handle bogus comment starting from eof. when does that trigger? // todo: handle bogus comment starting from eof. when does that trigger?
// rewind to capture character that lead us here // rewind to capture character that lead us here
r.unconsume() r.unconsume()
let comment : Token.Comment = Token.Comment() let comment: Token.Comment = Token.Comment()
comment.bogus = true comment.bogus = true
comment.data.append(r.consumeTo(">")) comment.data.append(r.consumeTo(">"))
// todo: replace nullChar with replaceChar // todo: replace nullChar with replaceChar
@ -1128,7 +1122,7 @@ enum TokeniserState: TokeniserStateProtocol
case .Doctype: case .Doctype:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
t.transition(.BeforeDoctypeName) t.transition(.BeforeDoctypeName)
break break
case TokeniserStateVars.eof: case TokeniserStateVars.eof:
@ -1154,7 +1148,7 @@ enum TokeniserState: TokeniserStateProtocol
} }
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
break // ignore whitespace break // ignore whitespace
case TokeniserStateVars.nullScalr: case TokeniserStateVars.nullScalr:
t.error(self) t.error(self)
@ -1187,7 +1181,7 @@ enum TokeniserState: TokeniserStateProtocol
try t.emitDoctypePending() try t.emitDoctypePending()
t.transition(.Data) t.transition(.Data)
break break
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
t.transition(.AfterDoctypeName) t.transition(.AfterDoctypeName)
break break
case TokeniserStateVars.nullScalr: case TokeniserStateVars.nullScalr:
@ -1212,9 +1206,9 @@ enum TokeniserState: TokeniserStateProtocol
t.transition(.Data) t.transition(.Data)
return return
} }
if (r.matchesAny("\t", "\n", "\r", UnicodeScalar.BackslashF, " ")){ if (r.matchesAny("\t", "\n", "\r", UnicodeScalar.BackslashF, " ")) {
r.advance() // ignore whitespace r.advance() // ignore whitespace
}else if (r.matches(">")) { } else if (r.matches(">")) {
try t.emitDoctypePending() try t.emitDoctypePending()
t.advanceTransition(.Data) t.advanceTransition(.Data)
} else if (r.matchConsumeIgnoreCase("PUBLIC")) { } else if (r.matchConsumeIgnoreCase("PUBLIC")) {
@ -1230,7 +1224,7 @@ enum TokeniserState: TokeniserStateProtocol
case .AfterDoctypePublicKeyword: case .AfterDoctypePublicKeyword:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
t.transition(.BeforeDoctypePublicIdentifier) t.transition(.BeforeDoctypePublicIdentifier)
break break
case "\"": case "\"":
@ -1264,7 +1258,7 @@ enum TokeniserState: TokeniserStateProtocol
case .BeforeDoctypePublicIdentifier: case .BeforeDoctypePublicIdentifier:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
break break
case "\"": case "\"":
// set public id to empty string // set public id to empty string
@ -1347,7 +1341,7 @@ enum TokeniserState: TokeniserStateProtocol
case .AfterDoctypePublicIdentifier: case .AfterDoctypePublicIdentifier:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
t.transition(.BetweenDoctypePublicAndSystemIdentifiers) t.transition(.BetweenDoctypePublicAndSystemIdentifiers)
break break
case ">": case ">":
@ -1379,7 +1373,7 @@ enum TokeniserState: TokeniserStateProtocol
case .BetweenDoctypePublicAndSystemIdentifiers: case .BetweenDoctypePublicAndSystemIdentifiers:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
break break
case ">": case ">":
try t.emitDoctypePending() try t.emitDoctypePending()
@ -1410,7 +1404,7 @@ enum TokeniserState: TokeniserStateProtocol
case .AfterDoctypeSystemKeyword: case .AfterDoctypeSystemKeyword:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
t.transition(.BeforeDoctypeSystemIdentifier) t.transition(.BeforeDoctypeSystemIdentifier)
break break
case ">": case ">":
@ -1444,7 +1438,7 @@ enum TokeniserState: TokeniserStateProtocol
case .BeforeDoctypeSystemIdentifier: case .BeforeDoctypeSystemIdentifier:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
break break
case "\"": case "\"":
// set system id to empty string // set system id to empty string
@ -1527,7 +1521,7 @@ enum TokeniserState: TokeniserStateProtocol
case .AfterDoctypeSystemIdentifier: case .AfterDoctypeSystemIdentifier:
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
break break
case ">": case ">":
try t.emitDoctypePending() try t.emitDoctypePending()
@ -1570,14 +1564,6 @@ enum TokeniserState: TokeniserStateProtocol
} }
} }
var description: String {return String(describing: type(of: self))} var description: String {return String(describing: type(of: self))}
/** /**
* Handles RawtextEndTagName, ScriptDataEndTagName, and ScriptDataEscapedEndTagName. Same body impl, just * Handles RawtextEndTagName, ScriptDataEndTagName, and ScriptDataEscapedEndTagName. Same body impl, just
@ -1595,7 +1581,7 @@ enum TokeniserState: TokeniserStateProtocol
if (try t.isAppropriateEndTagToken() && !r.isEmpty()) { if (try t.isAppropriateEndTagToken() && !r.isEmpty()) {
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ":
t.transition(BeforeAttributeName) t.transition(BeforeAttributeName)
break break
case "/": case "/":
@ -1641,9 +1627,9 @@ enum TokeniserState: TokeniserStateProtocol
private static func readCharRef(_ t: Tokeniser, _ advance: TokeniserState)throws { private static func readCharRef(_ t: Tokeniser, _ advance: TokeniserState)throws {
let c = try t.consumeCharacterReference(nil, false) let c = try t.consumeCharacterReference(nil, false)
if (c == nil){ if (c == nil) {
t.emit("&") t.emit("&")
}else{ } else {
t.emit(c!) t.emit(c!)
} }
t.transition(advance) t.transition(advance)
@ -1669,10 +1655,10 @@ enum TokeniserState: TokeniserStateProtocol
let c = r.consume() let c = r.consume()
switch (c) { switch (c) {
case "\t","\n","\r",UnicodeScalar.BackslashF," ","/",">": case "\t", "\n", "\r", UnicodeScalar.BackslashF, " ", "/", ">":
if (t.dataBuffer.toString() == "script"){ if (t.dataBuffer.toString() == "script") {
t.transition(primary) t.transition(primary)
}else{ } else {
t.transition(fallback) t.transition(fallback)
} }
t.emit(c) t.emit(c)
@ -1683,7 +1669,4 @@ enum TokeniserState: TokeniserStateProtocol
} }
} }
} }

View File

@ -21,17 +21,16 @@ public class TreeBuilder {
private let start: Token.StartTag = Token.StartTag() // start tag to process private let start: Token.StartTag = Token.StartTag() // start tag to process
private let end: Token.EndTag = Token.EndTag() private let end: Token.EndTag = Token.EndTag()
public func defaultSettings()->ParseSettings{preconditionFailure("This method must be overridden")} public func defaultSettings() -> ParseSettings {preconditionFailure("This method must be overridden")}
public init() { public init() {
doc = Document("") doc = Document("")
reader = CharacterReader("") reader = CharacterReader("")
tokeniser = Tokeniser(reader,nil) tokeniser = Tokeniser(reader, nil)
stack = Array<Element>() stack = Array<Element>()
baseUri = "" baseUri = ""
errors = ParseErrorList(0,0) errors = ParseErrorList(0, 0)
settings = ParseSettings(false,false) settings = ParseSettings(false, false)
} }
public func initialiseParse(_ input: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings) { public func initialiseParse(_ input: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings) {
@ -44,7 +43,6 @@ public class TreeBuilder {
self.baseUri = baseUri self.baseUri = baseUri
} }
func parse(_ input: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings)throws->Document { func parse(_ input: String, _ baseUri: String, _ errors: ParseErrorList, _ settings: ParseSettings)throws->Document {
initialiseParse(input, baseUri, errors, settings) initialiseParse(input, baseUri, errors, settings)
try runParser() try runParser()
@ -57,14 +55,14 @@ public class TreeBuilder {
try process(token) try process(token)
token.reset() token.reset()
if (token.type == Token.TokenType.EOF){ if (token.type == Token.TokenType.EOF) {
break break
} }
} }
} }
@discardableResult @discardableResult
public func process(_ token: Token)throws->Bool{preconditionFailure("This method must be overridden")} public func process(_ token: Token)throws->Bool {preconditionFailure("This method must be overridden")}
@discardableResult @discardableResult
public func processStartTag(_ name: String)throws->Bool { public func processStartTag(_ name: String)throws->Bool {
@ -93,8 +91,7 @@ public class TreeBuilder {
return try process(end.reset().name(name)) return try process(end.reset().name(name))
} }
public func currentElement() -> Element? {
public func currentElement()->Element? {
let size: Int = stack.count let size: Int = stack.count
return size > 0 ? stack[size-1] : nil return size > 0 ? stack[size-1] : nil
} }

View File

@ -15,9 +15,8 @@ private let alphaNumericSet = CharacterSet.alphanumerics
private let symbolSet = CharacterSet.symbols private let symbolSet = CharacterSet.symbols
private let digitSet = CharacterSet.decimalDigits private let digitSet = CharacterSet.decimalDigits
extension UnicodeScalar extension UnicodeScalar {
{ public static let BackslashF: UnicodeScalar = UnicodeScalar(12)
public static let BackslashF : UnicodeScalar = UnicodeScalar(12)
func isMemberOfCharacterSet(_ set: CharacterSet) -> Bool { func isMemberOfCharacterSet(_ set: CharacterSet) -> Bool {
return set.contains(self) return set.contains(self)
@ -43,7 +42,7 @@ extension UnicodeScalar
switch self { switch self {
case " ", "\t", "\n", "\r" ,UnicodeScalar.BackslashF: return true case " ", "\t", "\n", "\r", UnicodeScalar.BackslashF: return true
case "\u{000C}", "\u{000B}", "\u{0085}": return true // Form Feed, vertical tab, next line (nel) case "\u{000C}", "\u{000B}", "\u{0085}": return true // Form Feed, vertical tab, next line (nel)

View File

@ -8,15 +8,14 @@
import Foundation import Foundation
struct Validate struct Validate {
{
/** /**
* Validates that the object is not null * Validates that the object is not null
* @param obj object to test * @param obj object to test
*/ */
public static func notNull(obj:Any?) throws { public static func notNull(obj:Any?) throws {
if (obj == nil){ if (obj == nil) {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Object must not be null") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Object must not be null")
} }
} }
@ -26,8 +25,8 @@ struct Validate
* @param obj object to test * @param obj object to test
* @param msg message to output if validation fails * @param msg message to output if validation fails
*/ */
public static func notNull(obj:AnyObject?, msg:String) throws { public static func notNull(obj: AnyObject?, msg: String) throws {
if (obj == nil){ if (obj == nil) {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg) throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
} }
} }
@ -36,8 +35,8 @@ struct Validate
* Validates that the value is true * Validates that the value is true
* @param val object to test * @param val object to test
*/ */
public static func isTrue(val:Bool) throws { public static func isTrue(val: Bool) throws {
if (!val){ if (!val) {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Must be true") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Must be true")
} }
} }
@ -47,8 +46,8 @@ struct Validate
* @param val object to test * @param val object to test
* @param msg message to output if validation fails * @param msg message to output if validation fails
*/ */
public static func isTrue(val: Bool ,msg: String) throws { public static func isTrue(val: Bool, msg: String) throws {
if (!val){ if (!val) {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg) throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
} }
} }
@ -58,7 +57,7 @@ struct Validate
* @param val object to test * @param val object to test
*/ */
public static func isFalse(val: Bool) throws { public static func isFalse(val: Bool) throws {
if (val){ if (val) {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Must be false") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Must be false")
} }
} }
@ -69,7 +68,7 @@ struct Validate
* @param msg message to output if validation fails * @param msg message to output if validation fails
*/ */
public static func isFalse(val: Bool, msg: String) throws { public static func isFalse(val: Bool, msg: String) throws {
if (val){ if (val) {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg) throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
} }
} }
@ -79,7 +78,7 @@ struct Validate
* @param objects the array to test * @param objects the array to test
*/ */
public static func noNullElements(objects: [AnyObject?]) throws { public static func noNullElements(objects: [AnyObject?]) throws {
try noNullElements(objects: objects, msg: "Array must not contain any null objects"); try noNullElements(objects: objects, msg: "Array must not contain any null objects")
} }
/** /**
@ -89,7 +88,7 @@ struct Validate
*/ */
public static func noNullElements(objects: [AnyObject?], msg: String) throws { public static func noNullElements(objects: [AnyObject?], msg: String) throws {
for obj in objects { for obj in objects {
if (obj == nil){ if (obj == nil) {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg) throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
} }
} }
@ -99,9 +98,8 @@ struct Validate
* Validates that the string is not empty * Validates that the string is not empty
* @param string the string to test * @param string the string to test
*/ */
public static func notEmpty(string: String?) throws public static func notEmpty(string: String?) throws {
{ if (string == nil || string?.characters.count == 0) {
if (string == nil || string?.characters.count == 0){
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "String must not be empty") throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "String must not be empty")
} }
@ -113,7 +111,7 @@ struct Validate
* @param msg message to output if validation fails * @param msg message to output if validation fails
*/ */
public static func notEmpty(string: String?, msg: String ) throws { public static func notEmpty(string: String?, msg: String ) throws {
if (string == nil || string?.characters.count == 0){ if (string == nil || string?.characters.count == 0) {
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg) throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
} }
} }
@ -126,7 +124,6 @@ struct Validate
throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg) throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: msg)
} }
/** /**
Helper Helper
*/ */

View File

@ -57,21 +57,19 @@
import Foundation import Foundation
public class Whitelist { public class Whitelist {
private var tagNames : Set<TagName> // tags allowed, lower case. e.g. [p, br, span] private var tagNames: Set<TagName> // tags allowed, lower case. e.g. [p, br, span]
private var attributes : Dictionary<TagName, Set<AttributeKey>> // tag -> attribute[]. allowed attributes [href] for a tag. private var attributes: Dictionary<TagName, Set<AttributeKey>> // tag -> attribute[]. allowed attributes [href] for a tag.
private var enforcedAttributes : Dictionary<TagName, Dictionary<AttributeKey, AttributeValue>> // always set these attribute values private var enforcedAttributes: Dictionary<TagName, Dictionary<AttributeKey, AttributeValue>> // always set these attribute values
private var protocols : Dictionary<TagName, Dictionary<AttributeKey, Set<Protocol>>> // allowed URL protocols for attributes private var protocols: Dictionary<TagName, Dictionary<AttributeKey, Set<Protocol>>> // allowed URL protocols for attributes
private var preserveRelativeLinks : Bool // option to preserve relative links private var preserveRelativeLinks: Bool // option to preserve relative links
/** /**
This whitelist allows only text nodes: all HTML will be stripped. This whitelist allows only text nodes: all HTML will be stripped.
@return whitelist @return whitelist
*/ */
public static func none()->Whitelist { public static func none() -> Whitelist {
return Whitelist() return Whitelist()
} }
@ -172,10 +170,6 @@ public class Whitelist {
.addProtocols("q", "cite", "http", "https") .addProtocols("q", "cite", "http", "https")
} }
/** /**
Create a new, empty whitelist. Generally it will be better to start with a default prepared whitelist instead. Create a new, empty whitelist. Generally it will be better to start with a default prepared whitelist instead.
@ -200,8 +194,7 @@ public class Whitelist {
*/ */
@discardableResult @discardableResult
open func addTags(_ tags: String...)throws ->Whitelist { open func addTags(_ tags: String...)throws ->Whitelist {
for tagName in tags for tagName in tags {
{
try Validate.notEmpty(string: tagName) try Validate.notEmpty(string: tagName)
tagNames.insert(TagName.valueOf(tagName)) tagNames.insert(TagName.valueOf(tagName))
} }
@ -220,7 +213,7 @@ public class Whitelist {
for tag in tags { for tag in tags {
try Validate.notEmpty(string: tag) try Validate.notEmpty(string: tag)
let tagName : TagName = TagName.valueOf(tag) let tagName: TagName = TagName.valueOf(tag)
if(tagNames.contains(tagName)) { // Only look in sub-maps if tag was allowed if(tagNames.contains(tagName)) { // Only look in sub-maps if tag was allowed
tagNames.remove(tagName) tagNames.remove(tagName)
@ -253,19 +246,17 @@ public class Whitelist {
try Validate.isTrue(val: keys.count > 0, msg: "No attributes supplied.") try Validate.isTrue(val: keys.count > 0, msg: "No attributes supplied.")
let tagName = TagName.valueOf(tag) let tagName = TagName.valueOf(tag)
if (!tagNames.contains(tagName)){ if (!tagNames.contains(tagName)) {
tagNames.insert(tagName) tagNames.insert(tagName)
} }
var attributeSet = Set<AttributeKey>() var attributeSet = Set<AttributeKey>()
for key in keys for key in keys {
{
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
attributeSet.insert(AttributeKey.valueOf(key)) attributeSet.insert(AttributeKey.valueOf(key))
} }
if var currentSet = attributes[tagName] if var currentSet = attributes[tagName] {
{ for at in attributeSet {
for at in attributeSet{
currentSet.insert(at) currentSet.insert(at)
} }
attributes[tagName] = currentSet attributes[tagName] = currentSet
@ -296,39 +287,34 @@ public class Whitelist {
try Validate.notEmpty(string: tag) try Validate.notEmpty(string: tag)
try Validate.isTrue(val: keys.count > 0, msg: "No attributes supplied.") try Validate.isTrue(val: keys.count > 0, msg: "No attributes supplied.")
let tagName : TagName = TagName.valueOf(tag) let tagName: TagName = TagName.valueOf(tag)
var attributeSet = Set<AttributeKey>() var attributeSet = Set<AttributeKey>()
for key in keys { for key in keys {
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
attributeSet.insert(AttributeKey.valueOf(key)) attributeSet.insert(AttributeKey.valueOf(key))
} }
if(tagNames.contains(tagName)) { // Only look in sub-maps if tag was allowed if(tagNames.contains(tagName)) { // Only look in sub-maps if tag was allowed
if var currentSet = attributes[tagName] if var currentSet = attributes[tagName] {
{ for l in attributeSet {
for l in attributeSet
{
currentSet.remove(l) currentSet.remove(l)
} }
attributes[tagName] = currentSet attributes[tagName] = currentSet
if(currentSet.isEmpty){ // Remove tag from attribute map if no attributes are allowed for tag if(currentSet.isEmpty) { // Remove tag from attribute map if no attributes are allowed for tag
attributes.removeValue(forKey: tagName) attributes.removeValue(forKey: tagName)
} }
} }
} }
if(tag == ":all") { // Attribute needs to be removed from all individually set tags
if(tag == ":all"){ // Attribute needs to be removed from all individually set tags for name in attributes.keys {
for name in attributes.keys var currentSet: Set<AttributeKey> = attributes[name]!
{ for l in attributeSet {
var currentSet : Set<AttributeKey> = attributes[name]!
for l in attributeSet{
currentSet.remove(l) currentSet.remove(l)
} }
attributes[name] = currentSet attributes[name] = currentSet
if(currentSet.isEmpty){ // Remove tag from attribute map if no attributes are allowed for tag if(currentSet.isEmpty) { // Remove tag from attribute map if no attributes are allowed for tag
attributes.removeValue(forKey: name) attributes.removeValue(forKey: name)
} }
} }
@ -355,17 +341,17 @@ public class Whitelist {
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
try Validate.notEmpty(string: value) try Validate.notEmpty(string: value)
let tagName : TagName = TagName.valueOf(tag) let tagName: TagName = TagName.valueOf(tag)
if (!tagNames.contains(tagName)){ if (!tagNames.contains(tagName)) {
tagNames.insert(tagName) tagNames.insert(tagName)
} }
let attrKey : AttributeKey = AttributeKey.valueOf(key) let attrKey: AttributeKey = AttributeKey.valueOf(key)
let attrVal : AttributeValue = AttributeValue.valueOf(value) let attrVal: AttributeValue = AttributeValue.valueOf(value)
if (enforcedAttributes[tagName] != nil) { if (enforcedAttributes[tagName] != nil) {
enforcedAttributes[tagName]?[attrKey] = attrVal enforcedAttributes[tagName]?[attrKey] = attrVal
} else { } else {
var attrMap : Dictionary<AttributeKey, AttributeValue> = Dictionary<AttributeKey, AttributeValue>() var attrMap: Dictionary<AttributeKey, AttributeValue> = Dictionary<AttributeKey, AttributeValue>()
attrMap[attrKey] = attrVal attrMap[attrKey] = attrVal
enforcedAttributes[tagName] = attrMap enforcedAttributes[tagName] = attrMap
} }
@ -384,14 +370,14 @@ public class Whitelist {
try Validate.notEmpty(string: tag) try Validate.notEmpty(string: tag)
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
let tagName : TagName = TagName.valueOf(tag) let tagName: TagName = TagName.valueOf(tag)
if(tagNames.contains(tagName) && (enforcedAttributes[tagName] != nil)) { if(tagNames.contains(tagName) && (enforcedAttributes[tagName] != nil)) {
let attrKey : AttributeKey = AttributeKey.valueOf(key) let attrKey: AttributeKey = AttributeKey.valueOf(key)
var attrMap : Dictionary<AttributeKey, AttributeValue> = enforcedAttributes[tagName]! var attrMap: Dictionary<AttributeKey, AttributeValue> = enforcedAttributes[tagName]!
attrMap.removeValue(forKey: attrKey) attrMap.removeValue(forKey: attrKey)
enforcedAttributes[tagName] = attrMap enforcedAttributes[tagName] = attrMap
if(attrMap.isEmpty){ // Remove tag from enforced attribute map if no enforced attributes are present if(attrMap.isEmpty) { // Remove tag from enforced attribute map if no enforced attributes are present
enforcedAttributes.removeValue(forKey: tagName) enforcedAttributes.removeValue(forKey: tagName)
} }
} }
@ -414,7 +400,7 @@ public class Whitelist {
* @see #addProtocols * @see #addProtocols
*/ */
@discardableResult @discardableResult
open func preserveRelativeLinks(_ preserve: Bool)->Whitelist { open func preserveRelativeLinks(_ preserve: Bool) -> Whitelist {
preserveRelativeLinks = preserve preserveRelativeLinks = preserve
return self return self
} }
@ -441,10 +427,10 @@ public class Whitelist {
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
try Validate.notNull(obj: protocols) try Validate.notNull(obj: protocols)
let tagName : TagName = TagName.valueOf(tag) let tagName: TagName = TagName.valueOf(tag)
let attrKey : AttributeKey = AttributeKey.valueOf(key) let attrKey: AttributeKey = AttributeKey.valueOf(key)
var attrMap : Dictionary<AttributeKey, Set<Protocol>> var attrMap: Dictionary<AttributeKey, Set<Protocol>>
var protSet : Set<Protocol> var protSet: Set<Protocol>
if (self.protocols[tagName] != nil) { if (self.protocols[tagName] != nil) {
attrMap = self.protocols[tagName]! attrMap = self.protocols[tagName]!
@ -460,10 +446,9 @@ public class Whitelist {
attrMap[attrKey] = protSet attrMap[attrKey] = protSet
self.protocols[tagName] = attrMap self.protocols[tagName] = attrMap
} }
for ptl in protocols for ptl in protocols {
{
try Validate.notEmpty(string: ptl) try Validate.notEmpty(string: ptl)
let prot : Protocol = Protocol.valueOf(ptl) let prot: Protocol = Protocol.valueOf(ptl)
protSet.insert(prot) protSet.insert(prot)
} }
attrMap[attrKey] = protSet attrMap[attrKey] = protSet
@ -487,24 +472,23 @@ public class Whitelist {
try Validate.notEmpty(string: tag) try Validate.notEmpty(string: tag)
try Validate.notEmpty(string: key) try Validate.notEmpty(string: key)
let tagName : TagName = TagName.valueOf(tag) let tagName: TagName = TagName.valueOf(tag)
let attrKey : AttributeKey = AttributeKey.valueOf(key) let attrKey: AttributeKey = AttributeKey.valueOf(key)
if(self.protocols[tagName] != nil) { if(self.protocols[tagName] != nil) {
var attrMap : Dictionary<AttributeKey, Set<Protocol>>= self.protocols[tagName]! var attrMap: Dictionary<AttributeKey, Set<Protocol>>= self.protocols[tagName]!
if(attrMap[attrKey] != nil) { if(attrMap[attrKey] != nil) {
var protSet : Set<Protocol> = attrMap[attrKey]! var protSet: Set<Protocol> = attrMap[attrKey]!
for ptl in protocols for ptl in protocols {
{
try Validate.notEmpty(string: ptl) try Validate.notEmpty(string: ptl)
let prot : Protocol = Protocol.valueOf(ptl) let prot: Protocol = Protocol.valueOf(ptl)
protSet.remove(prot) protSet.remove(prot)
} }
attrMap[attrKey] = protSet attrMap[attrKey] = protSet
if(protSet.isEmpty) { // Remove protocol set if empty if(protSet.isEmpty) { // Remove protocol set if empty
attrMap.removeValue(forKey: attrKey) attrMap.removeValue(forKey: attrKey)
if(attrMap.isEmpty){ // Remove entry for tag if empty if(attrMap.isEmpty) { // Remove entry for tag if empty
self.protocols.removeValue(forKey: tagName) self.protocols.removeValue(forKey: tagName)
} }
@ -520,7 +504,7 @@ public class Whitelist {
* @param tag test tag * @param tag test tag
* @return true if allowed * @return true if allowed
*/ */
public func isSafeTag(_ tag: String)->Bool { public func isSafeTag(_ tag: String) -> Bool {
return tagNames.contains(TagName.valueOf(tag)) return tagNames.contains(TagName.valueOf(tag))
} }
@ -531,9 +515,9 @@ public class Whitelist {
* @param attr attribute under test * @param attr attribute under test
* @return true if allowed * @return true if allowed
*/ */
public func isSafeAttribute(_ tagName: String, _ el: Element, _ attr: Attribute)->Bool { public func isSafeAttribute(_ tagName: String, _ el: Element, _ attr: Attribute) -> Bool {
let tag : TagName = TagName.valueOf(tagName) let tag: TagName = TagName.valueOf(tagName)
let key : AttributeKey = AttributeKey.valueOf(attr.getKey()) let key: AttributeKey = AttributeKey.valueOf(attr.getKey())
if (attributes[tag] != nil) { if (attributes[tag] != nil) {
if (attributes[tag]?.contains(key))! { if (attributes[tag]?.contains(key))! {
@ -554,16 +538,15 @@ public class Whitelist {
private func testValidProtocol(_ el: Element, _ attr: Attribute, _ protocols: Set<Protocol>)throws->Bool { private func testValidProtocol(_ el: Element, _ attr: Attribute, _ protocols: Set<Protocol>)throws->Bool {
// try to resolve relative urls to abs, and optionally update the attribute so output html has abs. // try to resolve relative urls to abs, and optionally update the attribute so output html has abs.
// rels without a baseuri get removed // rels without a baseuri get removed
var value : String = try el.absUrl(attr.getKey()) var value: String = try el.absUrl(attr.getKey())
if (value.characters.count == 0){ if (value.characters.count == 0) {
value = attr.getValue() // if it could not be made abs, run as-is to allow custom unknown protocols value = attr.getValue() // if it could not be made abs, run as-is to allow custom unknown protocols
if (!preserveRelativeLinks){ if (!preserveRelativeLinks) {
attr.setValue(value: value) attr.setValue(value: value)
} }
for ptl in protocols for ptl in protocols {
{ var prot: String = ptl.toString()
var prot : String = ptl.toString()
if (prot=="#") { // allows anchor links if (prot=="#") { // allows anchor links
if (isValidAnchor(value)) { if (isValidAnchor(value)) {
@ -584,18 +567,15 @@ public class Whitelist {
return false return false
} }
private func isValidAnchor(_ value: String)->Bool private func isValidAnchor(_ value: String) -> Bool {
{
return value.startsWith("#") && !(Pattern(".*\\s.*").matcher(in: value).count > 0) return value.startsWith("#") && !(Pattern(".*\\s.*").matcher(in: value).count > 0)
} }
public func getEnforcedAttributes(_ tagName: String)throws->Attributes { public func getEnforcedAttributes(_ tagName: String)throws->Attributes {
let attrs: Attributes = Attributes() let attrs: Attributes = Attributes()
let tag: TagName = TagName.valueOf(tagName) let tag: TagName = TagName.valueOf(tagName)
if let keyVals: Dictionary<AttributeKey, AttributeValue> = enforcedAttributes[tag] if let keyVals: Dictionary<AttributeKey, AttributeValue> = enforcedAttributes[tag] {
{ for entry in keyVals {
for entry in keyVals
{
try attrs.put(entry.key.toString(), entry.value.toString()) try attrs.put(entry.key.toString(), entry.value.toString())
} }
} }
@ -604,61 +584,56 @@ public class Whitelist {
} }
// named types for config. All just hold strings, but here for my sanity. // named types for config. All just hold strings, but here for my sanity.
open class TagName : TypedValue { open class TagName: TypedValue {
override init(_ value: String) { override init(_ value: String) {
super.init(value) super.init(value)
} }
static func valueOf(_ value: String)->TagName{ static func valueOf(_ value: String) -> TagName {
return TagName(value) return TagName(value)
} }
} }
open class AttributeKey : TypedValue { open class AttributeKey: TypedValue {
override init(_ value: String) { override init(_ value: String) {
super.init(value) super.init(value)
} }
static func valueOf(_ value: String)->AttributeKey { static func valueOf(_ value: String) -> AttributeKey {
return AttributeKey(value) return AttributeKey(value)
} }
} }
open class AttributeValue : TypedValue { open class AttributeValue: TypedValue {
override init(_ value: String) { override init(_ value: String) {
super.init(value) super.init(value)
} }
static func valueOf(_ value: String)->AttributeValue { static func valueOf(_ value: String) -> AttributeValue {
return AttributeValue(value) return AttributeValue(value)
} }
} }
open class Protocol : TypedValue { open class Protocol: TypedValue {
override init(_ value: String) { override init(_ value: String) {
super.init(value) super.init(value)
} }
static func valueOf(_ value: String)->Protocol { static func valueOf(_ value: String) -> Protocol {
return Protocol(value) return Protocol(value)
} }
} }
open class TypedValue {
fileprivate let value: String
open class TypedValue
{
fileprivate let value : String
init(_ value: String) { init(_ value: String) {
self.value = value self.value = value
} }
public func toString()->String { public func toString() -> String {
return value return value
} }
} }
@ -667,13 +642,12 @@ extension TypedValue: Hashable {
public var hashValue: Int { public var hashValue: Int {
let prime = 31 let prime = 31
var result = 1 var result = 1
result = Int.addWithOverflow(Int.multiplyWithOverflow(prime,result).0, value.hash).0 result = Int.addWithOverflow(Int.multiplyWithOverflow(prime, result).0, value.hash).0
return result return result
} }
} }
public func == (lhs: TypedValue, rhs: TypedValue) -> Bool public func == (lhs: TypedValue, rhs: TypedValue) -> Bool {
{ if(lhs === rhs) {return true}
if(lhs === rhs){return true}
return lhs.value == rhs.value return lhs.value == rhs.value
} }

View File

@ -12,7 +12,7 @@ import Foundation
An XML Declaration. An XML Declaration.
@author Jonathan Hedley, jonathan@hedley.net */ @author Jonathan Hedley, jonathan@hedley.net */
public class XmlDeclaration : Node { public class XmlDeclaration: Node {
private let _name: String private let _name: String
private let isProcessingInstruction: Bool // <! if true, <? if false, declaration (and last data char should be ?) private let isProcessingInstruction: Bool // <! if true, <? if false, declaration (and last data char should be ?)
@ -28,16 +28,15 @@ public class XmlDeclaration : Node {
super.init(baseUri) super.init(baseUri)
} }
public override func nodeName()->String { public override func nodeName() -> String {
return "#declaration" return "#declaration"
} }
/** /**
* Get the name of this declaration. * Get the name of this declaration.
* @return name of this declaration. * @return name of this declaration.
*/ */
public func name()->String { public func name() -> String {
return _name return _name
} }
@ -54,9 +53,9 @@ public class XmlDeclaration : Node {
.append("<") .append("<")
.append(isProcessingInstruction ? "!" : "?") .append(isProcessingInstruction ? "!" : "?")
.append(_name) .append(_name)
do{ do {
try attributes?.html(accum: accum, out: out) try attributes?.html(accum: accum, out: out)
}catch{} } catch {}
accum accum
.append(isProcessingInstruction ? "!" : "?") .append(isProcessingInstruction ? "!" : "?")
.append(">") .append(">")
@ -64,26 +63,23 @@ public class XmlDeclaration : Node {
override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {} override func outerHtmlTail(_ accum: StringBuilder, _ depth: Int, _ out: OutputSettings) {}
public override func toString()->String { public override func toString() -> String {
do{ do {
return try outerHtml() return try outerHtml()
}catch{} } catch {}
return "" return ""
} }
public override func copy(with zone: NSZone? = nil) -> Any public override func copy(with zone: NSZone? = nil) -> Any {
{ let clone = XmlDeclaration(_name, baseUri!, isProcessingInstruction)
let clone = XmlDeclaration(_name,baseUri!,isProcessingInstruction)
return copy(clone: clone) return copy(clone: clone)
} }
public override func copy(parent: Node?)->Node public override func copy(parent: Node?) -> Node {
{ let clone = XmlDeclaration(_name, baseUri!, isProcessingInstruction)
let clone = XmlDeclaration(_name,baseUri!,isProcessingInstruction) return copy(clone: clone, parent: parent)
return copy(clone: clone,parent: parent)
} }
public override func copy(clone: Node, parent: Node?)->Node public override func copy(clone: Node, parent: Node?) -> Node {
{ return super.copy(clone: clone, parent: parent)
return super.copy(clone: clone,parent: parent)
} }
} }

View File

@ -14,18 +14,16 @@ import Foundation
* <p>Usage example: {@code Document xmlDoc = Jsoup.parse(html, baseUrl, Parser.xmlParser())}</p> * <p>Usage example: {@code Document xmlDoc = Jsoup.parse(html, baseUrl, Parser.xmlParser())}</p>
* *
*/ */
public class XmlTreeBuilder : TreeBuilder { public class XmlTreeBuilder: TreeBuilder {
public override init(){ public override init() {
super.init() super.init()
} }
public override func defaultSettings() -> ParseSettings {
public override func defaultSettings()->ParseSettings {
return ParseSettings.preserveCase return ParseSettings.preserveCase
} }
public func parse(_ input: String, _ baseUri: String)throws->Document { public func parse(_ input: String, _ baseUri: String)throws->Document {
return try parse(input, baseUri, ParseErrorList.noTracking(), ParseSettings.preserveCase) return try parse(input, baseUri, ParseErrorList.noTracking(), ParseSettings.preserveCase)
} }
@ -36,7 +34,6 @@ public class XmlTreeBuilder : TreeBuilder {
doc.outputSettings().syntax(syntax: OutputSettings.Syntax.xml) doc.outputSettings().syntax(syntax: OutputSettings.Syntax.xml)
} }
override public func process(_ token: Token)throws->Bool { override public func process(_ token: Token)throws->Bool {
// start tag, end tag, doctype, comment, character, eof // start tag, end tag, doctype, comment, character, eof
switch (token.type) { switch (token.type) {
@ -121,24 +118,21 @@ public class XmlTreeBuilder : TreeBuilder {
let elName: String = try endTag.name() let elName: String = try endTag.name()
var firstFound: Element? = nil var firstFound: Element? = nil
for pos in (0..<stack.count).reversed() for pos in (0..<stack.count).reversed() {
{
let next: Element = stack[pos] let next: Element = stack[pos]
if (next.nodeName().equals(elName)) { if (next.nodeName().equals(elName)) {
firstFound = next firstFound = next
break break
} }
} }
if (firstFound == nil){ if (firstFound == nil) {
return // not found, skip return // not found, skip
} }
for pos in (0..<stack.count).reversed() {
for pos in (0..<stack.count).reversed()
{
let next: Element = stack[pos] let next: Element = stack[pos]
stack.remove(at: pos) stack.remove(at: pos)
if (next == firstFound!){ if (next == firstFound!) {
break break
} }
} }

View File

@ -6,7 +6,6 @@
// Copyright © 2016 Nabil Chatbi. All rights reserved. // Copyright © 2016 Nabil Chatbi. All rights reserved.
// //
import XCTest import XCTest
@testable import SwiftSoupTests @testable import SwiftSoupTests

View File

@ -58,13 +58,13 @@ class AttributeParseTest: XCTestCase {
let html: String = "<a id=1 href='?foo=bar&mid&lt=true'>One</a> <a id=2 href='?foo=bar&lt;qux&lg=1'>Two</a>" let html: String = "<a id=1 href='?foo=bar&mid&lt=true'>One</a> <a id=2 href='?foo=bar&lt;qux&lg=1'>Two</a>"
let els: Elements = try SwiftSoup.parse(html).select("a") let els: Elements = try SwiftSoup.parse(html).select("a")
XCTAssertEqual("?foo=bar&mid&lt=true", try els.first()!.attr("href")) XCTAssertEqual("?foo=bar&mid&lt=true", try els.first()!.attr("href"))
XCTAssertEqual("?foo=bar<qux&lg=1",try els.last()!.attr("href")) XCTAssertEqual("?foo=bar<qux&lg=1", try els.last()!.attr("href"))
} }
func testmoreAttributeUnescapes()throws { func testmoreAttributeUnescapes()throws {
let html: String = "<a href='&wr_id=123&mid-size=true&ok=&wr'>Check</a>" let html: String = "<a href='&wr_id=123&mid-size=true&ok=&wr'>Check</a>"
let els: Elements = try SwiftSoup.parse(html).select("a") let els: Elements = try SwiftSoup.parse(html).select("a")
XCTAssertEqual("&wr_id=123&mid-size=true&ok=&wr",try els.first()!.attr("href")) XCTAssertEqual("&wr_id=123&mid-size=true&ok=&wr", try els.first()!.attr("href"))
} }
func testparsesBooleanAttributes()throws { func testparsesBooleanAttributes()throws {
@ -76,12 +76,12 @@ class AttributeParseTest: XCTestCase {
XCTAssertEqual("", try el.attr("empty")) XCTAssertEqual("", try el.attr("empty"))
let attributes: Array<Attribute> = el.getAttributes()!.asList() let attributes: Array<Attribute> = el.getAttributes()!.asList()
XCTAssertEqual(3, attributes.count,"There should be 3 attribute present") XCTAssertEqual(3, attributes.count, "There should be 3 attribute present")
// Assuming the list order always follows the parsed html // Assuming the list order always follows the parsed html
XCTAssertFalse((attributes[0] as? BooleanAttribute) != nil,"'normal' attribute should not be boolean") XCTAssertFalse((attributes[0] as? BooleanAttribute) != nil, "'normal' attribute should not be boolean")
XCTAssertTrue((attributes[1] as? BooleanAttribute) != nil,"'boolean' attribute should be boolean") XCTAssertTrue((attributes[1] as? BooleanAttribute) != nil, "'boolean' attribute should be boolean")
XCTAssertFalse((attributes[2] as? BooleanAttribute) != nil,"'empty' attribute should not be boolean") XCTAssertFalse((attributes[2] as? BooleanAttribute) != nil, "'empty' attribute should not be boolean")
XCTAssertEqual(html, try el.outerHtml()) XCTAssertEqual(html, try el.outerHtml())
} }
@ -89,7 +89,7 @@ class AttributeParseTest: XCTestCase {
func testdropsSlashFromAttributeName()throws { func testdropsSlashFromAttributeName()throws {
let html: String = "<img /onerror='doMyJob'/>" let html: String = "<img /onerror='doMyJob'/>"
var doc: Document = try SwiftSoup.parse(html) var doc: Document = try SwiftSoup.parse(html)
XCTAssertTrue(try doc.select("img[onerror]").size() != 0,"SelfClosingStartTag ignores last character") XCTAssertTrue(try doc.select("img[onerror]").size() != 0, "SelfClosingStartTag ignores last character")
XCTAssertEqual("<img onerror=\"doMyJob\">", try doc.body()!.html()) XCTAssertEqual("<img onerror=\"doMyJob\">", try doc.body()!.html())
doc = try SwiftSoup.parse(html, "", Parser.xmlParser()) doc = try SwiftSoup.parse(html, "", Parser.xmlParser())
@ -98,14 +98,14 @@ class AttributeParseTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testparsesRoughAttributeString" , testparsesRoughAttributeString), ("testparsesRoughAttributeString", testparsesRoughAttributeString),
("testhandlesNewLinesAndReturns" , testhandlesNewLinesAndReturns), ("testhandlesNewLinesAndReturns", testhandlesNewLinesAndReturns),
("testparsesEmptyString" , testparsesEmptyString), ("testparsesEmptyString", testparsesEmptyString),
("testcanStartWithEq" , testcanStartWithEq), ("testcanStartWithEq", testcanStartWithEq),
("teststrictAttributeUnescapes" , teststrictAttributeUnescapes), ("teststrictAttributeUnescapes", teststrictAttributeUnescapes),
("testmoreAttributeUnescapes" , testmoreAttributeUnescapes), ("testmoreAttributeUnescapes", testmoreAttributeUnescapes),
("testparsesBooleanAttributes" , testparsesBooleanAttributes), ("testparsesBooleanAttributes", testparsesBooleanAttributes),
("testdropsSlashFromAttributeName" , testdropsSlashFromAttributeName), ("testdropsSlashFromAttributeName", testdropsSlashFromAttributeName),
] ]
}() }()

View File

@ -10,8 +10,7 @@ import XCTest
@testable import SwiftSoup @testable import SwiftSoup
class AttributeTest: XCTestCase { class AttributeTest: XCTestCase {
func testHtml() func testHtml() {
{
let attr = try! Attribute(key: "key", value: "value &") let attr = try! Attribute(key: "key", value: "value &")
XCTAssertEqual("key=\"value &amp;\"", attr.html()) XCTAssertEqual("key=\"value &amp;\"", attr.html())
XCTAssertEqual(attr.html(), attr.toString()) XCTAssertEqual(attr.html(), attr.toString())
@ -26,8 +25,8 @@ class AttributeTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testHtml" , testHtml), ("testHtml", testHtml),
("testWithSupplementaryCharacterInAttributeKeyAndValue" , testWithSupplementaryCharacterInAttributeKeyAndValue) ("testWithSupplementaryCharacterInAttributeKeyAndValue", testWithSupplementaryCharacterInAttributeKeyAndValue)
] ]
}() }()

View File

@ -13,12 +13,11 @@ class AttributesTest: XCTestCase {
func testHtml() { func testHtml() {
let a: Attributes = Attributes() let a: Attributes = Attributes()
do{ do {
try a.put("Tot", "a&p") try a.put("Tot", "a&p")
try a.put("Hello", "There") try a.put("Hello", "There")
try a.put("data-name", "Jsoup") try a.put("data-name", "Jsoup")
}catch{} } catch {}
XCTAssertEqual(3, a.size()) XCTAssertEqual(3, a.size())
XCTAssertTrue(a.hasKey(key: "Tot")) XCTAssertTrue(a.hasKey(key: "Tot"))
@ -26,7 +25,7 @@ class AttributesTest: XCTestCase {
XCTAssertTrue(a.hasKey(key: "data-name")) XCTAssertTrue(a.hasKey(key: "data-name"))
XCTAssertFalse(a.hasKey(key: "tot")) XCTAssertFalse(a.hasKey(key: "tot"))
XCTAssertTrue(a.hasKeyIgnoreCase(key: "tot")) XCTAssertTrue(a.hasKeyIgnoreCase(key: "tot"))
XCTAssertEqual("There",try a.getIgnoreCase(key: "hEllo")) XCTAssertEqual("There", try a.getIgnoreCase(key: "hEllo"))
XCTAssertEqual(1, a.dataset().count) XCTAssertEqual(1, a.dataset().count)
XCTAssertEqual("Jsoup", a.dataset()["name"]) XCTAssertEqual("Jsoup", a.dataset()["name"])
@ -53,10 +52,9 @@ class AttributesTest: XCTestCase {
// XCTAssertEqual(2, a.size()) // XCTAssertEqual(2, a.size())
// } // }
func testIterator() { func testIterator() {
let a: Attributes = Attributes() let a: Attributes = Attributes()
let datas: [[String]] = [["Tot", "raul"],["Hello", "pismuth"],["data-name", "Jsoup"]] let datas: [[String]] = [["Tot", "raul"], ["Hello", "pismuth"], ["data-name", "Jsoup"]]
for atts in datas { for atts in datas {
try! a.put(atts[0], atts[1]) try! a.put(atts[0], atts[1])
@ -82,9 +80,9 @@ class AttributesTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testHtml" , testHtml), ("testHtml", testHtml),
("testIterator" , testIterator), ("testIterator", testIterator),
("testIteratorEmpty" , testIteratorEmpty) ("testIteratorEmpty", testIteratorEmpty)
] ]
}() }()
} }

View File

@ -167,7 +167,6 @@ class CharacterReaderTest: XCTestCase {
XCTAssertFalse(r.matches("ne")) XCTAssertFalse(r.matches("ne"))
} }
func testMatchesIgnoreCase() { func testMatchesIgnoreCase() {
let r = CharacterReader("One Two Three") let r = CharacterReader("One Two Three")
XCTAssertTrue(r.matchesIgnoreCase("O")) XCTAssertTrue(r.matchesIgnoreCase("O"))
@ -245,25 +244,25 @@ class CharacterReaderTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testConsume" , testConsume), ("testConsume", testConsume),
("testUnconsume" , testUnconsume), ("testUnconsume", testUnconsume),
("testMark" , testMark), ("testMark", testMark),
("testConsumeToEnd" , testConsumeToEnd), ("testConsumeToEnd", testConsumeToEnd),
("testNextIndexOfChar" , testNextIndexOfChar), ("testNextIndexOfChar", testNextIndexOfChar),
("testNextIndexOfString" , testNextIndexOfString), ("testNextIndexOfString", testNextIndexOfString),
("testNextIndexOfUnmatched" , testNextIndexOfUnmatched), ("testNextIndexOfUnmatched", testNextIndexOfUnmatched),
("testConsumeToChar" , testConsumeToChar), ("testConsumeToChar", testConsumeToChar),
("testConsumeToString" , testConsumeToString), ("testConsumeToString", testConsumeToString),
("testAdvance" , testAdvance), ("testAdvance", testAdvance),
("testConsumeToAny" , testConsumeToAny), ("testConsumeToAny", testConsumeToAny),
("testConsumeLetterSequence" , testConsumeLetterSequence), ("testConsumeLetterSequence", testConsumeLetterSequence),
("testConsumeLetterThenDigitSequence" , testConsumeLetterThenDigitSequence), ("testConsumeLetterThenDigitSequence", testConsumeLetterThenDigitSequence),
("testMatches" , testMatches), ("testMatches", testMatches),
("testMatchesIgnoreCase" , testMatchesIgnoreCase), ("testMatchesIgnoreCase", testMatchesIgnoreCase),
("testContainsIgnoreCase" , testContainsIgnoreCase), ("testContainsIgnoreCase", testContainsIgnoreCase),
("testMatchesAny" , testMatchesAny), ("testMatchesAny", testMatchesAny),
("testCachesStrings" , testCachesStrings), ("testCachesStrings", testCachesStrings),
("testRangeEquals" , testRangeEquals) ("testRangeEquals", testRangeEquals)
] ]
}() }()

View File

@ -19,13 +19,13 @@ class CssTest: XCTestCase {
let sb: StringBuilder = StringBuilder(string:"<html><head></head><body>") let sb: StringBuilder = StringBuilder(string:"<html><head></head><body>")
sb.append("<div id='pseudo'>") sb.append("<div id='pseudo'>")
for i in 1...10{ for i in 1...10 {
sb.append("<p>\(i)</p>") sb.append("<p>\(i)</p>")
} }
sb.append("</div>") sb.append("</div>")
sb.append("<div id='type'>") sb.append("<div id='type'>")
for i in 1...10{ for i in 1...10 {
sb.append("<p>\(i)</p>") sb.append("<p>\(i)</p>")
sb.append("<span>\(i)</span>") sb.append("<span>\(i)</span>")
sb.append("<em>\(i)</em>") sb.append("<em>\(i)</em>")
@ -45,7 +45,6 @@ class CssTest: XCTestCase {
html = try! SwiftSoup.parse(htmlString) html = try! SwiftSoup.parse(htmlString)
} }
func testFirstChild()throws { func testFirstChild()throws {
try check(html.select("#pseudo :first-child"), "1") try check(html.select("#pseudo :first-child"), "1")
try check(html.select("html:first-child")) try check(html.select("html:first-child"))
@ -82,7 +81,7 @@ class CssTest: XCTestCase {
func testNthLastOfType_simple()throws { func testNthLastOfType_simple()throws {
for i in 1...10 { for i in 1...10 {
try check(html.select("#type :nth-last-of-type(\(i))"), "\(11-i)","\(11-i)","\(11-i)","\(11-i)") try check(html.select("#type :nth-last-of-type(\(i))"), "\(11-i)", "\(11-i)", "\(11-i)", "\(11-i)")
} }
} }
@ -112,7 +111,6 @@ class CssTest: XCTestCase {
try check(html.select("#type :nth-of-type(+5)"), "5", "5", "5", "5") try check(html.select("#type :nth-of-type(+5)"), "5", "5", "5", "5")
} }
func testNthLastChild_advanced()throws { func testNthLastChild_advanced()throws {
try check(html.select("#pseudo :nth-last-child(-5)")) try check(html.select("#pseudo :nth-last-child(-5)"))
try check(html.select("#pseudo :nth-last-child(odd)"), "2", "4", "6", "8", "10") try check(html.select("#pseudo :nth-last-child(odd)"), "2", "4", "6", "8", "10")
@ -177,9 +175,8 @@ class CssTest: XCTestCase {
XCTAssertEqual("em", sel.get(5).tagName()) XCTAssertEqual("em", sel.get(5).tagName())
} }
func check(_ resut: Elements, _ expectedContent: String... ) { func check(_ resut: Elements, _ expectedContent: String... ) {
check(resut,expectedContent) check(resut, expectedContent)
} }
func check(_ result: Elements, _ expectedContent: [String] ) { func check(_ result: Elements, _ expectedContent: [String] ) {
@ -202,28 +199,25 @@ class CssTest: XCTestCase {
try XCTAssertEqual(Tag.valueOf("body"), sel2.get(0).tag()) try XCTAssertEqual(Tag.valueOf("body"), sel2.get(0).tag())
} }
static var allTests = { static var allTests = {
return [ return [
("testFirstChild" , testFirstChild), ("testFirstChild", testFirstChild),
("testLastChild" , testLastChild), ("testLastChild", testLastChild),
("testNthChild_simple" , testNthChild_simple), ("testNthChild_simple", testNthChild_simple),
("testNthOfType_unknownTag" , testNthOfType_unknownTag), ("testNthOfType_unknownTag", testNthOfType_unknownTag),
("testNthLastChild_simple" , testNthLastChild_simple), ("testNthLastChild_simple", testNthLastChild_simple),
("testNthOfType_simple" , testNthOfType_simple), ("testNthOfType_simple", testNthOfType_simple),
("testNthLastOfType_simple" , testNthLastOfType_simple), ("testNthLastOfType_simple", testNthLastOfType_simple),
("testNthChild_advanced" , testNthChild_advanced), ("testNthChild_advanced", testNthChild_advanced),
("testNthOfType_advanced" , testNthOfType_advanced), ("testNthOfType_advanced", testNthOfType_advanced),
("testNthLastChild_advanced" , testNthLastChild_advanced), ("testNthLastChild_advanced", testNthLastChild_advanced),
("testNthLastOfType_advanced" , testNthLastOfType_advanced), ("testNthLastOfType_advanced", testNthLastOfType_advanced),
("testFirstOfType" , testFirstOfType), ("testFirstOfType", testFirstOfType),
("testLastOfType" , testLastOfType), ("testLastOfType", testLastOfType),
("testEmpty" , testEmpty), ("testEmpty", testEmpty),
("testOnlyChild" , testOnlyChild), ("testOnlyChild", testOnlyChild),
("testOnlyOfType" , testOnlyOfType), ("testOnlyOfType", testOnlyOfType),
("testRoot" , testRoot) ("testRoot", testRoot)
] ]
}() }()
} }

View File

@ -15,19 +15,19 @@ class DocumentTest: XCTestCase {
private static let charsetIso8859 = String.Encoding.iso2022JP //"ISO-8859-1" private static let charsetIso8859 = String.Encoding.iso2022JP //"ISO-8859-1"
func testSetTextPreservesDocumentStructure() { func testSetTextPreservesDocumentStructure() {
do{ do {
let doc: Document = try SwiftSoup.parse("<p>Hello</p>") let doc: Document = try SwiftSoup.parse("<p>Hello</p>")
try doc.text("Replaced") try doc.text("Replaced")
XCTAssertEqual("Replaced", try doc.text()) XCTAssertEqual("Replaced", try doc.text())
XCTAssertEqual("Replaced", try doc.body()!.text()) XCTAssertEqual("Replaced", try doc.body()!.text())
XCTAssertEqual(1, try doc.select("head").size()) XCTAssertEqual(1, try doc.select("head").size())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testTitles() { func testTitles() {
do{ do {
let noTitle: Document = try SwiftSoup.parse("<p>Hello</p>") let noTitle: Document = try SwiftSoup.parse("<p>Hello</p>")
let withTitle: Document = try SwiftSoup.parse("<title>First</title><title>Ignore</title><p>Hello</p>") let withTitle: Document = try SwiftSoup.parse("<title>First</title><title>Ignore</title><p>Hello</p>")
@ -43,35 +43,34 @@ class DocumentTest: XCTestCase {
let normaliseTitle: Document = try SwiftSoup.parse("<title> Hello\nthere \n now \n") let normaliseTitle: Document = try SwiftSoup.parse("<title> Hello\nthere \n now \n")
XCTAssertEqual("Hello there now", try normaliseTitle.title()) XCTAssertEqual("Hello there now", try normaliseTitle.title())
}catch{ } catch {
} }
} }
func testOutputEncoding() { func testOutputEncoding() {
do{ do {
let doc: Document = try SwiftSoup.parse("<p title=π>π & < > </p>") let doc: Document = try SwiftSoup.parse("<p title=π>π & < > </p>")
// default is utf-8 // default is utf-8
XCTAssertEqual("<p title=\"π\">π &amp; &lt; &gt; </p>", try doc.body()?.html()) XCTAssertEqual("<p title=\"π\">π &amp; &lt; &gt; </p>", try doc.body()?.html())
XCTAssertEqual("UTF-8",doc.outputSettings().charset().displayName()) XCTAssertEqual("UTF-8", doc.outputSettings().charset().displayName())
doc.outputSettings().charset(String.Encoding.ascii) doc.outputSettings().charset(String.Encoding.ascii)
XCTAssertEqual(Entities.EscapeMode.base,doc.outputSettings().escapeMode()) XCTAssertEqual(Entities.EscapeMode.base, doc.outputSettings().escapeMode())
XCTAssertEqual("<p title=\"&#x3c0;\">&#x3c0; &amp; &lt; &gt; </p>", try doc.body()?.html()) XCTAssertEqual("<p title=\"&#x3c0;\">&#x3c0; &amp; &lt; &gt; </p>", try doc.body()?.html())
doc.outputSettings().escapeMode(Entities.EscapeMode.extended) doc.outputSettings().escapeMode(Entities.EscapeMode.extended)
XCTAssertEqual("<p title=\"&pi;\">&pi; &amp; &lt; &gt; </p>", try doc.body()?.html()) XCTAssertEqual("<p title=\"&pi;\">&pi; &amp; &lt; &gt; </p>", try doc.body()?.html())
}catch } catch {
{ XCTAssertEqual(1, 2)
XCTAssertEqual(1,2)
} }
} }
func testXhtmlReferences() { func testXhtmlReferences() {
let doc: Document = try! SwiftSoup.parse("&lt; &gt; &amp; &quot; &apos; &times;") let doc: Document = try! SwiftSoup.parse("&lt; &gt; &amp; &quot; &apos; &times;")
doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml) doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml)
XCTAssertEqual("&lt; &gt; &amp; \" ' ×",try! doc.body()?.html()) XCTAssertEqual("&lt; &gt; &amp; \" ' ×", try! doc.body()?.html())
} }
func testNormalisesStructure() { func testNormalisesStructure() {
@ -83,7 +82,7 @@ class DocumentTest: XCTestCase {
let doc: Document = try! SwiftSoup.parse("<title>Hello</title> <p>One<p>Two") let doc: Document = try! SwiftSoup.parse("<title>Hello</title> <p>One<p>Two")
let clone: Document = doc.copy() as! Document let clone: Document = doc.copy() as! Document
XCTAssertEqual("<html><head><title>Hello</title> </head><body><p>One</p><p>Two</p></body></html>",try! TextUtil.stripNewlines(clone.html())) XCTAssertEqual("<html><head><title>Hello</title> </head><body><p>One</p><p>Two</p></body></html>", try! TextUtil.stripNewlines(clone.html()))
try! clone.title("Hello there") try! clone.title("Hello there")
try! clone.select("p").first()!.text("One more").attr("id", "1") try! clone.select("p").first()!.text("One more").attr("id", "1")
XCTAssertEqual("<html><head><title>Hello there</title> </head><body><p id=\"1\">One more</p><p>Two</p></body></html>", try! TextUtil.stripNewlines(clone.html())) XCTAssertEqual("<html><head><title>Hello there</title> </head><body><p id=\"1\">One more</p><p>Two</p></body></html>", try! TextUtil.stripNewlines(clone.html()))
@ -191,13 +190,12 @@ class DocumentTest: XCTestCase {
func testMetaCharsetUpdateUtf8() { func testMetaCharsetUpdateUtf8() {
let doc: Document = createHtmlDocument("changeThis") let doc: Document = createHtmlDocument("changeThis")
doc.updateMetaCharsetElement(true) doc.updateMetaCharsetElement(true)
do{ do {
try doc.charset(DocumentTest.charsetUtf8) try doc.charset(DocumentTest.charsetUtf8)
}catch{ } catch {
print("") print("")
} }
let htmlCharsetUTF8: String = "<html>\n" + " <head>\n" + " <meta charset=\"" + "UTF-8" + "\">\n" + " </head>\n" + " <body></body>\n" + "</html>" let htmlCharsetUTF8: String = "<html>\n" + " <head>\n" + " <meta charset=\"" + "UTF-8" + "\">\n" + " </head>\n" + " <body></body>\n" + "</html>"
XCTAssertEqual(htmlCharsetUTF8, try! doc.toString()) XCTAssertEqual(htmlCharsetUTF8, try! doc.toString())
@ -219,7 +217,7 @@ class DocumentTest: XCTestCase {
" </head>\n" + " </head>\n" +
" <body></body>\n" + " <body></body>\n" +
"</html>" "</html>"
XCTAssertEqual(htmlCharsetISO,try doc.toString()) XCTAssertEqual(htmlCharsetISO, try doc.toString())
let selectedElement: Element = try doc.select("meta[charset]").first()! let selectedElement: Element = try doc.select("meta[charset]").first()!
XCTAssertEqual(String.Encoding.isoLatin2.displayName(), doc.charset().displayName()) XCTAssertEqual(String.Encoding.isoLatin2.displayName(), doc.charset().displayName())
@ -376,8 +374,7 @@ class DocumentTest: XCTestCase {
XCTAssertFalse(doc.updateMetaCharsetElement()) XCTAssertFalse(doc.updateMetaCharsetElement())
} }
private func createHtmlDocument(_ charset: String) -> Document {
private func createHtmlDocument(_ charset: String)->Document {
let doc: Document = Document.createShell("") let doc: Document = Document.createShell("")
try! doc.head()?.appendElement("meta").attr("charset", charset) try! doc.head()?.appendElement("meta").attr("charset", charset)
try! doc.head()?.appendElement("meta").attr("name", "charset").attr("content", charset) try! doc.head()?.appendElement("meta").attr("name", "charset").attr("content", charset)
@ -423,31 +420,31 @@ class DocumentTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testSetTextPreservesDocumentStructure" , testSetTextPreservesDocumentStructure), ("testSetTextPreservesDocumentStructure", testSetTextPreservesDocumentStructure),
("testTitles" , testTitles), ("testTitles", testTitles),
("testOutputEncoding" , testOutputEncoding), ("testOutputEncoding", testOutputEncoding),
("testXhtmlReferences" , testXhtmlReferences), ("testXhtmlReferences", testXhtmlReferences),
("testNormalisesStructure" , testNormalisesStructure), ("testNormalisesStructure", testNormalisesStructure),
("testClone" , testClone), ("testClone", testClone),
("testClonesDeclarations" , testClonesDeclarations), ("testClonesDeclarations", testClonesDeclarations),
("testHtmlAndXmlSyntax" , testHtmlAndXmlSyntax), ("testHtmlAndXmlSyntax", testHtmlAndXmlSyntax),
("testHtmlParseDefaultsToHtmlOutputSyntax" , testHtmlParseDefaultsToHtmlOutputSyntax), ("testHtmlParseDefaultsToHtmlOutputSyntax", testHtmlParseDefaultsToHtmlOutputSyntax),
("testHtmlAppendable" , testHtmlAppendable), ("testHtmlAppendable", testHtmlAppendable),
("testDocumentsWithSameContentAreEqual" , testDocumentsWithSameContentAreEqual), ("testDocumentsWithSameContentAreEqual", testDocumentsWithSameContentAreEqual),
("testDocumentsWithSameContentAreVerifialbe" , testDocumentsWithSameContentAreVerifialbe), ("testDocumentsWithSameContentAreVerifialbe", testDocumentsWithSameContentAreVerifialbe),
("testMetaCharsetUpdateUtf8" , testMetaCharsetUpdateUtf8), ("testMetaCharsetUpdateUtf8", testMetaCharsetUpdateUtf8),
("testMetaCharsetUpdateIsoLatin2" , testMetaCharsetUpdateIsoLatin2), ("testMetaCharsetUpdateIsoLatin2", testMetaCharsetUpdateIsoLatin2),
("testMetaCharsetUpdateNoCharset" , testMetaCharsetUpdateNoCharset), ("testMetaCharsetUpdateNoCharset", testMetaCharsetUpdateNoCharset),
("testMetaCharsetUpdateDisabled" , testMetaCharsetUpdateDisabled), ("testMetaCharsetUpdateDisabled", testMetaCharsetUpdateDisabled),
("testMetaCharsetUpdateDisabledNoChanges" , testMetaCharsetUpdateDisabledNoChanges), ("testMetaCharsetUpdateDisabledNoChanges", testMetaCharsetUpdateDisabledNoChanges),
("testMetaCharsetUpdateEnabledAfterCharsetChange" , testMetaCharsetUpdateEnabledAfterCharsetChange), ("testMetaCharsetUpdateEnabledAfterCharsetChange", testMetaCharsetUpdateEnabledAfterCharsetChange),
("testMetaCharsetUpdateCleanup" , testMetaCharsetUpdateCleanup), ("testMetaCharsetUpdateCleanup", testMetaCharsetUpdateCleanup),
("testMetaCharsetUpdateXmlUtf8" , testMetaCharsetUpdateXmlUtf8), ("testMetaCharsetUpdateXmlUtf8", testMetaCharsetUpdateXmlUtf8),
("testMetaCharsetUpdateXmlIso2022JP" , testMetaCharsetUpdateXmlIso2022JP), ("testMetaCharsetUpdateXmlIso2022JP", testMetaCharsetUpdateXmlIso2022JP),
("testMetaCharsetUpdateXmlNoCharset" , testMetaCharsetUpdateXmlNoCharset), ("testMetaCharsetUpdateXmlNoCharset", testMetaCharsetUpdateXmlNoCharset),
("testMetaCharsetUpdateXmlDisabled" , testMetaCharsetUpdateXmlDisabled), ("testMetaCharsetUpdateXmlDisabled", testMetaCharsetUpdateXmlDisabled),
("testMetaCharsetUpdateXmlDisabledNoChanges" , testMetaCharsetUpdateXmlDisabledNoChanges), ("testMetaCharsetUpdateXmlDisabledNoChanges", testMetaCharsetUpdateXmlDisabledNoChanges),
("testMetaCharsetUpdatedDisabledPerDefault" , testMetaCharsetUpdatedDisabledPerDefault) ("testMetaCharsetUpdatedDisabledPerDefault", testMetaCharsetUpdatedDisabledPerDefault)
] ]
}() }()

View File

@ -12,11 +12,10 @@ import SwiftSoup
class DocumentTypeTest: XCTestCase { class DocumentTypeTest: XCTestCase {
func testConstructorValidationOkWithBlankName() { func testConstructorValidationOkWithBlankName() {
let fail: DocumentType? = DocumentType("","", "", "") let fail: DocumentType? = DocumentType("", "", "", "")
XCTAssertTrue(fail != nil) XCTAssertTrue(fail != nil)
} }
func testConstructorValidationThrowsExceptionOnNulls() { func testConstructorValidationThrowsExceptionOnNulls() {
let fail: DocumentType? = DocumentType("html", "", "", "") let fail: DocumentType? = DocumentType("html", "", "", "")
XCTAssertTrue(fail != nil) XCTAssertTrue(fail != nil)
@ -43,10 +42,10 @@ class DocumentTypeTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testConstructorValidationOkWithBlankName" , testConstructorValidationOkWithBlankName), ("testConstructorValidationOkWithBlankName", testConstructorValidationOkWithBlankName),
("testConstructorValidationThrowsExceptionOnNulls" , testConstructorValidationThrowsExceptionOnNulls), ("testConstructorValidationThrowsExceptionOnNulls", testConstructorValidationThrowsExceptionOnNulls),
("testConstructorValidationOkWithBlankPublicAndSystemIds" , testConstructorValidationOkWithBlankPublicAndSystemIds), ("testConstructorValidationOkWithBlankPublicAndSystemIds", testConstructorValidationOkWithBlankPublicAndSystemIds),
("testOuterHtmlGeneration" , testOuterHtmlGeneration), ("testOuterHtmlGeneration", testOuterHtmlGeneration),
] ]
}() }()
} }

View File

@ -354,8 +354,7 @@ class ElementTest: XCTestCase {
// check sibling index (with short circuit on reindexChildren): // check sibling index (with short circuit on reindexChildren):
let ps: Elements = try doc.select("p") let ps: Elements = try doc.select("p")
for i in 0..<ps.size() for i in 0..<ps.size() {
{
XCTAssertEqual(i, ps.get(i).siblingIndex) XCTAssertEqual(i, ps.get(i).siblingIndex)
} }
} }
@ -414,7 +413,7 @@ class ElementTest: XCTestCase {
let doc: Document = try SwiftSoup.parse("<div id=1><p>Hello</p></div>") let doc: Document = try SwiftSoup.parse("<div id=1><p>Hello</p></div>")
let div: Element = try doc.getElementById("1")! let div: Element = try doc.getElementById("1")!
try div.appendText(" there & now >") try div.appendText(" there & now >")
XCTAssertEqual("<p>Hello</p> there &amp; now &gt;",try TextUtil.stripNewlines(div.html())) XCTAssertEqual("<p>Hello</p> there &amp; now &gt;", try TextUtil.stripNewlines(div.html()))
} }
func testPrependText()throws { func testPrependText()throws {
@ -447,7 +446,7 @@ class ElementTest: XCTestCase {
// check sibling index (no reindexChildren): // check sibling index (no reindexChildren):
let ps: Elements = try doc.select("p") let ps: Elements = try doc.select("p")
for i in 0..<ps.size(){ for i in 0..<ps.size() {
XCTAssertEqual(i, ps.get(i).siblingIndex) XCTAssertEqual(i, ps.get(i).siblingIndex)
} }
} }
@ -460,7 +459,7 @@ class ElementTest: XCTestCase {
// check sibling index (reindexChildren): // check sibling index (reindexChildren):
let ps: Elements = try doc.select("p") let ps: Elements = try doc.select("p")
for i in 0..<ps.size(){ for i in 0..<ps.size() {
XCTAssertEqual(i, ps.get(i).siblingIndex) XCTAssertEqual(i, ps.get(i).siblingIndex)
} }
} }
@ -516,7 +515,7 @@ class ElementTest: XCTestCase {
XCTAssertEqual("<div><p>Hello</p><div>one</div><div>two</div><p>There</p></div>", try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<div><p>Hello</p><div>one</div><div>two</div><p>There</p></div>", try TextUtil.stripNewlines(doc.body()!.html()))
try doc.select("p").last()?.after("<p>Three</p><!-- four -->") try doc.select("p").last()?.after("<p>Three</p><!-- four -->")
XCTAssertEqual("<div><p>Hello</p><div>one</div><div>two</div><p>There</p><p>Three</p><!-- four --></div>",TextUtil.stripNewlines(try doc.body()!.html())) XCTAssertEqual("<div><p>Hello</p><div>one</div><div>two</div><p>There</p><p>Three</p><!-- four --></div>", TextUtil.stripNewlines(try doc.body()!.html()))
} }
func testWrapWithRemainder()throws { func testWrapWithRemainder()throws {
@ -592,12 +591,12 @@ class ElementTest: XCTestCase {
XCTAssertNotNil(p.parent()) XCTAssertNotNil(p.parent())
try clone.append("<span>Three") try clone.append("<span>Three")
XCTAssertEqual("<p><span>Two</span><span>Three</span></p>",try TextUtil.stripNewlines(clone.outerHtml())) XCTAssertEqual("<p><span>Two</span><span>Three</span></p>", try TextUtil.stripNewlines(clone.outerHtml()))
XCTAssertEqual("<div><p>One</p><p><span>Two</span></p></div>",try TextUtil.stripNewlines(doc.body()!.html())) // not modified XCTAssertEqual("<div><p>One</p><p><span>Two</span></p></div>", try TextUtil.stripNewlines(doc.body()!.html())) // not modified
try doc.body()?.appendChild(clone) // adopt try doc.body()?.appendChild(clone) // adopt
XCTAssertNotNil(clone.parent()) XCTAssertNotNil(clone.parent())
XCTAssertEqual("<div><p>One</p><p><span>Two</span></p></div><p><span>Two</span><span>Three</span></p>",try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<div><p>One</p><p><span>Two</span></p></div><p><span>Two</span><span>Three</span></p>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testClonesClassnames()throws { func testClonesClassnames()throws {
@ -718,7 +717,7 @@ class ElementTest: XCTestCase {
XCTAssertEqual(0, children.count) // children is backed by div1.childNodes, moved, so should be 0 now XCTAssertEqual(0, children.count) // children is backed by div1.childNodes, moved, so should be 0 now
XCTAssertEqual(0, div1.childNodeSize()) XCTAssertEqual(0, div1.childNodeSize())
XCTAssertEqual(4, div2.childNodeSize()) XCTAssertEqual(4, div2.childNodeSize())
XCTAssertEqual("<div id=\"1\"></div>\n<div id=\"2\">\n Text \n <p>One</p> Text \n <p>Two</p>\n</div>",try doc.body()!.html()) XCTAssertEqual("<div id=\"1\"></div>\n<div id=\"2\">\n Text \n <p>One</p> Text \n <p>Two</p>\n</div>", try doc.body()!.html())
} }
func testInsertChildrenArgumentValidation()throws { func testInsertChildrenArgumentValidation()throws {
@ -729,13 +728,13 @@ class ElementTest: XCTestCase {
do { do {
try div2.insertChildren(6, children) try div2.insertChildren(6, children)
XCTAssertEqual(0,1) XCTAssertEqual(0, 1)
} catch{} } catch {}
do { do {
try div2.insertChildren(-5, children) try div2.insertChildren(-5, children)
XCTAssertEqual(0,1) XCTAssertEqual(0, 1)
} catch{ } catch {
} }
} }
@ -820,7 +819,6 @@ class ElementTest: XCTestCase {
try div.classNames(newSet) try div.classNames(newSet)
XCTAssertEqual("c1 c2 c3", try div.className()) XCTAssertEqual("c1 c2 c3", try div.className())
let set2 = try div.classNames() let set2 = try div.classNames()
@ -932,75 +930,75 @@ class ElementTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testGetElementsByTagName" , testGetElementsByTagName), ("testGetElementsByTagName", testGetElementsByTagName),
("testGetNamespacedElementsByTag" , testGetNamespacedElementsByTag), ("testGetNamespacedElementsByTag", testGetNamespacedElementsByTag),
("testGetElementById" , testGetElementById), ("testGetElementById", testGetElementById),
("testGetText" , testGetText), ("testGetText", testGetText),
("testGetChildText" , testGetChildText), ("testGetChildText", testGetChildText),
("testNormalisesText" , testNormalisesText), ("testNormalisesText", testNormalisesText),
("testKeepsPreText" , testKeepsPreText), ("testKeepsPreText", testKeepsPreText),
("testKeepsPreTextInCode" , testKeepsPreTextInCode), ("testKeepsPreTextInCode", testKeepsPreTextInCode),
("testBrHasSpace" , testBrHasSpace), ("testBrHasSpace", testBrHasSpace),
("testGetSiblings" , testGetSiblings), ("testGetSiblings", testGetSiblings),
("testGetSiblingsWithDuplicateContent" , testGetSiblingsWithDuplicateContent), ("testGetSiblingsWithDuplicateContent", testGetSiblingsWithDuplicateContent),
("testGetParents" , testGetParents), ("testGetParents", testGetParents),
("testElementSiblingIndex" , testElementSiblingIndex), ("testElementSiblingIndex", testElementSiblingIndex),
("testElementSiblingIndexSameContent" , testElementSiblingIndexSameContent), ("testElementSiblingIndexSameContent", testElementSiblingIndexSameContent),
("testGetElementsWithClass" , testGetElementsWithClass), ("testGetElementsWithClass", testGetElementsWithClass),
("testGetElementsWithAttribute" , testGetElementsWithAttribute), ("testGetElementsWithAttribute", testGetElementsWithAttribute),
("testGetElementsWithAttributeDash" , testGetElementsWithAttributeDash), ("testGetElementsWithAttributeDash", testGetElementsWithAttributeDash),
("testGetElementsWithAttributeValue" , testGetElementsWithAttributeValue), ("testGetElementsWithAttributeValue", testGetElementsWithAttributeValue),
("testClassDomMethods" , testClassDomMethods), ("testClassDomMethods", testClassDomMethods),
("testHasClassDomMethods" , testHasClassDomMethods), ("testHasClassDomMethods", testHasClassDomMethods),
("testClassUpdates" , testClassUpdates), ("testClassUpdates", testClassUpdates),
("testOuterHtml" , testOuterHtml), ("testOuterHtml", testOuterHtml),
("testInnerHtml" , testInnerHtml), ("testInnerHtml", testInnerHtml),
("testFormatHtml" , testFormatHtml), ("testFormatHtml", testFormatHtml),
("testFormatOutline" , testFormatOutline), ("testFormatOutline", testFormatOutline),
("testSetIndent" , testSetIndent), ("testSetIndent", testSetIndent),
("testNotPretty" , testNotPretty), ("testNotPretty", testNotPretty),
("testEmptyElementFormatHtml" , testEmptyElementFormatHtml), ("testEmptyElementFormatHtml", testEmptyElementFormatHtml),
("testNoIndentOnScriptAndStyle" , testNoIndentOnScriptAndStyle), ("testNoIndentOnScriptAndStyle", testNoIndentOnScriptAndStyle),
("testContainerOutput" , testContainerOutput), ("testContainerOutput", testContainerOutput),
("testSetText" , testSetText), ("testSetText", testSetText),
("testAddNewElement" , testAddNewElement), ("testAddNewElement", testAddNewElement),
("testAddBooleanAttribute" , testAddBooleanAttribute), ("testAddBooleanAttribute", testAddBooleanAttribute),
("testAppendRowToTable" , testAppendRowToTable), ("testAppendRowToTable", testAppendRowToTable),
("testPrependRowToTable" , testPrependRowToTable), ("testPrependRowToTable", testPrependRowToTable),
("testPrependElement" , testPrependElement), ("testPrependElement", testPrependElement),
("testAddNewText" , testAddNewText), ("testAddNewText", testAddNewText),
("testPrependText" , testPrependText), ("testPrependText", testPrependText),
("testAddNewHtml" , testAddNewHtml), ("testAddNewHtml", testAddNewHtml),
("testPrependNewHtml" , testPrependNewHtml), ("testPrependNewHtml", testPrependNewHtml),
("testSetHtml" , testSetHtml), ("testSetHtml", testSetHtml),
("testSetHtmlTitle" , testSetHtmlTitle), ("testSetHtmlTitle", testSetHtmlTitle),
("testWrap" , testWrap), ("testWrap", testWrap),
("testBefore" , testBefore), ("testBefore", testBefore),
("testAfter" , testAfter), ("testAfter", testAfter),
("testWrapWithRemainder" , testWrapWithRemainder), ("testWrapWithRemainder", testWrapWithRemainder),
("testHasText" , testHasText), ("testHasText", testHasText),
("testDataset" , testDataset), ("testDataset", testDataset),
("testpParentlessToString" , testpParentlessToString), ("testpParentlessToString", testpParentlessToString),
("testClone" , testClone), ("testClone", testClone),
("testClonesClassnames" , testClonesClassnames), ("testClonesClassnames", testClonesClassnames),
("testTagNameSet" , testTagNameSet), ("testTagNameSet", testTagNameSet),
("testHtmlContainsOuter" , testHtmlContainsOuter), ("testHtmlContainsOuter", testHtmlContainsOuter),
("testGetTextNodes" , testGetTextNodes), ("testGetTextNodes", testGetTextNodes),
("testManipulateTextNodes" , testManipulateTextNodes), ("testManipulateTextNodes", testManipulateTextNodes),
("testGetDataNodes" , testGetDataNodes), ("testGetDataNodes", testGetDataNodes),
("testElementIsNotASiblingOfItself" , testElementIsNotASiblingOfItself), ("testElementIsNotASiblingOfItself", testElementIsNotASiblingOfItself),
("testChildThrowsIndexOutOfBoundsOnMissing" , testChildThrowsIndexOutOfBoundsOnMissing), ("testChildThrowsIndexOutOfBoundsOnMissing", testChildThrowsIndexOutOfBoundsOnMissing),
("testMoveByAppend" , testMoveByAppend), ("testMoveByAppend", testMoveByAppend),
("testInsertChildrenArgumentValidation" , testInsertChildrenArgumentValidation), ("testInsertChildrenArgumentValidation", testInsertChildrenArgumentValidation),
("testInsertChildrenAtPosition" , testInsertChildrenAtPosition), ("testInsertChildrenAtPosition", testInsertChildrenAtPosition),
("testInsertChildrenAsCopy" , testInsertChildrenAsCopy), ("testInsertChildrenAsCopy", testInsertChildrenAsCopy),
("testCssPath" , testCssPath), ("testCssPath", testCssPath),
("testClassNames" , testClassNames), ("testClassNames", testClassNames),
("testHashAndEqualsAndValue" , testHashAndEqualsAndValue), ("testHashAndEqualsAndValue", testHashAndEqualsAndValue),
("testRelativeUrls" , testRelativeUrls), ("testRelativeUrls", testRelativeUrls),
("testAppendMustCorrectlyMoveChildrenInsideOneParentElement" , testAppendMustCorrectlyMoveChildrenInsideOneParentElement), ("testAppendMustCorrectlyMoveChildrenInsideOneParentElement", testAppendMustCorrectlyMoveChildrenInsideOneParentElement),
("testHashcodeIsStableWithContentChanges" , testHashcodeIsStableWithContentChanges), ("testHashcodeIsStableWithContentChanges", testHashcodeIsStableWithContentChanges),
("testNamespacedElements" , testNamespacedElements) ("testNamespacedElements", testNamespacedElements)
] ]
}() }()
} }

View File

@ -160,7 +160,7 @@ class ElementsTest: XCTestCase {
let h = "<p><b>This</b> is <b>jsoup</b>.</p> <p>How do you like it?</p>" let h = "<p><b>This</b> is <b>jsoup</b>.</p> <p>How do you like it?</p>"
let doc: Document = try SwiftSoup.parse(h) let doc: Document = try SwiftSoup.parse(h)
try doc.select("p").wrap("<div></div>") try doc.select("p").wrap("<div></div>")
XCTAssertEqual("<div><p><b>This</b> is <b>jsoup</b>.</p></div> <div><p>How do you like it?</p></div>",try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<div><p><b>This</b> is <b>jsoup</b>.</p></div> <div><p>How do you like it?</p></div>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testUnwrap()throws { func testUnwrap()throws {
@ -248,7 +248,7 @@ class ElementsTest: XCTestCase {
let doc: Document = try SwiftSoup.parse("<div><p>Hello</p></div><div>There</div>") let doc: Document = try SwiftSoup.parse("<div><p>Hello</p></div><div>There</div>")
let accum: StringBuilder = StringBuilder() let accum: StringBuilder = StringBuilder()
class nv : NodeVisitor { class nv: NodeVisitor {
let accum: StringBuilder let accum: StringBuilder
init(_ accum: StringBuilder) { init(_ accum: StringBuilder) {
self.accum = accum self.accum = accum
@ -284,39 +284,38 @@ class ElementsTest: XCTestCase {
try XCTAssertEqual("Check", els.text()) try XCTAssertEqual("Check", els.text())
} }
static var allTests = { static var allTests = {
return [ return [
("testFilter" , testFilter), ("testFilter", testFilter),
("testAttributes" , testAttributes), ("testAttributes", testAttributes),
("testHasAttr" , testHasAttr), ("testHasAttr", testHasAttr),
("testHasAbsAttr" , testHasAbsAttr), ("testHasAbsAttr", testHasAbsAttr),
("testAttr" , testAttr), ("testAttr", testAttr),
("testAbsAttr" , testAbsAttr), ("testAbsAttr", testAbsAttr),
("testClasses" , testClasses), ("testClasses", testClasses),
("testText" , testText), ("testText", testText),
("testHasText" , testHasText), ("testHasText", testHasText),
("testHtml" , testHtml), ("testHtml", testHtml),
("testOuterHtml" , testOuterHtml), ("testOuterHtml", testOuterHtml),
("testSetHtml" , testSetHtml), ("testSetHtml", testSetHtml),
("testVal" , testVal), ("testVal", testVal),
("testBefore" , testBefore), ("testBefore", testBefore),
("testAfter" , testAfter), ("testAfter", testAfter),
("testWrap" , testWrap), ("testWrap", testWrap),
("testWrapDiv" , testWrapDiv), ("testWrapDiv", testWrapDiv),
("testUnwrap" , testUnwrap), ("testUnwrap", testUnwrap),
("testUnwrapP" , testUnwrapP), ("testUnwrapP", testUnwrapP),
("testUnwrapKeepsSpace" , testUnwrapKeepsSpace), ("testUnwrapKeepsSpace", testUnwrapKeepsSpace),
("testEmpty" , testEmpty), ("testEmpty", testEmpty),
("testRemove" , testRemove), ("testRemove", testRemove),
("testEq" , testEq), ("testEq", testEq),
("testIs" , testIs), ("testIs", testIs),
("testParents" , testParents), ("testParents", testParents),
("testNot" , testNot), ("testNot", testNot),
("testTagNameSet" , testTagNameSet), ("testTagNameSet", testTagNameSet),
("testTraverse" , testTraverse), ("testTraverse", testTraverse),
("testForms" , testForms), ("testForms", testForms),
("testClassWithHyphen" , testClassWithHyphen) ("testClassWithHyphen", testClassWithHyphen)
] ]
}() }()
} }

View File

@ -15,15 +15,15 @@ class EntitiesTest: XCTestCase {
func testEscape()throws { func testEscape()throws {
let text = "Hello &<> Å å π 新 there ¾ © »" let text = "Hello &<> Å å π 新 there ¾ © »"
let escapedAscii = Entities.escape(text,OutputSettings().encoder(String.Encoding.ascii).escapeMode(Entities.EscapeMode.base)) let escapedAscii = Entities.escape(text, OutputSettings().encoder(String.Encoding.ascii).escapeMode(Entities.EscapeMode.base))
let escapedAsciiFull = Entities.escape(text, OutputSettings().charset(String.Encoding.ascii).escapeMode(Entities.EscapeMode.extended)) let escapedAsciiFull = Entities.escape(text, OutputSettings().charset(String.Encoding.ascii).escapeMode(Entities.EscapeMode.extended))
let escapedAsciiXhtml = Entities.escape(text, OutputSettings().charset(String.Encoding.ascii).escapeMode(Entities.EscapeMode.xhtml)) let escapedAsciiXhtml = Entities.escape(text, OutputSettings().charset(String.Encoding.ascii).escapeMode(Entities.EscapeMode.xhtml))
let escapedUtfFull = Entities.escape(text, OutputSettings().charset(String.Encoding.utf8).escapeMode(Entities.EscapeMode.extended)) let escapedUtfFull = Entities.escape(text, OutputSettings().charset(String.Encoding.utf8).escapeMode(Entities.EscapeMode.extended))
let escapedUtfMin = Entities.escape(text, OutputSettings().charset(String.Encoding.utf8).escapeMode(Entities.EscapeMode.xhtml)) let escapedUtfMin = Entities.escape(text, OutputSettings().charset(String.Encoding.utf8).escapeMode(Entities.EscapeMode.xhtml))
XCTAssertEqual("Hello &amp;&lt;&gt; &Aring; &aring; &#x3c0; &#x65b0; there &frac34; &copy; &raquo;", escapedAscii); XCTAssertEqual("Hello &amp;&lt;&gt; &Aring; &aring; &#x3c0; &#x65b0; there &frac34; &copy; &raquo;", escapedAscii)
XCTAssertEqual("Hello &amp;&lt;&gt; &angst; &aring; &pi; &#x65b0; there &frac34; &copy; &raquo;", escapedAsciiFull); XCTAssertEqual("Hello &amp;&lt;&gt; &angst; &aring; &pi; &#x65b0; there &frac34; &copy; &raquo;", escapedAsciiFull)
XCTAssertEqual("Hello &amp;&lt;&gt; &#xc5; &#xe5; &#x3c0; &#x65b0; there &#xbe; &#xa9; &#xbb;", escapedAsciiXhtml); XCTAssertEqual("Hello &amp;&lt;&gt; &#xc5; &#xe5; &#x3c0; &#x65b0; there &#xbe; &#xa9; &#xbb;", escapedAsciiXhtml)
XCTAssertEqual("Hello &amp;&lt;&gt; Å å π 新 there ¾ © »", escapedUtfFull) XCTAssertEqual("Hello &amp;&lt;&gt; Å å π 新 there ¾ © »", escapedUtfFull)
XCTAssertEqual("Hello &amp;&lt;&gt; Å å π 新 there ¾ © »", escapedUtfMin) XCTAssertEqual("Hello &amp;&lt;&gt; Å å π 新 there ¾ © »", escapedUtfMin)
// odd that it's defined as aring in base but angst in full // odd that it's defined as aring in base but angst in full
@ -36,8 +36,6 @@ class EntitiesTest: XCTestCase {
XCTAssertEqual(text, try Entities.unescape(escapedUtfMin)) XCTAssertEqual(text, try Entities.unescape(escapedUtfMin))
} }
func testXhtml() { func testXhtml() {
//let text = "&amp; &gt; &lt; &quot;"; //let text = "&amp; &gt; &lt; &quot;";
XCTAssertEqual(38, Entities.EscapeMode.xhtml.codepointForName("amp")) XCTAssertEqual(38, Entities.EscapeMode.xhtml.codepointForName("amp"))
@ -92,7 +90,6 @@ class EntitiesTest: XCTestCase {
XCTAssertEqual("Hello &= &", try Entities.unescape(string: text, strict: false)) XCTAssertEqual("Hello &= &", try Entities.unescape(string: text, strict: false))
} }
func testCaseSensitive()throws { func testCaseSensitive()throws {
let unescaped: String = "Ü ü & &" let unescaped: String = "Ü ü & &"
XCTAssertEqual("&Uuml; &uuml; &amp; &amp;", XCTAssertEqual("&Uuml; &uuml; &amp; &amp;",
@ -128,7 +125,6 @@ class EntitiesTest: XCTestCase {
func testUscapesGtInXmlAttributesButNotInHtml()throws { func testUscapesGtInXmlAttributesButNotInHtml()throws {
// https://github.com/jhy/jsoup/issues/528 - < is OK in HTML attribute values, but not in XML // https://github.com/jhy/jsoup/issues/528 - < is OK in HTML attribute values, but not in XML
let docHtml: String = "<a title='<p>One</p>'>One</a>" let docHtml: String = "<a title='<p>One</p>'>One</a>"
let doc: Document = try SwiftSoup.parse(docHtml) let doc: Document = try SwiftSoup.parse(docHtml)
let element: Element = try doc.select("a").first()! let element: Element = try doc.select("a").first()!
@ -137,25 +133,24 @@ class EntitiesTest: XCTestCase {
XCTAssertEqual("<a title=\"<p>One</p>\">One</a>", try element.outerHtml()) XCTAssertEqual("<a title=\"<p>One</p>\">One</a>", try element.outerHtml())
doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml) doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml)
XCTAssertEqual("<a title=\"&lt;p>One&lt;/p>\">One</a>",try element.outerHtml()) XCTAssertEqual("<a title=\"&lt;p>One&lt;/p>\">One</a>", try element.outerHtml())
} }
static var allTests = { static var allTests = {
return [ return [
("testEscape" , testEscape), ("testEscape", testEscape),
("testXhtml" , testXhtml), ("testXhtml", testXhtml),
("testGetByName" , testGetByName), ("testGetByName", testGetByName),
("testEscapeSupplementaryCharacter" , testEscapeSupplementaryCharacter), ("testEscapeSupplementaryCharacter", testEscapeSupplementaryCharacter),
("testNotMissingMultis" , testNotMissingMultis), ("testNotMissingMultis", testNotMissingMultis),
("testnotMissingSupplementals" , testnotMissingSupplementals), ("testnotMissingSupplementals", testnotMissingSupplementals),
("testUnescape" , testUnescape), ("testUnescape", testUnescape),
("testStrictUnescape" , testStrictUnescape), ("testStrictUnescape", testStrictUnescape),
("testCaseSensitive" , testCaseSensitive), ("testCaseSensitive", testCaseSensitive),
("testQuoteReplacements" , testQuoteReplacements), ("testQuoteReplacements", testQuoteReplacements),
("testLetterDigitEntities" , testLetterDigitEntities), ("testLetterDigitEntities", testLetterDigitEntities),
("testNoSpuriousDecodes" , testNoSpuriousDecodes), ("testNoSpuriousDecodes", testNoSpuriousDecodes),
("testUscapesGtInXmlAttributesButNotInHtml" , testUscapesGtInXmlAttributesButNotInHtml) ("testUscapesGtInXmlAttributesButNotInHtml", testUscapesGtInXmlAttributesButNotInHtml)
] ]
}() }()
} }

View File

@ -73,7 +73,6 @@ class FormElementTest: XCTestCase {
// assertEquals("http://example.com/", con.request().url().toExternalForm()); // assertEquals("http://example.com/", con.request().url().toExternalForm());
// } // }
//TODO: //TODO:
// @Test public void actionWithNoBaseUri() { // @Test public void actionWithNoBaseUri() {
// String html = "<form><input name='q'></form>"; // String html = "<form><input name='q'></form>";
@ -152,9 +151,9 @@ class FormElementTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testHasAssociatedControls" , testHasAssociatedControls), ("testHasAssociatedControls", testHasAssociatedControls),
("testFormsAddedAfterParseAreFormElements" , testFormsAddedAfterParseAreFormElements), ("testFormsAddedAfterParseAreFormElements", testFormsAddedAfterParseAreFormElements),
("testControlsAddedAfterParseAreLinkedWithForms" , testControlsAddedAfterParseAreLinkedWithForms) ("testControlsAddedAfterParseAreLinkedWithForms", testControlsAddedAfterParseAreLinkedWithForms)
] ]
}() }()
} }

View File

@ -121,11 +121,11 @@ class HtmlParserTest: XCTestCase {
XCTAssertEqual(3, head.children().size()) XCTAssertEqual(3, head.children().size())
XCTAssertEqual(1, body.children().size()) XCTAssertEqual(1, body.children().size())
XCTAssertEqual("keywords",try head.getElementsByTag("meta").get(0).attr("name")) XCTAssertEqual("keywords", try head.getElementsByTag("meta").get(0).attr("name"))
XCTAssertEqual(0, try body.getElementsByTag("meta").size()) XCTAssertEqual(0, try body.getElementsByTag("meta").size())
XCTAssertEqual("jsoup",try doc.title()) XCTAssertEqual("jsoup", try doc.title())
XCTAssertEqual("Hello world",try body.text()) XCTAssertEqual("Hello world", try body.text())
XCTAssertEqual("Hello world",try body.children().get(0).text()) XCTAssertEqual("Hello world", try body.children().get(0).text())
} }
func testCreatesStructureFromBodySnippet()throws { func testCreatesStructureFromBodySnippet()throws {
@ -133,7 +133,7 @@ class HtmlParserTest: XCTestCase {
// needs to move into the start of the body // needs to move into the start of the body
let html = "foo <b>bar</b> baz" let html = "foo <b>bar</b> baz"
let doc = try SwiftSoup.parse(html) let doc = try SwiftSoup.parse(html)
XCTAssertEqual("foo bar baz",try doc.text()) XCTAssertEqual("foo bar baz", try doc.text())
} }
@ -167,8 +167,8 @@ class HtmlParserTest: XCTestCase {
func testHandlesTextArea()throws { func testHandlesTextArea()throws {
let doc: Document = try SwiftSoup.parse("<textarea>Hello</textarea>") let doc: Document = try SwiftSoup.parse("<textarea>Hello</textarea>")
let els: Elements = try doc.select("textarea") let els: Elements = try doc.select("textarea")
XCTAssertEqual("Hello",try els.text()) XCTAssertEqual("Hello", try els.text())
XCTAssertEqual("Hello",try els.val()) XCTAssertEqual("Hello", try els.val())
} }
func testPreservesSpaceInTextArea()throws { func testPreservesSpaceInTextArea()throws {
@ -179,7 +179,7 @@ class HtmlParserTest: XCTestCase {
XCTAssertEqual(expect, try el.text()) XCTAssertEqual(expect, try el.text())
XCTAssertEqual(expect, try el.val()) XCTAssertEqual(expect, try el.val())
XCTAssertEqual(expect, try el.html()) XCTAssertEqual(expect, try el.html())
XCTAssertEqual("<textarea>\n\t" + expect + "\n</textarea>",try el.outerHtml()) // but preserved in round-trip html XCTAssertEqual("<textarea>\n\t" + expect + "\n</textarea>", try el.outerHtml()) // but preserved in round-trip html
} }
func testPreservesSpaceInScript()throws { func testPreservesSpaceInScript()throws {
@ -363,7 +363,7 @@ class HtmlParserTest: XCTestCase {
XCTAssertEqual(4, try doc.select("dt, dd").size()) XCTAssertEqual(4, try doc.select("dt, dd").size())
let dts: Elements = try doc.select("dt") let dts: Elements = try doc.select("dt")
XCTAssertEqual(2, dts.size()) XCTAssertEqual(2, dts.size())
XCTAssertEqual("Zug",try dts.get(1).nextElementSibling()?.text()) XCTAssertEqual("Zug", try dts.get(1).nextElementSibling()?.text())
} }
func testHandlesBlocksInDefinitions()throws { func testHandlesBlocksInDefinitions()throws {
@ -372,7 +372,7 @@ class HtmlParserTest: XCTestCase {
let doc = try SwiftSoup.parse(h) let doc = try SwiftSoup.parse(h)
XCTAssertEqual("dt", try doc.select("#1").first()!.parent()!.tagName()) XCTAssertEqual("dt", try doc.select("#1").first()!.parent()!.tagName())
XCTAssertEqual("dd", try doc.select("#2").first()!.parent()!.tagName()) XCTAssertEqual("dd", try doc.select("#2").first()!.parent()!.tagName())
XCTAssertEqual("<dl><dt><div id=\"1\">Term</div></dt><dd><div id=\"2\">Def</div></dd></dl>",try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<dl><dt><div id=\"1\">Term</div></dt><dd><div id=\"2\">Def</div></dd></dl>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testHandlesFrames()throws { func testHandlesFrames()throws {
@ -386,7 +386,7 @@ class HtmlParserTest: XCTestCase {
func testIgnoresContentAfterFrameset()throws { func testIgnoresContentAfterFrameset()throws {
let h = "<html><head><title>One</title></head><frameset><frame /><frame /></frameset><table></table></html>" let h = "<html><head><title>One</title></head><frameset><frame /><frame /></frameset><table></table></html>"
let doc = try SwiftSoup.parse(h) let doc = try SwiftSoup.parse(h)
XCTAssertEqual("<html><head><title>One</title></head><frameset><frame><frame></frameset></html>",try TextUtil.stripNewlines(doc.html())) XCTAssertEqual("<html><head><title>One</title></head><frameset><frame><frame></frameset></html>", try TextUtil.stripNewlines(doc.html()))
// no body, no table. No crash! // no body, no table. No crash!
} }
@ -404,7 +404,7 @@ class HtmlParserTest: XCTestCase {
let doc = try SwiftSoup.parse(h, "http://example.com/") let doc = try SwiftSoup.parse(h, "http://example.com/")
let a: Element = try doc.select("a").first()! let a: Element = try doc.select("a").first()!
XCTAssertEqual("/foo", try a.attr("href")) XCTAssertEqual("/foo", try a.attr("href"))
XCTAssertEqual("http://example.com/foo",try a.attr("abs:href")) XCTAssertEqual("http://example.com/foo", try a.attr("abs:href"))
} }
func testNormalisesDocument()throws { func testNormalisesDocument()throws {
@ -444,43 +444,43 @@ class HtmlParserTest: XCTestCase {
XCTAssertEqual("<h1>Hello </h1><h2>There <hgroup><h1>Another</h1><h2>headline</h2></hgroup> <hgroup><h1>More</h1><p>stuff</p></hgroup></h2>", try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<h1>Hello </h1><h2>There <hgroup><h1>Another</h1><h2>headline</h2></hgroup> <hgroup><h1>More</h1><p>stuff</p></hgroup></h2>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testRelaxedTags()throws{ func testRelaxedTags()throws {
let doc = try SwiftSoup.parse("<abc_def id=1>Hello</abc_def> <abc-def>There</abc-def>") let doc = try SwiftSoup.parse("<abc_def id=1>Hello</abc_def> <abc-def>There</abc-def>")
XCTAssertEqual("<abc_def id=\"1\">Hello</abc_def> <abc-def>There</abc-def>", try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<abc_def id=\"1\">Hello</abc_def> <abc-def>There</abc-def>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testHeaderContents()throws{ func testHeaderContents()throws {
// h* tags (h1 .. h9) in browsers can handle any internal content other than other h*. which is not per any // h* tags (h1 .. h9) in browsers can handle any internal content other than other h*. which is not per any
// spec, which defines them as containing phrasing content only. so, reality over theory. // spec, which defines them as containing phrasing content only. so, reality over theory.
let doc = try SwiftSoup.parse("<h1>Hello <div>There</div> now</h1> <h2>More <h3>Content</h3></h2>") let doc = try SwiftSoup.parse("<h1>Hello <div>There</div> now</h1> <h2>More <h3>Content</h3></h2>")
XCTAssertEqual("<h1>Hello <div>There</div> now</h1> <h2>More </h2><h3>Content</h3>", try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<h1>Hello <div>There</div> now</h1> <h2>More </h2><h3>Content</h3>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testSpanContents()throws{ func testSpanContents()throws {
// like h1 tags, the spec says SPAN is phrasing only, but browsers and publisher treat span as a block tag // like h1 tags, the spec says SPAN is phrasing only, but browsers and publisher treat span as a block tag
let doc = try SwiftSoup.parse("<span>Hello <div>there</div> <span>now</span></span>") let doc = try SwiftSoup.parse("<span>Hello <div>there</div> <span>now</span></span>")
XCTAssertEqual("<span>Hello <div>there</div> <span>now</span></span>", try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<span>Hello <div>there</div> <span>now</span></span>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testNoImagesInNoScriptInHead()throws{ func testNoImagesInNoScriptInHead()throws {
// jsoup used to allow, but against spec if parsing with noscript // jsoup used to allow, but against spec if parsing with noscript
let doc = try SwiftSoup.parse("<html><head><noscript><img src='foo'></noscript></head><body><p>Hello</p></body></html>") let doc = try SwiftSoup.parse("<html><head><noscript><img src='foo'></noscript></head><body><p>Hello</p></body></html>")
XCTAssertEqual("<html><head><noscript>&lt;img src=\"foo\"&gt;</noscript></head><body><p>Hello</p></body></html>", try TextUtil.stripNewlines(doc.html())) XCTAssertEqual("<html><head><noscript>&lt;img src=\"foo\"&gt;</noscript></head><body><p>Hello</p></body></html>", try TextUtil.stripNewlines(doc.html()))
} }
func testAFlowContents()throws{ func testAFlowContents()throws {
// html5 has <a> as either phrasing or block // html5 has <a> as either phrasing or block
let doc = try SwiftSoup.parse("<a>Hello <div>there</div> <span>now</span></a>") let doc = try SwiftSoup.parse("<a>Hello <div>there</div> <span>now</span></a>")
XCTAssertEqual("<a>Hello <div>there</div> <span>now</span></a>", try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<a>Hello <div>there</div> <span>now</span></a>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testFontFlowContents()throws{ func testFontFlowContents()throws {
// html5 has no definition of <font>; often used as flow // html5 has no definition of <font>; often used as flow
let doc = try SwiftSoup.parse("<font>Hello <div>there</div> <span>now</span></font>") let doc = try SwiftSoup.parse("<font>Hello <div>there</div> <span>now</span></font>")
XCTAssertEqual("<font>Hello <div>there</div> <span>now</span></font>", try TextUtil.stripNewlines(doc.body()!.html())) XCTAssertEqual("<font>Hello <div>there</div> <span>now</span></font>", try TextUtil.stripNewlines(doc.body()!.html()))
} }
func testhandlesMisnestedTagsBI()throws{ func testhandlesMisnestedTagsBI()throws {
// whatwg: <b><i></b></i> // whatwg: <b><i></b></i>
let h = "<p>1<b>2<i>3</b>4</i>5</p>" let h = "<p>1<b>2<i>3</b>4</i>5</p>"
let doc = try SwiftSoup.parse(h) let doc = try SwiftSoup.parse(h)
@ -488,7 +488,7 @@ class HtmlParserTest: XCTestCase {
// adoption agency on </b>, reconstruction of formatters on 4. // adoption agency on </b>, reconstruction of formatters on 4.
} }
func testhandlesMisnestedTagsBP()throws{ func testhandlesMisnestedTagsBP()throws {
// whatwg: <b><p></b></p> // whatwg: <b><p></b></p>
let h = "<b>1<p>2</b>3</p>" let h = "<b>1<p>2</b>3</p>"
let doc = try SwiftSoup.parse(h) let doc = try SwiftSoup.parse(h)
@ -587,73 +587,73 @@ class HtmlParserTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testParsesSimpleDocument" , testParsesSimpleDocument), ("testParsesSimpleDocument", testParsesSimpleDocument),
("testParsesRoughAttributes" , testParsesRoughAttributes), ("testParsesRoughAttributes", testParsesRoughAttributes),
("testParsesQuiteRoughAttributes" , testParsesQuiteRoughAttributes), ("testParsesQuiteRoughAttributes", testParsesQuiteRoughAttributes),
("testParsesComments" , testParsesComments), ("testParsesComments", testParsesComments),
("testParsesUnterminatedComments" , testParsesUnterminatedComments), ("testParsesUnterminatedComments", testParsesUnterminatedComments),
("testDropsUnterminatedTag" , testDropsUnterminatedTag), ("testDropsUnterminatedTag", testDropsUnterminatedTag),
("testDropsUnterminatedAttribute" , testDropsUnterminatedAttribute), ("testDropsUnterminatedAttribute", testDropsUnterminatedAttribute),
("testParsesUnterminatedTextarea" , testParsesUnterminatedTextarea), ("testParsesUnterminatedTextarea", testParsesUnterminatedTextarea),
("testParsesUnterminatedOption" , testParsesUnterminatedOption), ("testParsesUnterminatedOption", testParsesUnterminatedOption),
("testSpaceAfterTag" , testSpaceAfterTag), ("testSpaceAfterTag", testSpaceAfterTag),
("testCreatesDocumentStructure" , testCreatesDocumentStructure), ("testCreatesDocumentStructure", testCreatesDocumentStructure),
("testCreatesStructureFromBodySnippet" , testCreatesStructureFromBodySnippet), ("testCreatesStructureFromBodySnippet", testCreatesStructureFromBodySnippet),
("testHandlesEscapedData" , testHandlesEscapedData), ("testHandlesEscapedData", testHandlesEscapedData),
("testHandlesDataOnlyTags" , testHandlesDataOnlyTags), ("testHandlesDataOnlyTags", testHandlesDataOnlyTags),
("testHandlesTextAfterData" , testHandlesTextAfterData), ("testHandlesTextAfterData", testHandlesTextAfterData),
("testHandlesTextArea" , testHandlesTextArea), ("testHandlesTextArea", testHandlesTextArea),
("testPreservesSpaceInTextArea" , testPreservesSpaceInTextArea), ("testPreservesSpaceInTextArea", testPreservesSpaceInTextArea),
("testPreservesSpaceInScript" , testPreservesSpaceInScript), ("testPreservesSpaceInScript", testPreservesSpaceInScript),
("testDoesNotCreateImplicitLists" , testDoesNotCreateImplicitLists), ("testDoesNotCreateImplicitLists", testDoesNotCreateImplicitLists),
("testDiscardsNakedTds" , testDiscardsNakedTds), ("testDiscardsNakedTds", testDiscardsNakedTds),
("testHandlesNestedImplicitTable" , testHandlesNestedImplicitTable), ("testHandlesNestedImplicitTable", testHandlesNestedImplicitTable),
("testHandlesWhatWgExpensesTableExample" , testHandlesWhatWgExpensesTableExample), ("testHandlesWhatWgExpensesTableExample", testHandlesWhatWgExpensesTableExample),
("testHandlesTbodyTable" , testHandlesTbodyTable), ("testHandlesTbodyTable", testHandlesTbodyTable),
("testHandlesImplicitCaptionClose" , testHandlesImplicitCaptionClose), ("testHandlesImplicitCaptionClose", testHandlesImplicitCaptionClose),
("testNoTableDirectInTable" , testNoTableDirectInTable), ("testNoTableDirectInTable", testNoTableDirectInTable),
("testIgnoresDupeEndTrTag" , testIgnoresDupeEndTrTag), ("testIgnoresDupeEndTrTag", testIgnoresDupeEndTrTag),
("testHandlesBaseTags" , testHandlesBaseTags), ("testHandlesBaseTags", testHandlesBaseTags),
("testHandlesProtocolRelativeUrl" , testHandlesProtocolRelativeUrl), ("testHandlesProtocolRelativeUrl", testHandlesProtocolRelativeUrl),
("testHandlesCdata" , testHandlesCdata), ("testHandlesCdata", testHandlesCdata),
("testHandlesUnclosedCdataAtEOF" , testHandlesUnclosedCdataAtEOF), ("testHandlesUnclosedCdataAtEOF", testHandlesUnclosedCdataAtEOF),
("testHandlesInvalidStartTags" , testHandlesInvalidStartTags), ("testHandlesInvalidStartTags", testHandlesInvalidStartTags),
("testHandlesUnknownTags" , testHandlesUnknownTags), ("testHandlesUnknownTags", testHandlesUnknownTags),
("testHandlesUnknownInlineTags" , testHandlesUnknownInlineTags), ("testHandlesUnknownInlineTags", testHandlesUnknownInlineTags),
("testParsesBodyFragment" , testParsesBodyFragment), ("testParsesBodyFragment", testParsesBodyFragment),
("testHandlesUnknownNamespaceTags" , testHandlesUnknownNamespaceTags), ("testHandlesUnknownNamespaceTags", testHandlesUnknownNamespaceTags),
("testHandlesKnownEmptyBlocks" , testHandlesKnownEmptyBlocks), ("testHandlesKnownEmptyBlocks", testHandlesKnownEmptyBlocks),
("testHandlesSolidusAtAttributeEnd" , testHandlesSolidusAtAttributeEnd), ("testHandlesSolidusAtAttributeEnd", testHandlesSolidusAtAttributeEnd),
("testHandlesMultiClosingBody" , testHandlesMultiClosingBody), ("testHandlesMultiClosingBody", testHandlesMultiClosingBody),
("testHandlesUnclosedDefinitionLists" , testHandlesUnclosedDefinitionLists), ("testHandlesUnclosedDefinitionLists", testHandlesUnclosedDefinitionLists),
("testHandlesBlocksInDefinitions" , testHandlesBlocksInDefinitions), ("testHandlesBlocksInDefinitions", testHandlesBlocksInDefinitions),
("testHandlesFrames" , testHandlesFrames), ("testHandlesFrames", testHandlesFrames),
("testIgnoresContentAfterFrameset" , testIgnoresContentAfterFrameset), ("testIgnoresContentAfterFrameset", testIgnoresContentAfterFrameset),
("testHandlesJavadocFont" , testHandlesJavadocFont), ("testHandlesJavadocFont", testHandlesJavadocFont),
("testHandlesBaseWithoutHref" , testHandlesBaseWithoutHref), ("testHandlesBaseWithoutHref", testHandlesBaseWithoutHref),
("testNormalisesDocument" , testNormalisesDocument), ("testNormalisesDocument", testNormalisesDocument),
("testNormalisesEmptyDocument" , testNormalisesEmptyDocument), ("testNormalisesEmptyDocument", testNormalisesEmptyDocument),
("testNormalisesHeadlessBody" , testNormalisesHeadlessBody), ("testNormalisesHeadlessBody", testNormalisesHeadlessBody),
("testNormalisedBodyAfterContent" , testNormalisedBodyAfterContent), ("testNormalisedBodyAfterContent", testNormalisedBodyAfterContent),
("testfindsCharsetInMalformedMeta" , testfindsCharsetInMalformedMeta), ("testfindsCharsetInMalformedMeta", testfindsCharsetInMalformedMeta),
("testHgroup" , testHgroup), ("testHgroup", testHgroup),
("testRelaxedTags" , testRelaxedTags), ("testRelaxedTags", testRelaxedTags),
("testHeaderContents" , testHeaderContents), ("testHeaderContents", testHeaderContents),
("testSpanContents" , testSpanContents), ("testSpanContents", testSpanContents),
("testNoImagesInNoScriptInHead" , testNoImagesInNoScriptInHead), ("testNoImagesInNoScriptInHead", testNoImagesInNoScriptInHead),
("testAFlowContents" , testAFlowContents), ("testAFlowContents", testAFlowContents),
("testFontFlowContents" , testFontFlowContents), ("testFontFlowContents", testFontFlowContents),
("testhandlesMisnestedTagsBI" , testhandlesMisnestedTagsBI), ("testhandlesMisnestedTagsBI", testhandlesMisnestedTagsBI),
("testhandlesMisnestedTagsBP" , testhandlesMisnestedTagsBP), ("testhandlesMisnestedTagsBP", testhandlesMisnestedTagsBP),
("testhandlesUnexpectedMarkupInTables" , testhandlesUnexpectedMarkupInTables), ("testhandlesUnexpectedMarkupInTables", testhandlesUnexpectedMarkupInTables),
("testHandlesUnclosedFormattingElements" , testHandlesUnclosedFormattingElements), ("testHandlesUnclosedFormattingElements", testHandlesUnclosedFormattingElements),
("testhandlesUnclosedAnchors" , testhandlesUnclosedAnchors), ("testhandlesUnclosedAnchors", testhandlesUnclosedAnchors),
("testreconstructFormattingElements" , testreconstructFormattingElements), ("testreconstructFormattingElements", testreconstructFormattingElements),
("testreconstructFormattingElementsInTable" , testreconstructFormattingElementsInTable), ("testreconstructFormattingElementsInTable", testreconstructFormattingElementsInTable),
("testcommentBeforeHtml" , testcommentBeforeHtml), ("testcommentBeforeHtml", testcommentBeforeHtml),
("testemptyTdTag" , testemptyTdTag), ("testemptyTdTag", testemptyTdTag),
("testhandlesSolidusInA" , testhandlesSolidusInA), ("testhandlesSolidusInA", testhandlesSolidusInA),
("testhandlesSpanInTbody" , testhandlesSpanInTbody) ("testhandlesSpanInTbody", testhandlesSpanInTbody)
] ]
}() }()

View File

@ -12,7 +12,7 @@ import SwiftSoup
class NodeTest: XCTestCase { class NodeTest: XCTestCase {
func testHandlesBaseUri() { func testHandlesBaseUri() {
do{ do {
let tag: Tag = try Tag.valueOf("a") let tag: Tag = try Tag.valueOf("a")
let attribs: Attributes = Attributes() let attribs: Attributes = Attributes()
try attribs.put("relHref", "/foo") try attribs.put("relHref", "/foo")
@ -31,14 +31,14 @@ class NodeTest: XCTestCase {
XCTAssertEqual("http://bar/qux", try dodgyBase.absUrl("absHref")) // base fails, but href good, so get that XCTAssertEqual("http://bar/qux", try dodgyBase.absUrl("absHref")) // base fails, but href good, so get that
//TODO:Nabil in swift an url with scheme wtf is valid , find a method to validate schemes //TODO:Nabil in swift an url with scheme wtf is valid , find a method to validate schemes
//XCTAssertEqual("", try dodgyBase.absUrl("relHref")); // base fails, only rel href, so return nothing //XCTAssertEqual("", try dodgyBase.absUrl("relHref")); // base fails, only rel href, so return nothing
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testSetBaseUriIsRecursive() { func testSetBaseUriIsRecursive() {
do{ do {
let doc: Document = try SwiftSoup.parse("<div><p></p></div>") let doc: Document = try SwiftSoup.parse("<div><p></p></div>")
let baseUri: String = "https://jsoup.org" let baseUri: String = "https://jsoup.org"
try doc.setBaseUri(baseUri) try doc.setBaseUri(baseUri)
@ -46,37 +46,37 @@ class NodeTest: XCTestCase {
XCTAssertEqual(baseUri, doc.getBaseUri()) XCTAssertEqual(baseUri, doc.getBaseUri())
XCTAssertEqual(baseUri, try doc.select("div").first()?.getBaseUri()) XCTAssertEqual(baseUri, try doc.select("div").first()?.getBaseUri())
XCTAssertEqual(baseUri, try doc.select("p").first()?.getBaseUri()) XCTAssertEqual(baseUri, try doc.select("p").first()?.getBaseUri())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testHandlesAbsPrefix() { func testHandlesAbsPrefix() {
do{ do {
let doc: Document = try SwiftSoup.parse("<a href=/foo>Hello</a>", "https://jsoup.org/") let doc: Document = try SwiftSoup.parse("<a href=/foo>Hello</a>", "https://jsoup.org/")
let a: Element? = try doc.select("a").first() let a: Element? = try doc.select("a").first()
XCTAssertEqual("/foo", try a?.attr("href")) XCTAssertEqual("/foo", try a?.attr("href"))
XCTAssertEqual("https://jsoup.org/foo", try a?.attr("abs:href")) XCTAssertEqual("https://jsoup.org/foo", try a?.attr("abs:href"))
//XCTAssertTrue(a!.hasAttr("abs:href"));//TODO:nabil //XCTAssertTrue(a!.hasAttr("abs:href"));//TODO:nabil
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testHandlesAbsOnImage() { func testHandlesAbsOnImage() {
do{ do {
let doc: Document = try SwiftSoup.parse("<p><img src=\"/rez/osi_logo.png\" /></p>", "https://jsoup.org/") let doc: Document = try SwiftSoup.parse("<p><img src=\"/rez/osi_logo.png\" /></p>", "https://jsoup.org/")
let img: Element? = try doc.select("img").first() let img: Element? = try doc.select("img").first()
XCTAssertEqual("https://jsoup.org/rez/osi_logo.png", try img?.attr("abs:src")) XCTAssertEqual("https://jsoup.org/rez/osi_logo.png", try img?.attr("abs:src"))
XCTAssertEqual(try img?.absUrl("src"), try img?.attr("abs:src")) XCTAssertEqual(try img?.absUrl("src"), try img?.attr("abs:src"))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testHandlesAbsPrefixOnHasAttr() { func testHandlesAbsPrefixOnHasAttr() {
do{ do {
// 1: no abs url; 2: has abs url // 1: no abs url; 2: has abs url
let doc: Document = try SwiftSoup.parse("<a id=1 href='/foo'>One</a> <a id=2 href='https://jsoup.org/'>Two</a>") let doc: Document = try SwiftSoup.parse("<a id=1 href='/foo'>One</a> <a id=2 href='https://jsoup.org/'>Two</a>")
let one: Element = try doc.select("#1").first()! let one: Element = try doc.select("#1").first()!
@ -89,22 +89,21 @@ class NodeTest: XCTestCase {
XCTAssertTrue(two.hasAttr("abs:href")) XCTAssertTrue(two.hasAttr("abs:href"))
XCTAssertTrue(two.hasAttr("href")) XCTAssertTrue(two.hasAttr("href"))
XCTAssertEqual("https://jsoup.org/", try two.absUrl("href")) XCTAssertEqual("https://jsoup.org/", try two.absUrl("href"))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testLiteralAbsPrefix() { func testLiteralAbsPrefix() {
do{ do {
// if there is a literal attribute "abs:xxx", don't try and make absolute. // if there is a literal attribute "abs:xxx", don't try and make absolute.
let doc: Document = try SwiftSoup.parse("<a abs:href='odd'>One</a>") let doc: Document = try SwiftSoup.parse("<a abs:href='odd'>One</a>")
let el: Element = try doc.select("a").first()! let el: Element = try doc.select("a").first()!
XCTAssertTrue(el.hasAttr("abs:href")) XCTAssertTrue(el.hasAttr("abs:href"))
XCTAssertEqual("odd", try el.attr("abs:href")) XCTAssertEqual("odd", try el.attr("abs:href"))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
@ -123,17 +122,17 @@ class NodeTest: XCTestCase {
} }
*/ */
func testHandleAbsOnLocalhostFileUris() { func testHandleAbsOnLocalhostFileUris() {
do{ do {
let doc: Document = try SwiftSoup.parse("<a href='password'>One/a><a href='/var/log/messages'>Two</a>", "file://localhost/etc/") let doc: Document = try SwiftSoup.parse("<a href='password'>One/a><a href='/var/log/messages'>Two</a>", "file://localhost/etc/")
let one: Element? = try doc.select("a").first() let one: Element? = try doc.select("a").first()
XCTAssertEqual("file://localhost/etc/password", try one?.absUrl("href")) XCTAssertEqual("file://localhost/etc/password", try one?.absUrl("href"))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testHandlesAbsOnProtocolessAbsoluteUris() { func testHandlesAbsOnProtocolessAbsoluteUris() {
do{ do {
let doc1: Document = try SwiftSoup.parse("<a href='//example.net/foo'>One</a>", "http://example.com/") let doc1: Document = try SwiftSoup.parse("<a href='//example.net/foo'>One</a>", "http://example.com/")
let doc2: Document = try SwiftSoup.parse("<a href='//example.net/foo'>One</a>", "https://example.com/") let doc2: Document = try SwiftSoup.parse("<a href='//example.net/foo'>One</a>", "https://example.com/")
@ -145,13 +144,13 @@ class NodeTest: XCTestCase {
let doc3: Document = try SwiftSoup.parse("<img src=//www.google.com/images/errors/logo_sm.gif alt=Google>", "https://google.com") let doc3: Document = try SwiftSoup.parse("<img src=//www.google.com/images/errors/logo_sm.gif alt=Google>", "https://google.com")
XCTAssertEqual("https://www.google.com/images/errors/logo_sm.gif", try doc3.select("img").attr("abs:src")) XCTAssertEqual("https://www.google.com/images/errors/logo_sm.gif", try doc3.select("img").attr("abs:src"))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testAbsHandlesRelativeQuery() { func testAbsHandlesRelativeQuery() {
do{ do {
let doc: Document = try SwiftSoup.parse("<a href='?foo'>One</a> <a href='bar.html?foo'>Two</a>", "https://jsoup.org/path/file?bar") let doc: Document = try SwiftSoup.parse("<a href='?foo'>One</a> <a href='bar.html?foo'>Two</a>", "https://jsoup.org/path/file?bar")
let a1: Element? = try doc.select("a").first() let a1: Element? = try doc.select("a").first()
@ -159,61 +158,61 @@ class NodeTest: XCTestCase {
let a2: Element? = try doc.select("a").get(1) let a2: Element? = try doc.select("a").get(1)
XCTAssertEqual("https://jsoup.org/path/bar.html?foo", try a2?.absUrl("href")) XCTAssertEqual("https://jsoup.org/path/bar.html?foo", try a2?.absUrl("href"))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testAbsHandlesDotFromIndex() { func testAbsHandlesDotFromIndex() {
do{ do {
let doc: Document = try SwiftSoup.parse("<a href='./one/two.html'>One</a>", "http://example.com") let doc: Document = try SwiftSoup.parse("<a href='./one/two.html'>One</a>", "http://example.com")
let a1: Element? = try doc.select("a").first() let a1: Element? = try doc.select("a").first()
XCTAssertEqual("http://example.com/one/two.html", try a1?.absUrl("href")) XCTAssertEqual("http://example.com/one/two.html", try a1?.absUrl("href"))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testRemove() { func testRemove() {
do{ do {
let doc: Document = try SwiftSoup.parse("<p>One <span>two</span> three</p>") let doc: Document = try SwiftSoup.parse("<p>One <span>two</span> three</p>")
let p: Element? = try doc.select("p").first() let p: Element? = try doc.select("p").first()
try p?.childNode(0).remove() try p?.childNode(0).remove()
XCTAssertEqual("two three", try p?.text()) XCTAssertEqual("two three", try p?.text())
XCTAssertEqual("<span>two</span> three", TextUtil.stripNewlines(try p!.html())) XCTAssertEqual("<span>two</span> three", TextUtil.stripNewlines(try p!.html()))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testReplace() { func testReplace() {
do{ do {
let doc: Document = try SwiftSoup.parse("<p>One <span>two</span> three</p>") let doc: Document = try SwiftSoup.parse("<p>One <span>two</span> three</p>")
let p: Element? = try doc.select("p").first() let p: Element? = try doc.select("p").first()
let insert: Element = try doc.createElement("em").text("foo") let insert: Element = try doc.createElement("em").text("foo")
try p?.childNode(1).replaceWith(insert) try p?.childNode(1).replaceWith(insert)
XCTAssertEqual("One <em>foo</em> three", try p?.html()) XCTAssertEqual("One <em>foo</em> three", try p?.html())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testOwnerDocument() { func testOwnerDocument() {
do{ do {
let doc: Document = try SwiftSoup.parse("<p>Hello") let doc: Document = try SwiftSoup.parse("<p>Hello")
let p: Element? = try doc.select("p").first() let p: Element? = try doc.select("p").first()
XCTAssertTrue(p?.ownerDocument() == doc) XCTAssertTrue(p?.ownerDocument() == doc)
XCTAssertTrue(doc.ownerDocument() == doc) XCTAssertTrue(doc.ownerDocument() == doc)
XCTAssertNil(doc.parent()) XCTAssertNil(doc.parent())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testBefore() { func testBefore() {
do{ do {
let doc: Document = try SwiftSoup.parse("<p>One <b>two</b> three</p>") let doc: Document = try SwiftSoup.parse("<p>One <b>two</b> three</p>")
let newNode: Element = Element(try Tag.valueOf("em"), "") let newNode: Element = Element(try Tag.valueOf("em"), "")
try newNode.appendText("four") try newNode.appendText("four")
@ -223,13 +222,13 @@ class NodeTest: XCTestCase {
try doc.select("b").first()?.before("<i>five</i>") try doc.select("b").first()?.before("<i>five</i>")
XCTAssertEqual("<p>One <em>four</em><i>five</i><b>two</b> three</p>", try doc.body()?.html()) XCTAssertEqual("<p>One <em>four</em><i>five</i><b>two</b> three</p>", try doc.body()?.html())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testAfter() { func testAfter() {
do{ do {
let doc: Document = try SwiftSoup.parse("<p>One <b>two</b> three</p>") let doc: Document = try SwiftSoup.parse("<p>One <b>two</b> three</p>")
let newNode: Element = Element(try Tag.valueOf("em"), "") let newNode: Element = Element(try Tag.valueOf("em"), "")
try newNode.appendText("four") try newNode.appendText("four")
@ -239,14 +238,14 @@ class NodeTest: XCTestCase {
try doc.select("b").first()?.after("<i>five</i>") try doc.select("b").first()?.after("<i>five</i>")
XCTAssertEqual("<p>One <b>two</b><i>five</i><em>four</em> three</p>", try doc.body()?.html()) XCTAssertEqual("<p>One <b>two</b><i>five</i><em>four</em> three</p>", try doc.body()?.html())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testUnwrap() { func testUnwrap() {
do{ do {
let doc: Document = try SwiftSoup.parse("<div>One <span>Two <b>Three</b></span> Four</div>") let doc: Document = try SwiftSoup.parse("<div>One <span>Two <b>Three</b></span> Four</div>")
let span: Element? = try doc.select("span").first() let span: Element? = try doc.select("span").first()
let twoText: Node? = span?.childNode(0) let twoText: Node? = span?.childNode(0)
@ -257,49 +256,49 @@ class NodeTest: XCTestCase {
XCTAssertEqual("Two ", (node as? TextNode)?.text()) XCTAssertEqual("Two ", (node as? TextNode)?.text())
XCTAssertEqual(node, twoText) XCTAssertEqual(node, twoText)
XCTAssertEqual(node?.parent(), try doc.select("div").first()) XCTAssertEqual(node?.parent(), try doc.select("div").first())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testUnwrapNoChildren() { func testUnwrapNoChildren() {
do{ do {
let doc: Document = try SwiftSoup.parse("<div>One <span></span> Two</div>") let doc: Document = try SwiftSoup.parse("<div>One <span></span> Two</div>")
let span: Element? = try doc.select("span").first() let span: Element? = try doc.select("span").first()
let node: Node? = try span?.unwrap() let node: Node? = try span?.unwrap()
XCTAssertEqual("<div>One Two</div>", TextUtil.stripNewlines(try doc.body()!.html())) XCTAssertEqual("<div>One Two</div>", TextUtil.stripNewlines(try doc.body()!.html()))
XCTAssertTrue(node == nil) XCTAssertTrue(node == nil)
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testTraverse() { func testTraverse() {
do{ do {
let doc: Document = try SwiftSoup.parse("<div><p>Hello</p></div><div>There</div>") let doc: Document = try SwiftSoup.parse("<div><p>Hello</p></div><div>There</div>")
let accum: StringBuilder = StringBuilder() let accum: StringBuilder = StringBuilder()
class nv : NodeVisitor{ class nv: NodeVisitor {
let accum: StringBuilder let accum: StringBuilder
init (_ accum: StringBuilder){ init (_ accum: StringBuilder) {
self.accum = accum self.accum = accum
} }
func head(_ node: Node, _ depth: Int)throws{ func head(_ node: Node, _ depth: Int)throws {
accum.append("<" + node.nodeName() + ">") accum.append("<" + node.nodeName() + ">")
} }
func tail(_ node: Node, _ depth: Int)throws{ func tail(_ node: Node, _ depth: Int)throws {
accum.append("</" + node.nodeName() + ">") accum.append("</" + node.nodeName() + ">")
} }
} }
try doc.select("div").first()?.traverse(nv(accum)) try doc.select("div").first()?.traverse(nv(accum))
XCTAssertEqual("<div><p><#text></#text></p></div>", accum.toString()) XCTAssertEqual("<div><p><#text></#text></p></div>", accum.toString())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testOrphanNodeReturnsNullForSiblingElements() { func testOrphanNodeReturnsNullForSiblingElements() {
do{ do {
let node: Node = Element(try Tag.valueOf("p"), "") let node: Node = Element(try Tag.valueOf("p"), "")
let el: Element = Element(try Tag.valueOf("p"), "") let el: Element = Element(try Tag.valueOf("p"), "")
@ -312,13 +311,13 @@ class NodeTest: XCTestCase {
XCTAssertEqual(0, el.siblingElements().size()) XCTAssertEqual(0, el.siblingElements().size())
XCTAssertNil(try el.previousElementSibling()) XCTAssertNil(try el.previousElementSibling())
XCTAssertNil(try el.nextElementSibling()) XCTAssertNil(try el.nextElementSibling())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testNodeIsNotASiblingOfItself() { func testNodeIsNotASiblingOfItself() {
do{ do {
let doc: Document = try SwiftSoup.parse("<div><p>One<p>Two<p>Three</div>") let doc: Document = try SwiftSoup.parse("<div><p>One<p>Two<p>Three</div>")
let p2: Element = try doc.select("p").get(1) let p2: Element = try doc.select("p").get(1)
@ -327,13 +326,13 @@ class NodeTest: XCTestCase {
XCTAssertEqual(2, nodes.count) XCTAssertEqual(2, nodes.count)
XCTAssertEqual("<p>One</p>", try nodes[0].outerHtml()) XCTAssertEqual("<p>One</p>", try nodes[0].outerHtml())
XCTAssertEqual("<p>Three</p>", try nodes[1].outerHtml()) XCTAssertEqual("<p>Three</p>", try nodes[1].outerHtml())
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testChildNodesCopy() { func testChildNodesCopy() {
do{ do {
let doc: Document = try SwiftSoup.parse("<div id=1>Text 1 <p>One</p> Text 2 <p>Two<p>Three</div><div id=2>") let doc: Document = try SwiftSoup.parse("<div id=1>Text 1 <p>One</p> Text 2 <p>Two<p>Three</div><div id=2>")
let div1: Element? = try doc.select("#1").first() let div1: Element? = try doc.select("#1").first()
let div2: Element? = try doc.select("#2").first() let div2: Element? = try doc.select("#2").first()
@ -345,13 +344,13 @@ class NodeTest: XCTestCase {
XCTAssertEqual("Text 1 ", tn1?.text()) XCTAssertEqual("Text 1 ", tn1?.text())
try div2?.insertChildren(-1, divChildren!) try div2?.insertChildren(-1, divChildren!)
XCTAssertEqual("<div id=\"1\">Text 1 <p>One</p> Text 2 <p>Two</p><p>Three</p></div><div id=\"2\">Text 1 updated"+"<p>One</p> Text 2 <p>Two</p><p>Three</p></div>", TextUtil.stripNewlines(try doc.body()!.html())) XCTAssertEqual("<div id=\"1\">Text 1 <p>One</p> Text 2 <p>Two</p><p>Three</p></div><div id=\"2\">Text 1 updated"+"<p>One</p> Text 2 <p>Two</p><p>Three</p></div>", TextUtil.stripNewlines(try doc.body()!.html()))
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
func testSupportsClone() { func testSupportsClone() {
do{ do {
let doc: Document = try SwiftSoup.parse("<div class=foo>Text</div>") let doc: Document = try SwiftSoup.parse("<div class=foo>Text</div>")
let el: Element = try doc.select("div").first()! let el: Element = try doc.select("div").first()!
XCTAssertTrue(el.hasClass("foo")) XCTAssertTrue(el.hasClass("foo"))
@ -366,35 +365,35 @@ class NodeTest: XCTestCase {
XCTAssertTrue(elClone.hasClass("foo")) XCTAssertTrue(elClone.hasClass("foo"))
XCTAssertTrue(try el.text() == "None") XCTAssertTrue(try el.text() == "None")
XCTAssertTrue(try elClone.text()=="Text") XCTAssertTrue(try elClone.text()=="Text")
}catch{ } catch {
XCTAssertEqual(1,2) XCTAssertEqual(1, 2)
} }
} }
static var allTests = { static var allTests = {
return [ return [
("testHandlesBaseUri" , testHandlesBaseUri), ("testHandlesBaseUri", testHandlesBaseUri),
("testSetBaseUriIsRecursive" , testSetBaseUriIsRecursive), ("testSetBaseUriIsRecursive", testSetBaseUriIsRecursive),
("testHandlesAbsPrefix" , testHandlesAbsPrefix), ("testHandlesAbsPrefix", testHandlesAbsPrefix),
("testHandlesAbsOnImage" , testHandlesAbsOnImage), ("testHandlesAbsOnImage", testHandlesAbsOnImage),
("testHandlesAbsPrefixOnHasAttr" , testHandlesAbsPrefixOnHasAttr), ("testHandlesAbsPrefixOnHasAttr", testHandlesAbsPrefixOnHasAttr),
("testLiteralAbsPrefix" , testLiteralAbsPrefix), ("testLiteralAbsPrefix", testLiteralAbsPrefix),
("testHandleAbsOnLocalhostFileUris" , testHandleAbsOnLocalhostFileUris), ("testHandleAbsOnLocalhostFileUris", testHandleAbsOnLocalhostFileUris),
("testHandlesAbsOnProtocolessAbsoluteUris" , testHandlesAbsOnProtocolessAbsoluteUris), ("testHandlesAbsOnProtocolessAbsoluteUris", testHandlesAbsOnProtocolessAbsoluteUris),
("testAbsHandlesRelativeQuery" , testAbsHandlesRelativeQuery), ("testAbsHandlesRelativeQuery", testAbsHandlesRelativeQuery),
("testAbsHandlesDotFromIndex" , testAbsHandlesDotFromIndex), ("testAbsHandlesDotFromIndex", testAbsHandlesDotFromIndex),
("testRemove" , testRemove), ("testRemove", testRemove),
("testReplace" , testReplace), ("testReplace", testReplace),
("testOwnerDocument" , testOwnerDocument), ("testOwnerDocument", testOwnerDocument),
("testBefore" , testBefore), ("testBefore", testBefore),
("testAfter" , testAfter), ("testAfter", testAfter),
("testUnwrap" , testUnwrap), ("testUnwrap", testUnwrap),
("testUnwrapNoChildren" , testUnwrapNoChildren), ("testUnwrapNoChildren", testUnwrapNoChildren),
("testTraverse" , testTraverse), ("testTraverse", testTraverse),
("testOrphanNodeReturnsNullForSiblingElements" , testOrphanNodeReturnsNullForSiblingElements), ("testOrphanNodeReturnsNullForSiblingElements", testOrphanNodeReturnsNullForSiblingElements),
("testNodeIsNotASiblingOfItself" , testNodeIsNotASiblingOfItself), ("testNodeIsNotASiblingOfItself", testNodeIsNotASiblingOfItself),
("testChildNodesCopy" , testChildNodesCopy), ("testChildNodesCopy", testChildNodesCopy),
("testSupportsClone" , testSupportsClone) ("testSupportsClone", testSupportsClone)
] ]
}() }()
} }

View File

@ -32,7 +32,7 @@ class ParseSettingsTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testCaseSupport" , testCaseSupport) ("testCaseSupport", testCaseSupport)
] ]
}() }()
} }

View File

@ -18,7 +18,7 @@ class QueryParserTest: XCTestCase {
XCTAssertTrue((eval as? CombiningEvaluator.Or) != nil) XCTAssertTrue((eval as? CombiningEvaluator.Or) != nil)
let or: CombiningEvaluator.Or = eval as! CombiningEvaluator.Or let or: CombiningEvaluator.Or = eval as! CombiningEvaluator.Or
XCTAssertEqual(3, or.evaluators.count) XCTAssertEqual(3, or.evaluators.count)
for innerEval:Evaluator in or.evaluators { for innerEval: Evaluator in or.evaluators {
XCTAssertTrue((innerEval as? CombiningEvaluator.And) != nil) XCTAssertTrue((innerEval as? CombiningEvaluator.And) != nil)
let and: CombiningEvaluator.And = innerEval as! CombiningEvaluator.And let and: CombiningEvaluator.And = innerEval as! CombiningEvaluator.And
XCTAssertEqual(2, and.evaluators.count) XCTAssertEqual(2, and.evaluators.count)
@ -44,8 +44,8 @@ class QueryParserTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testOrGetsCorrectPrecedence" , testOrGetsCorrectPrecedence), ("testOrGetsCorrectPrecedence", testOrGetsCorrectPrecedence),
("testParsesMultiCorrectly" , testParsesMultiCorrectly) ("testParsesMultiCorrectly", testParsesMultiCorrectly)
] ]
}() }()

View File

@ -695,58 +695,58 @@ class SelectorTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testByTag" , testByTag), ("testByTag", testByTag),
("testById" , testById), ("testById", testById),
("testByClass" , testByClass), ("testByClass", testByClass),
("testByAttribute" , testByAttribute), ("testByAttribute", testByAttribute),
("testNamespacedTag" , testNamespacedTag), ("testNamespacedTag", testNamespacedTag),
("testWildcardNamespacedTag" , testWildcardNamespacedTag), ("testWildcardNamespacedTag", testWildcardNamespacedTag),
("testByAttributeStarting" , testByAttributeStarting), ("testByAttributeStarting", testByAttributeStarting),
("testByAttributeRegex" , testByAttributeRegex), ("testByAttributeRegex", testByAttributeRegex),
("testByAttributeRegexCharacterClass" , testByAttributeRegexCharacterClass), ("testByAttributeRegexCharacterClass", testByAttributeRegexCharacterClass),
("testByAttributeRegexCombined" , testByAttributeRegexCombined), ("testByAttributeRegexCombined", testByAttributeRegexCombined),
("testCombinedWithContains" , testCombinedWithContains), ("testCombinedWithContains", testCombinedWithContains),
("testAllElements" , testAllElements), ("testAllElements", testAllElements),
("testAllWithClass" , testAllWithClass), ("testAllWithClass", testAllWithClass),
("testGroupOr" , testGroupOr), ("testGroupOr", testGroupOr),
("testGroupOrAttribute" , testGroupOrAttribute), ("testGroupOrAttribute", testGroupOrAttribute),
("testDescendant" , testDescendant), ("testDescendant", testDescendant),
("testAnd" , testAnd), ("testAnd", testAnd),
("testDeeperDescendant" , testDeeperDescendant), ("testDeeperDescendant", testDeeperDescendant),
("testParentChildElement" , testParentChildElement), ("testParentChildElement", testParentChildElement),
("testParentWithClassChild" , testParentWithClassChild), ("testParentWithClassChild", testParentWithClassChild),
("testParentChildStar" , testParentChildStar), ("testParentChildStar", testParentChildStar),
("testMultiChildDescent" , testMultiChildDescent), ("testMultiChildDescent", testMultiChildDescent),
("testCaseInsensitive" , testCaseInsensitive), ("testCaseInsensitive", testCaseInsensitive),
("testAdjacentSiblings" , testAdjacentSiblings), ("testAdjacentSiblings", testAdjacentSiblings),
("testAdjacentSiblingsWithId" , testAdjacentSiblingsWithId), ("testAdjacentSiblingsWithId", testAdjacentSiblingsWithId),
("testNotAdjacent" , testNotAdjacent), ("testNotAdjacent", testNotAdjacent),
("testMixCombinator" , testMixCombinator), ("testMixCombinator", testMixCombinator),
("testMixCombinatorGroup" , testMixCombinatorGroup), ("testMixCombinatorGroup", testMixCombinatorGroup),
("testGeneralSiblings" , testGeneralSiblings), ("testGeneralSiblings", testGeneralSiblings),
("testCharactersInIdAndClass" , testCharactersInIdAndClass), ("testCharactersInIdAndClass", testCharactersInIdAndClass),
("testSupportsLeadingCombinator" , testSupportsLeadingCombinator), ("testSupportsLeadingCombinator", testSupportsLeadingCombinator),
("testPseudoLessThan" , testPseudoLessThan), ("testPseudoLessThan", testPseudoLessThan),
("testPseudoGreaterThan" , testPseudoGreaterThan), ("testPseudoGreaterThan", testPseudoGreaterThan),
("testPseudoEquals" , testPseudoEquals), ("testPseudoEquals", testPseudoEquals),
("testPseudoBetween" , testPseudoBetween), ("testPseudoBetween", testPseudoBetween),
("testPseudoCombined" , testPseudoCombined), ("testPseudoCombined", testPseudoCombined),
("testPseudoHas" , testPseudoHas), ("testPseudoHas", testPseudoHas),
("testNestedHas" , testNestedHas), ("testNestedHas", testNestedHas),
("testPseudoContains" , testPseudoContains), ("testPseudoContains", testPseudoContains),
("testPsuedoContainsWithParentheses" , testPsuedoContainsWithParentheses), ("testPsuedoContainsWithParentheses", testPsuedoContainsWithParentheses),
("testContainsOwn" , testContainsOwn), ("testContainsOwn", testContainsOwn),
("testMatches" , testMatches), ("testMatches", testMatches),
("testMatchesOwn" , testMatchesOwn), ("testMatchesOwn", testMatchesOwn),
("testRelaxedTags" , testRelaxedTags), ("testRelaxedTags", testRelaxedTags),
("testNotParas" , testNotParas), ("testNotParas", testNotParas),
("testNotAll" , testNotAll), ("testNotAll", testNotAll),
("testNotClass" , testNotClass), ("testNotClass", testNotClass),
("testHandlesCommasInSelector" , testHandlesCommasInSelector), ("testHandlesCommasInSelector", testHandlesCommasInSelector),
("testSelectSupplementaryCharacter" , testSelectSupplementaryCharacter), ("testSelectSupplementaryCharacter", testSelectSupplementaryCharacter),
("testSelectClassWithSpace" , testSelectClassWithSpace), ("testSelectClassWithSpace", testSelectClassWithSpace),
("testSelectSameElements" , testSelectSameElements), ("testSelectSameElements", testSelectSameElements),
("testAttributeWithBrackets" , testAttributeWithBrackets) ("testAttributeWithBrackets", testAttributeWithBrackets)
] ]
}() }()

View File

@ -42,9 +42,9 @@ class StringUtilTest: XCTestCase {
// } // }
func testJoin() { func testJoin() {
XCTAssertEqual("",StringUtil.join([""], sep: " ")) XCTAssertEqual("", StringUtil.join([""], sep: " "))
XCTAssertEqual("one",StringUtil.join(["one"], sep: " ")) XCTAssertEqual("one", StringUtil.join(["one"], sep: " "))
XCTAssertEqual("one two three",StringUtil.join(["one", "two", "three"], sep: " ")) XCTAssertEqual("one two three", StringUtil.join(["one", "two", "three"], sep: " "))
} }
func testPadding() { func testPadding() {
@ -75,7 +75,6 @@ class StringUtilTest: XCTestCase {
XCTAssertTrue(StringUtil.isNumeric("1234")) XCTAssertTrue(StringUtil.isNumeric("1234"))
} }
func testIsWhitespace() { func testIsWhitespace() {
XCTAssertTrue(StringUtil.isWhitespace("\t")) XCTAssertTrue(StringUtil.isWhitespace("\t"))
XCTAssertTrue(StringUtil.isWhitespace("\n")) XCTAssertTrue(StringUtil.isWhitespace("\n"))

View File

@ -72,15 +72,15 @@ class TagTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testIsCaseSensitive" , testIsCaseSensitive), ("testIsCaseSensitive", testIsCaseSensitive),
("testCanBeInsensitive" , testCanBeInsensitive), ("testCanBeInsensitive", testCanBeInsensitive),
("testTrims" , testTrims), ("testTrims", testTrims),
("testEquality" , testEquality), ("testEquality", testEquality),
("testDivSemantics" , testDivSemantics), ("testDivSemantics", testDivSemantics),
("testPSemantics" , testPSemantics), ("testPSemantics", testPSemantics),
("testImgSemantics" , testImgSemantics), ("testImgSemantics", testImgSemantics),
("testDefaultSemantics" , testDefaultSemantics), ("testDefaultSemantics", testDefaultSemantics),
("testValueOfChecksNotEmpty" , testValueOfChecksNotEmpty), ("testValueOfChecksNotEmpty", testValueOfChecksNotEmpty),
] ]
}() }()
} }

View File

@ -27,7 +27,7 @@ class TextNodeTest: XCTestCase {
func testTextBean()throws { func testTextBean()throws {
let doc = try SwiftSoup.parse("<p>One <span>two &amp;</span> three &amp;</p>") let doc = try SwiftSoup.parse("<p>One <span>two &amp;</span> three &amp;</p>")
let p : Element = try doc.select("p").first()! let p: Element = try doc.select("p").first()!
let span: Element = try doc.select("span").first()! let span: Element = try doc.select("span").first()!
XCTAssertEqual("two &", try span.text()) XCTAssertEqual("two &", try span.text())
@ -67,7 +67,7 @@ class TextNodeTest: XCTestCase {
XCTAssertEqual("Hello <b>there</b>", TextUtil.stripNewlines(try div.html())) // not great that we get \n<b>there there... must correct XCTAssertEqual("Hello <b>there</b>", TextUtil.stripNewlines(try div.html())) // not great that we get \n<b>there there... must correct
} }
func testWithSupplementaryCharacter()throws{ func testWithSupplementaryCharacter()throws {
#if !os(Linux) #if !os(Linux)
let doc: Document = try SwiftSoup.parse(String(Character(UnicodeScalar(135361)!))) let doc: Document = try SwiftSoup.parse(String(Character(UnicodeScalar(135361)!)))
let t: TextNode = doc.body()!.textNodes()[0] let t: TextNode = doc.body()!.textNodes()[0]
@ -77,12 +77,11 @@ class TextNodeTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testBlank" , testBlank), ("testBlank", testBlank),
("testTextBean" , testTextBean), ("testTextBean", testTextBean),
("testSplitText" , testSplitText), ("testSplitText", testSplitText),
("testSplitAnEmbolden" , testSplitAnEmbolden), ("testSplitAnEmbolden", testSplitAnEmbolden),
("testWithSupplementaryCharacter" , testWithSupplementaryCharacter) ("testWithSupplementaryCharacter", testWithSupplementaryCharacter)
] ]
}() }()
} }

View File

@ -10,7 +10,7 @@ import Foundation
@testable import SwiftSoup @testable import SwiftSoup
class TextUtil { class TextUtil {
public static func stripNewlines(_ text: String)->String { public static func stripNewlines(_ text: String) -> String {
let regex = try! NCRegularExpression(pattern: "\\n\\s*", options: .caseInsensitive) let regex = try! NCRegularExpression(pattern: "\\n\\s*", options: .caseInsensitive)
var str = text var str = text
str = regex.stringByReplacingMatches(in: str, options: [], range: NSRange(0..<str.utf16.count), withTemplate: "") str = regex.stringByReplacingMatches(in: str, options: [], range: NSRange(0..<str.utf16.count), withTemplate: "")

View File

@ -65,12 +65,12 @@ class TokenQueueTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testChompBalanced" , testChompBalanced), ("testChompBalanced", testChompBalanced),
("testChompEscapedBalanced" , testChompEscapedBalanced), ("testChompEscapedBalanced", testChompEscapedBalanced),
("testChompBalancedMatchesAsMuchAsPossible" , testChompBalancedMatchesAsMuchAsPossible), ("testChompBalancedMatchesAsMuchAsPossible", testChompBalancedMatchesAsMuchAsPossible),
("testUnescape" , testUnescape), ("testUnescape", testUnescape),
("testChompToIgnoreCase" , testChompToIgnoreCase), ("testChompToIgnoreCase", testChompToIgnoreCase),
("testAddFirst" , testAddFirst) ("testAddFirst", testAddFirst)
] ]
}() }()

View File

@ -15,7 +15,7 @@ class XmlTreeBuilderTest: XCTestCase {
let xml = "<doc id=2 href='/bar'>Foo <br /><link>One</link><link>Two</link></doc>" let xml = "<doc id=2 href='/bar'>Foo <br /><link>One</link><link>Two</link></doc>"
let tb: XmlTreeBuilder = XmlTreeBuilder() let tb: XmlTreeBuilder = XmlTreeBuilder()
let doc: Document = try tb.parse(xml, "http://foo.com/") let doc: Document = try tb.parse(xml, "http://foo.com/")
XCTAssertEqual("<doc id=\"2\" href=\"/bar\">Foo <br /><link>One</link><link>Two</link></doc>",try TextUtil.stripNewlines(doc.html())) XCTAssertEqual("<doc id=\"2\" href=\"/bar\">Foo <br /><link>One</link><link>Two</link></doc>", try TextUtil.stripNewlines(doc.html()))
XCTAssertEqual(try doc.getElementById("2")?.absUrl("href"), "http://foo.com/bar") XCTAssertEqual(try doc.getElementById("2")?.absUrl("href"), "http://foo.com/bar")
} }
@ -24,20 +24,20 @@ class XmlTreeBuilderTest: XCTestCase {
let xml = "<doc><val>One<val>Two</val></bar>Three</doc>" let xml = "<doc><val>One<val>Two</val></bar>Three</doc>"
let tb: XmlTreeBuilder = XmlTreeBuilder() let tb: XmlTreeBuilder = XmlTreeBuilder()
let doc = try tb.parse(xml, "http://foo.com/") let doc = try tb.parse(xml, "http://foo.com/")
XCTAssertEqual("<doc><val>One<val>Two</val>Three</val></doc>",try TextUtil.stripNewlines(doc.html())) XCTAssertEqual("<doc><val>One<val>Two</val>Three</val></doc>", try TextUtil.stripNewlines(doc.html()))
} }
func testCommentAndDocType()throws { func testCommentAndDocType()throws {
let xml = "<!DOCTYPE HTML><!-- a comment -->One <qux />Two" let xml = "<!DOCTYPE HTML><!-- a comment -->One <qux />Two"
let tb: XmlTreeBuilder = XmlTreeBuilder() let tb: XmlTreeBuilder = XmlTreeBuilder()
let doc = try tb.parse(xml, "http://foo.com/") let doc = try tb.parse(xml, "http://foo.com/")
XCTAssertEqual("<!DOCTYPE HTML><!-- a comment -->One <qux />Two",try TextUtil.stripNewlines(doc.html())) XCTAssertEqual("<!DOCTYPE HTML><!-- a comment -->One <qux />Two", try TextUtil.stripNewlines(doc.html()))
} }
func testSupplyParserToJsoupClass()throws { func testSupplyParserToJsoupClass()throws {
let xml = "<doc><val>One<val>Two</val></bar>Three</doc>" let xml = "<doc><val>One<val>Two</val></bar>Three</doc>"
let doc = try SwiftSoup.parse(xml, "http://foo.com/", Parser.xmlParser()) let doc = try SwiftSoup.parse(xml, "http://foo.com/", Parser.xmlParser())
try XCTAssertEqual("<doc><val>One<val>Two</val>Three</val></doc>",TextUtil.stripNewlines(doc.html())) try XCTAssertEqual("<doc><val>One<val>Two</val>Three</val></doc>", TextUtil.stripNewlines(doc.html()))
} }
//TODO: nabil //TODO: nabil
@ -69,20 +69,19 @@ class XmlTreeBuilderTest: XCTestCase {
// TextUtil.stripNewlines(doc.html())); // TextUtil.stripNewlines(doc.html()));
// } // }
func testDoesNotForceSelfClosingKnownTags()throws { func testDoesNotForceSelfClosingKnownTags()throws {
// html will force "<br>one</br>" to logically "<br />One<br />". XML should be stay "<br>one</br> -- don't recognise tag. // html will force "<br>one</br>" to logically "<br />One<br />". XML should be stay "<br>one</br> -- don't recognise tag.
let htmlDoc = try SwiftSoup.parse("<br>one</br>") let htmlDoc = try SwiftSoup.parse("<br>one</br>")
XCTAssertEqual("<br>one\n<br>", try htmlDoc.body()?.html()) XCTAssertEqual("<br>one\n<br>", try htmlDoc.body()?.html())
let xmlDoc = try SwiftSoup.parse("<br>one</br>", "",Parser.xmlParser()) let xmlDoc = try SwiftSoup.parse("<br>one</br>", "", Parser.xmlParser())
XCTAssertEqual("<br>one</br>", try xmlDoc.html()) XCTAssertEqual("<br>one</br>", try xmlDoc.html())
} }
func testHandlesXmlDeclarationAsDeclaration()throws { func testHandlesXmlDeclarationAsDeclaration()throws {
let html = "<?xml encoding='UTF-8' ?><body>One</body><!-- comment -->" let html = "<?xml encoding='UTF-8' ?><body>One</body><!-- comment -->"
let doc = try SwiftSoup.parse(html, "", Parser.xmlParser()) let doc = try SwiftSoup.parse(html, "", Parser.xmlParser())
try XCTAssertEqual("<?xml encoding=\"UTF-8\"?> <body> One </body> <!-- comment -->",StringUtil.normaliseWhitespace(doc.outerHtml())) try XCTAssertEqual("<?xml encoding=\"UTF-8\"?> <body> One </body> <!-- comment -->", StringUtil.normaliseWhitespace(doc.outerHtml()))
XCTAssertEqual("#declaration", doc.childNode(0).nodeName()) XCTAssertEqual("#declaration", doc.childNode(0).nodeName())
XCTAssertEqual("#comment", doc.childNode(2).nodeName()) XCTAssertEqual("#comment", doc.childNode(2).nodeName())
} }
@ -159,21 +158,21 @@ class XmlTreeBuilderTest: XCTestCase {
static var allTests = { static var allTests = {
return [ return [
("testSimpleXmlParse" , testSimpleXmlParse), ("testSimpleXmlParse", testSimpleXmlParse),
("testPopToClose" , testPopToClose), ("testPopToClose", testPopToClose),
("testCommentAndDocType" , testCommentAndDocType), ("testCommentAndDocType", testCommentAndDocType),
("testSupplyParserToJsoupClass" , testSupplyParserToJsoupClass), ("testSupplyParserToJsoupClass", testSupplyParserToJsoupClass),
("testDoesNotForceSelfClosingKnownTags" , testDoesNotForceSelfClosingKnownTags), ("testDoesNotForceSelfClosingKnownTags", testDoesNotForceSelfClosingKnownTags),
("testHandlesXmlDeclarationAsDeclaration" , testHandlesXmlDeclarationAsDeclaration), ("testHandlesXmlDeclarationAsDeclaration", testHandlesXmlDeclarationAsDeclaration),
("testXmlFragment" , testXmlFragment), ("testXmlFragment", testXmlFragment),
("testXmlParseDefaultsToHtmlOutputSyntax" , testXmlParseDefaultsToHtmlOutputSyntax), ("testXmlParseDefaultsToHtmlOutputSyntax", testXmlParseDefaultsToHtmlOutputSyntax),
("testDoesHandleEOFInTag" , testDoesHandleEOFInTag), ("testDoesHandleEOFInTag", testDoesHandleEOFInTag),
("testParseDeclarationAttributes" , testParseDeclarationAttributes), ("testParseDeclarationAttributes", testParseDeclarationAttributes),
("testCaseSensitiveDeclaration" , testCaseSensitiveDeclaration), ("testCaseSensitiveDeclaration", testCaseSensitiveDeclaration),
("testCreatesValidProlog" , testCreatesValidProlog), ("testCreatesValidProlog", testCreatesValidProlog),
("preservesCaseByDefault" , preservesCaseByDefault), ("preservesCaseByDefault", preservesCaseByDefault),
("canNormalizeCase" , canNormalizeCase) ("canNormalizeCase", canNormalizeCase)
] ]
}() }()