Синтаксический анализ XML из URL в Swift



Я совершенно новичок в синтаксическом анализе и не могу найти ни одного учебника, который не устарел и не вызывает больше вопросов. У меня есть простой url-адрес xml-файла, который я пытаюсь разобрать. Xml очень прост:



<xml>
<record>
<EmpName>A Employee</EmpName>
<EmpPhone>111-222-3333</EmpPhone>
<EmpEmail>[email protected]</EmpEmail>
<EmpAddress>12345 Fake Street</EmpAddress>
<EmpAddress1>MyTown, Mystate ZIP</EmpAddress1>
</record>
</xml>


И просто хотел сохранить это как NSDictionary (теги как ключи и данные как значения). До сих пор все, что мне удалось сделать успешно, это распечатать строку xml в консоли с помощью:



let url = NSURL(string: "http://www.urlexample.com/file.xml")

let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
print(task)
task.resume()


Я просмотрел все онлайн-учебники, которые я нашел, и они либо устарели, либо слишком устарели. сложный. Любая помощь ценится.

554   3  

3 ответов:

Процесс прост:

  1. создайте XMLParser объект, передав ему данные.
  2. укажите delegate для этого синтаксического анализатора.
  3. провести анализ.

Итак, в Swift 3/4 это выглядит следующим образом:

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    guard let data = data, error == nil else {
        print(error ?? "Unknown error")
        return
    }

    let parser = XMLParser(data: data)
    parser.delegate = self
    if parser.parse() {
        print(self.results ?? "No results")
    }
}
task.resume()
Вопрос в том, как вы реализуете методы XMLParserDelegate. Три критических метода: didStartElement (где вы готовитесь к получению символов), foundCharacters (где вы обрабатываете фактические значения, проанализированные) и didEndElement (где вы сохраняете результаты).

Вы спросили, как для разбора одной записи (то есть одного словаря), но я покажу вам более общий шаблон для разбора ряда из них, что является гораздо более распространенной ситуацией с XML. Очевидно, вы можете увидеть, как упростить это, если вам не нужен массив значений (или просто захватить первый).

// a few constants that identify what element names we're looking for inside the XML

// a few constants that identify what element names we're looking for inside the XML

let recordKey = "record"
let dictionaryKeys = Set<String>(["EmpName", "EmpPhone", "EmpEmail", "EmpAddress", "EmpAddress1"])

// 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 = [:]
        } else if dictionaryKeys.contains(elementName) {
            currentValue = ""
        }
    }

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

}

Для перевода Swift 2 смотритепредыдущую редакцию этого ответа .

Ответ Роба для Свифта 3 / 4

func getDataFrom(url: URL, completion: @escaping (_ data: Data?, _ error: Error?)->()) {

        let session = URLSession(configuration: .default)

        let download = session.dataTask(with: url) { data, response, error in

            completion(data, error)

        }

        download.resume()

}

getDataFrom(url: url) { data, error in

        guard let data = data else {
            return
        }

        let parser = XMLParser(data: data)
        parser.delegate = self

        if parser.parse() {

            print(self.results)

        }

}

// a few constants that identify what element names we're looking for inside the XML

let recordKey = "record"
let dictionaryKeys = ["EmpName", "EmpPhone", "EmpEmail", "EmpAddress", "EmpAddress1"]

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

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

        self.currentDictionary = [String : String]()

    } else if dictionaryKeys.contains(elementName) {

        self.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) {

    self.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 == self.recordKey {

        self.results.append(self.currentDictionary)

        self.currentDictionary = nil

    } else if dictionaryKeys.contains(elementName) {

        self.currentDictionary[elementName] = currentValue

        self.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)

    self.currentValue = nil
    self.currentDictionary = nil
    self.results = nil

}

Я написал модуль для отображения XML в объекты, называемый XMLMapper. (использует ту же технику, что и ObjectMapper )

Для того, чего вы хотите достичь, вы можете просто использовать класс XMLSerialization, например:

let url = URL(string: "http://www.urlexample.com/file.xml")

let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
    do{
        let xmlDictionary = try XMLSerialization.xmlObject(with: data!) as? [String: Any]
    } catch {
        print("Serialization error occurred: \(error.localizedDescription)")
    }
}
task.resume()

Вы также можете реализовать протокол XMLMappable следующим образом:

class XMLResponse: XMLMappable {
    var nodeName: String!

    var record: Record?

    required init(map: XMLMap) {

    }

    func mapping(map: XMLMap) {
        record <- map["record"]
    }
}

class Record: XMLMappable {
    var nodeName: String!

    var empName: String!
    var empPhone: String!
    var empEmail: String?
    var empAddress: String?
    var empAddress1: String?

    required init(map: XMLMap) {

    }

    func mapping(map: XMLMap) {
        empName <- map["EmpName"]
        empPhone <- map["EmpPhone"]
        empEmail <- map["EmpEmail"]
        empAddress <- map["EmpAddress"]
        empAddress1 <- map["EmpAddress1"]
    }
}

И сопоставить XML-ответ в эти объекты с помощью класса XMLMapper:

let xmlResponse = XMLMapper<XMLResponse>().map(XMLObject: xmlDictionary)

Попробуйте.

Comments

    Ничего не найдено.