复杂转换XML到字典(Swift / iOS)

时间:2016-07-17 21:28:53

标签: ios swift multidimensional-array xml-parsing

我正在绞尽脑汁如何将这个解析过的xml转换为数组或词典。 xml标签没有帮助,因为标签是通用的,并且有大约10个标头。我可以根据标签的顺序做一些事情。任何想法?

NSXMLParser方法代码:

class MyXMLParserDelegate: NSObject, NSXMLParserDelegate {

@objc func parserDidStartDocument(parser: NSXMLParser) {
    print("parserDidStartDocument")
}

@objc func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
    print("didStartElement       --> \(elementName)")
}

@objc func parser(parser: NSXMLParser, foundCharacters string: String) {
    print("foundCharacters       --> \(string)")
}

@objc func parser(parser: NSXMLParser, didEndElement elementName: String,
                  namespaceURI: String?, qualifiedName qName: String?) {
    print("didEndElement         --> \(elementName)")
}

@objc func parser(parser: NSXMLParser, didStartMappingPrefix prefix: String,
                  toURI namespaceURI: String) {
    print("didStartMappingPrefix --> Prefix: \(prefix) toURI: \(namespaceURI)")
}

@objc func parser(parser: NSXMLParser, didEndMappingPrefix prefix: String) {
    print("didEndMappingPrefix   --> Prefix: \(prefix)")
}

@objc func parserDidEndDocument(parser: NSXMLParser) {
    //reload table with array
    print("parserDidEndDocument")
}
}

使用NSXMLParser方法进行XML解析的示例结果:

<result>
 <header>
    <col>
      <label>Tree Name</label>
    </col>
    <col>
      <label>Num Levels</label>
    </col>
    <col>
      <label>Defaults Weight</label>
    </col>
    <col>
      <label>Name</label>
    </col>
    <col>
      <label>Abbrev</label>
    </col>
    <col>
      <label>Level</label>
    </col>
    <col>
      <label>Full Name</label>
    </col>
  </header>
  <body>
    <row>
      <col>Cost Center 1</col>
      <col>2</col>
      <col>5</col>
      <col>Miami Dolphins Front Office</col>
      <col/>
      <col>0</col>
      <col/>
    </row>
    <row>
      <col>Cost Center 1</col>
      <col>2</col>
      <col>5</col>
      <col>Accounts Receivable</col>
      <col>A/R</col>
      <col>1</col>
      <col>Accounts Receivable</col>
    </row>
    <row>
      <col>Cost Center 1</col>
      <col>2</col>
      <col>5</col>
      <col>06</col>
      <col>06</col>
      <col>1</col>
      <col>06</col>
    </row>
    <row>
      <col>Cost Center 2</col>
      <col>3</col>
      <col>5</col>
      <col>Cost Center 2</col>
      <col/>
      <col>0</col>
      <col/>
    </row>
    <row>
      <col>Cost Center 2</col>
      <col>3</col>
      <col>5</col>
      <col>test2</col>
      <col/>
      <col>1</col>
      <col>test2</col>
    </row>
    <row>
      <col>Cost Center 2</col>
      <col>3</col>
      <col>5</col>
      <col>test</col>
      <col/>
      <col>1</col>
      <col>test</col>
    </row>
    <row>
      <col>Cost Center 3</col>
      <col>3</col>
      <col>5</col>
      <col>Cost Center 3</col>
      <col/>
      <col>0</col>
      <col/>
    </row>
    <row>
      <col>Cost Center 3</col>
      <col>3</col>
      <col>5</col>
      <col>test</col>
      <col/>
      <col>1</col>
      <col>test</col>
    </row>
  </body>
  <footer/>
</result>

parserDidStartDocument

didStartElement - &gt;结果

foundCharacters - &gt;

didStartElement - &gt;头

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;标签

foundCharacters - &gt;树名

didEndElement - &gt;标签

foundCharacters - &gt;

didEndElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;标签

foundCharacters - &gt; Num Levels

didEndElement - &gt;标签

foundCharacters - &gt;

didEndElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;标签

foundCharacters - &gt;默认值重量

didEndElement - &gt;标签

foundCharacters - &gt;

didEndElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;标签

foundCharacters - &gt;名称

didEndElement - &gt;标签

foundCharacters - &gt;

didEndElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;标签

foundCharacters - &gt;缩写

didEndElement - &gt;标签

foundCharacters - &gt;

didEndElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;标签

foundCharacters - &gt;水平

didEndElement - &gt;标签

foundCharacters - &gt;

didEndElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;标签

foundCharacters - &gt;全名

didEndElement - &gt;标签

foundCharacters - &gt;

didEndElement - &gt;山口

foundCharacters - &gt;

didEndElement - &gt;头

foundCharacters - &gt;

didStartElement - &gt;体

foundCharacters - &gt;

didStartElement - &gt;行

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt;成本中心1

didEndElement - &gt;山口

foundCharacters - &gt;

didStartElement - &gt;山口

foundCharacters - &gt; 2

didEndElement - &gt;山口

foundCharacters - &gt;

...

4 个答案:

答案 0 :(得分:1)

查看https://github.com/nicklockwood/XMLDictionary图书馆 库已用Obj-C编写,但在Swift中使用它不是问题。

  

在iOS和Mac OS上解析和生成XML的简单方法。将XML文件转换为NSDictionary,然后可以使用标准Cocoa keyPath机制轻松遍历该文件。也可以将任何字典的内容输出为XML。

答案 1 :(得分:1)

我需要这样的东西来测试生成的XML,但我必须自己动手。我创建了一个嵌套的元素树,每个元素都包含有关xml标记的信息。

fileprivate class XmlToDictionaryParserDelegate: NSObject, XMLParserDelegate {

    private var currentElement: XmlElement?

    fileprivate init(_ element: XmlElement) {
        self.currentElement = element
    }

    public func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        self.currentElement = self.currentElement?.pop(elementName)
    }

    public func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        self.currentElement = self.currentElement?.push(elementName)
        self.currentElement?.attributeDict = attributeDict
    }

    func parser(_ parser: XMLParser, foundCharacters string: String) {
        self.currentElement?.text += string
    }
}

public class XmlElement {
    public private(set) var name = "unnamed"
    public private(set) var children = [String: XmlElement]()
    public private(set) var parent: XmlElement? = nil
    public fileprivate(set) var text = ""
    public fileprivate(set) var attributeDict: [String : String] = [:]

    private init(_ parent: XmlElement? = nil, name: String = "") {
        self.parent = parent
        self.name = name
    }

    public convenience init?(fromString: String) {
        guard let data = fromString.data(using: .utf8) else {
            return nil
        }
        self.init(fromData: data)
    }

    public init(fromData: Data) {
        let parser = XMLParser(data: fromData)
        let delegate = XmlToDictionaryParserDelegate(self)
        parser.delegate = delegate
        parser.parse()
    }

    fileprivate func push(_ elementName: String) -> XmlElement {
        let childElement = XmlElement(self, name: elementName)
        children[elementName] = childElement
        return childElement
    }

    fileprivate func pop(_ elementName: String) -> XmlElement? {
        assert(elementName == self.name)
        return self.parent
    }

    public subscript(name: String) -> XmlElement? {
        return self.children[name]
    }
}

使用从字符串(或数据)创建元素

let xml = XmlElement(fromString: "<first>text<second bar="foo"/></first>")

然后像这样使用:

XCTAssert(xml["first"]?.text == "text")

XCTAssert(xml["first"]?["second"].attributeDict["bar"] == "foo")

答案 2 :(得分:-1)

这太长了,所以我不建议你按原样使用它。 只是为了告诉你使用NSXMLParser有时会像这样一团糟。

import Foundation

let url = NSBundle.mainBundle().URLForResource("complex", withExtension: "xml")!

protocol ElementParserType: class {
    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        attributes attributeDict: [String : String],
        in parserController: MyXMLParserController)
    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        in parserController: MyXMLParserController)
    func foundCharacters(string: String, in parserController: MyXMLParserController)

}
class HeaderCol {
    var label: String = ""
}
class Header {
    var cols: [HeaderCol] = []
}
class Row {
    var cols: [String] = []
}
class Body {
    var rows: [Row] = []
}
class Result {
    var header: Header?
    var body: Body?
}
class LabelParser: ElementParserType {
    var label: String?
    let parent: HeaderColParser

    init(parent: HeaderColParser) {
        self.parent = parent
    }

    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      attributes attributeDict: [String : String],
                                 in parserController: MyXMLParserController)
    {
        parserController.didFail("Invalid start element `\(elementName)` in label")
    }

    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        in parserController: MyXMLParserController)
    {
        guard elementName == "label" else {
            parserController.didFail("Invalid end element `\(elementName)` in label")
            return
        }
        parent.col.label = self.label ?? ""
        parserController.popParser()
    }
    func foundCharacters(string: String,
            in parserController: MyXMLParserController)
    {
        self.label = string
    }
}
class HeaderColParser: ElementParserType {
    let col = HeaderCol()
    let parent: HeaderParser

    init(parent: HeaderParser) {
        self.parent = parent
    }

    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      attributes attributeDict: [String : String],
                                 in parserController: MyXMLParserController)
    {
        switch elementName {
        case "label":
            let labelParser = LabelParser(parent: self)
            parserController.pushParser(labelParser)
        default:
            parserController.didFail("Invalid start element `\(elementName)` in col")
        }
    }
    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      in parserController: MyXMLParserController)
    {
        guard elementName == "col" else {
            parserController.didFail("Invalid end element `\(elementName)` in col")
            return
        }
        parent.header.cols.append(self.col)
        parserController.popParser()
    }
    func foundCharacters(string: String,
                         in parserController: MyXMLParserController)
    {
        if string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).isEmpty {
            parserController.didFail("Invalid characters '\(string)' in col")
        }
    }
}
class HeaderParser: ElementParserType {
    let header = Header()
    let parent: ResultParser

    init(parent: ResultParser) {
        self.parent = parent
    }

    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      attributes attributeDict: [String : String],
                                 in parserController: MyXMLParserController)
    {
        switch elementName {
        case "col":
            let headerColParser = HeaderColParser(parent: self)
            parserController.pushParser(headerColParser)
        default:
            parserController.didFail("Invalid start element `\(elementName)` in header")
        }
    }
    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      in parserController: MyXMLParserController)
    {
        guard elementName == "header" else {
            parserController.didFail("Invalid end element `\(elementName)` in header")
            return
        }
        parent.result.header = self.header
        parserController.popParser()
    }
    func foundCharacters(string: String,
                         in parserController: MyXMLParserController)
    {
        if string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).isEmpty {
            parserController.didFail("Invalid characters '\(string)' in header")
        }
    }
}
class RowColParser: ElementParserType {
    var col: String?
    let parent: RowParser

    init(parent: RowParser) {
        self.parent = parent
    }

    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      attributes attributeDict: [String : String],
                                 in parserController: MyXMLParserController)
    {
        parserController.didFail("Invalid start element `\(elementName)` in col")
    }

    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      in parserController: MyXMLParserController)
    {
        guard elementName == "col" else {
            parserController.didFail("Invalid end element `\(elementName)` in col")
            return
        }
        parent.row.cols.append(self.col ?? "")
        parserController.popParser()
    }
    func foundCharacters(string: String,
                         in parserController: MyXMLParserController)
    {
        self.col = string
    }
}
class RowParser: ElementParserType {
    let row = Row()
    let parent: BodyParser

    init(parent: BodyParser) {
        self.parent = parent
    }

    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      attributes attributeDict: [String : String],
                                 in parserController: MyXMLParserController)
    {
        switch elementName {
        case "col":
            let rowColParser = RowColParser(parent: self)
            parserController.pushParser(rowColParser)
        default:
            parserController.didFail("Invalid start element `\(elementName)` in row")
        }
    }
    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      in parserController: MyXMLParserController)
    {
        guard elementName == "row" else {
            parserController.didFail("Invalid end element `\(elementName)` in row")
            return
        }
        parent.body.rows.append(self.row)
        parserController.popParser()
    }
    func foundCharacters(string: String,
                         in parserController: MyXMLParserController)
    {
        if string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).isEmpty {
            parserController.didFail("Invalid characters '\(string)' in row")
        }
    }
}
class BodyParser: ElementParserType {
    let body = Body()
    let parent: ResultParser

    init(parent: ResultParser) {
        self.parent = parent
    }

    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      attributes attributeDict: [String : String],
                                 in parserController: MyXMLParserController)
    {
        switch elementName {
        case "row":
            let rowParser = RowParser(parent: self)
            parserController.pushParser(rowParser)
        default:
            parserController.didFail("Invalid start element `\(elementName)` in body")
        }
    }
    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      in parserController: MyXMLParserController)
    {
        guard elementName == "body" else {
            parserController.didFail("Invalid end element `\(elementName)` in body")
            return
        }
        parent.result.body = self.body
        parserController.popParser()
    }
    func foundCharacters(string: String,
                         in parserController: MyXMLParserController)
    {
        if string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).isEmpty {
            parserController.didFail("Invalid characters '\(string)' in footer")
        }
    }
}
class FooterParser: ElementParserType {
    let parent: ResultParser

    init(parent: ResultParser) {
        self.parent = parent
    }

    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      attributes attributeDict: [String : String],
                                 in parserController: MyXMLParserController)
    {
        switch elementName {
        default:
            parserController.didFail("Invalid start element `\(elementName)` in footer")
        }
    }
    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      in parserController: MyXMLParserController)
    {
        guard elementName == "footer" else {
            parserController.didFail("Invalid end element `\(elementName)` in footer")
            return
        }
        //Do nothing
        parserController.popParser()
    }
    func foundCharacters(string: String,
                         in parserController: MyXMLParserController)
    {
        if string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).isEmpty {
            parserController.didFail("Invalid characters '\(string)' in footer")
        }
    }
}
class ResultParser: ElementParserType {
    let result = Result()

    func startElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        attributes attributeDict: [String : String],
                   in parserController: MyXMLParserController)
    {
        switch elementName {
        case "header":
            let headerParser = HeaderParser(parent: self)
            parserController.pushParser(headerParser)
        case "body":
            let headerParser = BodyParser(parent: self)
            parserController.pushParser(headerParser)
        case "footer":
            let headerParser = FooterParser(parent: self)
            parserController.pushParser(headerParser)
        default:
            parserController.didFail("Invalid start element `\(elementName)` in result")
        }
    }
    func endElement(
        elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
                      in parserController: MyXMLParserController)
    {
        guard elementName == "result" else {
            parserController.didFail("Invalid end element `\(elementName)` in result")
            return
        }
        parserController.result = self.result
        parserController.popParser()
    }
    func foundCharacters(string: String,
                         in parserController: MyXMLParserController)
    {
        if string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).isEmpty {
            parserController.didFail("Invalid characters '\(string)' in footer")
        }
    }
}
class MyXMLParserController: NSObject, NSXMLParserDelegate {
    let xmlParser: NSXMLParser
    var result: AnyObject?

    var parserStack: [ElementParserType] = []
    var currentParser: ElementParserType? {
        return parserStack.last
    }
    init?(url: NSURL) {
        if let xmlParser = NSXMLParser(contentsOfURL: url) {
            self.xmlParser = xmlParser
            super.init()
            self.xmlParser.delegate = self //The delegate is not retained.
        } else {
            return nil
        }
    }
    func pushParser(parser: ElementParserType) {
        parserStack.append(parser)
    }
    func popParser() {
        parserStack.removeLast()
    }
    func parse() {
        self.xmlParser.parse()
    }

    func parserDidStartDocument(parser: NSXMLParser) {
        print(#function)
    }

    func parserDidEndDocument(parser: NSXMLParser) {
        print(#function)
    }

    func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        print(#function, elementName)
        if let parser = currentParser {
            parser.startElement(elementName,
                                namespaceURI: namespaceURI,
                                qualifiedName: qName,
                                attributes: attributeDict,
                                in: self)
        } else {
            guard elementName == "result" else {
                print("Root element needs to be `result`")
                parser.abortParsing()
                return
            }
            pushParser(ResultParser())
        }
    }

    func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if let parser = currentParser {
            parser.endElement(elementName,
                                namespaceURI: namespaceURI,
                                qualifiedName: qName,
                                in: self)
        } else {
            didFail("Invalid end element \(elementName) at top level")
        }
    }

    func parser(parser: NSXMLParser, foundCharacters string: String) {
        if let parser = currentParser {
            parser.foundCharacters(string,
                              in: self)
        } else {
            didFail("Invalid characters '\(string)' at top level")
        }
    }

    func didFail(error: String) {
        xmlParser.abortParsing()
    }
}

let parserController = MyXMLParserController(url: url)!
parserController.parse()
let result = parserController.result as! Result
if let header = result.header, body = result.body {
    var rowArray: [[String: String]] = []
    for row in body.rows {
        assert(header.cols.count == row.cols.count)
        var rowDict: [String: String] = [:]
        for i in 0..<header.cols.count {
            rowDict[header.cols[i].label] = row.cols[i]
        }
        rowArray.append(rowDict)
    }
    print(rowArray as NSArray)
} else {
    print("header or body is missing")
}

答案 3 :(得分:-1)

计算出来,虽然可能有更多的复杂性,但稍后会进行优化:

UIColor(