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 -->
...
Take a look at https://github.com/nicklockwood/XMLDictionary library.
Library has written in Obj-C
but it is not a problem to use it in Swift
.
A simple way to parse and generate XML on iOS and Mac OS. Converts an XML file to an NSDictionary which can then be easily traversed using the standard Cocoa keyPath mechanism. Can also output the contents of any dictionary as XML.
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")
This is too long, so I cannot recommend you to use it as is. Just to show you using NSXMLParser would sometimes be a mess like this.
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")
}
Figured it out, albeit likely has more complexity than necessary, will optimize later:
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 {
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.