feat: delayed processing of attributes
This commit is contained in:
@@ -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 = #"<?xml version="1.0" encoding="utf-8"?>"#
|
||||
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() + #"<svg version="1.1" xmlns="http://www.w3.org/2000/svg""#)
|
||||
if let val = dict.removeValue(forKey: "android:height") {
|
||||
|
||||
Reference in New Issue
Block a user