Skip to content

Instantly share code, notes, and snippets.

@nh7a
Last active November 9, 2020 19:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nh7a/40cbbeea9e591f69689c84e5afb32476 to your computer and use it in GitHub Desktop.
Save nh7a/40cbbeea9e591f69689c84e5afb32476 to your computer and use it in GitHub Desktop.
A Simple XMLParser wrapper
import Foundation
public struct XML: CustomStringConvertible {
public let root: Element?
public init(string: String) throws {
root = try Parser().parse(data: Data(string.utf8))
}
public var description: String { root?.description ?? "" }
// MARK: Element
public class Element: CustomStringConvertible {
public let name: String
public var contents = [CustomStringConvertible]() // String or Element
public var attributes = [String: String]()
public var children = [Element]()
public weak var parent: Element?
fileprivate var isNested = false
public init(name: String = "") {
self.name = name
}
public var description: String {
let attr = attributes.map { "\($0)=\"\($1)\"" }.joined(separator: " ")
let text = contents.map { "\($0)" }.joined()
let rest = children.compactMap { $0.isNested ? nil : "\($0)" }.joined()
let space = attr.isEmpty ? "" : " "
if text.isEmpty, rest.isEmpty {
return "<\(name)\(space)\(attr)/>"
} else {
return "<\(name)\(space)\(attr)>\(text)\(rest)</\(name)>"
}
}
}
// MARK: Parser
private class Parser: NSObject, XMLParserDelegate {
private var elements = [Element]()
private var parseError: Error?
func parse(data: Data) throws -> Element? {
let root = Element()
elements = [root]
let parser = XMLParser(data: data)
parser.delegate = self
parser.parse()
if let parseError = parseError {
throw parseError
}
return root.children.last
}
// MARK: XMLParserDelegate
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String] = [:]) {
guard let last = elements.last else { fatalError() }
assert(!elements.isEmpty)
let ele = Element(name: elementName)
ele.attributes = attributeDict
ele.parent = last
last.children.append(ele)
if !last.contents.isEmpty {
last.contents.append(ele)
ele.isNested = true // This is a nested element in the middle of text.
}
elements.append(ele)
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
guard let ele = elements.last else { fatalError() }
if ele.contents.isEmpty, !ele.children.isEmpty {
ele.children.forEach {
$0.isNested = true // This is a nested element in the beginning of text.
ele.contents.append($0)
}
}
ele.contents.append(string)
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
elements.removeLast()
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
self.parseError = parseError
}
}
}
try XML(string: "")
try XML(string: #"<?xml version="1.0" encoding="UTF-8"?><br/>"#)
try XML(string: #"<foo><bar>baz</bar></foo>"#)
try XML(string: #"<foo><bar/>baz</foo>"#)
try XML(string: #"<foo>bar<baz/></foo>"#)
try XML(string: #"<data src="foo" dst="bar">abc<b>def</b>ghi<i>jkl</i>mno<p>pqr</p><br/></data>"#)
try XML(string: #"<img src="https://example.com/image.jpg" alt="Example Image"/>"#)
@dshusta-coursera
Copy link

fancy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment