CsCody CsCody - 2 months ago 12
iOS Question

Why does my XML Parser keep iterating through the tags in Swift?

I'm trying to parse through weather data in XML format, but my parser seems to be iterating through all of the tags instead of the one I specify.
So, I have in my didStartElement function:

func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String])
{
if(elementName == "cap:event") {
capEventFound = true
}
}


and my didEndElement function:

func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject])
{
if(elementName == "cap:event") {
capEventFound = false
}
}


my parser function:

func parser(parser: NSXMLParser, foundCharacters string: String)
{
if(capEventFound)
{
newsStories.append(string)
print(newsStories)
}
else
{
print("No cap:event found")
}
}


Other relevant functions for parsing:

override func viewDidLoad() {
super.viewDidLoad()
self.pvState.delegate = self
self.pvState.dataSource = self
stateCode = ["AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY"]
}

func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int
{
return 1
}

func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
{
return stateCode.count
}

func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
{
return stateCode[row]
}

func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
{
selectedState = stateCode[pickerView.selectedRowInComponent(0)]
url = "http://alerts.weather.gov/cap/\(selectedState).php?x=0"
urlStr = url.stringByAddingPercentEncodingWithAllowedCharacters( NSCharacterSet.URLQueryAllowedCharacterSet())!
searchURL = NSURL(string: urlStr as String)!
xmlParser = NSXMLParser(contentsOfURL: searchURL)!
xmlParser.delegate = self
xmlParser.parse()
}


My output for example when I choose Colorado is here: http://pastebin.com/v3ULvQ0G

Why is it doing this and how can I get it to only post the relevant cap:event?

Rob Rob
Answer

It's continuing to iterate through tags because it's never calling didEndElement. This is because your didEndElement declaration is not correct, and therefore isn't getting called like you expected. This declaration:

func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject]) { ... }

should be:

func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { ... }

Unrelated, but your process that appends values in foundCharacters is a little dangerous because it's theoretically possible that it will require multiple calls to foundCharacters to return the full event name. You should generally only append characters in foundCharacters, and do the updating of your model structure in didEndElement. For example, I might suggest:

var parsedString: String?

// element starts

func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
    if elementName == "cap:event" {
        parsedString = String()
    }
}

// characters found
//
// this may be called more than once

func parser(parser: NSXMLParser, foundCharacters string: String) {
    parsedString? += string
}

// element ended
//
// but, by the time we get here, we have the full parsed string 

func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
    if elementName == "cap:event" {
        newsStories.append(parsedString!)
        parsedString = nil
    }
}