diff --git a/Cartfile b/Cartfile
new file mode 100644
index 0000000..c8bea08
--- /dev/null
+++ b/Cartfile
@@ -0,0 +1,3 @@
+github "tid-kijyun/Kanna" ~> 1.1.0
+github "kodlian/Eki"
+github "Thomvis/BrightFutures"
\ No newline at end of file
diff --git a/Erik.podspec b/Erik.podspec
index 20dc20d..efe7301 100644
--- a/Erik.podspec
+++ b/Erik.podspec
@@ -2,7 +2,7 @@ Pod::Spec.new do |s|
# ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
s.name = "Erik"
- s.version = "1.0.3"
+ s.version = "1.1.0"
s.summary = "A headless browser written in Swift"
s.description = <<-DESC
Erik is an headless browser based on WebKit and HTML parser Kanna.
diff --git a/Erik/Document.swift b/Erik/Document.swift
index c6f232c..df63ab8 100644
--- a/Erik/Document.swift
+++ b/Erik/Document.swift
@@ -50,14 +50,14 @@ public class Document : Node {
super.init(rawValue: rawValue, selectors: [])
}
- var title: String? { return (rawValue as? HTMLDocument)?.title }
- var head: Element? {
+ public var title: String? { return (rawValue as? HTMLDocument)?.title }
+ public var head: Element? {
guard let doc = self.rawValue as? HTMLDocument, element = doc.head else {
return nil
}
return Element(rawValue: element, selectors: ["head"])
}
- var body: Element? {
+ public var body: Element? {
guard let doc = self.rawValue as? HTMLDocument, element = doc.body else {
return nil
}
@@ -65,6 +65,7 @@ public class Document : Node {
}
}
+// HTML Node
public class Node {
var layoutEngine: LayoutEngine?
@@ -82,6 +83,7 @@ public class Node {
self.rawValue = rawValue
}
+ // Select elements using css selector
public func querySelectorAll(selector: String) -> [Element] {
let selectors = self.selectors + [selector]
return rawValue.css(selector).map {
@@ -91,6 +93,7 @@ public class Node {
}
}
+ // Select an element using css selector
public func querySelector(selector: String) -> Element? {
guard let element = rawValue.at_css(selector) else {
return nil
@@ -101,20 +104,45 @@ public class Node {
return elem
}
+ // Get all children element
public var elements: [Element] {
return querySelectorAll("*")
}
+ // Get first child element
public var firstChild: Element? {
return querySelectorAll(":first-child").first
}
+ // Get last child element
public var lastChild: Element? {
return querySelectorAll(":last-child").first
}
}
+extension Node {
+
+ // Fill value of selected child
+ public func type(selector: String, value: String, key: String = "value") -> Element? {
+ if let element = self.querySelector(selector) {
+ element[key] = value
+ return element
+ }
+ return nil
+ }
+
+ // Click on selected child
+ public func click(selector: String) -> Element? {
+ if let element = self.querySelector(selector) {
+ element.click()
+ return element
+ }
+ return nil
+ }
+
+}
+
extension Node: CustomStringConvertible {
public var description: String {
return self.toHTML ?? ""
diff --git a/Erik/Erik.swift b/Erik/Erik.swift
index 3c5357d..6d60521 100644
--- a/Erik/Erik.swift
+++ b/Erik/Erik.swift
@@ -24,11 +24,29 @@ SOFTWARE.
import Foundation
import WebKit
+// MARK: Error
+public enum ErikError: ErrorType {
+ // Error provided by javascript
+ case JavaScriptError(message: String)
+ // A timeout occurs
+ case TimeOutError
+ // No content returned
+ case NoContent
+ // HTML is not parsable
+ case HTMLNotParsable(html: String)
+ // Invalid url submited (NSURL init failed)
+ case InvalidURL(urlString: String)
+}
+
+// MARK: Erik class
+
+// Instance of headless browser
public class Erik {
public var layoutEngine: LayoutEngine
public var htmlParser: HTMLParser
+ // Init the headless browser
public init(webView: WKWebView? = nil) {
if let view = webView {
self.layoutEngine = WebKitLayoutEngine(webView: view)
@@ -38,22 +56,77 @@ public class Erik {
self.htmlParser = KanaParser.instance
}
+ @available(*, deprecated=1.1, obsoleted=2.0, message="Use url")
+ public var currentURL: NSURL? {
+ return layoutEngine.url
+ }
+
+ // Get current url
+ public var url: NSURL? {
+ return layoutEngine.url
+ }
+
+ // Get current title
+ public var title: String? {
+ return layoutEngine.title
+ }
+
+ // Go to specific url
public func visitURL(URL: NSURL, completionHandler: ((Document?, ErrorType?) -> Void)?) {
layoutEngine.browseURL(URL) {[unowned self] (object, error) -> Void in
self.publishContent(object, error: error, completionHandler: completionHandler)
}
}
- public var currentURL: NSURL? {
- return layoutEngine.currentURL
+ // Go to specific url
+ public func visitURL(urlString: String, completionHandler: ((Document?, ErrorType?) -> Void)?) {
+ if let url = NSURL(string: urlString) {
+ visitURL(url, completionHandler: completionHandler)
+ } else {
+ completionHandler?(nil, ErikError.InvalidURL(urlString: urlString))
+ }
}
+ // Go to specific url using url request
+ public func loadURLRequest(URLRequest: NSURLRequest, completionHandler: ((Document?, ErrorType?) -> Void)?) {
+ layoutEngine.browseURL(URLRequest) {[unowned self] (object, error) -> Void in
+ self.publishContent(object, error: error, completionHandler: completionHandler)
+ }
+ }
+
+ // Get current content
public func currentContent(completionHandler: ((Document?, ErrorType?) -> Void)?) {
layoutEngine.currentContent {[unowned self] (object, error) -> Void in
self.publishContent(object, error: error, completionHandler: completionHandler)
}
}
+
+ // Navigates to the previous loaded page.
+ public func goBack() {
+ layoutEngine.goBack()
+ }
+
+ // Navigates to the next page ie. the one loaded before `goBack`
+ public func goForward() {
+ layoutEngine.goForward()
+ }
+
+ // A Boolean value indicating whether browser can go back
+ public var canGoBack: Bool {
+ return layoutEngine.canGoBack
+ }
+
+ // A Boolean value indicating whether browser can go forward
+ public var canGoForward: Bool {
+ return layoutEngine.canGoForward
+ }
+
+ // Reloads the current page
+ public func reload() {
+ layoutEngine.reload()
+ }
+ // MARK: private
private func publishContent(object: AnyObject?, error: ErrorType?, completionHandler: ((Document?, ErrorType?) -> Void)?) {
guard let html = object as? String else {
completionHandler?(nil, ErikError.NoContent)
@@ -74,8 +147,9 @@ public class Erik {
completionHandler?(doc, error)
}
-
}
+
+// MARK: javascript
extension Erik: JavaScriptEvaluator {
public func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject?, ErrorType?) -> Void)?) {
@@ -83,23 +157,35 @@ extension Erik: JavaScriptEvaluator {
}
}
-public enum ErikError: ErrorType {
- case JavaScriptError(message: String)
- case TimeOutError
- case NoContent
- case HTMLNotParsable(html: String)
-}
-// MARK: static
+// MARK: Erik static
extension Erik {
+ // Shared instance used for static functions
public static let sharedInstance = Erik()
public static func visitURL(URL: NSURL, completionHandler: ((Document?, ErrorType?) -> Void)?) {
Erik.sharedInstance.visitURL(URL, completionHandler: completionHandler)
}
-
+
+ public static func visitURL(urlString: String, completionHandler: ((Document?, ErrorType?) -> Void)?) {
+ Erik.sharedInstance.visitURL(urlString, completionHandler: completionHandler)
+ }
+
+ public static func loadURLRequest(URLRequest: NSURLRequest, completionHandler: ((Document?, ErrorType?) -> Void)?) {
+ Erik.sharedInstance.loadURLRequest(URLRequest, completionHandler: completionHandler)
+ }
+
+ @available(*, deprecated=1.1, obsoleted=1.2, message="Use url")
public static var currentURL: NSURL? {
- return Erik.sharedInstance.currentURL
+ return Erik.sharedInstance.url
+ }
+
+ public static var url: NSURL? {
+ return Erik.sharedInstance.url
+ }
+
+ public static var title: String? {
+ return Erik.sharedInstance.title
}
public static func currentContent(completionHandler: ((Document?, ErrorType?) -> Void)?) {
@@ -109,6 +195,25 @@ extension Erik {
public static func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject?, ErrorType?) -> Void)?) {
Erik.sharedInstance.evaluateJavaScript(javaScriptString, completionHandler: completionHandler)
}
+
+ public static func goBack() {
+ Erik.sharedInstance.goBack()
+ }
+ public static func goForward() {
+ Erik.sharedInstance.goForward()
+ }
+
+ public static var canGoBack: Bool {
+ return Erik.sharedInstance.canGoBack
+ }
+
+ public static var canGoForward: Bool {
+ return Erik.sharedInstance.canGoForward
+ }
+
+ public static func reload() {
+ Erik.sharedInstance.reload()
+ }
}
\ No newline at end of file
diff --git a/Erik/Info.plist b/Erik/Info.plist
index d3de8ee..a6f720e 100644
--- a/Erik/Info.plist
+++ b/Erik/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 1.0
+ 1.1
CFBundleSignature
????
CFBundleVersion
diff --git a/Erik/LayoutEngine.swift b/Erik/LayoutEngine.swift
index 067b059..8f04851 100644
--- a/Erik/LayoutEngine.swift
+++ b/Erik/LayoutEngine.swift
@@ -28,9 +28,18 @@ public protocol JavaScriptEvaluator {
public protocol URLBrowser {
func browseURL(URL: NSURL, completionHandler: ((AnyObject?, ErrorType?) -> Void)?)
- var currentURL: NSURL? {get}
+ func browseURL(URLRequest: NSURLRequest, completionHandler: ((AnyObject?, ErrorType?) -> Void)?)
+ var url: NSURL? {get}
+ var title: String? {get}
func currentContent(completionHandler: ((AnyObject?, ErrorType?) -> Void)?)
+ func goBack()
+ func goForward()
+
+ var canGoBack: Bool { get }
+ var canGoForward: Bool { get }
+ func reload()
+
func clear()
}
public typealias LayoutEngine = protocol
@@ -66,14 +75,46 @@ extension WebKitLayoutEngine {
public func browseURL(URL: NSURL, completionHandler: ((AnyObject?, ErrorType?) -> Void)?) {
let request = NSURLRequest(URL: URL)
- webView.loadRequest(request)
+ self.browseURL(request, completionHandler: completionHandler)
+ }
+
+ public func browseURL(URLRequest: NSURLRequest, completionHandler: ((AnyObject?, ErrorType?) -> Void)?) {
+ webView.loadRequest(URLRequest)
self.currentContent(completionHandler)
}
-
+
+ @available(*, deprecated=1.1, obsoleted=2.0, message="Use url")
public var currentURL: NSURL? {
return self.webView.URL
}
+ public var url: NSURL? {
+ return self.webView.URL
+ }
+
+ public var title: String? {
+ return self.webView.title
+ }
+
+ public func goBack() {
+ self.webView.goBack()
+ }
+ public func goForward() {
+ self.webView.goForward()
+ }
+
+ public var canGoBack: Bool {
+ return self.webView.canGoBack
+ }
+
+ public var canGoForward: Bool {
+ return self.webView.canGoForward
+ }
+
+ public func reload() {
+ self.webView.reload()
+ }
+
public func currentContent(completionHandler: ((AnyObject?, ErrorType?) -> Void)?) {
handleLoadRequestCompletion {
self.handleHTML(completionHandler)
diff --git a/ErikOSX/Info.plist b/ErikOSX/Info.plist
index 68c1c75..2b5f3f4 100644
--- a/ErikOSX/Info.plist
+++ b/ErikOSX/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 1.0
+ 1.1
CFBundleSignature
????
CFBundleVersion
diff --git a/ErikTests/ErikTests.swift b/ErikTests/ErikTests.swift
index f15e6fc..cbaa572 100644
--- a/ErikTests/ErikTests.swift
+++ b/ErikTests/ErikTests.swift
@@ -15,10 +15,10 @@ import BrightFutures
+let url = NSURL(string:"https://www.google.com")!
class ErikTests: XCTestCase {
- let url = NSURL(string:"http://www.google.com")!
#if os(OSX)
let googleFormSelector = "f"
#elseif os(iOS)
@@ -33,6 +33,27 @@ class ErikTests: XCTestCase {
super.tearDown()
}
+ func testVisit() {
+
+ let visitExpectation = self.expectationWithDescription("visit")
+
+ Erik.visitURL(url) { (obj, err) -> Void in
+ if let error = err {
+ print(error)
+
+ XCTFail("\(error)")
+ }
+ else if let _ = obj {
+ XCTAssertNotNil(Erik.title)
+ visitExpectation.fulfill()
+
+ // XCTAssertEqual(Erik.url?.host ?? "dummy", url.host) // failed is redirected to google.XXX TODO how to force lang (request header?)
+ XCTAssertEqual(Erik.url?.scheme ?? "dummy", url.scheme)
+ }
+ }
+ self.waitForExpectationsWithTimeout(5, handler: nil)
+ }
+
func testAsync() {
let expt = self.expectationWithDescription("Dispatch")
@@ -97,7 +118,7 @@ class ErikTests: XCTestCase {
}
- Erik.currentContent {[unowned self] (obj, err) -> Void in
+ Erik.currentContent { (obj, err) -> Void in
if let error = err {
print(error)
XCTFail("\(error)")
@@ -106,9 +127,9 @@ class ErikTests: XCTestCase {
print(doc)
currentContentExpectation.fulfill()
- XCTAssertNotEqual(self.url, Erik.currentURL)
+ XCTAssertNotEqual(url, Erik.url)
- XCTAssertNotNil("\(Erik.currentURL)".rangeOfString(value!))
+ XCTAssertNotNil("\(Erik.url)".rangeOfString(value!))
}
}
}