Sources/OSS/Utils/XmlParser.swift (132 lines of code) (raw):
import Foundation
#if canImport(FoundationXML)
import FoundationXML
#endif
protocol Parser {
func parse(data: Data) -> [String: Any]
}
public class XmlParser: NSObject, Parser, XMLParserDelegate {
var response: [String: Any] = [:]
let elementStack = Stack<ElementNode>()
var rootNode: ElementNode?
public private(set) var lastError: Error?
public func parse(data: Data) -> [String: Any] {
let xml = XMLParser(data: data)
xml.delegate = self
let _ = xml.parse()
return response
}
public func parserDidStartDocument(_: XMLParser) {}
public func parserDidEndDocument(_: XMLParser) {
if let e = rootNode?.asDictionary() {
response = e
}
}
public func parser(_: XMLParser, didStartElement elementName: String, namespaceURI _: String?, qualifiedName _: String?, attributes _: [String: String] = [:]) {
let elementNode = ElementNode(key: elementName)
if let topNode = elementStack.top() {
topNode.nodes.append(elementNode)
} else {
rootNode = elementNode
}
elementStack.push(element: elementNode)
}
public func parser(_: XMLParser, didEndElement _: String, namespaceURI _: String?, qualifiedName _: String?) {
elementStack.pop()
}
public func parser(_: XMLParser, foundCharacters string: String) {
let value = string.trimmingCharacters(in: .whitespacesAndNewlines)
guard value != "" else {
return
}
if let elementNode = elementStack.top() {
if let v = elementNode.value {
elementNode.value = v.appending(value)
} else {
elementNode.value = value
}
}
}
public func parser(_: XMLParser, parseErrorOccurred parseError: Error) {
lastError = parseError
}
}
class Stack<Element> {
private var rootNode: Node?
public func push(element: Element) {
let node = Node(value: element)
if let rootNode = rootNode {
node.next = rootNode
}
rootNode = node
}
@discardableResult
public func pop() -> Element? {
let node = rootNode
rootNode = node?.next
return node?.value
}
public func top() -> Element? {
return rootNode?.value
}
public func isEmpty() -> Bool {
return rootNode == nil
}
class Node {
let value: Element
var next: Node?
init(value: Element) {
self.value = value
}
}
}
class ElementNode {
private let key: String
var value: String?
var nodes: [ElementNode]
init(key: String) {
self.key = key
nodes = []
}
public func asDictionary() -> [String: Any]? {
if let e = getEntry() {
return [e.0: e.1]
}
return nil
}
private func getEntry() -> (String, Any)? {
if let value = value {
return (key, value)
}
if nodes.count == 0 {
return nil
}
let entry = nodes.reduce(into: [String: Any]()) {
if let entry = $1.getEntry() {
if let value = $0[entry.0] {
var array = value as? [Any] ?? [value]
array.append(entry.1)
$0[entry.0] = array
} else {
$0[entry.0] = entry.1
}
}
}
return (key, entry)
}
}
public extension Dictionary {
static func withXMLData(data: Data) throws -> [String: Any] {
let response = XmlParser()
let result = response.parse(data: data)
if let error = response.lastError {
throw error
}
return result
}
static func withXMLDataError(data: Data) -> ([String: Any], Error?) {
let response = XmlParser()
let result = response.parse(data: data)
return (result, response.lastError)
}
}