1

I have a HTML string like this:

<html>
<head>
<script>
window.user = {"username":"admin", "meta":{"city": "Los Angeles", "country": "US"}}
window.flash = {"success": "", "warning": "you cannot do this"}
</script>
</head>
</body>
</html>

I am implementing a function

func dictionaryWithHtml(html: String, variable: String) -> [String: AnyObject]? {

}

usage:

calling dictionaryWithHtml(html, variable: "window.user")

should return the dictionary

{"username":"admin", "meta":{"city": "Los Angeles", "country": "US"}}

I am trying to search window.user = substring as index1, then find window.flash = substring as index2, then I can easily find the range of JSON for user dictionary.

For window.flash, I could find </script> substring, and get the range similarly, but what if the server updates and add more variables in the tag? or if server changes the order of assignment.

So is there any better and more robust approach?

3
  • 1
    there is one question related to you might help you check it (it's in objective-c) stackoverflow.com/questions/31377780/… Commented Jan 11, 2016 at 11:10
  • Maybe possible to get the length by counting { and } Commented Jan 11, 2016 at 11:21
  • It's just better than the currently implementation. And if this doesn't work, none of other methods would possibly get the correct output. Commented Jan 12, 2016 at 9:16

1 Answer 1

3

You could use an html parser to retrieve the values of the script tag, or you could load the html string into a WKIWebView, and ask it to execute the javascript code that would return to you the value of the variables you search within the window object. This has the advantage of being more reliable as it will not break if the script structure changes, and also works for dynamically javascript computed values.

let webView = WKWebView(frame: CGRectZero)
self.view.addSubview(webView)
webView.loadHTMLString(htmlString, baseURL: nil)
// wait until the page has loaded
// either this, or execute the javascript code in
// the didFinishLoading delegate method 
while (webView.loading) {
    CFRunLoopRunInMode(NSDefaultRunLoopMode, 0.1, false)
}
// get the two objects in one round-trip to the javascript engine
webView.evaluateJavaScript("[window.user, window.flash]") { (result, error) -> Void in
    print(result, error)
}

In both cases you need to add the web view to another view, so this code likely will need to be run into a view controller. Another possible disadvantage is the fact that the code will need to run on the main thread.

Note 1. WKWebView was added in iOS 8, for iOS 7 and earlier you can use an UIWebView instead:

let webView = UIWebView(frame: CGRectZero)
self.view.addSubview(webView)
webView.loadHTMLString(htmlString, baseURL: nil)
// wait until the page has loaded
// either this, or execute the javascript code in
// the didFinishLoading delegate method 
while (webView.loading) {
    CFRunLoopRunInMode(NSDefaultRunLoopMode, 0.1, false)
}
let userJSON = webView.stringByEvaluatingJavaScriptFromString("window.user")
let flashJSON = webView.stringByEvaluatingJavaScriptFromString("window.flash")

Note 2. The javascript engine will return the parsed json, if you need to obtain the values as strings you can query after JSON.stringify(window.user) and JSON.stringify(window.flash).


Playground execution

If you want to run this code in a playground, then you need to configure the playground to not terminate when the last line of code executes, and we need to wait until the javascript execution callback is called. Thanks to this SO question I found out how it can be done.

This code works in a playground:

import WebKit
import XCPlayground

// configuring the playground to not stop after evaluating the last line of code
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

let htmlString = "<html>\n" +
    "<head>\n" +
    "<title>aaa</title>\n" +
    "<script>\n" +
    "window.user = {\"username\":\"admin\", \"meta\":{\"city\": \"Los Angeles\", \"country\": \"US\"}};\n" +
    "window.flash = {\"success\": \"\", \"warning\": \"you cannot do this\"};\n" +
    "</script>\n" +
    "</head>\n" +
    "</body>\n" +
"</html>"

let webView = WKWebView(frame: CGRectZero)
// commented out the addSubview call as we don't have a view in a playground
//self.view.addSubview(webView)
webView.loadHTMLString(htmlString, baseURL: nil)
// wait until the page has loaded
// either this, or execute the javascript code in
// the didFinishLoading delegate method
while (webView.loading) {
    CFRunLoopRunInMode(NSDefaultRunLoopMode, 0.1, false)
}
// get the two objects in one round-trip to the javascript engine
webView.evaluateJavaScript("[window.user, window.flash]") { (result, error) -> Void in
    print(result, error)
}

and prints the following to the console:

Optional((
        {
        meta =         {
            city = "Los Angeles";
            country = US;
        };
        username = admin;
    },
        {
        success = "";
        warning = "you cannot do this";
    }
)) nil
Sign up to request clarification or add additional context in comments.

2 Comments

I am not able to run that in Playground.
@OMGPOP Looked into running in a playground, I updated the answer, let me know if it runs on your playground

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.