Mukesh Lokare
Mukesh Lokare

Reputation: 2189

Parsing xml from from url in iOS Swift

    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <soap:Body>
            <CreditCheckMSResponse xmlns="https://ebs.gsm.co.za/">
                <CreditCheckMSResult>
                    <result message="Success" ref="505790" network="CellC" xmlns="" />
                </CreditCheckMSResult>
            </CreditCheckMSResponse>
        </soap:Body>
    </soap:Envelope>

Here is my attempt:

let parser = XMLParser(data: data)
 parser.delegate = self
 if parser.parse() {
     print(self.results ?? "No results")
 }
 let recordKey = "result"
    let dictionaryKeys = Set<String>(["message", "ref", "network", "xmlns"])

    // 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
    


    extension ViewController: XMLParserDelegate {
    
        // initialize results structure
    
        func parserDidStartDocument(_ parser: XMLParser) {
            results = []
        }
    
        // 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
        }
    
    }

Any help would be greatly appreciated!

Thank you in the advance!

Upvotes: 0

Views: 890

Answers (1)

Paulw11
Paulw11

Reputation: 115076

It isn't entirely clear from your question what your goal is, but I assume it is to extract the attributes of the result elements contained in the CreditCheckMSResult element.

The values are attributes of the result element, not children. So, when you get the start of the result element in didStartElement, the values you want are in the attributes dictionary that is passed to that function.

A dictionary is rarely a good final data model. In this case I would suggest that you create an array of structs to contain your data.

struct CreditCheckResult {
    let message: String
    let ref: String
    let network: String
}

The xmlns attribute is to detail the XML namespace that apples to the element, if there is one. You wouldn't want to store this.

Since you aren't interested in the children of the result element (it doesn't have any). You pretty much only need the didStart delegate methods.

let recordKey = "result"
let results = [CreditCheckResult]?
extension ViewController: XMLParserDelegate {

    func parserDidStartDocument(_ parser: XMLParser) {
        results = []
    }
    
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {

        // We only care about "result" elements
         guard elementName == recordKey else {
             return
         }

         guard let message = attributes["message"],
               let ref = attributes["ref"],
               let network = attributes["network"] else {
                   print("Malformed result element = required attribute missing")
                   return
         }
 
         self.results?.append(CreditCheckResult(message: message, ref: ref, network:network))
    }

    func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
        print(parseError)
        results = []
    }

}

Upvotes: 1

Related Questions