Parsing Hierarchical XML in Swift using NSXMLParser

3.2k views Asked by At

I'm really having problems getting hierarchical XML values back in a form that I can actually use, so any assistance would be much appreciated. I'm pretty new to Swift and IOS development, so to be honest I do not fully understand the parser, but I am hopeful after this that I will!

Here's an example XML that I am trying to parse, this comes back from a soap web service. All that side in terms of connecting and getting the data works perfectly fine

<s:Envelope
    xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <get_Entry_DetailsResponse
            xmlns="http://tempuri.org/">
            <get_Entry_DetailsResult>
                <ApiResult
                    xmlns="">
                    <Status>Success</Status>
                    <Parameters>
                        <TotalRecords>1</TotalRecords>
                        <Records>
                            <Entry>
                                <Entry_ID>1234</Entry_ID>
                                <Title>This is the Title</Title>
                                <Description>This is a description.</Description>
                                <Charge_USD>3</Charge_USD>
                                <Charge_GBP>1.5</Charge_GBP>
                                <Classifications>
                                    <Classification_Type>No. Of Colours</Classification_Type>
                                    <Description>10</Description>
                                    <Classification_Type>Height</Classification_Type>
                                    <Description>16.712</Description>
                                    <Classification_Type>Width</Classification_Type>
                                    <Description>1.485</Description>
                                    <Classification_Type>Width Count</Classification_Type>
                                    <Description>11</Description>
                                    <Classification_Type>Pages</Classification_Type>
                                    <Description>6</Description>
                                    <Classification_Type>Type</Classification_Type>
                                    <Description>Type Description</Description>
                                    <Classification_Type>Topic</Classification_Type>
                                    <Description>Transport</Description>
                                    <Classification_Type>Item</Classification_Type>
                                    <Description>Shop Item</Description>
                                    <Classification_Type>Material</Classification_Type>
                                    <Description>Metal</Description>
                                    <Classification_Type>Manufacturer</Classification_Type>
                                    <Description>Unknown</Description>
                                </Classifications>
                            </Entry>
                        </Records>
                    </Parameters>
                </ApiResult>
            </get_Entry_DetailsResult>
        </get_Entry_DetailsResponse>
    </s:Body>
</s:Envelope>

So I can get elements like the Entry_ID, Title, Description, Charge_GBP and Charge_USD fine, it's how to get the classifications back into the code so I can actually use it to output it on the page.

So here is my connectionDidFinishLoading, where I start the parser off and running:

func connectionDidFinishLoading(connection: NSURLConnection!) {
    var xmlParser = NSXMLParser(data: mutableData)
    xmlParser.delegate = self
    xmlParser.parse()
    xmlParser.shouldResolveExternalEntities = true
}

Then here is the XMLParser Delegate stuff:

var mutableData:NSMutableData  = NSMutableData.alloc()
var currentElementName:NSString = ""
var foundCharacters = ""
var appParsedData = [Dictionary<String, String>]()
var currentDataDictionary = Dictionary<String, String>()

func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: NSDictionary!) {

        currentElementName = elementName

    }

func parser(parser: NSXMLParser, didEndElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!) {

    if !foundCharacters.isEmpty {
        currentDataDictionary[currentElementName] = foundCharacters
        foundCharacters = ""

        //Last Element so add to main Dictionary
        //Cannot find last element yet on XML so use Charge_GBP for testing until fixed
        if currentElementName == "Charge_GBP" {
            appParsedData.append(currentDataDictionary)
        }
    }
}

func parser(parser: NSXMLParser, foundCharacters string: String!) {
    if (currentElementName == "Entry_ID") || (currentElementName == "Title") || currentElementName == "Description" || currentElementName == "Charge_USD" || currentElementName == "Charge_GBP" || currentElementName == "Classification_Type" {
        foundCharacters += string
    }
}

func parserDidEndDocument(parser: NSXMLParser!) {

    self.setupItemsOnView()

}

Then my setupItemsOnView function which is where I intend to get the values and display them on the view controller so far is:

func setupItemsOnView() {

    for (currentElementName) in appParsedData {
        println("\(currentElementName):")
    }

    let test = appParsedData[0] as Dictionary<String, String>
    let test_entryid = test["Entry_ID"]
    println("Entry ID is \(test_entryid)")

}

Phew, ok, so basically I can get the values Entry_ID, Title, Description, Charge_GBP and Charge_USD in the setupItemsOnView function, but using this method I cannot get the classifications and descriptions values back in a meaningful way from the classifications parent. I'm currently using as you can see a dictionary to store the data, so I wonder if using a Dictionary is the right thing to do or not when you have hierarchical XML, but I have no idea what else to try.

From my background you'd be able to reference the XML with a dot notation and just loop around the parent for all the elements, but I can't get that working in swift, so I just need a usable way to get back the all data in a form that I can then output.

Any code you may offer to help with this would be greatly appreciated.

Cheers

D

1

There are 1 answers

5
fred02138 On BEST ANSWER

What you'd like is something comparable to jaxb for Swift -- but I'm not aware that it exists. Anyway, parsing in Swift is akin to using a SAX parser. Presumably, you have an class hierarchy in your Swift code that corresponds to the element hierarchy in the XML. The approach I used was stateful -- just instantiate the class corresponding to the element parsed, and keep it in a variable designated for that purpose. Then, your parsing code can populate the class data as it is encountered in subsequent callbacks. Of course, you have to do this hierarchically, and use the didEndElement callback to detect completion.