2

I'm trying to abstract an XML parser into a custom class to run it from a VC. It compiles perfectly and my error handler shows up success. However, the actual delegate methods are skipped over. No data is getting parsed.

It all ran fine when I had every running it the VC, but I am now try to get away from spaghetti code.

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let parser = XMLParserHelper()
        //try create file for persistent data
        //CreatePlist.createPlist()
        parser.runParser()
    }
}

class XMLParserHelper:  NSObject, XMLParserDelegate {
    //list type variables to hold XML values (update list base on XML structure):
    static var station: String = ""
    static var latitude: String = ""
    static var longitude: String = ""
    private static var code: String = ""
    private static var id: String = ""

    //reusable method type veriales (do not touch)
    static var strXMLData:String = ""
    static var currentElement:String = ""
    static var passData:Bool=false
    static var passName:Bool=false
    static var xmlParser = XMLParser()

    //parser methods
    func runParser(){
        let xmlPath = Bundle.main.url(forResource: "station", withExtension: "xml")
        let xmlParser = XMLParser(contentsOf: (xmlPath)!)
        xmlParser?.delegate = self
        let success:Bool = xmlParser!.parse()
        xmlParser?.parse()
        if success {
            print("parse success!")
            print(XMLParserHelper.currentElement)
        } else {
            print("parse failure!")
        }
    }

    private static func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        XMLParserHelper.currentElement=elementName;
        if (elementName=="StationDesc" || elementName=="StationLatitude" || elementName=="StationLongitude" || elementName=="StationCode" || elementName=="StationId" ) {
            if (elementName=="StationDesc") {
                XMLParserHelper.passName=true;
            }
            XMLParserHelper.passData=true;
        }
    }

    private static func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        XMLParserHelper.currentElement="";
        if (elementName=="StationDesc" || elementName=="StationLatitude" || elementName=="StationLongitude" || elementName=="StationCode" || elementName=="StationId" ) {
            if(elementName=="StationDesc") {
                XMLParserHelper.passName=false;
            }
            XMLParserHelper.passData=false;
        }
    }

    private static func parser(_ parser: XMLParser, foundCharacters string: String) {
        if (XMLParserHelper.passName) {
            XMLParserHelper.strXMLData=XMLParserHelper.strXMLData+"\n\n"+string
        }

        if (XMLParserHelper.passData) {
            //ready content for codable struct
            switch XMLParserHelper.currentElement {
            case "StationDesc":
                XMLParserHelper.station = string
            case "StationLatitude":
                XMLParserHelper.latitude = string
            case "StationLongitude":
                XMLParserHelper.longitude = string
            case "StationCode":
                XMLParserHelper.code = string
            case "StationId":
                XMLParserHelper.id = string
                print(string)

            default:
                XMLParserHelper.id = string
            }
        }
    }

    func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
        print("failure error: ", parseError)
    }
}
3
  • My eyes hurt of all the repeating string literals :( Commented Nov 10, 2018 at 12:56
  • Use a guard in runParser if xmlParser is nil do a fatalError Commented Nov 10, 2018 at 13:47
  • Unrelated to your issue but you don't need ; in Swift and you don't need ( ) in an if statement. Commented Nov 10, 2018 at 16:05

2 Answers 2

3

To make XMLParserDelegate methods work, all the methods needs to be non-static, non-private methods.

So, all the properties should also be non-static.

class XMLParserHelper: NSObject, XMLParserDelegate {

    //list type variables to hold XML values (update list base on XML structure):
    var station: String = ""
    var latitude: String = ""
    var longitude: String = ""
    private var code: String = ""
    private var id: String = ""

    //reusable method type veriales (do not touch)
    var strXMLData: String = ""
    var currentElement: String = ""
    var passData: Bool = false
    var passName: Bool = false

    //parser methods
    func runParser() {
        let xmlURL = Bundle.main.url(forResource: "station", withExtension: "xml")!
        let xmlParser = XMLParser(contentsOf: xmlURL)!
        xmlParser.delegate = self
        let success = xmlParser.parse()
        if success {
            print("parse success!")
            print(currentElement)
        } else {
            print("parse failure!")
        }
    }

    //MARK: XMLParserDelegate methods

    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        currentElement = elementName
        if elementName == "StationDesc"
        || elementName == "StationLatitude"
        || elementName == "StationLongitude"
        || elementName == "StationCode"
        || elementName == "StationId"
        {
            if elementName == "StationDesc" {
                passName = true
            }
            passData = true
        }
    }

    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        currentElement = ""
        if elementName == "StationDesc"
        || elementName == "StationLatitude"
        || elementName == "StationLongitude"
        || elementName == "StationCode"
        || elementName == "StationId"
        {
            if elementName == "StationDesc" {
                passName = false
            }
            passData = false
        }
    }

    func parser(_ parser: XMLParser, foundCharacters string: String) {
        if passName {
            strXMLData = strXMLData+"\n\n"+string
        }

        if passData {
            //ready content for codable struct
            switch currentElement {
            case "StationDesc":
                station = string
            case "StationLatitude":
                latitude = string
            case "StationLongitude":
                longitude = string
            case "StationCode":
                code = string
            case "StationId":
                id = string
                print(string)

            default:
                id = string
            }
        }
    }

    func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
        print("failure error: ", parseError)
    }
}

The XMLParser just needs to be held while parse() is running, so you have no need to declare xmlParser as a property of XMLParserHelper.

Sign up to request clarification or add additional context in comments.

3 Comments

OOPer, you Gem, that worked perfectly - been struggling with this for some time so it was quite a feeling to see those parser methods getting called! I think I see now where I went wrong now, as my custom class was calling these parse methods I assumed them to type methods rather than instance methods (hence I marked them static/private, but as they are infact delegate methods they are not private or type methods.
@FrankFerdinandMcGovern Please don't forget to indicate that your question has been solved by clicking the checkmark to the left of the most helpful answer.
@maddy, thanks for reminder and pointer, quite new to SO, so was wondering about that - have updated accordingly
0

You need a strong reference

class ViewController: UIViewController{
   var parser:XMLParserHelper!
   override func viewDidLoad() {
     super.viewDidLoad()
     parser = XMLParserHelper()
     parser.runParser()
   }
}

2 Comments

Good answer. With no strong reference, the parser will be dealloc'd before it can do its job.
This is not the issue since runParser is not asynchronous.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.