feat: delayed processing of attributes
This commit is contained in:
@@ -30,9 +30,71 @@ public struct Xml_Parser {
|
|||||||
return rv
|
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.
|
/// 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.
|
/// 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
|
// cache StringPool lookups for faster access
|
||||||
var _lookupTable: [StringPoolRef: String] = [:]
|
var _lookupTable: [StringPoolRef: String] = [:]
|
||||||
func lookup(_ index: StringPoolRef) -> String {
|
func lookup(_ index: StringPoolRef) -> String {
|
||||||
@@ -67,34 +129,25 @@ public struct Xml_Parser {
|
|||||||
|
|
||||||
case .XmlStartElement:
|
case .XmlStartElement:
|
||||||
let elem = XmlStartElement(chunk)
|
let elem = XmlStartElement(chunk)
|
||||||
var attrs: [(String, ResValue)] = []
|
let attrs = AttributesDict(elem, stringPool, lookupFn: lookup(_:), namespaces: namespaces, nsNeedsAppend: nsOnNextTag)
|
||||||
// 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)))
|
|
||||||
}
|
|
||||||
nsOnNextTag = nil
|
nsOnNextTag = nil
|
||||||
// generate attributes
|
try start(lookup(elem.name), attrs)
|
||||||
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)
|
|
||||||
|
|
||||||
case .XmlEndElement:
|
case .XmlEndElement:
|
||||||
end(lookup(XmlEndElement(chunk).name))
|
try end(lookup(XmlEndElement(chunk).name))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw AXMLError("Dont know how to handle \(chunk.type) (offset: \(chunk.index(.startOfChunk)))")
|
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 {
|
public func xmlString(prettyPrint: Bool = true, collapseEmptyElements: Bool = true, indent: String = " ") throws -> String {
|
||||||
var rv = #"<?xml version="1.0" encoding="utf-8"?>"#
|
var rv = #"<?xml version="1.0" encoding="utf-8"?>"#
|
||||||
func newLine() -> String {
|
func newLine() -> String {
|
||||||
@@ -109,7 +162,7 @@ public struct Xml_Parser {
|
|||||||
rv.append(">")
|
rv.append(">")
|
||||||
}
|
}
|
||||||
rv.append(newLine() + "<" + startTag)
|
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.
|
// yes, strings can contain quotes. Thus we must escape them.
|
||||||
// Hint: no `replace` because that is only available with Foundation
|
// Hint: no `replace` because that is only available with Foundation
|
||||||
let val = attr.dataType == .String
|
let val = attr.dataType == .String
|
||||||
@@ -138,7 +191,7 @@ public struct Xml_Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: SVG converter
|
// MARK: - SVG converter
|
||||||
|
|
||||||
extension Xml_Parser {
|
extension Xml_Parser {
|
||||||
/// Same as `xmlString()` but converts tag names and attribute names to SVG standard.
|
/// Same as `xmlString()` but converts tag names and attribute names to SVG standard.
|
||||||
@@ -162,7 +215,7 @@ extension Xml_Parser {
|
|||||||
if lastOpen {
|
if lastOpen {
|
||||||
rv.append(">")
|
rv.append(">")
|
||||||
}
|
}
|
||||||
var dict = attrs.reduce(into: [:]) { $0[$1.0] = $1.1 }
|
var dict = try attrs.asDict()
|
||||||
if startTag == "vector" {
|
if startTag == "vector" {
|
||||||
rv.append(newLine() + #"<svg version="1.1" xmlns="http://www.w3.org/2000/svg""#)
|
rv.append(newLine() + #"<svg version="1.1" xmlns="http://www.w3.org/2000/svg""#)
|
||||||
if let val = dict.removeValue(forKey: "android:height") {
|
if let val = dict.removeValue(forKey: "android:height") {
|
||||||
|
|||||||
Reference in New Issue
Block a user