Parsing XML in Swift 4

20,643

Solution 1

Your code never actually initializes results so the first time you try to use it you are trying to force-unwrap an nil optional value. That's bad. And there's no reason to declare it as an implicitly unwrapped optional.

You need to change:

var results: [[String: String]]!

to:

var results = [[String: String]]()

You will also need to remove the line:

results = nil

from your parser(_:parseErrorOccurred:) method.

If you would rather have results be optional then you can make the following changes to your code:

Change the declaration of results to:

var results: [[String: String]]? = [[String: String]]()

Change:

results.append(currentDictionary)

to:

results?.append(currentDictionary)

And you leave the results = nil line in parser(_:parseErrorOccurred:).

Solution 2

rmaddy has correctly diagnosed the problem (+1). The results was never initialized.

I would suggest leaving your code largely as is, but merely add a parserDidStartDocument method that initializes results as follows:

func parserDidStartDocument(_ parser: XMLParser) {
    results = [[:]]
}
Share:
20,643
Ricky
Author by

Ricky

Updated on August 22, 2020

Comments

  • Ricky
    Ricky almost 4 years

    I'm new to XML parsing in Swift and I found this code on Parsing XML from URL in Swift but I get a EXC_BAD_INSTRUCTION error when I try running the code. The description of the error reads: fatal error: unexpectedly found nil while unwrapping an Optional value

    This is my simple XML file:

    <xml>
        <book>
            <title>Book Title</title>
            <author>Book Author</author>
        </book>
    </xml>
    

    The following code creates an XMLParser object and parses the XML file located in my Documents.

    // get xml file path from Documents and parse
    
    let filePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last?.appendingPathComponent("example.xml")
    
    let parser = XMLParser(contentsOf: filePath!)
    parser?.delegate = self
    
    if (parser?.parse())! {
        print(self.results)
    }
    

    Here I implement the XMLParserDelegate methods and define my dictionaries:

    // a few constants that identify what element names we're looking for inside the XML
    
    let recordKey = "book"
    let dictionaryKeys = ["title","author"]
    
    // a few variables to hold the results as we parse the XML
    
    var results: [[String: String]]!          // the whole array of dictionaries
    var currentDictionary: [String: String]!  // the current dictionary
    var currentValue: String?                 // the current value for one of the keys in the dictionary
    
    // start element
    //
    // - If we're starting a "record" create the dictionary that will hold the results
    // - If we're starting one of our dictionary keys, initialize `currentValue` (otherwise leave `nil`)
    
    
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
    
        if elementName == recordKey {
    
            currentDictionary = [String : String]()
    
        } else if dictionaryKeys.contains(elementName) {
    
            currentValue = String()
    
        }
    }
    
    // found characters
    //
    // - If this is an element we care about, append those characters.
    // - If `currentValue` still `nil`, then do nothing.
    
    func parser(_ parser: XMLParser, foundCharacters string: String) {
    
        currentValue? += string
    
    }
    
    // end element
    //
    // - If we're at the end of the whole dictionary, then save that dictionary in our array
    // - If we're at the end of an element that belongs in the dictionary, then save that value in the dictionary
    
    
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
    
        if elementName == recordKey {
    
            results.append(currentDictionary)
            currentDictionary = nil
    
        } else if dictionaryKeys.contains(elementName) {
    
            currentDictionary[elementName] = currentValue
            currentValue = nil
    
        }
    }
    
    // Just in case, if there's an error, report it. (We don't want to fly blind here.)
    
    func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
    
        print(parseError)
    
        currentValue = nil
        currentDictionary = nil
        results = nil
    
    }
    

    The error is found on the didEndElement method when the currentDictionary is appended to the results dictionary.

    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
    
        if elementName == recordKey {
    
            results.append(currentDictionary)    // Line with Error
            currentDictionary = nil
    
        } else if dictionaryKeys.contains(elementName) {
    
            currentDictionary[elementName] = currentValue
            currentValue = nil
    
        }
    }
    

    Please help me solve this issue. I'm using the exact same code provided on Parsing XML from URL in Swift and they don't seem to have any issues. Am I doing anything wrong?