Parsing XML (Final Solution)

An overview on how to use XMLParser for parsing XML documents. This contains the final solution. Make sure to see the other post for the how-to.

Final solution

Copy and paste this into a playground to test drive. If you want to learn how this all works check out this step by step tutorial


import UIKit

struct RSSPodcastItem {
    var title: String
    var description: String
    var date: String
    var category: [String]

    // 1. Start off with an empty struct
    init() {
        self.title = ""
        self.description = ""
        self.date = ""
        self.category = []
    }

    // 2. Reset all the properites
    mutating func resetAllProperties() {
        self = RSSPodcastItem()
    }
}


class RSSPodcastParser: NSObject, XMLParserDelegate {

    // What will be available to the consumer
    var parsedData = [RSSPodcastItem]()
    // ---------------------------------------------
    private var xmlParser: XMLParser!
    // we track currentElement so we know what element the XMLParserDelegate is currently on
    private var currentElement = ""
    // RSS Items that are tracked internally
    private var rssItems = [RSSPodcastItem]()
    // RSS Template object that is used to to build up our RSSItem
    private var rssPodcastItem =  RSSPodcastItem.init()

    // 0. This is the function where we pass a URL and get a completion handler
    func startParsingWithContentsOfURL(rssURL: URL, with completion: (Bool)-> ()) {
        let parser = XMLParser(contentsOf: rssURL)
        parser?.delegate = self
        if let flag = parser?.parse() {
            completion(flag)
        }
    }

    // 1. This gets called when the opening elementName is the currentElement name == "item"
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        currentElement = elementName
        if currentElement == "item" {
            self.rssPodcastItem.resetAllProperties() // Starting a brand new item with all properties reset to ""
        }
    }

    // 2. This gets parsed when the currentElement is found. currentElement got assigned in the method above
    // - this going through word by word and adding them together into a sentence
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        let string = string.trimmingCharacters(in: .whitespacesAndNewlines)
        switch currentElement {
        case "title":
            self.rssPodcastItem.title += string
        case "description":
            self.rssPodcastItem.description += string
        case "pubDate":
            self.rssPodcastItem.date += string
        case "category":
            if (!string.isEmpty) {
                self.rssPodcastItem.category.append(string)
            }
        default: break
        }
    }


    // 3. Closing Tag. When the element tag ends
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if elementName == "item" {
            let item = self.rssPodcastItem
            self.rssItems.append(item)
        }
    }

    // 4. Notifies Delegate when completed. At this point we just assigned it the parsedData property
    func parserDidEndDocument(_ parser: XMLParser) {
        self.parsedData = self.rssItems
    }


    //5. Errors
    func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
        print(parseError.localizedDescription)
    }

}

// ------------------------------- //
// Run the examples                //
// ------------------------------- //

var model: [RSSPodcastItem]?

func parseRSSFeedSetUp() {
    let feed = "https://rss.itunes.apple.com/api/v1/us/apple-music/coming-soon/all/10/explicit.rss"
    let feedURL = URL.init(string: feed)
    let parser = RSSPodcastParser()
    parser.startParsingWithContentsOfURL(rssURL: feedURL!) { (success) in
        if success {
            model = parser.parsedData
            print("Success")
        } else {
            print("Not successful")
        }
    }
}


// Run the function
parseRSSFeedSetUp()

// Review the model in the console
model?.forEach({ (item) in
    print(item);
})

// 1. Run the playground
// 2. Check your console screen

console

You should see something like this in your console (see image)

error

If your getting “Not Successful” in your console it could be that feedURL is not working anymore. Just verify it works.