Parsing a large XML file from the server when loading using LibXML2 in Swift 3

I have a problem with LibXML2 SAX parser in Swift 3.
I need something like XMLPullParser from Android in iOS. Which downloads XML from the server and parses it when loading Stream.

My XML looks like this:

 <?xml version="1.0" encoding="UTF-8" ?> <ResultList id="12345678-0" platforms="A;B;C;D;E"> <Book id="1111111111" author="Author A" title="Title A" price="9.95" ... /> <Book id="1111111112" author="Author B" title="Title B" price="2.00" ... /> <Book id="1111111113" author="Author C" title="Title C" price="5.00" ... /> <ResultInfo bookcount="3" /> </ResultList> 

So, all data is stored in attributes, not in child nodes.

I myself made the following class mainly based on these examples:
XMLPerformance , XMLPerformance-Swift, and iOS-XML-Streaming

 import Foundation class LibXMLParser: NSObject, URLSessionDataDelegate { var url: URL? var delegate: LibXMLParserDelegate? var done = false var context: xmlParserCtxtPtr? var simpleSAXHandlerStruct: xmlSAXHandler = { var handler = xmlSAXHandler() handler.initialized = XML_SAX2_MAGIC handler.startElementNs = startElementSAX handler.endElementNs = endElementSAX handler.characters = charactersFoundSAX //handler.error = errorEncounteredSAX return handler }() init(url: URL) { super.init() self.url = url } func parse() { self.done = false let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main) let dataTask = session.dataTask(with: URLRequest(url: url!)) dataTask.resume() self.context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, Unmanaged.passUnretained(self).toOpaque(), nil, 0, nil) self.delegate?.parserDidStartDocument() repeat { RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture) } while !self.done xmlFreeParserCtxt(self.context) self.delegate?.parserDidEndDocument() } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { print("Did receive data") data.withUnsafeBytes { (bytes: UnsafePointer<CChar>) -> Void in xmlParseChunk(self.context, bytes, CInt(data.count), 0) } } func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { xmlParseChunk(self.context, nil, 0, 1) self.done = true } func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { self.done = true //self.delegate?.parserErrorOccurred(error) } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { self.done = true //self.delegate?.parserErrorOccurred(error) } } private func startElementSAX(_ ctx: UnsafeMutableRawPointer?, name: UnsafePointer<xmlChar>?, prefix: UnsafePointer<xmlChar>?, URI: UnsafePointer<xmlChar>?, nb_namespaces: CInt, namespaces: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?, nb_attributes: CInt, nb_defaulted: CInt, attributes: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?) { let parser = Unmanaged<LibXMLParser>.fromOpaque(ctx!).takeUnretainedValue() parser.delegate?.parserDidStartElement(String(cString: name!), nb_attributes: nb_attributes, attributes: attributes) } private func endElementSAX(_ ctx: UnsafeMutableRawPointer?, name: UnsafePointer<xmlChar>?, prefix: UnsafePointer<xmlChar>?, URI: UnsafePointer<xmlChar>?) { let parser = Unmanaged<LibXMLParser>.fromOpaque(ctx!).takeUnretainedValue() parser.delegate?.parserDidEndElement(String(cString: name!)) } private func charactersFoundSAX(_ ctx: UnsafeMutableRawPointer?, ch: UnsafePointer<xmlChar>?, len: CInt) { let parser = Unmanaged<LibXMLParser>.fromOpaque(ctx!).takeUnretainedValue() parser.delegate?.parserFoundCharacters(String(cString: ch!)) } 

I initialize this class using a URL . When I call parse() , it creates a URLSession and a URLSessionDataTask with a self delegate to override the didReceive data: Data method. After that, I create xmlParserCtxtPtr and loop until the dataTask ends.

When it receives data, I parse it using the xmlParseChunk method, and startElementSAX calls the delegate that I set from the ViewController class. (I just need the name of the element, the number of attributes and attributes.)
So far so good.

In my ViewController (UITableViewController), I have the following code:

 func downloadBooksLibXML() { print("Downloadingโ€ฆ") UIApplication.shared.isNetworkActivityIndicatorVisible = true DispatchQueue.global().async { print("Setting up parser") let parser = LibXMLParser(url: URL(string: self.baseUrl + self.parameters!)!) parser.delegate = self parser.parse() } } func parserDidStartDocument() { } func parserDidEndDocument() { DispatchQueue.main.sync { UIApplication.shared.isNetworkActivityIndicatorVisible = false self.isDone = true print("Finished") } } func parserDidStartElement(_ elementName: String, nb_attributes: CInt, attributes: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?) { print(elementName) switch elementName { case "Book": DispatchQueue.main.async { let book = self.buildBook(nb_attributes: nb_attributes, attributes: attributes) self.books.append(book) self.tableView.beginUpdates() self.tableView.insertRows(at: [IndexPath(row: self.books.count - 1, section: 0)], with: .automatic) self.tableView.endUpdates() self.navigationItem.title = String(format: NSLocalizedString("books_found", comment: "Books found"), "\(self.books.count)") } case "ResultList": break case "ResultInfo": break default: break } } func buildBook(nb_attributes: CInt, attributes: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?) -> Book { let fields = 5 /* (localname/prefix/URI/value/end) */ let book = Book() for i in 0..<Int(nb_attributes) { if let localname = attributes?[i * fields + 0], //let prefix = attributes?[i * fields + 1], //let URI = attributes?[i * fields + 2], let value_start = attributes?[i * fields + 3], let value_end = attributes?[i * fields + 4] { let localnameString = String(cString: localname) let string_start = String(cString: value_start) let string_end = String(cString: value_end) let diff = string_start.characters.count - string_end.characters.count if diff > 0 { let value = string_start.substring(to: string_start.index(string_start.startIndex, offsetBy: diff)) book.setValue(value, forKey: localnameString) } } } return book } func parserDidEndElement(_ elementName: String) { } func parserFoundCharacters(_ string: String) { } func parserErrorOccurred(_ parseError: Error?) { } 

------

Update

Thus, the problem of getting attribute values โ€‹โ€‹was resolved by nwellnhof's answer. I updated my code above to much better code. Now it does not iterate over all attributes. Now my new problem:

I created a buildBook method to get the Book attribute of XML attributes. I basically translated the method here What is the correct way to get the attribute value in a parser package (C ++)? in Swift and use setValue(value: Any?, forKey: String) to set the attributes of my book object.

But now my problem is that it does not update the tableView. I tried to execute the buildBook method synchronously in the background thread using DispatchQueue.global().sync Sync and update the tableView in the asynchronous main thread using DispatchQueue.main.async . But then it falls on tableView.endUpdates() , although it is in the main thread.

------

Any help would be greatly appreciated.

+7
c xml ios swift libxml2
source share
1 answer

This seems to be a simple mistake. To iterate over an attribute array in C, I would write something like:

 for (int i = 0; i < nb_attributes; i++) 

But you are using a closed range operator that includes an upper bound:

 for i in 0...Int(nb_attributes) 

Thus, you should use the semi-open range operator :

 for i in 0..<Int(nb_attributes) 

By the way, libxml2 also has a pull parser interface modeled after C # XmlTextReader , which is much easier to use than the SAX parser.

0
source share

All Articles