Accessing element values when using NSXMLParser in Swift
I am trying to parse XML at http://apps.wku.edu/iwku/maps/buildings/data/SouthCampus-Buildings.xml
Using the following code on Swift Playground
import UIKit
var str = "Hello, playground"
class Parse: NSObject, NSXMLParserDelegate{
func beginParse(){
let url = NSURL(string: "http://apps.wku.edu/iwku/maps/buildings/data/SouthCampus-Buildings.xml")
var xml = NSXMLParser(contentsOfURL: url)
xml?.delegate = self
xml?.parse()
}
func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: NSDictionary!) {
println("Element name is \(elementName)")
println("Element attributes are \(attributeDict)")
}
}
var instance = Parse()
instance.beginParse()
Unfortunately my console output looks like this:
Why does my dict attribute appear to be empty and how can I access the values associated with these elements?
source to share
Parsing an XML file is NSXMLParser
not as easy as you might expect. You must implement several methods from the protocol NSXMLParserDelegate
to capture events. You have already done this for didStartElement
, which gives the expected results (for me): Element name and its attributes (there are no attributes in the XML file you linked above).
Everything works fine ... so far.
Now that you've caught the start of an element, you need to take care of further processing:
-
The protocol method
foundCharacters
runs between the start and end of the element. Maybe several times. You have to implement this method and add the characters found to the string variable depending on the element name. -
When
didEndElement
run, your string variable is fully populated with the content of the element
If the XML file is deeply hierarchical, this can get complicated.
Sorry for this broad answer, but unfortunately this XML file is not there and give me a deeply nested word method with NSXMLParser
.
source to share
Attributes are those included in the tag. For example, if you have a tag that looks like this:
<building id="foo">
In this case, it id
will be in attributeDict
.
In this case, you have XML that looks like this:
<buildings>
<building>
<name>Commonwealth School</name>
<building_code>SC</building_code>
<latitude>36.965075</latitude>
<longitude>-86.467144</longitude>
<image_url>
http://www.wku.edu/marketingandcommunications/images/wkucuptallrb.jpg
</image_url>
<description/>
<handicap_accessible/>
<address/>
<url/>
<aliases>
<alias>South Campus</alias>
<alias>Community College</alias>
</aliases>
<email/>
<phone/>
<organizations/>
</building>
...
So, given <name>Commonwealth School</name>
this will result in a series of calls
-
didStartElement
, -
foundCharacters
(which can be called multiple times) and -
didEndElement
...
source to share
I made a simple example app that reads XML from this feed - jokes-n-fun RSS feed and displays the extracted data in the view table, here is the link: jokes-n-fun @github , generic source can be used as a link to create similar iOS applications. To answer your question, you can refer to the code written in the AtomParser.swift class , copy the same into it:
import UIKit
// MARK: - declaring typealias to store closures with dictionary
typealias StartTagRuleBlock = (NSMutableDictionary, [NSObject : AnyObject]) -> Void
typealias EndTagRuleBlock = (NSMutableDictionary, String) -> Void
// MARK: - Protocol declared
protocol CompletionObserver {
func dataSourcePopulated(dataSourceArray : NSArray) -> ()
}
class AtomParser : NSObject, NSXMLParserDelegate {
// MARK: - Properties
let triggerTag : String
let parseUrl : String
var dataArray : NSMutableArray = NSMutableArray()
var collectedCharacters : NSMutableString?
var recordDict : NSMutableDictionary?
var parser : NSXMLParser?
var startElementRuleMappingDict:[String: StartTagRuleBlock] = [String: StartTagRuleBlock]()
var endElementRuleMappingDict:[String: EndTagRuleBlock] = [String: EndTagRuleBlock]()
var endTagRules : NSDictionary?
var completionObserver : CompletionObserver?
// MARK: - Designated initializer
/**
Designated initializer to initialize AtomParser class.
:param: triggerTag Tag which distinguishes each record.
:param: parseUrl URL supplying xml to be parser.
:returns: Void.
*/
init(triggerTag : String, parseUrl : String) {
self.triggerTag = triggerTag
self.parseUrl = parseUrl
}
// MARK: - Initiate parsing
func startParsing () {
let url = NSURL(string: self.parseUrl)
parser = NSXMLParser(contentsOfURL: url)
parser?.delegate = self
parser?.parse()
}
// MARK: - Parser delegates
func parserDidStartDocument(parser: NSXMLParser!) {
self.dataArray.removeAllObjects()
}
func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: [NSObject : AnyObject]!) {
if elementName == triggerTag {
recordDict = NSMutableDictionary(capacity: 1)
}
else if recordDict != nil {
if let startTagMappingElement = self.startElementRuleMappingDict[elementName] {
startTagMappingElement(recordDict!,attributeDict)
}
collectedCharacters = NSMutableString(string: "")
}
else {
// no need to handle these tags
}
}
func parser(parser: NSXMLParser!, foundCharacters string: String!) {
collectedCharacters?.appendString(string)
}
func parser(parser: NSXMLParser!, didEndElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!) {
if let mutableDictionary = recordDict {
if elementName == triggerTag {
dataArray.addObject(recordDict!)
}
else if recordDict != nil {
if let endTagMappingElement = self.endElementRuleMappingDict[elementName] {
endTagMappingElement(recordDict!,"\(String(collectedCharacters!))")
}
}
else {
// no need to handle these tags
}
}
}
func parserDidEndDocument(parser: NSXMLParser) {
let arrayToReturn = NSArray(array: dataArray)
completionObserver?.dataSourcePopulated(arrayToReturn)
}
}
source to share