From cc7513b6637dc3cd92bdcf098f06cf8642864d22 Mon Sep 17 00:00:00 2001 From: Daniil Manin Date: Mon, 4 Oct 2021 17:50:17 +0700 Subject: [PATCH] #769 Update SWXMLHash --- .../{SWXMLHash.swift => XMLHash.swift} | 396 +++++++++++++----- ...XMLIndexer+XMLIndexerDeserializable.swift} | 259 ++++++++++-- Dependencies/SWXMLHash/shim.swift | 36 ++ Macaw.xcodeproj/project.pbxproj | 30 +- Source/svg/CSSParser.swift | 2 +- Source/svg/SVGParser.swift | 30 +- 6 files changed, 597 insertions(+), 156 deletions(-) rename Dependencies/SWXMLHash/{SWXMLHash.swift => XMLHash.swift} (69%) mode change 100755 => 100644 rename Dependencies/SWXMLHash/{SWXMLHash+TypeConversion.swift => XMLIndexer+XMLIndexerDeserializable.swift} (71%) mode change 100755 => 100644 create mode 100644 Dependencies/SWXMLHash/shim.swift diff --git a/Dependencies/SWXMLHash/SWXMLHash.swift b/Dependencies/SWXMLHash/XMLHash.swift old mode 100755 new mode 100644 similarity index 69% rename from Dependencies/SWXMLHash/SWXMLHash.swift rename to Dependencies/SWXMLHash/XMLHash.swift index 1ad42d73..5ea2acf6 --- a/Dependencies/SWXMLHash/SWXMLHash.swift +++ b/Dependencies/SWXMLHash/XMLHash.swift @@ -1,5 +1,5 @@ // -// SWXMLHash.swift +// XMLHash.swift // SWXMLHash // // Copyright (c) 2014 David Mohundro @@ -30,11 +30,14 @@ // swiftlint:disable file_length import Foundation +#if canImport(FoundationXML) +import FoundationXML +#endif let rootElementName = "SWXMLHash_Root_Element" /// Parser options -public class SWXMLHashOptions { +public class XMLHashOptions { internal init() {} /// determines whether to parse the XML with lazy parsing or not @@ -50,13 +53,20 @@ public class SWXMLHashOptions { /// Encoding used for XML parsing. Default is set to UTF8 public var encoding = String.Encoding.utf8 + + /// Any contextual information set by the user for encoding + public var userInfo = [CodingUserInfoKey: Any]() + + /// Detect XML parsing errors... defaults to false as this library will + /// attempt to handle HTML which isn't always XML-compatible + public var detectParsingErrors = false } /// Simple XML parser -public class SWXMLHash { - let options: SWXMLHashOptions +public class XMLHash { + let options: XMLHashOptions - private init(_ options: SWXMLHashOptions = SWXMLHashOptions()) { + private init(_ options: XMLHashOptions = XMLHashOptions()) { self.options = options } @@ -64,14 +74,14 @@ public class SWXMLHash { Method to configure how parsing works. - parameters: - - configAction: a block that passes in an `SWXMLHashOptions` object with + - configAction: a block that passes in an `XMLHashOptions` object with options to be set - - returns: an `SWXMLHash` instance + - returns: an `XMLHash` instance */ - class public func config(_ configAction: (SWXMLHashOptions) -> Void) -> SWXMLHash { - let opts = SWXMLHashOptions() + class public func config(_ configAction: (XMLHashOptions) -> Void) -> XMLHash { + let opts = XMLHashOptions() configAction(opts) - return SWXMLHash(opts) + return XMLHash(opts) } /** @@ -110,7 +120,7 @@ public class SWXMLHash { - returns: An XMLIndexer instance that is used to look up elements in the XML */ class public func parse(_ xml: String) -> XMLIndexer { - return SWXMLHash().parse(xml) + XMLHash().parse(xml) } /** @@ -120,7 +130,7 @@ public class SWXMLHash { - returns: An XMLIndexer instance that is used to look up elements in the XML */ class public func parse(_ data: Data) -> XMLIndexer { - return SWXMLHash().parse(data) + XMLHash().parse(data) } /** @@ -130,7 +140,7 @@ public class SWXMLHash { - returns: An XMLIndexer instance that is used to look up elements in the XML */ class public func lazy(_ xml: String) -> XMLIndexer { - return config { conf in conf.shouldProcessLazily = true }.parse(xml) + config { conf in conf.shouldProcessLazily = true }.parse(xml) } /** @@ -140,128 +150,137 @@ public class SWXMLHash { - returns: An XMLIndexer instance that is used to look up elements in the XML */ class public func lazy(_ data: Data) -> XMLIndexer { - return config { conf in conf.shouldProcessLazily = true }.parse(data) + config { conf in conf.shouldProcessLazily = true }.parse(data) } } struct Stack { var items = [T]() + mutating func push(_ item: T) { items.append(item) } + mutating func pop() -> T { - return items.removeLast() + items.removeLast() } + mutating func drop() { _ = pop() } + mutating func removeAll() { items.removeAll(keepingCapacity: false) } + func top() -> T { - return items[items.count - 1] + items[items.count - 1] } } protocol SimpleXmlParser { - init(_ options: SWXMLHashOptions) + init(_ options: XMLHashOptions) func parse(_ data: Data) -> XMLIndexer } #if os(Linux) extension XMLParserDelegate { + func parserDidStartDocument(_ parser: XMLParser) { } + func parserDidEndDocument(_ parser: XMLParser) { } - func parserDidStartDocument(_ parser: Foundation.XMLParser) { } - func parserDidEndDocument(_ parser: Foundation.XMLParser) { } - - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundNotationDeclarationWithName name: String, publicID: String?, systemID: String?) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundUnparsedEntityDeclarationWithName name: String, publicID: String?, systemID: String?, notationName: String?) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundAttributeDeclarationWithName attributeName: String, forElement elementName: String, type: String?, defaultValue: String?) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundElementDeclarationWithName elementName: String, model: String) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundInternalEntityDeclarationWithName name: String, value: String?) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundExternalEntityDeclarationWithName name: String, publicID: String?, systemID: String?) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, - attributes attributeDict: [String : String]) { } + attributes attributeDict: [String: String]) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, didStartMappingPrefix prefix: String, toURI namespaceURI: String) { } - func parser(_ parser: Foundation.XMLParser, didEndMappingPrefix prefix: String) { } + func parser(_ parser: XMLParser, + didEndMappingPrefix prefix: String) { } - func parser(_ parser: Foundation.XMLParser, foundCharacters string: String) { } + func parser(_ parser: XMLParser, + foundCharacters string: String) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundIgnorableWhitespace whitespaceString: String) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundProcessingInstructionWithTarget target: String, data: String?) { } - func parser(_ parser: Foundation.XMLParser, foundComment comment: String) { } + func parser(_ parser: XMLParser, + foundComment comment: String) { } - func parser(_ parser: Foundation.XMLParser, foundCDATA CDATABlock: Data) { } + func parser(_ parser: XMLParser, + foundCDATA CDATABlock: Data) { } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, resolveExternalEntityName name: String, - systemID: String?) -> Data? { return nil } + systemID: String?) -> Data? { nil } - func parser(_ parser: Foundation.XMLParser, parseErrorOccurred parseError: NSError) { } + func parser(_ parser: XMLParser, + parseErrorOccurred parseError: Error) { } - func parser(_ parser: Foundation.XMLParser, - validationErrorOccurred validationError: NSError) { } + func parser(_ parser: XMLParser, + validationErrorOccurred validationError: Error) { } } #endif /// The implementation of XMLParserDelegate and where the lazy parsing actually happens. class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { - required init(_ options: SWXMLHashOptions) { + required init(_ options: XMLHashOptions) { + root = XMLElement(name: rootElementName, options: options) self.options = options - self.root.caseInsensitive = options.caseInsensitive super.init() } - var root = XMLElement(name: rootElementName, caseInsensitive: false) + var root: XMLElement var parentStack = Stack() var elementStack = Stack() var data: Data? var ops: [IndexOp] = [] - let options: SWXMLHashOptions + let options: XMLHashOptions func parse(_ data: Data) -> XMLIndexer { self.data = data @@ -269,25 +288,23 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { } func startParsing(_ ops: [IndexOp]) { - // clear any prior runs of parse... expected that this won't be necessary, - // but you never know + // reset state for a new lazy parsing run + root = XMLElement(name: rootElementName, options: root.options) parentStack.removeAll() - root = XMLElement(name: rootElementName, caseInsensitive: options.caseInsensitive) parentStack.push(root) self.ops = ops - let parser = Foundation.XMLParser(data: data!) + let parser = XMLParser(data: data!) parser.shouldProcessNamespaces = options.shouldProcessNamespaces parser.delegate = self _ = parser.parse() } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String]) { - elementStack.push(elementName) if !onMatch() { @@ -300,7 +317,7 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { parentStack.push(currentNode) } - func parser(_ parser: Foundation.XMLParser, foundCharacters string: String) { + func parser(_ parser: XMLParser, foundCharacters string: String) { if !onMatch() { return } @@ -310,11 +327,22 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { current.addText(string) } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data) { + if !onMatch() { + return + } + + if let cdataText = String(data: CDATABlock, encoding: String.Encoding.utf8) { + let current = parentStack.top() + + current.addText(cdataText) + } + } + + func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { - let match = onMatch() elementStack.drop() @@ -337,15 +365,16 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { /// The implementation of XMLParserDelegate and where the parsing actually happens. class FullXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { - required init(_ options: SWXMLHashOptions) { + required init(_ options: XMLHashOptions) { + root = XMLElement(name: rootElementName, options: options) self.options = options - self.root.caseInsensitive = options.caseInsensitive super.init() } - var root = XMLElement(name: rootElementName, caseInsensitive: false) + let root: XMLElement var parentStack = Stack() - let options: SWXMLHashOptions + let options: XMLHashOptions + var parsingError: ParsingError? func parse(_ data: Data) -> XMLIndexer { // clear any prior runs of parse... expected that this won't be necessary, @@ -354,20 +383,23 @@ class FullXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { parentStack.push(root) - let parser = Foundation.XMLParser(data: data) + let parser = XMLParser(data: data) parser.shouldProcessNamespaces = options.shouldProcessNamespaces parser.delegate = self _ = parser.parse() - return XMLIndexer(root) + if options.detectParsingErrors, let err = parsingError { + return XMLIndexer.parsingError(err) + } else { + return XMLIndexer(root) + } } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String]) { - let currentNode = parentStack .top() .addElement(elementName, withAttributes: attributeDict, caseInsensitive: self.options.caseInsensitive) @@ -375,19 +407,39 @@ class FullXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { parentStack.push(currentNode) } - func parser(_ parser: Foundation.XMLParser, foundCharacters string: String) { + func parser(_ parser: XMLParser, foundCharacters string: String) { let current = parentStack.top() current.addText(string) } - func parser(_ parser: Foundation.XMLParser, + func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { - parentStack.drop() } + + func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data) { + if let cdataText = String(data: CDATABlock, encoding: String.Encoding.utf8) { + let current = parentStack.top() + + current.addText(cdataText) + } + } + + func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) { +#if os(Linux) && !swift(>=4.1.50) + if let err = parseError as? NSError { + parsingError = ParsingError(line: err.userInfo["NSXMLParserErrorLineNumber"] as? Int ?? 0, + column: err.userInfo["NSXMLParserErrorColumn"] as? Int ?? 0) + } +#else + let err = parseError as NSError + parsingError = ParsingError(line: err.userInfo["NSXMLParserErrorLineNumber"] as? Int ?? 0, + column: err.userInfo["NSXMLParserErrorColumn"] as? Int ?? 0) +#endif + } } /// Represents an indexed operation against a lazily parsed `XMLIndexer` @@ -424,10 +476,10 @@ public class IndexOps { parser.startParsing(ops) let indexer = XMLIndexer(parser.root) var childIndex = indexer - for op in ops { - childIndex = childIndex[op.key] - if op.index >= 0 { - childIndex = childIndex[op.index] + for oper in ops { + childIndex = childIndex[oper.key] + if oper.index >= 0 { + childIndex = childIndex[oper.index] } } ops.removeAll(keepingCapacity: false) @@ -435,14 +487,19 @@ public class IndexOps { } func stringify() -> String { - var s = "" - for op in ops { - s += "[" + op.toString() + "]" + var ret = "" + for oper in ops { + ret += "[" + oper.toString() + "]" } - return s + return ret } } +public struct ParsingError: Error { + public let line: Int + public let column: Int +} + /// Error type that is thrown when an indexing or parsing operation fails. public enum IndexingError: Error { case attribute(attr: String) @@ -459,22 +516,27 @@ public enum IndexingError: Error { public static func Attribute(attr: String) -> IndexingError { fatalError("unavailable") } + @available(*, unavailable, renamed: "attributeValue(attr:value:)") public static func AttributeValue(attr: String, value: String) -> IndexingError { fatalError("unavailable") } + @available(*, unavailable, renamed: "key(key:)") public static func Key(key: String) -> IndexingError { fatalError("unavailable") } + @available(*, unavailable, renamed: "index(idx:)") public static func Index(idx: Int) -> IndexingError { fatalError("unavailable") } + @available(*, unavailable, renamed: "initialize(instance:)") public static func Init(instance: AnyObject) -> IndexingError { fatalError("unavailable") } + @available(*, unavailable, renamed: "error") public static var Error: IndexingError { fatalError("unavailable") @@ -482,12 +544,13 @@ public enum IndexingError: Error { // swiftlint:enable identifier_name } -/// Returned from SWXMLHash, allows easy element lookup into XML data. +/// Returned from XMLHash, allows easy element lookup into XML data. public enum XMLIndexer { case element(XMLElement) case list([XMLElement]) case stream(IndexOps) case xmlError(IndexingError) + case parsingError(ParsingError) // swiftlint:disable identifier_name // unavailable @@ -528,18 +591,18 @@ public enum XMLIndexer { /// All elements at the currently indexed level public var all: [XMLIndexer] { + allElements.map { XMLIndexer($0) } + } + + private var allElements: [XMLElement] { switch self { case .list(let list): - var xmlList = [XMLIndexer]() - for elem in list { - xmlList.append(XMLIndexer(elem)) - } - return xmlList + return list case .element(let elem): - return [XMLIndexer(elem)] + return [elem] case .stream(let ops): let list = ops.findElements() - return list.all + return list.allElements default: return [] } @@ -547,15 +610,56 @@ public enum XMLIndexer { /// All child elements from the currently indexed level public var children: [XMLIndexer] { - var list = [XMLIndexer]() + childElements.map { XMLIndexer($0) } + } + + private var childElements: [XMLElement] { + var list = [XMLElement]() for elem in all.compactMap({ $0.element }) { for elem in elem.xmlChildren { - list.append(XMLIndexer(elem)) + list.append(elem) } } return list } + @available(*, unavailable, renamed: "filterChildren(_:)") + public func filter(_ included: (_ elem: XMLElement, _ index: Int) -> Bool) -> XMLIndexer { + filterChildren(included) + } + + public func filterChildren(_ included: (_ elem: XMLElement, _ index: Int) -> Bool) -> XMLIndexer { + let children = handleFilteredResults(list: childElements, included: included) + if let current = self.element { + let filteredElem = XMLElement(name: current.name, index: current.index, options: current.options) + filteredElem.children = children.allElements + return .element(filteredElem) + } + return .xmlError(IndexingError.error) + } + + public func filterAll(_ included: (_ elem: XMLElement, _ index: Int) -> Bool) -> XMLIndexer { + handleFilteredResults(list: allElements, included: included) + } + + private func handleFilteredResults(list: [XMLElement], + included: (_ elem: XMLElement, _ index: Int) -> Bool) -> XMLIndexer { + let results = zip(list.indices, list).filter { included($1, $0) }.map { $1 } + if results.count == 1 { + return .element(results.first!) + } + return .list(results) + } + + public var userInfo: [CodingUserInfoKey: Any] { + switch self { + case .element(let elem): + return elem.userInfo + default: + return [:] + } + } + /** Allows for element lookup by matching attribute values. @@ -627,8 +731,8 @@ public enum XMLIndexer { public func byKey(_ key: String) throws -> XMLIndexer { switch self { case .stream(let opStream): - let op = IndexOp(key) - opStream.ops.append(op) + let oper = IndexOp(key) + opStream.ops.append(oper) return .stream(opStream) case .element(let elem): let match = elem.xmlChildren.filter({ @@ -641,7 +745,7 @@ public enum XMLIndexer { return .list(match) } } - fallthrough + throw IndexingError.key(key: key) default: throw IndexingError.key(key: key) } @@ -676,7 +780,7 @@ public enum XMLIndexer { opStream.ops[opStream.ops.count - 1].index = index return .stream(opStream) case .list(let list): - if index <= list.count { + if index < list.count { return .element(list[index]) } return .xmlError(IndexingError.index(idx: index)) @@ -684,7 +788,7 @@ public enum XMLIndexer { if index == 0 { return .element(elem) } - fallthrough + return .xmlError(IndexingError.index(idx: index)) default: return .xmlError(IndexingError.index(idx: index)) } @@ -733,7 +837,7 @@ extension IndexingError: CustomStringConvertible { switch self { case .attribute(let attr): return "XML Attribute Error: Missing attribute [\"\(attr)\"]" - case .attributeValue(let attr, let value): + case let .attributeValue(attr, value): return "XML Attribute Error: Missing attribute [\"\(attr)\"] with value [\"\(value)\"]" case .key(let key): return "XML Element Error: Incorrect key [\"\(key)\"]" @@ -756,6 +860,7 @@ public protocol XMLContent: CustomStringConvertible { } public class TextElement: XMLContent { /// The underlying text value public let text: String + init(text: String) { self.text = text } @@ -764,6 +869,7 @@ public class TextElement: XMLContent { public struct XMLAttribute { public let name: String public let text: String + init(name: String, text: String) { self.name = name self.text = text @@ -775,11 +881,19 @@ public class XMLElement: XMLContent { /// The name of the element public let name: String - public var caseInsensitive: Bool + /// Whether the element is case insensitive or not + public var caseInsensitive: Bool { + options.caseInsensitive + } + + var userInfo: [CodingUserInfoKey: Any] { + options.userInfo + } /// All attributes public var allAttributes = [String: XMLAttribute]() + /// Find an attribute by name public func attribute(by name: String) -> XMLAttribute? { if caseInsensitive { return allAttributes.first(where: { $0.key.compare(name, true) })?.value @@ -789,7 +903,7 @@ public class XMLElement: XMLContent { /// The inner text of the element, if it exists public var text: String { - return children.reduce("", { + children.reduce("", { if let element = $1 as? TextElement { return $0 + element.text } @@ -800,7 +914,7 @@ public class XMLElement: XMLContent { /// The inner text of the element and its children public var recursiveText: String { - return children.reduce("", { + children.reduce("", { if let textElement = $1 as? TextElement { return $0 + textElement.text } else if let xmlElement = $1 as? XMLElement { @@ -811,13 +925,21 @@ public class XMLElement: XMLContent { }) } + public var innerXML: String { + children.reduce("", { + $0.description + $1.description + }) + } + /// All child elements (text or XML) public var children = [XMLContent]() + var count: Int = 0 var index: Int + let options: XMLHashOptions var xmlChildren: [XMLElement] { - return children.compactMap { $0 as? XMLElement } + children.compactMap { $0 as? XMLElement } } /** @@ -826,11 +948,12 @@ public class XMLElement: XMLContent { - parameters: - name: The name of the element to be initialized - index: The index of the element to be initialized + - options: The XMLHash options */ - init(name: String, index: Int = 0, caseInsensitive: Bool) { + init(name: String, index: Int = 0, options: XMLHashOptions) { self.name = name - self.caseInsensitive = caseInsensitive self.index = index + self.options = options } /** @@ -843,7 +966,7 @@ public class XMLElement: XMLContent { */ func addElement(_ name: String, withAttributes attributes: [String: String], caseInsensitive: Bool) -> XMLElement { - let element = XMLElement(name: name, index: count, caseInsensitive: caseInsensitive) + let element = XMLElement(name: name, index: count, options: options) count += 1 children.append(element) @@ -865,14 +988,14 @@ public class XMLElement: XMLContent { extension TextElement: CustomStringConvertible { /// The text value for a `TextElement` instance. public var description: String { - return text + text } } extension XMLAttribute: CustomStringConvertible { /// The textual representation of an `XMLAttribute` instance. public var description: String { - return "\(name)=\"\(text)\"" + "\(name)=\"\(text)\"" } } @@ -888,7 +1011,7 @@ extension XMLElement: CustomStringConvertible { xmlReturn.append(child.description) } xmlReturn.append("") - return xmlReturn.joined(separator: "") + return xmlReturn.joined() } return "<\(name)\(attributesString)>\(text)" @@ -900,11 +1023,11 @@ extension XMLElement: CustomStringConvertible { // On macOS, `XMLElement` is defined in Foundation. // So, the code referencing `XMLElement` generates above error. // Following code allow to using `SWXMLhash.XMLElement` in client codes. -extension SWXMLHash { - public typealias XMLElement = SWXMLHashXMLElement +extension XMLHash { + public typealias XMLElement = XMLHashXMLElement } -public typealias SWXMLHashXMLElement = XMLElement +public typealias XMLHashXMLElement = XMLElement fileprivate extension String { func compare(_ str2: String?, _ insensitive: Bool) -> Bool { @@ -918,3 +1041,72 @@ fileprivate extension String { return str1 == str2 } } + +// MARK: - XMLIndexer String RawRepresentables + +/*: Provides XMLIndexer Serialization/Deserialization using String backed RawRepresentables + Added by [PeeJWeeJ](https://github.com/PeeJWeeJ) */ +extension XMLIndexer { + /** + Allows for element lookup by matching attribute values + using a String backed RawRepresentables (E.g. `String` backed `enum` cases) + + - Note: + Convenience for withAttribute(String, String) + + - parameters: + - attr: should the name of the attribute to match on + - value: should be the value of the attribute to match on + - throws: an XMLIndexer.XMLError if an element with the specified attribute isn't found + - returns: instance of XMLIndexer + */ + public func withAttribute(_ attr: A, _ value: V) throws -> XMLIndexer + where A.RawValue == String, V.RawValue == String { + try withAttribute(attr.rawValue, value.rawValue) + } + + /** + Find an XML element at the current level by element name + using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for byKey(String) + + - parameter key: The element name to index by + - returns: instance of XMLIndexer to match the element (or elements) found by key + - throws: Throws an XMLIndexingError.Key if no element was found + */ + public func byKey(_ key: K) throws -> XMLIndexer where K.RawValue == String { + try byKey(key.rawValue) + } + + /** + Find an XML element at the current level by element name + using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for self[String] + + - parameter key: The element name to index by + - returns: instance of XMLIndexer to match the element (or elements) found by + */ + public subscript(key: K) -> XMLIndexer where K.RawValue == String { + self[key.rawValue] + } +} + +// MARK: - XMLElement String RawRepresentables + +/*: Provides XMLIndexer Serialization/Deserialization using String backed RawRepresentables + Added by [PeeJWeeJ](https://github.com/PeeJWeeJ) */ +extension XMLElement { + /** + Find an attribute by name using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for self[String] + */ + public func attribute(by name: N) -> XMLAttribute? where N.RawValue == String { + attribute(by: name.rawValue) + } +} diff --git a/Dependencies/SWXMLHash/SWXMLHash+TypeConversion.swift b/Dependencies/SWXMLHash/XMLIndexer+XMLIndexerDeserializable.swift old mode 100755 new mode 100644 similarity index 71% rename from Dependencies/SWXMLHash/SWXMLHash+TypeConversion.swift rename to Dependencies/SWXMLHash/XMLIndexer+XMLIndexerDeserializable.swift index 28f12103..2ddb8f31 --- a/Dependencies/SWXMLHash/SWXMLHash+TypeConversion.swift +++ b/Dependencies/SWXMLHash/XMLIndexer+XMLIndexerDeserializable.swift @@ -1,5 +1,5 @@ // -// SWXMLHash+TypeConversion.swift +// XMLHash+TypeConversion.swift // SWXMLHash // // Copyright (c) 2016 Maciek Grzybowskio @@ -34,6 +34,8 @@ import Foundation public protocol XMLIndexerDeserializable { /// Method for deserializing elements from XMLIndexer static func deserialize(_ element: XMLIndexer) throws -> Self + /// Method for validating elements post deserialization + func validate() throws } /// Provides XMLIndexer deserialization / type transformation support @@ -50,6 +52,12 @@ public extension XMLIndexerDeserializable { throw XMLDeserializationError.implementationIsMissing( method: "XMLIndexerDeserializable.deserialize(element: XMLIndexer)") } + + /** + A default do nothing implementation of validation. + - throws: nothing + */ + func validate() throws {} } // MARK: - XMLElementDeserializable @@ -58,6 +66,8 @@ public extension XMLIndexerDeserializable { public protocol XMLElementDeserializable { /// Method for deserializing elements from XMLElement static func deserialize(_ element: XMLElement) throws -> Self + /// Method for validating elements from XMLElement post deserialization + func validate() throws } /// Provides XMLElement deserialization / type transformation support @@ -74,13 +84,22 @@ public extension XMLElementDeserializable { throw XMLDeserializationError.implementationIsMissing( method: "XMLElementDeserializable.deserialize(element: XMLElement)") } + + /** + A default do nothing implementation of validation. + - throws: nothing + */ + func validate() throws {} } // MARK: - XMLAttributeDeserializable /// Provides XMLAttribute deserialization / type transformation support public protocol XMLAttributeDeserializable { + /// Method for deserializing elements from XMLAttribute static func deserialize(_ attribute: XMLAttribute) throws -> Self + /// Method for validating elements from XMLAttribute post deserialization + func validate() throws } /// Provides XMLAttribute deserialization / type transformation support @@ -97,12 +116,16 @@ public extension XMLAttributeDeserializable { throw XMLDeserializationError.implementationIsMissing( method: "XMLAttributeDeserializable(element: XMLAttribute)") } + /** + A default do nothing implementation of validation. + - throws: nothing + */ + func validate() throws {} } // MARK: - XMLIndexer Extensions public extension XMLIndexer { - // MARK: - XMLAttributeDeserializable /** @@ -216,7 +239,9 @@ public extension XMLIndexer { func value() throws -> T { switch self { case .element(let element): - return try T.deserialize(element) + let deserialized = try T.deserialize(element) + try deserialized.validate() + return deserialized case .stream(let opStream): return try opStream.findElements().value() default: @@ -233,7 +258,9 @@ public extension XMLIndexer { func value() throws -> T? { switch self { case .element(let element): - return try T.deserialize(element) + let deserialized = try T.deserialize(element) + try deserialized.validate() + return deserialized case .stream(let opStream): return try opStream.findElements().value() default: @@ -250,9 +277,17 @@ public extension XMLIndexer { func value() throws -> [T] { switch self { case .list(let elements): - return try elements.map { try T.deserialize($0) } + return try elements.map { + let deserialized = try T.deserialize($0) + try deserialized.validate() + return deserialized + } case .element(let element): - return try [element].map { try T.deserialize($0) } + return try [element].map { + let deserialized = try T.deserialize($0) + try deserialized.validate() + return deserialized + } case .stream(let opStream): return try opStream.findElements().value() default: @@ -269,9 +304,17 @@ public extension XMLIndexer { func value() throws -> [T]? { switch self { case .list(let elements): - return try elements.map { try T.deserialize($0) } + return try elements.map { + let deserialized = try T.deserialize($0) + try deserialized.validate() + return deserialized + } case .element(let element): - return try [element].map { try T.deserialize($0) } + return try [element].map { + let deserialized = try T.deserialize($0) + try deserialized.validate() + return deserialized + } case .stream(let opStream): return try opStream.findElements().value() default: @@ -288,9 +331,17 @@ public extension XMLIndexer { func value() throws -> [T?] { switch self { case .list(let elements): - return try elements.map { try T.deserialize($0) } + return try elements.map { + let deserialized = try T.deserialize($0) + try deserialized.validate() + return deserialized + } case .element(let element): - return try [element].map { try T.deserialize($0) } + return try [element].map { + let deserialized = try T.deserialize($0) + try deserialized.validate() + return deserialized + } case .stream(let opStream): return try opStream.findElements().value() default: @@ -309,7 +360,9 @@ public extension XMLIndexer { func value() throws -> T { switch self { case .element: - return try T.deserialize(self) + let deserialized = try T.deserialize(self) + try deserialized.validate() + return deserialized case .stream(let opStream): return try opStream.findElements().value() default: @@ -326,7 +379,9 @@ public extension XMLIndexer { func value() throws -> T? { switch self { case .element: - return try T.deserialize(self) + let deserialized = try T.deserialize(self) + try deserialized.validate() + return deserialized case .stream(let opStream): return try opStream.findElements().value() default: @@ -343,9 +398,17 @@ public extension XMLIndexer { func value() throws -> [T] where T: XMLIndexerDeserializable { switch self { case .list(let elements): - return try elements.map { try T.deserialize( XMLIndexer($0) ) } + return try elements.map { + let deserialized = try T.deserialize( XMLIndexer($0) ) + try deserialized.validate() + return deserialized + } case .element(let element): - return try [element].map { try T.deserialize( XMLIndexer($0) ) } + return try [element].map { + let deserialized = try T.deserialize( XMLIndexer($0) ) + try deserialized.validate() + return deserialized + } case .stream(let opStream): return try opStream.findElements().value() default: @@ -362,9 +425,17 @@ public extension XMLIndexer { func value() throws -> [T]? { switch self { case .list(let elements): - return try elements.map { try T.deserialize( XMLIndexer($0) ) } + return try elements.map { + let deserialized = try T.deserialize(XMLIndexer($0)) + try deserialized.validate() + return deserialized + } case .element(let element): - return try [element].map { try T.deserialize( XMLIndexer($0) ) } + return try [element].map { + let deserialized = try T.deserialize(XMLIndexer($0)) + try deserialized.validate() + return deserialized + } case .stream(let opStream): return try opStream.findElements().value() default: @@ -381,9 +452,17 @@ public extension XMLIndexer { func value() throws -> [T?] { switch self { case .list(let elements): - return try elements.map { try T.deserialize( XMLIndexer($0) ) } + return try elements.map { + let deserialized = try T.deserialize(XMLIndexer($0)) + try deserialized.validate() + return deserialized + } case .element(let element): - return try [element].map { try T.deserialize( XMLIndexer($0) ) } + return try [element].map { + let deserialized = try T.deserialize(XMLIndexer($0)) + try deserialized.validate() + return deserialized + } case .stream(let opStream): return try opStream.findElements().value() default: @@ -392,10 +471,89 @@ public extension XMLIndexer { } } +// MARK: - XMLAttributeDeserializable String RawRepresentable + +/*: Provides XMLIndexer XMLAttributeDeserializable deserialization from String backed RawRepresentables + Added by [PeeJWeeJ](https://github.com/PeeJWeeJ) */ +public extension XMLIndexer { + /** + Attempts to deserialize the value of the specified attribute of the current XMLIndexer + element to `T` using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for value(ofAttribute: String) + + - parameter attr: The attribute to deserialize + - throws: an XMLDeserializationError if there is a problem with deserialization + - returns: The deserialized `T` value + */ + func value(ofAttribute attr: A) throws -> T where A.RawValue == String { + try value(ofAttribute: attr.rawValue) + } + + /** + Attempts to deserialize the value of the specified attribute of the current XMLIndexer + element to `T?` using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for value(ofAttribute: String) + + - parameter attr: The attribute to deserialize + - returns: The deserialized `T?` value, or nil if the attribute does not exist + */ + func value(ofAttribute attr: A) -> T? where A.RawValue == String { + value(ofAttribute: attr.rawValue) + } + + /** + Attempts to deserialize the value of the specified attribute of the current XMLIndexer + element to `[T]` using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for value(ofAttribute: String) + + - parameter attr: The attribute to deserialize + - throws: an XMLDeserializationError if there is a problem with deserialization + - returns: The deserialized `[T]` value + */ + func value(ofAttribute attr: A) throws -> [T] where A.RawValue == String { + try value(ofAttribute: attr.rawValue) + } + + /** + Attempts to deserialize the value of the specified attribute of the current XMLIndexer + element to `[T]?` using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for value(ofAttribute: String) + + - parameter attr: The attribute to deserialize + - throws: an XMLDeserializationError if there is a problem with deserialization + - returns: The deserialized `[T]?` value + */ + func value(ofAttribute attr: A) throws -> [T]? where A.RawValue == String { + try value(ofAttribute: attr.rawValue) + } + + /** + Attempts to deserialize the value of the specified attribute of the current XMLIndexer + element to `[T?]` using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for value(ofAttribute: String) + + - parameter attr: The attribute to deserialize + - throws: an XMLDeserializationError if there is a problem with deserialization + - returns: The deserialized `[T?]` value + */ + func value(ofAttribute attr: A) throws -> [T?] where A.RawValue == String { + try value(ofAttribute: attr.rawValue) + } +} + // MARK: - XMLElement Extensions extension XMLElement { - /** Attempts to deserialize the specified attribute of the current XMLElement to `T` @@ -405,7 +563,9 @@ extension XMLElement { */ public func value(ofAttribute attr: String) throws -> T { if let attr = self.attribute(by: attr) { - return try T.deserialize(attr) + let deserialized = try T.deserialize(attr) + try deserialized.validate() + return deserialized } else { throw XMLDeserializationError.attributeDoesNotExist(element: self, attribute: attr) } @@ -419,7 +579,9 @@ extension XMLElement { */ public func value(ofAttribute attr: String) -> T? { if let attr = self.attribute(by: attr) { - return try? T.deserialize(attr) + let deserialized = try? T.deserialize(attr) + if deserialized != nil { try? deserialized?.validate() } + return deserialized } else { return nil } @@ -441,6 +603,41 @@ extension XMLElement { } } +// MARK: String RawRepresentable + +/*: Provides XMLElement XMLAttributeDeserializable deserialization from String backed RawRepresentables + Added by [PeeJWeeJ](https://github.com/PeeJWeeJ) */ +public extension XMLElement { + /** + Attempts to deserialize the specified attribute of the current XMLElement to `T` + using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for value(ofAttribute: String) + + - parameter attr: The attribute to deserialize + - throws: an XMLDeserializationError if there is a problem with deserialization + - returns: The deserialized `T` value + */ + func value(ofAttribute attr: A) throws -> T where A.RawValue == String { + try value(ofAttribute: attr.rawValue) + } + + /** + Attempts to deserialize the specified attribute of the current XMLElement to `T?` + using a String backed RawRepresentable (E.g. `String` backed `enum` cases) + + - Note: + Convenience for value(ofAttribute: String) + + - parameter attr: The attribute to deserialize + - returns: The deserialized `T?` value, or nil if the attribute does not exist. + */ + func value(ofAttribute attr: A) -> T? where A.RawValue == String { + value(ofAttribute: attr.rawValue) + } +} + // MARK: - XMLDeserializationError /// The error that is thrown if there is a problem with deserialization @@ -484,11 +681,11 @@ public enum XMLDeserializationError: Error, CustomStringConvertible { return "This node is invalid: \(node)" case .nodeHasNoValue: return "This node is empty" - case .typeConversionFailed(let type, let node): + case let .typeConversionFailed(type, node): return "Can't convert node \(node) to value of type \(type)" - case .attributeDoesNotExist(let element, let attribute): + case let .attributeDoesNotExist(element, attribute): return "Element \(element) does not contain attribute: \(attribute)" - case .attributeDeserializationFailed(let type, let attribute): + case let .attributeDeserializationFailed(type, attribute): return "Can't convert attribute \(attribute) to value of type \(type)" } } @@ -506,7 +703,7 @@ extension String: XMLElementDeserializable, XMLAttributeDeserializable { - returns: the deserialized String value */ public static func deserialize(_ element: XMLElement) -> String { - return element.text + element.text } /** @@ -516,8 +713,10 @@ extension String: XMLElementDeserializable, XMLAttributeDeserializable { - returns: the deserialized String value */ public static func deserialize(_ attribute: XMLAttribute) -> String { - return attribute.text + attribute.text } + + public func validate() {} } extension Int: XMLElementDeserializable, XMLAttributeDeserializable { @@ -551,6 +750,8 @@ extension Int: XMLElementDeserializable, XMLAttributeDeserializable { } return value } + + public func validate() {} } extension Double: XMLElementDeserializable, XMLAttributeDeserializable { @@ -584,6 +785,8 @@ extension Double: XMLElementDeserializable, XMLAttributeDeserializable { } return value } + + public func validate() {} } extension Float: XMLElementDeserializable, XMLAttributeDeserializable { @@ -617,6 +820,8 @@ extension Float: XMLElementDeserializable, XMLAttributeDeserializable { } return value } + + public func validate() {} } extension Bool: XMLElementDeserializable, XMLAttributeDeserializable { @@ -649,4 +854,6 @@ extension Bool: XMLElementDeserializable, XMLAttributeDeserializable { return value } // swiftlint:enable line_length + + public func validate() {} } diff --git a/Dependencies/SWXMLHash/shim.swift b/Dependencies/SWXMLHash/shim.swift new file mode 100644 index 00000000..f0ec8967 --- /dev/null +++ b/Dependencies/SWXMLHash/shim.swift @@ -0,0 +1,36 @@ +// +// shim.swift +// SWXMLHash +// +// Copyright (c) 2018 David Mohundro +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#if (!swift(>=4.1) && swift(>=4.0)) || !swift(>=3.3) + + extension Sequence { + func compactMap( + _ transform: (Self.Element + ) throws -> ElementOfResult?) rethrows -> [ElementOfResult] { + try flatMap(transform) + } + } + +#endif diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index 7507a2b0..c99329d5 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -147,15 +147,12 @@ 5713C4F51E5AE2C300BBA4D9 /* CombineAnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5713C4F41E5AE2C300BBA4D9 /* CombineAnimationTests.swift */; }; 5713C4F71E5C34C700BBA4D9 /* SequenceAnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5713C4F61E5C34C700BBA4D9 /* SequenceAnimationTests.swift */; }; 5713C4F91E5C3FEE00BBA4D9 /* DelayedAnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5713C4F81E5C3FEE00BBA4D9 /* DelayedAnimationTests.swift */; }; - 572CEFC71E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572CEFC51E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift */; }; - 572CEFC81E2CED4B008C7C83 /* SWXMLHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572CEFC61E2CED4B008C7C83 /* SWXMLHash.swift */; }; 57614AFD1F83D15600875933 /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1381E3B393900D1CB28 /* Group.swift */; }; 57614AFE1F83D15600875933 /* TextRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1441E3B393900D1CB28 /* TextRenderer.swift */; }; 57614AFF1F83D15600875933 /* CGFloat+Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14D1E3B393900D1CB28 /* CGFloat+Double.swift */; }; 57614B021F83D15600875933 /* RoundRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1341E3B393900D1CB28 /* RoundRect.swift */; }; 57614B031F83D15600875933 /* UIImage2Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57900FF81EA0DEBF00809FFB /* UIImage2Image.swift */; }; 57614B041F83D15600875933 /* SVGParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1471E3B393900D1CB28 /* SVGParser.swift */; }; - 57614B051F83D15600875933 /* SWXMLHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572CEFC61E2CED4B008C7C83 /* SWXMLHash.swift */; }; 57614B071F83D15600875933 /* RenderUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1421E3B393900D1CB28 /* RenderUtils.swift */; }; 57614B081F83D15600875933 /* FuncBounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0F11E3B393900D1CB28 /* FuncBounds.swift */; }; 57614B0A1F83D15600875933 /* DoubleInterpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0EC1E3B393900D1CB28 /* DoubleInterpolation.swift */; }; @@ -234,7 +231,6 @@ 57614B671F83D15600875933 /* ContentsInterpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57A27BCE1E44C4EC0057BD3A /* ContentsInterpolation.swift */; }; 57614B681F83D15600875933 /* GroupRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E13E1E3B393900D1CB28 /* GroupRenderer.swift */; }; 57614B6B1F83D15600875933 /* NSTimer+Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14E1E3B393900D1CB28 /* NSTimer+Closure.swift */; }; - 57614B6C1F83D15600875933 /* SWXMLHash+TypeConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572CEFC51E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift */; }; 57614B6D1F83D15600875933 /* AnimationSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FF1E3B393900D1CB28 /* AnimationSequence.swift */; }; 57614B6E1F83D15600875933 /* MorphingGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FB1E3B393900D1CB28 /* MorphingGenerator.swift */; }; 57614B6F1F83D15600875933 /* SVGConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1461E3B393900D1CB28 /* SVGConstants.swift */; }; @@ -681,6 +677,12 @@ A7B47E44230EA402009DD7E5 /* Graphics_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD451F45C28700966E06 /* Graphics_iOS.swift */; }; A7B47E45230EA404009DD7E5 /* Common_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD431F45C28200966E06 /* Common_iOS.swift */; }; A7E675561EC4213500BD9ECB /* NodeBoundsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */; }; + A7F46DE0270B0EE8008DA1DF /* XMLHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F46DDD270B0EE8008DA1DF /* XMLHash.swift */; }; + A7F46DE1270B0EE8008DA1DF /* XMLHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F46DDD270B0EE8008DA1DF /* XMLHash.swift */; }; + A7F46DE2270B0EE8008DA1DF /* shim.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F46DDE270B0EE8008DA1DF /* shim.swift */; }; + A7F46DE3270B0EE8008DA1DF /* shim.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F46DDE270B0EE8008DA1DF /* shim.swift */; }; + A7F46DE4270B0EE8008DA1DF /* XMLIndexer+XMLIndexerDeserializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F46DDF270B0EE8008DA1DF /* XMLIndexer+XMLIndexerDeserializable.swift */; }; + A7F46DE5270B0EE8008DA1DF /* XMLIndexer+XMLIndexerDeserializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F46DDF270B0EE8008DA1DF /* XMLIndexer+XMLIndexerDeserializable.swift */; }; C410148E1F834D290022EE44 /* style.svg in Resources */ = {isa = PBXBuildFile; fileRef = C410148D1F834D280022EE44 /* style.svg */; }; C4153A8F1F8793DE001BA5EE /* small-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = C4153A8E1F8793DD001BA5EE /* small-logo.png */; }; C46E83551F94B20E00208037 /* transform.svg in Resources */ = {isa = PBXBuildFile; fileRef = C46E83541F94B20E00208037 /* transform.svg */; }; @@ -859,8 +861,6 @@ 5713C4F41E5AE2C300BBA4D9 /* CombineAnimationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineAnimationTests.swift; sourceTree = ""; }; 5713C4F61E5C34C700BBA4D9 /* SequenceAnimationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SequenceAnimationTests.swift; sourceTree = ""; }; 5713C4F81E5C3FEE00BBA4D9 /* DelayedAnimationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelayedAnimationTests.swift; sourceTree = ""; }; - 572CEFC51E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SWXMLHash+TypeConversion.swift"; sourceTree = ""; }; - 572CEFC61E2CED4B008C7C83 /* SWXMLHash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SWXMLHash.swift; sourceTree = ""; }; 57614B791F83D15600875933 /* MacawOSX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MacawOSX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 57614BD91F8739EE00875933 /* MacawView+PDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MacawView+PDF.swift"; sourceTree = ""; }; 57900FF81EA0DEBF00809FFB /* UIImage2Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImage2Image.swift; sourceTree = ""; }; @@ -1260,6 +1260,9 @@ A74C832B229FB7690085A832 /* color-prop-04-t-manual-osx.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "color-prop-04-t-manual-osx.svg"; sourceTree = ""; }; A74C832D229FBA4C0085A832 /* color-prop-04-t-manual-osx.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "color-prop-04-t-manual-osx.reference"; sourceTree = ""; }; A7E675551EC4213500BD9ECB /* NodeBoundsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NodeBoundsTests.swift; path = Bounds/NodeBoundsTests.swift; sourceTree = ""; }; + A7F46DDD270B0EE8008DA1DF /* XMLHash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLHash.swift; sourceTree = ""; }; + A7F46DDE270B0EE8008DA1DF /* shim.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = shim.swift; sourceTree = ""; }; + A7F46DDF270B0EE8008DA1DF /* XMLIndexer+XMLIndexerDeserializable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XMLIndexer+XMLIndexerDeserializable.swift"; sourceTree = ""; }; C410148D1F834D280022EE44 /* style.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = style.svg; sourceTree = ""; }; C4153A8E1F8793DD001BA5EE /* small-logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "small-logo.png"; sourceTree = ""; }; C43B064C1F9738EF00787A35 /* clip.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = clip.svg; sourceTree = ""; }; @@ -1448,8 +1451,9 @@ 572CEFC41E2CED4B008C7C83 /* SWXMLHash */ = { isa = PBXGroup; children = ( - 572CEFC51E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift */, - 572CEFC61E2CED4B008C7C83 /* SWXMLHash.swift */, + A7F46DDE270B0EE8008DA1DF /* shim.swift */, + A7F46DDD270B0EE8008DA1DF /* XMLHash.swift */, + A7F46DDF270B0EE8008DA1DF /* XMLIndexer+XMLIndexerDeserializable.swift */, ); path = SWXMLHash; sourceTree = ""; @@ -2697,7 +2701,6 @@ 5B6E194020AC58F900454E7E /* Stroke.swift in Sources */, 57614B041F83D15600875933 /* SVGParser.swift in Sources */, A7B47E42230EA3FC009DD7E5 /* MDisplayLink_iOS.swift in Sources */, - 57614B051F83D15600875933 /* SWXMLHash.swift in Sources */, 57614B071F83D15600875933 /* RenderUtils.swift in Sources */, 57614B081F83D15600875933 /* FuncBounds.swift in Sources */, 57614B0A1F83D15600875933 /* DoubleInterpolation.swift in Sources */, @@ -2738,6 +2741,7 @@ 5B6E193020AC58F900454E7E /* LinearGradient.swift in Sources */, 57614B261F83D15600875933 /* ShapeAnimation.swift in Sources */, 57614B271F83D15600875933 /* TransformInterpolation.swift in Sources */, + A7F46DE1270B0EE8008DA1DF /* XMLHash.swift in Sources */, 57614B281F83D15600875933 /* ShapeAnimationGenerator.swift in Sources */, 57614B291F83D15600875933 /* AnimationUtils.swift in Sources */, 57614B2A1F83D15600875933 /* Polygon.swift in Sources */, @@ -2776,6 +2780,7 @@ 30FF496D215CF27E00FF653C /* MCAShapeLayerLineCap_macOS.swift in Sources */, 57614B491F83D15600875933 /* MView_macOS.swift in Sources */, A7B47E3F230EA3F5009DD7E5 /* MCAShapeLayerLineCap_iOS.swift in Sources */, + A7F46DE3270B0EE8008DA1DF /* shim.swift in Sources */, 5B6E192E20AC58F900454E7E /* Font.swift in Sources */, 5BFEF5D120B80A83008DAC11 /* ColorMatrixEffect.swift in Sources */, 57614B4A1F83D15600875933 /* Easing.swift in Sources */, @@ -2810,6 +2815,7 @@ 5B9B970D2486506C00CAF2CE /* PathAnimationGenerator.swift in Sources */, 57614B631F83D15600875933 /* Insets.swift in Sources */, 3081E77E20DB58B100640F96 /* DescriptionExtensions.swift in Sources */, + A7F46DE5270B0EE8008DA1DF /* XMLIndexer+XMLIndexerDeserializable.swift in Sources */, 57614B641F83D15600875933 /* Rect.swift in Sources */, 30FF4971215CF4CE00FF653C /* MCAMediaTimingFunctionName_macOS.swift in Sources */, 57614B651F83D15600875933 /* PathBuilder.swift in Sources */, @@ -2820,7 +2826,6 @@ 5B6E192A20AC58F900454E7E /* Align.swift in Sources */, 57614B6B1F83D15600875933 /* NSTimer+Closure.swift in Sources */, 5B6E193620AC58F900454E7E /* Stop.swift in Sources */, - 57614B6C1F83D15600875933 /* SWXMLHash+TypeConversion.swift in Sources */, 57614B6D1F83D15600875933 /* AnimationSequence.swift in Sources */, 5B1A8C7720A15F7300E5FFAE /* SVGNodeLayout.swift in Sources */, 57614B6E1F83D15600875933 /* MorphingGenerator.swift in Sources */, @@ -2842,7 +2847,6 @@ 57900FF91EA0DEBF00809FFB /* UIImage2Image.swift in Sources */, 57E5E1AB1E3B393900D1CB28 /* SVGParser.swift in Sources */, 5B6E193F20AC58F900454E7E /* Stroke.swift in Sources */, - 572CEFC81E2CED4B008C7C83 /* SWXMLHash.swift in Sources */, 57E5E1A71E3B393900D1CB28 /* RenderUtils.swift in Sources */, 57E5E1601E3B393900D1CB28 /* FuncBounds.swift in Sources */, A718CD481F45C28700966E06 /* MView_iOS.swift in Sources */, @@ -2884,6 +2888,7 @@ 57A27BD31E44C5570057BD3A /* ShapeAnimationGenerator.swift in Sources */, 57E5E1571E3B393900D1CB28 /* AnimationUtils.swift in Sources */, 30FF496F215CF3B000FF653C /* MCAMediaTimingFunctionName_iOS.swift in Sources */, + A7F46DE0270B0EE8008DA1DF /* XMLHash.swift in Sources */, 57E5E1981E3B393900D1CB28 /* Polygon.swift in Sources */, 57E5E1701E3B393900D1CB28 /* TransformAnimation.swift in Sources */, 57E5E16C1E3B393900D1CB28 /* CombineAnimation.swift in Sources */, @@ -2922,6 +2927,7 @@ 5BFEF5CE20B80A83008DAC11 /* BlendEffect.swift in Sources */, A718CD501F45C28F00966E06 /* MView_macOS.swift in Sources */, 57E5E1581E3B393900D1CB28 /* Easing.swift in Sources */, + A7F46DE2270B0EE8008DA1DF /* shim.swift in Sources */, 5BFEF5D020B80A83008DAC11 /* ColorMatrixEffect.swift in Sources */, 5B6E192D20AC58F900454E7E /* Font.swift in Sources */, 57E5E1971E3B393900D1CB28 /* Point.swift in Sources */, @@ -2956,6 +2962,7 @@ 3081E77D20DB58B100640F96 /* DescriptionExtensions.swift in Sources */, 57E5E19A1E3B393900D1CB28 /* Rect.swift in Sources */, 57E5E1941E3B393900D1CB28 /* PathBuilder.swift in Sources */, + A7F46DE4270B0EE8008DA1DF /* XMLIndexer+XMLIndexerDeserializable.swift in Sources */, 57E5E1761E3B393900D1CB28 /* PinchEvent.swift in Sources */, 57A27BCF1E44C4EC0057BD3A /* ContentsInterpolation.swift in Sources */, 57E5E1A31E3B393900D1CB28 /* GroupRenderer.swift in Sources */, @@ -2964,7 +2971,6 @@ 57E5E1B11E3B393900D1CB28 /* NSTimer+Closure.swift in Sources */, 30FF4962215CE97300FF653C /* MCAMediaTimingFillMode_iOS.swift in Sources */, 5B6E193520AC58F900454E7E /* Stop.swift in Sources */, - 572CEFC71E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift in Sources */, 30FF496B215CF0ED00FF653C /* MCAShapeLayerLineCap_iOS.swift in Sources */, 5BECDC7B222FE6DE009C8E6A /* PathAnimation.swift in Sources */, 57E5E16B1E3B393900D1CB28 /* AnimationSequence.swift in Sources */, diff --git a/Source/svg/CSSParser.swift b/Source/svg/CSSParser.swift index e919abcc..d22f0092 100644 --- a/Source/svg/CSSParser.swift +++ b/Source/svg/CSSParser.swift @@ -69,7 +69,7 @@ class CSSParser { return .byTag(text) } - func getStyles(element: SWXMLHash.XMLElement) -> [String: String] { + func getStyles(element: XMLHash.XMLElement) -> [String: String] { var styleAttributes = [String: String]() if let styles = stylesByTag[element.name] { diff --git a/Source/svg/SVGParser.swift b/Source/svg/SVGParser.swift index 20c26c01..cbecf21a 100644 --- a/Source/svg/SVGParser.swift +++ b/Source/svg/SVGParser.swift @@ -133,12 +133,12 @@ open class SVGParser { } fileprivate func parse() throws -> Group { - let config = SWXMLHash.config { config in + let config = XMLHash.config { config in config.shouldProcessNamespaces = true } let parsedXml = config.parse(xmlString) - var svgElement: SWXMLHash.XMLElement? + var svgElement: XMLHash.XMLElement? for child in parsedXml.children { if let element = child.element { if element.name == "svg" { @@ -203,7 +203,7 @@ open class SVGParser { } } - fileprivate func parseViewBox(_ element: SWXMLHash.XMLElement) throws -> SVGNodeLayout? { + fileprivate func parseViewBox(_ element: XMLHash.XMLElement) throws -> SVGNodeLayout? { let widthAttributeNil = element.allAttributes["width"] == nil let heightAttributeNil = element.allAttributes["height"] == nil let viewBoxAttributeNil = element.allAttributes["viewBox"] == nil @@ -508,7 +508,7 @@ open class SVGParser { return Group(contents: groupNodes, place: getPosition(element), tag: getTag(element)) } - fileprivate func getPosition(_ element: SWXMLHash.XMLElement) -> Transform { + fileprivate func getPosition(_ element: XMLHash.XMLElement) -> Transform { guard let transformAttribute = element.allAttributes["transform"]?.text else { return Transform.identity } @@ -666,7 +666,7 @@ open class SVGParser { } fileprivate func getStyleAttributes(_ groupAttributes: [String: String], - element: SWXMLHash.XMLElement) -> [String: String] { + element: XMLHash.XMLElement) -> [String: String] { var styleAttributes: [String: String] = groupAttributes for (att, val) in styles.getStyles(element: element) { @@ -876,7 +876,7 @@ open class SVGParser { return dashes } - fileprivate func getMatrix(_ element: SWXMLHash.XMLElement, attribute: String) -> [Double] { + fileprivate func getMatrix(_ element: XMLHash.XMLElement, attribute: String) -> [Double] { var result = [Double]() if let values = element.allAttributes[attribute]?.text { let separatedValues = values.components(separatedBy: CharacterSet(charactersIn: " ,")) @@ -896,7 +896,7 @@ open class SVGParser { return 0 } - fileprivate func getTag(_ element: SWXMLHash.XMLElement) -> [String] { + fileprivate func getTag(_ element: XMLHash.XMLElement) -> [String] { let id = element.allAttributes["id"]?.text return id.map { [$0] } ?? [] } @@ -1081,7 +1081,7 @@ open class SVGParser { return Align.min } - fileprivate func parseSimpleText(_ text: SWXMLHash.XMLElement, + fileprivate func parseSimpleText(_ text: XMLHash.XMLElement, textAnchor: String?, fill: Fill?, stroke: Stroke?, @@ -1156,7 +1156,7 @@ open class SVGParser { baseline: .alphabetic, place: place, opacity: opacity) - } else if let tspanElement = element as? SWXMLHash.XMLElement, + } else if let tspanElement = element as? XMLHash.XMLElement, tspanElement.name == "tspan" { // parse as element // ultimately skip it if it cannot be parsed @@ -1187,7 +1187,7 @@ open class SVGParser { return collection } - fileprivate func parseTspan(_ element: SWXMLHash.XMLElement, + fileprivate func parseTspan(_ element: XMLHash.XMLElement, withWhitespace: Bool = false, textAnchor: String?, fill: Fill?, @@ -1242,7 +1242,7 @@ open class SVGParser { weight: getFontWeight(attributes) ?? fontWeight ?? "normal") } - fileprivate func getTspanPosition(_ element: SWXMLHash.XMLElement, + fileprivate func getTspanPosition(_ element: XMLHash.XMLElement, bounds: Rect, previousCollectedTspan: Node?, withWhitespace: inout Bool) -> Transform { @@ -1653,14 +1653,14 @@ open class SVGParser { return .none } - fileprivate func getDoubleValue(_ element: SWXMLHash.XMLElement, attribute: String) -> Double? { + fileprivate func getDoubleValue(_ element: XMLHash.XMLElement, attribute: String) -> Double? { guard let attributeValue = element.allAttributes[attribute]?.text else { return .none } return doubleFromString(attributeValue) } - fileprivate func getDimensionValue(_ element: SWXMLHash.XMLElement, attribute: String) -> SVGLength? { + fileprivate func getDimensionValue(_ element: XMLHash.XMLElement, attribute: String) -> SVGLength? { guard let attributeValue = element.allAttributes[attribute]?.text else { return .none } @@ -1701,7 +1701,7 @@ open class SVGParser { } } - fileprivate func getDoubleValueFromPercentage(_ element: SWXMLHash.XMLElement, attribute: String) -> Double? { + fileprivate func getDoubleValueFromPercentage(_ element: XMLHash.XMLElement, attribute: String) -> Double? { guard let attributeValue = element.allAttributes[attribute]?.text else { return .none } @@ -1716,7 +1716,7 @@ open class SVGParser { return .none } - fileprivate func getIntValue(_ element: SWXMLHash.XMLElement, attribute: String) -> Int? { + fileprivate func getIntValue(_ element: XMLHash.XMLElement, attribute: String) -> Int? { if let attributeValue = element.allAttributes[attribute]?.text { if let doubleValue = Double(attributeValue) { return Int(doubleValue)