diff --git a/Source/SWXMLHash.swift b/Source/SWXMLHash.swift index 719e5a2a..f242a804 100644 --- a/Source/SWXMLHash.swift +++ b/Source/SWXMLHash.swift @@ -50,6 +50,9 @@ 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]() } /// Simple XML parser @@ -250,12 +253,12 @@ extension XMLParserDelegate { /// The implementation of XMLParserDelegate and where the lazy parsing actually happens. class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { required init(_ options: SWXMLHashOptions) { + 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() var elementStack = Stack() @@ -272,7 +275,6 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { // clear any prior runs of parse... expected that this won't be necessary, // but you never know parentStack.removeAll() - root = XMLElement(name: rootElementName, caseInsensitive: options.caseInsensitive) parentStack.push(root) self.ops = ops @@ -338,12 +340,12 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { /// The implementation of XMLParserDelegate and where the parsing actually happens. class FullXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { required init(_ options: SWXMLHashOptions) { + 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 @@ -556,6 +558,15 @@ public enum XMLIndexer { return list } + public var userInfo: [CodingUserInfoKey: Any] { + switch self { + case .element(let elem): + return elem.userInfo + default: + return [:] + } + } + /** Allows for element lookup by matching attribute values. @@ -775,11 +786,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 { + return options.caseInsensitive + } + + var userInfo: [CodingUserInfoKey: Any] { + return 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 @@ -813,8 +832,10 @@ public class XMLElement: XMLContent { /// All child elements (text or XML) public var children = [XMLContent]() + var count: Int = 0 var index: Int + let options: SWXMLHashOptions var xmlChildren: [XMLElement] { return children.flatMap { $0 as? XMLElement } @@ -827,10 +848,10 @@ public class XMLElement: XMLContent { - name: The name of the element to be initialized - index: The index of the element to be initialized */ - init(name: String, index: Int = 0, caseInsensitive: Bool) { + init(name: String, index: Int = 0, options: SWXMLHashOptions) { self.name = name - self.caseInsensitive = caseInsensitive self.index = index + self.options = options } /** @@ -843,7 +864,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) diff --git a/Tests/SWXMLHashTests/LazyTypesConversionTests.swift b/Tests/SWXMLHashTests/LazyTypesConversionTests.swift index d1345d25..03a6a731 100644 --- a/Tests/SWXMLHashTests/LazyTypesConversionTests.swift +++ b/Tests/SWXMLHashTests/LazyTypesConversionTests.swift @@ -66,13 +66,28 @@ class LazyTypesConversionTests: XCTestCase { XCTFail("\(error)") } } + + func testShouldBeAbleToGetUserInfoDuringDeserialization() { + parser = SWXMLHash.config { config in + let options = SampleUserInfo(apiVersion: .v1) + config.userInfo = [ SampleUserInfo.key: options ] + }.parse(xmlWithBasicTypes) + + do { + let value: BasicItem = try parser!["root"]["basicItem"].value() + XCTAssertEqual(value.name, "the name of basic item (v1)") + } catch { + XCTFail("\(error)") + } + } } extension LazyTypesConversionTests { static var allTests: [(String, (LazyTypesConversionTests) -> () throws -> Void)] { return [ ("testShouldConvertValueToNonOptional", testShouldConvertValueToNonOptional), - ("testShouldConvertAttributeToNonOptional", testShouldConvertAttributeToNonOptional) + ("testShouldConvertAttributeToNonOptional", testShouldConvertAttributeToNonOptional), + ("testShouldBeAbleToGetUserInfoDuringDeserialization", testShouldBeAbleToGetUserInfoDuringDeserialization) ] } } diff --git a/Tests/SWXMLHashTests/TypeConversionBasicTypesTests.swift b/Tests/SWXMLHashTests/TypeConversionBasicTypesTests.swift index 79f90b44..533db8d9 100644 --- a/Tests/SWXMLHashTests/TypeConversionBasicTypesTests.swift +++ b/Tests/SWXMLHashTests/TypeConversionBasicTypesTests.swift @@ -31,6 +31,29 @@ import XCTest // swiftlint:disable line_length // swiftlint:disable type_body_length +struct SampleUserInfo { + enum ApiVersion { + case v1 + case v2 + } + + var apiVersion = ApiVersion.v2 + + func suffix() -> String { + if apiVersion == ApiVersion.v1 { + return " (v1)" + } else { + return "" + } + } + + static let key = CodingUserInfoKey(rawValue: "test")! + + init(apiVersion: ApiVersion) { + self.apiVersion = apiVersion + } +} + class TypeConversionBasicTypesTests: XCTestCase { var parser: XMLIndexer? let xmlWithBasicTypes = """ @@ -524,6 +547,20 @@ class TypeConversionBasicTypesTests: XCTestCase { XCTFail("\(error)") } } + + func testShouldBeAbleToGetUserInfoDuringDeserialization() { + parser = SWXMLHash.config { config in + let options = SampleUserInfo(apiVersion: .v1) + config.userInfo = [ SampleUserInfo.key: options ] + }.parse(xmlWithBasicTypes) + + do { + let value: BasicItem = try parser!["root"]["basicItem"].value() + XCTAssertEqual(value.name, "the name of basic item (v1)") + } catch { + XCTFail("\(error)") + } + } } struct BasicItem: XMLIndexerDeserializable { @@ -531,8 +568,14 @@ struct BasicItem: XMLIndexerDeserializable { let price: Double static func deserialize(_ node: XMLIndexer) throws -> BasicItem { + var name: String = try node["name"].value() + + if let opts = node.userInfo[SampleUserInfo.key] as? SampleUserInfo { + name += opts.suffix() + } + return try BasicItem( - name: node["name"].value(), + name: name, price: node["price"].value() ) } @@ -618,7 +661,8 @@ extension TypeConversionBasicTypesTests { ("testAttributeItemShouldThrowWhenConvertingMissingToNonOptional", testAttributeItemShouldThrowWhenConvertingMissingToNonOptional), ("testAttributeItemShouldConvertAttributeItemToOptional", testAttributeItemShouldConvertAttributeItemToOptional), ("testAttributeItemShouldConvertEmptyToOptional", testAttributeItemShouldConvertEmptyToOptional), - ("testAttributeItemShouldConvertMissingToOptional", testAttributeItemShouldConvertMissingToOptional) + ("testAttributeItemShouldConvertMissingToOptional", testAttributeItemShouldConvertMissingToOptional), + ("testShouldBeAbleToGetUserInfoDuringDeserialization", testShouldBeAbleToGetUserInfoDuringDeserialization) ] } }