【问题标题】:Complex Conversion XML to Dictionary (Swift / iOS)复杂的 XML 到字典转换 (Swift / iOS)
【发布时间】:2016-11-20 10:25:54
【问题描述】:

我正在绞尽脑汁如何将这个解析的 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 --> 结果

找到的字符 -->

didStartElement --> 标题

找到的字符 -->

didStartElement --> col

找到的字符 -->

didStartElement --> 标签

foundCharacters --> 树名

didEndElement --> 标签

找到的字符 -->

didEndElement --> col

找到的字符 -->

didStartElement --> col

找到的字符 -->

didStartElement --> 标签

foundCharacters --> Num Levels

didEndElement --> 标签

找到的字符 -->

didEndElement --> col

找到的字符 -->

didStartElement --> col

找到的字符 -->

didStartElement --> 标签

foundCharacters --> 默认权重

didEndElement --> 标签

找到的字符 -->

didEndElement --> col

找到的字符 -->

didStartElement --> col

找到的字符 -->

didStartElement --> 标签

找到的字符 --> 名称

didEndElement --> 标签

找到的字符 -->

didEndElement --> col

找到的字符 -->

didStartElement --> col

找到的字符 -->

didStartElement --> 标签

foundCharacters --> 缩写

didEndElement --> 标签

找到的字符 -->

didEndElement --> col

找到的字符 -->

didStartElement --> col

找到的字符 -->

didStartElement --> 标签

foundCharacters --> 等级

didEndElement --> 标签

找到的字符 -->

didEndElement --> col

找到的字符 -->

didStartElement --> col

找到的字符 -->

didStartElement --> 标签

foundCharacters --> 全名

didEndElement --> 标签

找到的字符 -->

didEndElement --> col

找到的字符 -->

didEndElement --> 标题

找到的字符 -->

didStartElement --> 正文

找到的字符 -->

didStartElement --> 行

找到的字符 -->

didStartElement --> col

foundCharacters --> 成本中心 1

didEndElement --> col

找到的字符 -->

didStartElement --> col

找到的字符 --> 2

didEndElement --> col

找到的字符 -->

...

【问题讨论】:

  • 您的 XML 文件和预期输出是什么?
  • 您能给我们一份您的 XML 文档样本吗?
  • 在原帖中添加了 xml

标签: ios swift multidimensional-array xml-parsing


【解决方案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】:

看看https://github.com/nicklockwood/XMLDictionary库。
库已经写在Obj-C,但是在Swift使用是没有问题的。

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

【讨论】:

  • 谢谢,试图避免向该项目添加另一个库
【解决方案3】:

想通了,虽然可能比必要的复杂,稍后会优化:

class MyXMLParserDelegate: NSObject, NSXMLParserDelegate {

var counter = 1
var insideBody = false
var insideElement = false
var treeName = [String]()
var numLevels = [String]()
var defaultsWeight = [String]()
var name = [String]()
var abbrev = [String]()
var level = [String]()
var fullName = [String]()


@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)")
    if elementName == "body" {
        insideBody = true
    }
    if insideBody == true && elementName == "col" {
        insideElement = true
    }
    if elementName == "row" {
        counter = 1
    }
    print("inside element -->\(insideElement)")
}

@objc func parser(parser: NSXMLParser, foundCharacters string: String) {
    print("foundCharacters       --> \(string)")
    if insideElement == true && insideBody == true {
        switch counter {
        case 1: treeName.append(string)
        case 2: numLevels.append(string)
        case 3: defaultsWeight.append(string)
        case 4: name.append(string)
        case 5: abbrev.append(string)
        case 6: level.append(string)
        case 7: fullName.append(string)
        default: print("nothing to append")
        }
        insideElement = false
        print("inside element -->\(insideElement)")
    }
}

@objc func parser(parser: NSXMLParser, didEndElement elementName: String,
                  namespaceURI: String?, qualifiedName qName: String?) {
    print("didEndElement         --> \(elementName)")
    if elementName == "body" {
        insideBody = false
    }
    if insideBody == true && elementName == "col" {
        if insideElement == true {
            switch counter {
            case 1: treeName.append("--*n/a*--")
            case 2: numLevels.append("--*n/a*--")
            case 3: defaultsWeight.append("--*n/a*--")
            case 4: name.append("--*n/a*--")
            case 5: abbrev.append("--*n/a*--")
            case 6: level.append("--*n/a*--")
            case 7: fullName.append("--*n/a*--")
            default: print("nothing to append")
            }
            insideElement = false
        }
        counter += 1
    }

print("inside element \(insideElement)")
print("counter \(counter)")
}

@objc func parserDidEndDocument(parser: NSXMLParser) {
    //reload table with array
    print("parserDidEndDocument")
    print(treeName)
    print(numLevels)
    print(defaultsWeight)
    print(name)
    print(abbrev)
    print(level)
    print(fullName)
    struct ccArrays {
    }
}

【讨论】:

    【解决方案4】:

    这太长了,所以我不建议您按原样使用它。 只是为了向您展示使用 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")
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-07
      相关资源
      最近更新 更多