A swift wrapper around libxml for parsing HTML - html

I'm getting an odd error message saying "Extra argument 'endocing' in call", but it's in the method, so it's not an extra argument? Why is this happening and how can I resolve this? The error message appears when declaring the variable "parser" as you can see. Thanks!
if let checkedUrl = NSURL(string:"http://www.mobladet.se") {
if let htmlString = String(contentsOfURL: checkedUrl, encoding: NSUTF8StringEncoding, error: nil) {
// Parsing HTML
let opt = CInt(HTML_PARSE_NOERROR.value | HTML_PARSE_RECOVER.value)
var err : NSError?
var parser = HTMLParser(html: htmlString, encoding: NSUTF8StringEncoding, option: opt, error: &err)
var bodyNode = parser.body
// Create an array of the part of HTML you need
if let inputNodes = bodyNode?.findChildTags("h4") {
for node in inputNodes {
let result = html2String(node.rawContents)
println(result)
}
}
} else {
println("Could not load HTML Content")
}
}

html should be HTML code to be parsed not a NSURL. You need to use String( contentsOfURL:) to extract its contents and them parse it
if let checkedUrl = NSURL(string:"http://stackoverflow.com/questions/28751228/a-swift-wrapper-around-libxml-for-parsing-html"){
if let htmlString = String(contentsOfURL: checkedUrl, encoding: NSUTF8StringEncoding, error: nil) {
println(htmlString)
} else {
println("could not load html string from the url")
}
} else {
println("invalid url")
}

Related

How do I correctly write this data to a JSON file without overwriting the file?

I am writing a JSON file to documents directory, I would like to keep it in one file and read it later. The struct looks like this:
struct SymptomD:Codable
{
var symptom:String
var severity:String
var comment:String
var timestamp:String
}
Then I write to documents like so:
var completeData = SymptomD(symptom: "", severity: "", comment: "", timestamp: "")
func writeTrackedSymptomValues(symptom: String, comment: String, time: String, timestamp: String) {
completeData.symptom = symptom
completeData.severity = self.severity
completeData.comment = comment
completeData.timestamp = timestamp
createJSON()
}
var logFile: URL? {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
let fileName = "symptom_data.json"
return documentsDirectory.appendingPathComponent(fileName)
}
func createJSON() {
guard let logFile = logFile else {
return
}
let jsonData = try! JSONEncoder().encode(completeData)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
if FileManager.default.fileExists(atPath: logFile.path) {
if let fileHandle = try? FileHandle(forWritingTo: logFile) {
fileHandle.seekToEndOfFile()
fileHandle.write(completeData) //This does not work, I am not sure how to add data without overwriting the previous file.
fileHandle.closeFile()
}
} else {
do {
try JSONEncoder().encode(completeData)
.write(to: logFile)
} catch {
print(error)
}
}
}
With this I can only add the data once, I am not sure how I should go about adding another 'row' basically to the JSON file, so that I can read these and decode them with my struct for use in a tableView later. The JSON file made looks like this:
What is a way I can call the createJSON function again, without overwriting the whole file, and how should I go about organising this so that when I read the JSON I can decode it simply and access the info.
Update:
Using this I am able to add more lines to the JSON,
let jsonData = try! JSONEncoder().encode(completeData)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
if FileManager.default.fileExists(atPath: logFile.path) {
if let fileHandle = try? FileHandle(forWritingTo: logFile) {
fileHandle.seekToEndOfFile()
fileHandle.write(jsonData)
fileHandle.closeFile()
}
Giving me this:
{"timestamp":"1592341465","comment":"","severity":"Mild","symptom":"Anxiety"}{"timestamp":"1592342433","comment":"","severity":"Moderate","symptom":"Anxiety"}{"timestamp":"1592342458","comment":"","severity":"Mild","symptom":"Anxiety"}{"timestamp":"1592343853","comment":"","severity":"Mild","symptom":"Anxiety"}{"timestamp":"1592329440","comment":"","severity":"Mild","symptom":"Fatigue"}{"timestamp":"1592344328","comment":"","severity":"Mild","symptom":"Mood Swings"}{"timestamp":"1592257920","comment":"test","severity":"Mild","symptom":"Anxiety"}
But when trying to parse this, it crashes with an error:
Code=3840 "Garbage at end."
What am I doing wrong?
The issue looks pretty clear to me. You are appending another dictionary to an existing dictionary but you should have created an array of dictionaries to be able to append a dictionary to it.
struct SymptomD: Codable {
var symptom, severity, comment, timestamp: String
init(symptom: String = "", severity: String = "", comment: String = "", timestamp: String = "") {
self.symptom = symptom
self.severity = severity
self.comment = comment
self.timestamp = timestamp
}
}
If you would like to manually append the text to your json string you will need to seek to the position before the end of your file, add a comma before the next json object and a closed bracket after it:
extension SymptomD {
func write(to url: URL) throws {
if FileManager.default.fileExists(atPath: url.path) {
let fileHandle = try FileHandle(forWritingTo: url)
try fileHandle.seek(toOffset: fileHandle.seekToEndOfFile()-1)
let data = try JSONEncoder().encode(self)
fileHandle.write(Data(",".utf8) + data + Data("]".utf8))
fileHandle.closeFile()
} else {
try JSONEncoder().encode([self]).write(to: url)
}
}
}
Playground testing:
var logFile: URL? {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("symptom_data.json")
}
var symptomD = SymptomD()
symptomD.symptom = "Anxiety"
symptomD.severity = "Mild"
symptomD.timestamp = .init(Date().timeIntervalSince1970)
do {
if let logFile = logFile {
try symptomD.write(to: logFile)
}
} catch {
print(error)
}
var symptomD2 = SymptomD()
symptomD2.symptom = "Depression"
symptomD2.severity = "Moderate"
symptomD2.timestamp = .init(Date().timeIntervalSince1970)
do {
if let logFile = logFile {
try symptomD2.write(to: logFile)
}
} catch {
print(error)
}
do {
if let logFile = logFile {
let symptoms = try JSONDecoder().decode([SymptomD].self, from: .init(contentsOf: logFile))
print(symptoms)
}
} catch {
print(error)
}
This will print:
[__lldb_expr_532.SymptomD(symptom: "Anxiety", severity: "Mild",
comment: "", timestamp: "1592356106.9662929"),
__lldb_expr_532.SymptomD(symptom: "Depression", severity: "Moderate", comment: "", timestamp: "1592356106.978864")]
edit/update:
If you need to update a single "row" of your JSON, you will need to make your struc conform to equatable, read your collection and find its index:
extension SymptomD: Equatable {
static func ==(lhs: SymptomD, rhs: SymptomD) {
(lhs.symptom, lhs.severity, lhs.comment ,lhs.timestamp) ==
(rhs.symptom, rhs.severity, rhs.comment ,rhs.timestamp)
}
#discardableResult
mutating func updateAndWrite(symptom: String? = nil, severity: String? = nil, comment: String? = nil, timestamp: String? = nil, at url: URL) throws -> [SymptomD]? {
var symptoms = try JSONDecoder().decode([SymptomD].self, from: .init(contentsOf: url))
if let index = symptoms.firstIndex(of: self) {
self.symptom = symptom ?? self.symptom
self.severity = severity ?? self.severity
self.comment = comment ?? self.comment
self.timestamp = timestamp ?? self.timestamp
symptoms[index] = self
try JSONEncoder().encode(symptoms).write(to: url, options: .atomic)
return symptoms
}
return nil
}
}

SWIFT Json check if object title exists

My code checks if code: "this data" is not empty, how can I check also if code itself exists. Some responses might give me almost an empty JSON with just time stamp. So the var code won't be there.
Or is there a better way altogether to do this? as My JSON is
"Variable Based on text input" which leads to "code" which might not be there which will have "some info" or ""
Top of script:
struct Variables: Decodable {
var code: String
}
typealias DecoderX = [String: Variables]
Previous function sets inputs from user text which are cross checked with the database so GetInfo will only be called if UserDefault inputs are set.
func GetInfo() {
let Input1 = UserDefaults.standard.string(forKey: “UserInput1”) ?? “”
let Input2 = UserDefaults.standard.string(forKey: “UserInput2”) ?? “”
let Input3 = UserDefaults.standard.string(forKey: “UserInput3”) ?? “”
print(“Input Check 1: \(Input1) \(Input2) \(Input3)”)
// URL INFO With API key hidden
let jsonTask = URLSession.shared.dataTask(with: requestURL) { data, response, error in
if response == response {
if let data = data, let body = String(data: data, encoding: .utf8) {
do {
let json = try? decoder.decode(DeocderX.self, from: data);
DispatchQueue.main.async {
print(“Input Check 2: \(json![Input1]!.code) \(json![Input2]!.code) \(json![Input3]!.code)”)
if json?[Input1]?.code != nil {
print("Good Information 1")
} else {
print("Found Nothing 1")
}
if json?[Input2]?.code != nil {
print("Good Information 2")
} else {
print("Found Nothing 2")
}
if json?[Input3]?.code != nil {
print("Good Information 3")
} else {
print("Found Nothing 3")
}
}
// rest of code not applicable
You can use SwiftyJSON library. You can find it at this link
if let json = try? JSON(data: response.data!){
if json["yourKey"]["otherKey"].exists() {
print("exists")
}
}
Hope it helps...

HTML code in Swift String (not NSString)

I am downloading HTML source code of a webpage this way:
let url = NSURL(string: "http://www.example.com")
var error: NSError?
let html = NSString(contentsOfURL: url!, encoding: NSUTF8StringEncoding, error: &error)
if (error != nil) {
println("whoops, something went wrong")
} else {
println(html!)
}
But I would like to get it as String instead of NSString. Is there any way?
Swift's String also accepts the same initializer:
let html = String(contentsOfURL: url!, encoding: NSUTF8StringEncoding, error: &error)
I would suggest to use safe unwrapping with if let for your values:
var error: NSError?
if let url = NSURL(string: "http://www.example.com"), let html = String(contentsOfURL: url, encoding: NSUTF8StringEncoding, error: &error) {
if error != nil {
println(error)
} else {
println(html)
}
}
Last note: no need to use brackets around the condition in Swift.
Update for Swift 2 (Xcode 7)
if let url = NSURL(string: "http://www.example.com"),
let html = try? String(contentsOfURL: url, encoding: NSUTF8StringEncoding) {
print(html)
}

What is the best practice to parse html in swift?

I'm a Swift newbie. I need for something like Python's BeautifulSoup in Swift iOS project. Precisely, I need to get all href of <a> that ends with ".txt". What are the steps that I should take?
There are several nice libraries of HTML Parsing using Swift and Objective-C like the followings:
hpple
NDHpple
Kanna( old Swift-HTML-Parser)
Fuzi
SwiftSoup
Ji
Take a look in the following examples in the four libraries posted above, mainly parsed using XPath 2.0:
hpple:
let data = NSData(contentsOfFile: path)
let doc = TFHpple(htmlData: data)
if let elements = doc.searchWithXPathQuery("//a/#href[ends-with(.,'.txt')]") as? [TFHppleElement] {
for element in elements {
println(element.content)
}
}
NDHpple:
let data = NSData(contentsOfFile: path)!
let html = NSString(data: data, encoding: NSUTF8StringEncoding)!
let doc = NDHpple(HTMLData: html)
if let elements = doc.searchWithXPathQuery("//a/#href[ends-with(.,'.txt')]") {
for element in elements {
println(element.children?.first?.content)
}
}
Kanna (Xpath and CSS Selectors):
let html = "<html><head></head><body><ul><li><input type='image' name='input1' value='string1value' class='abc' /></li><li><input type='image' name='input2' value='string2value' class='def' /></li></ul><span class='spantext'><b>Hello World 1</b></span><span class='spantext'><b>Hello World 2</b></span><a href='example.com'>example(English)</a><a href='example.co.jp'>example(JP)</a></body>"
if let doc = Kanna.HTML(html: html, encoding: NSUTF8StringEncoding) {
var bodyNode = doc.body
if let inputNodes = bodyNode?.xpath("//a/#href[ends-with(.,'.txt')]") {
for node in inputNodes {
println(node.contents)
}
}
}
Fuzi (Xpath and CSS Selectors):
let html = "<html><head></head><body><ul><li><input type='image' name='input1' value='string1value' class='abc' /></li><li><input type='image' name='input2' value='string2value' class='def' /></li></ul><span class='spantext'><b>Hello World 1</b></span><span class='spantext'><b>Hello World 2</b></span><a href='example.com'>example(English)</a><a href='example.co.jp'>example(JP)</a></body>"
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let doc = try HTMLDocument(string: html, encoding: NSUTF8StringEncoding)
// XPath queries
for anchor in doc.xpath("//a/#href[ends-with(.,'.txt')]") {
print(anchor.stringValue)
}
} catch let error {
print(error)
}
The ends-with function is part of Xpath 2.0.
SwiftSoup (CSS Selectors):
do{
let doc: Document = try SwiftSoup.parse("...")
let links: Elements = try doc.select("a[href]") // a with href
let pngs: Elements = try doc.select("img[src$=.png]")
// img with src ending .png
let masthead: Element? = try doc.select("div.masthead").first()
// div with class=masthead
let resultLinks: Elements? = try doc.select("h3.r > a") // direct a after h3
} catch Exception.Error(let type, let message){
print(message)
} catch {
print("error")
}
Ji (XPath):
let jiDoc = Ji(htmlURL: URL(string: "http://www.apple.com/support")!)
let titleNode = jiDoc?.xPath("//head/title")?.first
print("title: \(titleNode?.content)") // title: Optional("Official Apple Support")
Try SwiftSoup, a port of jsoup to Swift.
let html: String = "<a id=1 href='?foo=bar&mid&lt=true'>One</a> <a id=2 href='?foo=bar<qux&lg=1'>Two</a>";
let els: Elements = try SwiftSoup.parse(html).select("a");
for element: Element in els.array(){
print(try element.attr("href"))
}
You could try this swift-html-parser:
https://github.com/tid-kijyun/Swift-HTML-Parser
It helps a lot.
And for getting your html from a txt you can:
let file = "file.txt"
if let dirs : [String] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true) as? [String] {
let dir = dirs[0] //documents directory
let path = dir.stringByAppendingPathComponent(file);
let html = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: nil)
Edit:
To get what you need you could use as the exemple:
import Foundation
let html = "theHtmlYouWannaParse"
var err : NSError?
var parser = HTMLParser(html: html, error: &err)
if err != nil {
println(err)
exit(1)
}
var bodyNode = parser.body
if let inputNodes = bodyNode?.findChildTags("b") {
for node in inputNodes {
println(node.contents)
}
}
if let inputNodes = bodyNode?.findChildTags("a") {
for node in inputNodes {
println(node.getAttributeNamed("href")) //<- Here you would get your files link
}
}

Casting AnyObject to Double

I am trying to parse JSON with the following code:
func ltchandler(response: NSURLResponse!, data : NSData!, error : NSError!) { //Is passed the results of a NSURLRequest
if ((error) != nil) {
//Error Handling Stuff
} else {
if (NSString(data:data, encoding:NSUTF8StringEncoding) == "") {
//Error Handling Stuff
} else {
var data = NSData(data: data);
// Define JSON string
var JSONString = "\(data)"
// Get NSData using string
if let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
// Parse JSONData into JSON object
var parsingError: NSError?
if let JSONObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parsingError) as? [String: AnyObject] {
// If the parsing was successful grab the rate object
var rateObject: Double! = JSONObject["price"]?.doubleValue
// Make sure the rate object is the expected type
if let rate = rateObject as? Double! { // THIS IS NOT WORKING!!!
//Do stuff with data
} else {
println("Parsing Issue")
}
}
}
}
}
}
The line marked THIS IS NOT WORKING!!! is not being called.
From what I can tell, it cannot cast rateObject as a double - why not? It is not showing any errors.
To clarify, the expected behavior is that a double is created from the JSON object.
To strictly answer your question have you tried printing the rateObject. Also why are you casting to Double! rather than just Double in the problematic line?
Personally I don't use ! in almost all cases. You are better off using either non-optionals or proper optionals.
In the relevent section I would write:
// Make sure the rate object is the expected type
if let rate = JSONObject["price"]?.doubleValue {
//Do stuff with rate
} else {
print("Parsing Issue")
}
Of course if the JSONObject["price"] is not something with a doubleValue method or the method returns nil you will end up with nil and the else case being taken.
the code worked for me, try this code:
// if the value equals nil or any String, the instruction abort the if
// SWIFT 2.0 in xcode beta 5
if let rate = Double((JSONObject["price"] as? String)!){
// insert you code here
} else {
print("error message")
}