4

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?

1

2 Answers 2

6

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:).

Sign up to request clarification or add additional context in comments.

2 Comments

@Rob Thanks. My initial answer focused more on the cause of the crash without looking at the bigger picture. Your comment is a good one and my answer now reflects both possible approaches.
I knew it was an initiation problem and I tried it without it being optional and it didn't work. This makes way more sense! Thank you guys! @rob
1

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 = [[:]]
}

Comments

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.