Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Complex Conversion XML to Dictionary (Swift / iOS)

I'm racking my brain how to convert this parsed xml into arrays or dictionaries. the xml tags are not helpful because the labels are generic and there are ~10 headers. I might be able to do something based on the order of the labels. any ideas?

NSXMLParser Method Code:

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")
}
}

Sample results of XML parsing using NSXMLParser methods:

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

foundCharacters -->

didStartElement --> header

foundCharacters -->

didStartElement --> col

foundCharacters -->

didStartElement --> label

foundCharacters --> Tree Name

didEndElement --> label

foundCharacters -->

didEndElement --> col

foundCharacters -->

didStartElement --> col

foundCharacters -->

didStartElement --> label

foundCharacters --> Num Levels

didEndElement --> label

foundCharacters -->

didEndElement --> col

foundCharacters -->

didStartElement --> col

foundCharacters -->

didStartElement --> label

foundCharacters --> Defaults Weight

didEndElement --> label

foundCharacters -->

didEndElement --> col

foundCharacters -->

didStartElement --> col

foundCharacters -->

didStartElement --> label

foundCharacters --> Name

didEndElement --> label

foundCharacters -->

didEndElement --> col

foundCharacters -->

didStartElement --> col

foundCharacters -->

didStartElement --> label

foundCharacters --> Abbrev

didEndElement --> label

foundCharacters -->

didEndElement --> col

foundCharacters -->

didStartElement --> col

foundCharacters -->

didStartElement --> label

foundCharacters --> Level

didEndElement --> label

foundCharacters -->

didEndElement --> col

foundCharacters -->

didStartElement --> col

foundCharacters -->

didStartElement --> label

foundCharacters --> Full Name

didEndElement --> label

foundCharacters -->

didEndElement --> col

foundCharacters -->

didEndElement --> header

foundCharacters -->

didStartElement --> body

foundCharacters -->

didStartElement --> row

foundCharacters -->

didStartElement --> col

foundCharacters --> Cost Center 1

didEndElement --> col

foundCharacters -->

didStartElement --> col

foundCharacters --> 2

didEndElement --> col

foundCharacters -->

...

like image 816
Zach Rochon Avatar asked Oct 15 '25 18:10

Zach Rochon


1 Answers

I needed something like this for testing generated XML but had to roll my own. I created a nested tree of elements, each containing information about the xml tag.

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

To use create an element from a string (or data)

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

Then use like this:

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

XCTAssert(xml["first"]?["second"].attributeDict["bar"] == "foo")
like image 190
DrPhill Avatar answered Oct 17 '25 08:10

DrPhill



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!