diff --git a/Sources/AndroidXML/Xml_Parser.swift b/Sources/AndroidXML/Xml_Parser.swift index 6b3668f..1d8a4da 100644 --- a/Sources/AndroidXML/Xml_Parser.swift +++ b/Sources/AndroidXML/Xml_Parser.swift @@ -30,9 +30,71 @@ public struct Xml_Parser { return rv } + // MARK: Iterate Elements + + /// Helper struct for delayed attribute processing + public struct AttributesDict { + private let element: XmlStartElement + private let lookupFn: (StringPoolRef) -> String + private let namespaces: [StringPoolRef: String] + private let nsNeedsAppend: StringPoolRef? + private let stringPool: StringPool + + fileprivate init(_ elem: XmlStartElement, _ pool: StringPool, lookupFn: @escaping (StringPoolRef) -> String, namespaces: [StringPoolRef : String], nsNeedsAppend: StringPoolRef?) { + self.element = elem + self.stringPool = pool + self.lookupFn = lookupFn + self.namespaces = namespaces + self.nsNeedsAppend = nsNeedsAppend + } + + /// Create new list of attributes (keeping original order) + public func asList() throws -> [(String, ResValue)] { + var rv: [(String, ResValue)] = [] + // if tag is preceded by a namespace, apply ns-attribute + if let uri = nsNeedsAppend, let prefix = namespaces[uri] { + rv.append(("xmlns:\(prefix)", ResValue(type: .String, data: uri))) + } + // generate attributes + for attr in try element.attributes() { + var key = lookupFn(attr.name) + if let prefix = namespaces[attr.ns] { + key = prefix + ":" + key + } + rv.append((key, attr.typedValue)) + } + return rv + } + + /// Create new dictionary of attributes (loosing original order) + public func asDict() throws -> [String: ResValue] { + var rv: [String: ResValue] = [:] + // if tag is preceded by a namespace, apply ns-attribute + if let uri = nsNeedsAppend, let prefix = namespaces[uri] { + rv["xmlns:\(prefix)"] = ResValue(type: .String, data: uri) + } + // generate attributes + for attr in try element.attributes() { + var key = lookupFn(attr.name) + if let prefix = namespaces[attr.ns] { + key = prefix + ":" + key + } + rv[key] = attr.typedValue + } + return rv + } + + /// Same as `asDict()` but calls `resolve()` on all values. + /// @Note String-values will contain unescaped quotes (`""`). + public func asDictStr() throws -> [String: String] { + // No `lookupFn` because attribute values are likely unique + try asDict().mapValues { $0.resolve(stringPool)} + } + } + /// Iterate over whole tree and call `start` and `end` blocks for each element. /// Each `start` block contains the `tag` name and its attribute `(name, value)` pairs. - public func iterElements(_ start: (_ startTag: String, _ attributes: [(String, ResValue)]) -> Void, _ end: (_ endTag: String) -> Void) throws { + public func iterElements(_ start: (_ startTag: String, _ attributes: AttributesDict) throws -> Void, _ end: (_ endTag: String) throws -> Void) throws { // cache StringPool lookups for faster access var _lookupTable: [StringPoolRef: String] = [:] func lookup(_ index: StringPoolRef) -> String { @@ -67,34 +129,25 @@ public struct Xml_Parser { case .XmlStartElement: let elem = XmlStartElement(chunk) - var attrs: [(String, ResValue)] = [] - // if tag is preceded by a namespace, apply ns-attribute - if let uri = nsOnNextTag, let prefix = namespaces[uri] { - attrs.append(("xmlns:\(prefix)", ResValue(type: .String, data: uri))) - } + let attrs = AttributesDict(elem, stringPool, lookupFn: lookup(_:), namespaces: namespaces, nsNeedsAppend: nsOnNextTag) nsOnNextTag = nil - // generate attributes - for attr in try elem.attributes() { - var key = lookup(attr.name) - if let prefix = namespaces[attr.ns] { - key = prefix + ":" + key - } - attrs.append((key, attr.typedValue)) - } - start(lookup(elem.name), attrs) + try start(lookup(elem.name), attrs) case .XmlEndElement: - end(lookup(XmlEndElement(chunk).name)) + try end(lookup(XmlEndElement(chunk).name)) default: throw AXMLError("Dont know how to handle \(chunk.type) (offset: \(chunk.index(.startOfChunk)))") } } } - - // MARK: XML string - - /// Return data as XML string. +} + + +// MARK: - XML string + +extension Xml_Parser { + /// Convenience getter to return data as XML string. public func xmlString(prettyPrint: Bool = true, collapseEmptyElements: Bool = true, indent: String = " ") throws -> String { var rv = #""# func newLine() -> String { @@ -109,7 +162,7 @@ public struct Xml_Parser { rv.append(">") } rv.append(newLine() + "<" + startTag) - for (key, attr) in attrs { + for (key, attr) in try attrs.asList() { // yes, strings can contain quotes. Thus we must escape them. // Hint: no `replace` because that is only available with Foundation let val = attr.dataType == .String @@ -138,7 +191,7 @@ public struct Xml_Parser { } -// MARK: SVG converter +// MARK: - SVG converter extension Xml_Parser { /// Same as `xmlString()` but converts tag names and attribute names to SVG standard. @@ -162,7 +215,7 @@ extension Xml_Parser { if lastOpen { rv.append(">") } - var dict = attrs.reduce(into: [:]) { $0[$1.0] = $1.1 } + var dict = try attrs.asDict() if startTag == "vector" { rv.append(newLine() + #"