iosxmlswiftrssnsxml

Swift iOS - Excluding root/parent elements when parsing XML - NSXML


I am attempting to parse an XML RSS feed using NSXMLParser and creating an array dictionary and displaying in a table view - which is working, however I am getting mismatched results. For each result I'm trying to show the "title" and "source" elements for each "item" (i.e. each child) - but because "title" exists in the parent node it is producing a mismatch of results.

Is it possible to specify only to parse the elements under item? i.e. something like ["items"]["title"]? Here is a sample of the structure I'm parsing:

<rss xmlns:atom="example" xmlns:dc="http://example" version="2.0">
<channel>
<title>Main title</title>
<link>http://main-site.com/xmltest</link>
<description>Main description</description>
<pubDate>Sun, 07 Feb 2016 10:41:05 +0000</pubDate>
    <item>
        <title>Title 1</title>
        <link>https://item1.com</link>
        <description>Description1</description>
        <pubDate>Date1</pubDate>
        <source>Source1</source>
    </item>
    <item>
        <title>Title 2</title>
        <link>https://item2.com</link>
        <description>Description2</description>
        <pubDate>Date2</pubDate>
        <source>Source2</source>
    </item>
    <item>
        <title>Title 3</title>
        <link>https://item3.com</link>
        <description>Description3</description>
        <pubDate>Date3</pubDate>
        <source>Source3</source>
    </item>
</channel>

The NSXML parser code I have is:

func parser(parser: NSXMLParser,
    didStartElement elementName: String,
    namespaceURI: String?,
    qualifiedName: String?,
    attributes attributeDict: [String : String]){
        if elementName == "title"{
            entryTitle = String()
            currentParsedElement = "title"
        }
        if elementName == "description"{
            entryDescription = String()
            currentParsedElement = "description"
        }
        if elementName == "link"{
            entryLink = String()
            currentParsedElement = "link"
        }
        if elementName == "source"{
            entrySource = String()
            currentParsedElement = "source"
        }
        if elementName == "pubDate"{
            entryDate = String()
            currentParsedElement = "pubDate"
        }
}

func parser(parser: NSXMLParser,
    foundCharacters string: String){
        if currentParsedElement == "title"{
            self.entryTitle = entryTitle + string
        }
        if currentParsedElement == "description"{
            self.entryDescription = entryDescription + string
        }
        if currentParsedElement == "link"{
            self.entryLink = entryLink + string
        }
        if currentParsedElement == "source"{
            self.entrySource = entrySource + string
        }
        if currentParsedElement == "pubDate"{
            self.entryDate = entryDate + string
        }

}

func parser(parser: NSXMLParser,
    didEndElement elementName: String,
    namespaceURI: String?,
    qualifiedName qName: String?){
        if elementName == "title"{
            entryDictionary["title"] = entryTitle
        }
        if elementName == "link"{
            entryDictionary["link"] = entryLink
        }
        if elementName == "description"{
            entryDictionary["description"] = entryDescription
        }
        if elementName == "source"{
            entryDictionary["source"] = entrySource
        }
        if elementName == "pubDate"{
            entryDictionary["pubDate"] = entryDate
            entriesArray.append(entryDictionary)
        }

}

func parserDidEndDocument(parser: NSXMLParser){
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        self.tableView.reloadData()
    })
}

The results I get from this are:

  1. Main Title, (no source)
  2. Title 1, (no source)
  3. Title 2, Source 1
  4. Title 3, Source 2

Is this occurring because "title" exists in the root and child elements, whereas source doesn't? If so, how can I exclude the initial root elements?

I'm grabbing other values too (such the link) and these values do match (i.e. Title 1 will return with Link1); presumably because "link" also appears in the root?

Any help would be appreciated!

Many thanks.


Solution

  • After some more research I've managed to get this working. I re-worked the XML parser so that I explicitly specified to start parsing from the "item" node; effectively excluding the root/parent nodes. Refactored parser looks like:

    func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String])
    {
        element = elementName
        if (elementName as NSString).isEqualToString("item")
        {
            elements = NSMutableDictionary()
            elements = [:]
            title1 = NSMutableString()
            title1 = ""
            source = NSMutableString()
            source = ""
            pubDate = NSMutableString()
            pubDate = ""
            link = NSMutableString()
            link = ""
        }
    }
    
    func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
    {
        if (elementName as NSString).isEqualToString("item") {
            if !title1.isEqual(nil) {
                elements.setObject(title1, forKey: "title")
            }
            if !source.isEqual(nil) {
                elements.setObject(source, forKey: "source")
            }
            if !pubDate.isEqual(nil) {
                elements.setObject(pubDate, forKey: "pubDate")
            }
            if !link.isEqual(nil) {
                elements.setObject(link, forKey: "link")
            }
            posts.addObject(elements)
        }
    }
    
    func parser(parser: NSXMLParser, foundCharacters string: String)
    {
        if element.isEqualToString("title") {
            title1.appendString(string)
        } else if element.isEqualToString("source") {
            source.appendString(string)
        }
        else if element.isEqualToString("pubDate") {
            pubDate.appendString(string)
        }
        else if element.isEqualToString("link"){
            link.appendString(string)
        }
    }