diff --git a/Sources/XMLCoder/Auxiliaries/Box/BoolBox.swift b/Sources/XMLCoder/Auxiliaries/Box/BoolBox.swift index 6412bf0c..9ab936b5 100644 --- a/Sources/XMLCoder/Auxiliaries/Box/BoolBox.swift +++ b/Sources/XMLCoder/Auxiliaries/Box/BoolBox.swift @@ -17,9 +17,9 @@ struct BoolBox: Equatable { } init?(xmlString: String) { - switch xmlString { - case "false", "0": self.init(false) - case "true", "1": self.init(true) + switch xmlString.lowercased() { + case "false", "0", "n", "no": self.init(false) + case "true", "1", "y", "yes": self.init(true) case _: return nil } } diff --git a/Sources/XMLCoder/Auxiliaries/Box/Box.swift b/Sources/XMLCoder/Auxiliaries/Box/Box.swift index 8dfdb829..7f3e78ab 100644 --- a/Sources/XMLCoder/Auxiliaries/Box/Box.swift +++ b/Sources/XMLCoder/Auxiliaries/Box/Box.swift @@ -17,6 +17,17 @@ protocol SimpleBox: Box { // A simple tagging protocol, for now. } -protocol SharedBoxProtocol { - func unbox() -> Box +protocol TypeErasedSharedBoxProtocol { + func typeErasedUnbox() -> Box +} + +protocol SharedBoxProtocol: TypeErasedSharedBoxProtocol { + associatedtype B: Box + func unbox() -> B +} + +extension SharedBoxProtocol { + func typeErasedUnbox() -> Box { + return unbox() + } } diff --git a/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift b/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift index 140dce33..408c2d9f 100644 --- a/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift +++ b/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift @@ -47,9 +47,7 @@ struct KeyedStorage { } extension KeyedStorage: Sequence { - typealias Iterator = Buffer.Iterator - - func makeIterator() -> Iterator { + func makeIterator() -> Buffer.Iterator { return buffer.makeIterator() } } @@ -74,30 +72,27 @@ struct KeyedBox { typealias Attributes = KeyedStorage typealias Elements = KeyedStorage - var attributes: Attributes = [:] var elements: Elements = [:] + var attributes: Attributes = [:] - init() { - attributes = [:] - elements = [:] + func unbox() -> (elements: Elements, attributes: Attributes) { + return ( + elements: elements, + attributes: attributes + ) } +} +extension KeyedBox { init(elements: E, attributes: A) where E: Sequence, E.Element == (Key, Element), A: Sequence, A.Element == (Key, Attribute) { - self.elements = Elements(Dictionary(uniqueKeysWithValues: elements)) - self.attributes = Attributes(Dictionary(uniqueKeysWithValues: attributes)) + let elements = Elements(Dictionary(uniqueKeysWithValues: elements)) + let attributes = Attributes(Dictionary(uniqueKeysWithValues: attributes)) + self.init(elements: elements, attributes: attributes) } init(elements: [Key: Element], attributes: [Key: Attribute]) { - self.elements = Elements(elements) - self.attributes = Attributes(attributes) - } - - func unbox() -> (elements: Elements, attributes: Attributes) { - return ( - elements: elements, - attributes: attributes - ) + self.init(elements: Elements(elements), attributes: Attributes(attributes)) } } diff --git a/Sources/XMLCoder/Auxiliaries/Box/SharedBox.swift b/Sources/XMLCoder/Auxiliaries/Box/SharedBox.swift index 8106a15c..ea215705 100644 --- a/Sources/XMLCoder/Auxiliaries/Box/SharedBox.swift +++ b/Sources/XMLCoder/Auxiliaries/Box/SharedBox.swift @@ -30,7 +30,7 @@ extension SharedBox: Box { } extension SharedBox: SharedBoxProtocol { - func unbox() -> Box { + func unbox() -> Unboxed { return unboxed } } diff --git a/Sources/XMLCoder/Auxiliaries/String+Extensions.swift b/Sources/XMLCoder/Auxiliaries/String+Extensions.swift index 99fc8c48..14a1b920 100644 --- a/Sources/XMLCoder/Auxiliaries/String+Extensions.swift +++ b/Sources/XMLCoder/Auxiliaries/String+Extensions.swift @@ -7,9 +7,9 @@ import Foundation -extension String { +extension StringProtocol where Self.Index == String.Index { func escape(_ characterSet: [(character: String, escapedCharacter: String)]) -> String { - var string = self + var string = String(self) for set in characterSet { string = string.replacingOccurrences(of: set.character, with: set.escapedCharacter, options: .literal) @@ -18,3 +18,28 @@ extension String { return string } } + +extension StringProtocol { + func capitalizingFirstLetter() -> Self { + guard count > 1 else { + return self + } + return Self(prefix(1).uppercased() + dropFirst())! + } + + mutating func capitalizeFirstLetter() { + self = capitalizingFirstLetter() + } + + func lowercasingFirstLetter() -> Self { + // avoid lowercasing single letters (I), or capitalized multiples (AThing ! to aThing, leave as AThing) + guard count > 1, !(String(prefix(2)) == prefix(2).lowercased()) else { + return self + } + return Self(prefix(1).lowercased() + dropFirst())! + } + + mutating func lowercaseFirstLetter() { + self = lowercasingFirstLetter() + } +} diff --git a/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift b/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift index 7301e3b9..fc1f5cd6 100644 --- a/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift +++ b/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift @@ -34,83 +34,6 @@ struct XMLCoderElement: Equatable { self.attributes = attributes } - init(key: String, box: UnkeyedBox) { - let elements = box.map { box in - XMLCoderElement(key: key, box: box) - } - - self.init(key: key, elements: elements) - } - - init(key: String, box: KeyedBox) { - var elements: [XMLCoderElement] = [] - - for (key, box) in box.elements { - let fail = { - preconditionFailure("Unclassified box: \(type(of: box))") - } - - switch box { - case let sharedUnkeyedBox as SharedBox: - guard let box = sharedUnkeyedBox.unbox() as? UnkeyedBox else { - fail() - } - elements.append(contentsOf: box.map { - XMLCoderElement(key: key, box: $0) - }) - case let unkeyedBox as UnkeyedBox: - // This basically injects the unkeyed children directly into self: - elements.append(contentsOf: unkeyedBox.map { - XMLCoderElement(key: key, box: $0) - }) - case let sharedKeyedBox as SharedBox: - guard let box = sharedKeyedBox.unbox() as? KeyedBox else { - fail() - } - elements.append(XMLCoderElement(key: key, box: box)) - case let keyedBox as KeyedBox: - elements.append(XMLCoderElement(key: key, box: keyedBox)) - case let simpleBox as SimpleBox: - elements.append(XMLCoderElement(key: key, box: simpleBox)) - default: - fail() - } - } - - let attributes: [String: String] = Dictionary( - uniqueKeysWithValues: box.attributes.compactMap { key, box in - guard let value = box.xmlString() else { - return nil - } - return (key, value) - } - ) - - self.init(key: key, elements: elements, attributes: attributes) - } - - init(key: String, box: SimpleBox) { - self.init(key: key) - value = box.xmlString() - } - - init(key: String, box: Box) { - switch box { - case let sharedUnkeyedBox as SharedBox: - self.init(key: key, box: sharedUnkeyedBox.unbox()) - case let sharedKeyedBox as SharedBox: - self.init(key: key, box: sharedKeyedBox.unbox()) - case let unkeyedBox as UnkeyedBox: - self.init(key: key, box: unkeyedBox) - case let keyedBox as KeyedBox: - self.init(key: key, box: keyedBox) - case let simpleBox as SimpleBox: - self.init(key: key, box: simpleBox) - case let box: - preconditionFailure("Unclassified box: \(type(of: box))") - } - } - mutating func append(value string: String) { var value = self.value ?? "" value += string.trimmingCharacters(in: .whitespacesAndNewlines) @@ -124,9 +47,8 @@ struct XMLCoderElement: Equatable { func flatten() -> KeyedBox { let attributes = self.attributes.mapValues { StringBox($0) } - var elements: [String: Box] = [:] - - for element in self.elements { + let keyedElements: [String: Box] = elements.reduce([String: Box]()) { (result, element) -> [String: Box] in + var result = result let key = element.key let hasValue = element.value != nil @@ -135,42 +57,43 @@ struct XMLCoderElement: Equatable { if hasValue || hasElements || hasAttributes { if let content = element.value { - switch elements[key] { + switch result[key] { case var unkeyedBox as UnkeyedBox: unkeyedBox.append(StringBox(content)) - elements[key] = unkeyedBox + result[key] = unkeyedBox case let stringBox as StringBox: - elements[key] = UnkeyedBox([stringBox, StringBox(content)]) + result[key] = UnkeyedBox([stringBox, StringBox(content)]) default: - elements[key] = StringBox(content) + result[key] = StringBox(content) } } if hasElements || hasAttributes { let content = element.flatten() - switch elements[key] { + switch result[key] { case var unkeyedBox as UnkeyedBox: unkeyedBox.append(content) - elements[key] = unkeyedBox + result[key] = unkeyedBox case let box?: - elements[key] = UnkeyedBox([box, content]) + result[key] = UnkeyedBox([box, content]) default: - elements[key] = content + result[key] = content } } } else { - switch elements[key] { + switch result[key] { case var unkeyedBox as UnkeyedBox: unkeyedBox.append(NullBox()) - elements[key] = unkeyedBox + result[key] = unkeyedBox case let box?: - elements[key] = UnkeyedBox([box, NullBox()]) + result[key] = UnkeyedBox([box, NullBox()]) default: - elements[key] = NullBox() + result[key] = NullBox() } } + return result } - let keyedBox = KeyedBox(elements: elements, attributes: attributes) + let keyedBox = KeyedBox(elements: keyedElements, attributes: attributes) return keyedBox } @@ -314,25 +237,34 @@ struct XMLCoderElement: Equatable { repeating: " ", count: (prettyPrinted ? level : 0) * 4 ) var string = indentation - string += "<\(key)" + if !key.isEmpty { + string += "<\(key)" + } formatXMLAttributes(formatting, &string) if let value = value { - string += ">" + if !key.isEmpty { + string += ">" + } if !ignoreEscaping { string += (cdata == true ? "" : "\(value.escape(XMLCoderElement.escapedCharacterSet))") } else { string += "\(value)" } - string += "" + + if !key.isEmpty { + string += "" + } } else if !elements.isEmpty { string += prettyPrinted ? ">\n" : ">" formatXMLElements(formatting, &string, level, cdata, prettyPrinted) string += indentation - string += "" + if !key.isEmpty { + string += "" + } } else { string += " />" } @@ -340,3 +272,80 @@ struct XMLCoderElement: Equatable { return string } } + +// MARK: - Convenience Initializers + +extension XMLCoderElement { + init(key: String, box: UnkeyedBox) { + let elements = box.map { box in + XMLCoderElement(key: key, box: box) + } + + self.init(key: key, elements: elements) + } + + init(key: String, box: KeyedBox) { + var elements: [XMLCoderElement] = [] + + for (key, box) in box.elements { + let fail = { + preconditionFailure("Unclassified box: \(type(of: box))") + } + + switch box { + case let sharedUnkeyedBox as SharedBox: + let box = sharedUnkeyedBox.unbox() + elements.append(contentsOf: box.map { + XMLCoderElement(key: key, box: $0) + }) + case let unkeyedBox as UnkeyedBox: + // This basically injects the unkeyed children directly into self: + elements.append(contentsOf: unkeyedBox.map { + XMLCoderElement(key: key, box: $0) + }) + case let sharedKeyedBox as SharedBox: + let box = sharedKeyedBox.unbox() + elements.append(XMLCoderElement(key: key, box: box)) + case let keyedBox as KeyedBox: + elements.append(XMLCoderElement(key: key, box: keyedBox)) + case let simpleBox as SimpleBox: + elements.append(XMLCoderElement(key: key, box: simpleBox)) + default: + fail() + } + } + + let attributes: [String: String] = Dictionary( + uniqueKeysWithValues: box.attributes.compactMap { key, box in + guard let value = box.xmlString() else { + return nil + } + return (key, value) + } + ) + + self.init(key: key, elements: elements, attributes: attributes) + } + + init(key: String, box: SimpleBox) { + self.init(key: key) + value = box.xmlString() + } + + init(key: String, box: Box) { + switch box { + case let sharedUnkeyedBox as SharedBox: + self.init(key: key, box: sharedUnkeyedBox.unbox()) + case let sharedKeyedBox as SharedBox: + self.init(key: key, box: sharedKeyedBox.unbox()) + case let unkeyedBox as UnkeyedBox: + self.init(key: key, box: unkeyedBox) + case let keyedBox as KeyedBox: + self.init(key: key, box: keyedBox) + case let simpleBox as SimpleBox: + self.init(key: key, box: simpleBox) + case let box: + preconditionFailure("Unclassified box: \(type(of: box))") + } + } +} diff --git a/Sources/XMLCoder/Decoder/XMLDecoder.swift b/Sources/XMLCoder/Decoder/XMLDecoder.swift index 525e04ef..5f13f006 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoder.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoder.swift @@ -298,375 +298,3 @@ open class XMLDecoder { return box } } - -class XMLDecoderImplementation: Decoder { - // MARK: Properties - - /// The decoder's storage. - var storage: XMLDecodingStorage - - /// Options set on the top-level decoder. - let options: XMLDecoder.Options - - /// The path to the current point in encoding. - public internal(set) var codingPath: [CodingKey] - - /// Contextual user-provided information for use during encoding. - public var userInfo: [CodingUserInfoKey: Any] { - return options.userInfo - } - - // The error context lenght - open var errorContextLenght: UInt = 0 - - // MARK: - Initialization - - /// Initializes `self` with the given top-level container and options. - init(referencing container: Box, at codingPath: [CodingKey] = [], options: XMLDecoder.Options) { - storage = XMLDecodingStorage() - storage.push(container: container) - self.codingPath = codingPath - self.options = options - } - - // MARK: - Decoder Methods - - private func topContainer() throws -> Box { - guard let topContainer = storage.topContainer() else { - throw DecodingError.valueNotFound(Box.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: "Cannot get decoding container -- empty container stack." - )) - } - return topContainer - } - - private func popContainer() throws -> Box { - guard let topContainer = storage.popContainer() else { - throw DecodingError.valueNotFound(Box.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: "Cannot get decoding container -- empty container stack." - )) - } - return topContainer - } - - public func container(keyedBy _: Key.Type) throws -> KeyedDecodingContainer { - let topContainer = try self.topContainer() - - guard !topContainer.isNull else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: "Cannot get keyed decoding container -- found null box instead." - )) - } - - guard let keyed = topContainer as? SharedBox else { - throw DecodingError._typeMismatch( - at: codingPath, - expectation: [String: Any].self, - reality: topContainer - ) - } - - let container = XMLKeyedDecodingContainer(referencing: self, wrapping: keyed) - return KeyedDecodingContainer(container) - } - - public func unkeyedContainer() throws -> UnkeyedDecodingContainer { - let topContainer = try self.topContainer() - - guard !topContainer.isNull else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: "Cannot get unkeyed decoding container -- found null box instead." - )) - } - - let unkeyed = (topContainer as? SharedBox) ?? SharedBox(UnkeyedBox([topContainer])) - - return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed) - } - - public func singleValueContainer() throws -> SingleValueDecodingContainer { - return self - } -} - -extension XMLDecoderImplementation: SingleValueDecodingContainer { - // MARK: SingleValueDecodingContainer Methods - - public func decodeNil() -> Bool { - return (try? topContainer().isNull) ?? true - } - - public func decode(_: Bool.Type) throws -> Bool { - return try unbox(try topContainer()) - } - - public func decode(_: Decimal.Type) throws -> Decimal { - return try unbox(try topContainer()) - } - - public func decode(_: T.Type) throws -> T { - return try unbox(try topContainer()) - } - - public func decode(_: T.Type) throws -> T { - return try unbox(try topContainer()) - } - - public func decode(_: T.Type) throws -> T { - return try unbox(try topContainer()) - } - - public func decode(_: String.Type) throws -> String { - return try unbox(try topContainer()) - } - - public func decode(_: String.Type) throws -> Date { - return try unbox(try topContainer()) - } - - public func decode(_: String.Type) throws -> Data { - return try unbox(try topContainer()) - } - - public func decode(_: T.Type) throws -> T { - return try unbox(try topContainer()) - } -} - -// MARK: - Concrete Value Representations - -extension XMLDecoderImplementation { - /// Returns the given box unboxed from a container. - - private func typedBox(_ box: Box, for valueType: T.Type) throws -> B { - guard let typedBox = box as? B else { - if box is NullBox { - throw DecodingError.valueNotFound(valueType, DecodingError.Context( - codingPath: codingPath, - debugDescription: "Expected \(valueType) but found null instead." - )) - } else { - throw DecodingError._typeMismatch(at: codingPath, expectation: valueType, reality: box) - } - } - - return typedBox - } - - func unbox(_ box: Box) throws -> Bool { - let stringBox: StringBox = try typedBox(box, for: Bool.self) - let string = stringBox.unbox() - - guard let boolBox = BoolBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: Bool.self, reality: box) - } - - return boolBox.unbox() - } - - func unbox(_ box: Box) throws -> Decimal { - let stringBox: StringBox = try typedBox(box, for: Decimal.self) - let string = stringBox.unbox() - - guard let decimalBox = DecimalBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: Decimal.self, reality: box) - } - - return decimalBox.unbox() - } - - func unbox(_ box: Box) throws -> T { - let stringBox: StringBox = try typedBox(box, for: T.self) - let string = stringBox.unbox() - - guard let intBox = IntBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) - } - - guard let int: T = intBox.unbox() else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)." - )) - } - - return int - } - - func unbox(_ box: Box) throws -> T { - let stringBox: StringBox = try typedBox(box, for: T.self) - let string = stringBox.unbox() - - guard let uintBox = UIntBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) - } - - guard let uint: T = uintBox.unbox() else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)." - )) - } - - return uint - } - - func unbox(_ box: Box) throws -> T { - let stringBox: StringBox = try typedBox(box, for: T.self) - let string = stringBox.unbox() - - guard let floatBox = FloatBox(xmlString: string) else { - throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) - } - - guard let float: T = floatBox.unbox() else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)." - )) - } - - return float - } - - func unbox(_ box: Box) throws -> String { - let stringBox: StringBox = try typedBox(box, for: String.self) - let string = stringBox.unbox() - - return string - } - - func unbox(_ box: Box) throws -> Date { - switch options.dateDecodingStrategy { - case .deferredToDate: - storage.push(container: box) - defer { storage.popContainer() } - return try Date(from: self) - - case .secondsSince1970: - let stringBox: StringBox = try typedBox(box, for: Date.self) - let string = stringBox.unbox() - - guard let dateBox = DateBox(secondsSince1970: string) else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Expected date string to be formatted in seconds since 1970." - )) - } - return dateBox.unbox() - case .millisecondsSince1970: - let stringBox: StringBox = try typedBox(box, for: Date.self) - let string = stringBox.unbox() - - guard let dateBox = DateBox(millisecondsSince1970: string) else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Expected date string to be formatted in milliseconds since 1970." - )) - } - return dateBox.unbox() - case .iso8601: - let stringBox: StringBox = try typedBox(box, for: Date.self) - let string = stringBox.unbox() - - guard let dateBox = DateBox(iso8601: string) else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Expected date string to be ISO8601-formatted." - )) - } - return dateBox.unbox() - case let .formatted(formatter): - let stringBox: StringBox = try typedBox(box, for: Date.self) - let string = stringBox.unbox() - - guard let dateBox = DateBox(xmlString: string, formatter: formatter) else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Date string does not match format expected by formatter." - )) - } - return dateBox.unbox() - case let .custom(closure): - storage.push(container: box) - defer { storage.popContainer() } - return try closure(self) - } - } - - func unbox(_ box: Box) throws -> Data { - switch options.dataDecodingStrategy { - case .deferredToData: - storage.push(container: box) - defer { storage.popContainer() } - return try Data(from: self) - case .base64: - let stringBox: StringBox = try typedBox(box, for: Data.self) - let string = stringBox.unbox() - - guard let dataBox = DataBox(base64: string) else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Encountered Data is not valid Base64" - )) - } - return dataBox.unbox() - case let .custom(closure): - storage.push(container: box) - defer { storage.popContainer() } - return try closure(self) - } - } - - func unbox(_ box: Box) throws -> URL { - let stringBox: StringBox = try typedBox(box, for: URL.self) - let string = stringBox.unbox() - - guard let urlBox = URLBox(xmlString: string) else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Encountered Data is not valid Base64" - )) - } - - return urlBox.unbox() - } - - private struct TypeMismatch: Error {} - - func unbox(_ box: Box) throws -> T { - let decoded: T? - let type = T.self - - if type == Date.self || type == NSDate.self { - let date: Date = try unbox(box) - - decoded = date as? T - } else if type == Data.self || type == NSData.self { - let data: Data = try unbox(box) - decoded = data as? T - } else if type == URL.self || type == NSURL.self { - let data: URL = try unbox(box) - decoded = data as? T - } else if type == Decimal.self || type == NSDecimalNumber.self { - let decimal: Decimal = try unbox(box) - decoded = decimal as? T - } else { - storage.push(container: box) - decoded = try type.init(from: self) - storage.popContainer() - } - - guard let result = decoded else { - throw DecodingError._typeMismatch( - at: codingPath, expectation: type, reality: box - ) - } - - return result - } -} diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation+SingleValueDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation+SingleValueDecodingContainer.swift new file mode 100644 index 00000000..989da383 --- /dev/null +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation+SingleValueDecodingContainer.swift @@ -0,0 +1,53 @@ +// +// XMLDecoder.swift +// XMLCoder +// +// Created by Shawn Moore on 11/20/17. +// Copyright © 2017 Shawn Moore. All rights reserved. +// + +import Foundation + +extension XMLDecoderImplementation: SingleValueDecodingContainer { + // MARK: SingleValueDecodingContainer Methods + + public func decodeNil() -> Bool { + return (try? topContainer().isNull) ?? true + } + + public func decode(_: Bool.Type) throws -> Bool { + return try unbox(try topContainer()) + } + + public func decode(_: Decimal.Type) throws -> Decimal { + return try unbox(try topContainer()) + } + + public func decode(_: T.Type) throws -> T { + return try unbox(try topContainer()) + } + + public func decode(_: T.Type) throws -> T { + return try unbox(try topContainer()) + } + + public func decode(_: T.Type) throws -> T { + return try unbox(try topContainer()) + } + + public func decode(_: String.Type) throws -> String { + return try unbox(try topContainer()) + } + + public func decode(_: String.Type) throws -> Date { + return try unbox(try topContainer()) + } + + public func decode(_: String.Type) throws -> Data { + return try unbox(try topContainer()) + } + + public func decode(_: T.Type) throws -> T { + return try unbox(try topContainer()) + } +} diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift new file mode 100644 index 00000000..37e48534 --- /dev/null +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -0,0 +1,340 @@ +// +// XMLDecoder.swift +// XMLCoder +// +// Created by Shawn Moore on 11/20/17. +// Copyright © 2017 Shawn Moore. All rights reserved. +// + +import Foundation + +class XMLDecoderImplementation: Decoder { + // MARK: Properties + + /// The decoder's storage. + var storage: XMLDecodingStorage = XMLDecodingStorage() + + /// Options set on the top-level decoder. + let options: XMLDecoder.Options + + /// The path to the current point in encoding. + public internal(set) var codingPath: [CodingKey] + + /// Contextual user-provided information for use during encoding. + public var userInfo: [CodingUserInfoKey: Any] { + return options.userInfo + } + + // The error context lenght + open var errorContextLenght: UInt = 0 + + // MARK: - Initialization + + /// Initializes `self` with the given top-level container and options. + init(referencing container: Box, at codingPath: [CodingKey] = [], options: XMLDecoder.Options) { + storage.push(container: container) + self.codingPath = codingPath + self.options = options + } + + // MARK: - Decoder Methods + + internal func topContainer() throws -> Box { + guard let topContainer = storage.topContainer() else { + throw DecodingError.valueNotFound(Box.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get decoding container -- empty container stack." + )) + } + return topContainer + } + + private func popContainer() throws -> Box { + guard let topContainer = storage.popContainer() else { + throw DecodingError.valueNotFound(Box.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get decoding container -- empty container stack." + )) + } + return topContainer + } + + public func container(keyedBy _: Key.Type) throws -> KeyedDecodingContainer { + let topContainer = try self.topContainer() + + guard !topContainer.isNull else { + throw DecodingError.valueNotFound(KeyedDecodingContainer.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get keyed decoding container -- found null box instead." + )) + } + + guard let keyed = topContainer as? SharedBox else { + throw DecodingError._typeMismatch( + at: codingPath, + expectation: [String: Any].self, + reality: topContainer + ) + } + + let container = XMLKeyedDecodingContainer(referencing: self, wrapping: keyed) + return KeyedDecodingContainer(container) + } + + public func unkeyedContainer() throws -> UnkeyedDecodingContainer { + let topContainer = try self.topContainer() + + guard !topContainer.isNull else { + throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get unkeyed decoding container -- found null box instead." + )) + } + + let unkeyed = (topContainer as? SharedBox) ?? SharedBox(UnkeyedBox([topContainer])) + + return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed) + } + + public func singleValueContainer() throws -> SingleValueDecodingContainer { + return self + } +} + +// MARK: - Concrete Value Representations + +extension XMLDecoderImplementation { + /// Returns the given box unboxed from a container. + + private func typedBox(_ box: Box, for valueType: T.Type) throws -> B { + guard let typedBox = box as? B else { + if box is NullBox { + throw DecodingError.valueNotFound(valueType, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected \(valueType) but found null instead." + )) + } else { + throw DecodingError._typeMismatch(at: codingPath, expectation: valueType, reality: box) + } + } + + return typedBox + } + + func unbox(_ box: Box) throws -> Bool { + let stringBox: StringBox = try typedBox(box, for: Bool.self) + let string = stringBox.unbox() + + guard let boolBox = BoolBox(xmlString: string) else { + throw DecodingError._typeMismatch(at: codingPath, expectation: Bool.self, reality: box) + } + + return boolBox.unbox() + } + + func unbox(_ box: Box) throws -> Decimal { + let stringBox: StringBox = try typedBox(box, for: Decimal.self) + let string = stringBox.unbox() + + guard let decimalBox = DecimalBox(xmlString: string) else { + throw DecodingError._typeMismatch(at: codingPath, expectation: Decimal.self, reality: box) + } + + return decimalBox.unbox() + } + + func unbox(_ box: Box) throws -> T { + let stringBox: StringBox = try typedBox(box, for: T.self) + let string = stringBox.unbox() + + guard let intBox = IntBox(xmlString: string) else { + throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) + } + + guard let int: T = intBox.unbox() else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)." + )) + } + + return int + } + + func unbox(_ box: Box) throws -> T { + let stringBox: StringBox = try typedBox(box, for: T.self) + let string = stringBox.unbox() + + guard let uintBox = UIntBox(xmlString: string) else { + throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) + } + + guard let uint: T = uintBox.unbox() else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)." + )) + } + + return uint + } + + func unbox(_ box: Box) throws -> T { + let stringBox: StringBox = try typedBox(box, for: T.self) + let string = stringBox.unbox() + + guard let floatBox = FloatBox(xmlString: string) else { + throw DecodingError._typeMismatch(at: codingPath, expectation: T.self, reality: box) + } + + guard let float: T = floatBox.unbox() else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)." + )) + } + + return float + } + + func unbox(_ box: Box) throws -> String { + let stringBox: StringBox = try typedBox(box, for: String.self) + let string = stringBox.unbox() + + return string + } + + func unbox(_ box: Box) throws -> Date { + switch options.dateDecodingStrategy { + case .deferredToDate: + storage.push(container: box) + defer { storage.popContainer() } + return try Date(from: self) + + case .secondsSince1970: + let stringBox: StringBox = try typedBox(box, for: Date.self) + let string = stringBox.unbox() + + guard let dateBox = DateBox(secondsSince1970: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected date string to be formatted in seconds since 1970." + )) + } + return dateBox.unbox() + case .millisecondsSince1970: + let stringBox: StringBox = try typedBox(box, for: Date.self) + let string = stringBox.unbox() + + guard let dateBox = DateBox(millisecondsSince1970: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected date string to be formatted in milliseconds since 1970." + )) + } + return dateBox.unbox() + case .iso8601: + let stringBox: StringBox = try typedBox(box, for: Date.self) + let string = stringBox.unbox() + + guard let dateBox = DateBox(iso8601: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected date string to be ISO8601-formatted." + )) + } + return dateBox.unbox() + case let .formatted(formatter): + let stringBox: StringBox = try typedBox(box, for: Date.self) + let string = stringBox.unbox() + + guard let dateBox = DateBox(xmlString: string, formatter: formatter) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Date string does not match format expected by formatter." + )) + } + return dateBox.unbox() + case let .custom(closure): + storage.push(container: box) + defer { storage.popContainer() } + return try closure(self) + } + } + + func unbox(_ box: Box) throws -> Data { + switch options.dataDecodingStrategy { + case .deferredToData: + storage.push(container: box) + defer { storage.popContainer() } + return try Data(from: self) + case .base64: + let stringBox: StringBox = try typedBox(box, for: Data.self) + let string = stringBox.unbox() + + guard let dataBox = DataBox(base64: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Encountered Data is not valid Base64" + )) + } + return dataBox.unbox() + case let .custom(closure): + storage.push(container: box) + defer { storage.popContainer() } + return try closure(self) + } + } + + func unbox(_ box: Box) throws -> URL { + let stringBox: StringBox = try typedBox(box, for: URL.self) + let string = stringBox.unbox() + + guard let urlBox = URLBox(xmlString: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Encountered Data is not valid Base64" + )) + } + + return urlBox.unbox() + } + + private struct TypeMismatch: Error {} + + func unbox(_ box: Box) throws -> T { + let decoded: T? + let type = T.self + + if type == Date.self || type == NSDate.self { + let date: Date = try unbox(box) + + decoded = date as? T + } else if type == Data.self || type == NSData.self { + let data: Data = try unbox(box) + decoded = data as? T + } else if type == URL.self || type == NSURL.self { + let data: URL = try unbox(box) + decoded = data as? T + } else if type == Decimal.self || type == NSDecimalNumber.self { + let decimal: Decimal = try unbox(box) + decoded = decimal as? T + } else if + type == String.self || type == NSString.self, + let str: String = try? unbox(box), let value = str as? T { + decoded = value + } else { + storage.push(container: box) + decoded = try type.init(from: self) + storage.popContainer() + } + + guard let result = decoded else { + throw DecodingError._typeMismatch( + at: codingPath, expectation: type, reality: box + ) + } + + return result + } +} diff --git a/Sources/XMLCoder/Encoder/DynamicNodeEncoding.swift b/Sources/XMLCoder/Encoder/DynamicNodeEncoding.swift new file mode 100644 index 00000000..dbe39a2a --- /dev/null +++ b/Sources/XMLCoder/Encoder/DynamicNodeEncoding.swift @@ -0,0 +1,24 @@ +// +// DynamicNodeEncoding.swift +// XMLCoder +// +// Created by Joseph Mattiello on 1/24/19. +// + +import Foundation + +public protocol DynamicNodeEncoding: Encodable { + static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding +} + +extension Array: DynamicNodeEncoding where Element: DynamicNodeEncoding { + public static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { + return Element.nodeEncoding(forKey: key) + } +} + +extension DynamicNodeEncoding where Self: Collection, Self.Iterator.Element: DynamicNodeEncoding { + public static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { + return Element.nodeEncoding(forKey: key) + } +} diff --git a/Sources/XMLCoder/Encoder/XMLEncoder.swift b/Sources/XMLCoder/Encoder/XMLEncoder.swift index eeb12d0a..708f5495 100644 --- a/Sources/XMLCoder/Encoder/XMLEncoder.swift +++ b/Sources/XMLCoder/Encoder/XMLEncoder.swift @@ -37,6 +37,7 @@ open class XMLEncoder { public enum NodeEncoding { case attribute case element + case both public static let `default`: NodeEncoding = .element } @@ -122,6 +123,18 @@ open class XMLEncoder { /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. case convertToSnakeCase + /// Capitalize the first letter only + /// `oneTwoThree` becomes `OneTwoThree` + case capitalized + + /// Uppercase ize all letters + /// `oneTwoThree` becomes `ONETWOTHREE` + case uppercased + + /// Lowercase all letters + /// `oneTwoThree` becomes `onetwothree` + case lowercased + /// Provide a custom conversion to the key in the encoded XML from the /// keys specified by the encoded types. /// The full path to the current encoding position is provided for @@ -186,29 +199,51 @@ open class XMLEncoder { }).joined(separator: "_") return result } + + static func _convertToCapitalized(_ stringKey: String) -> String { + return stringKey.capitalizingFirstLetter() + } + + static func _convertToLowercased(_ stringKey: String) -> String { + return stringKey.lowercased() + } + + static func _convertToUppercased(_ stringKey: String) -> String { + return stringKey.uppercased() + } } @available(*, deprecated, renamed: "NodeEncodingStrategy") public typealias NodeEncodingStrategies = NodeEncodingStrategy + public typealias XMLNodeEncoderClosure = ((CodingKey) -> XMLEncoder.NodeEncoding) + public typealias XMLEncodingClosure = (Encodable.Type, Encoder) -> XMLNodeEncoderClosure + /// Set of strategies to use for encoding of nodes. public enum NodeEncodingStrategy { /// Defer to `Encoder` for choosing an encoding. This is the default strategy. case deferredToEncoder /// Return a closure computing the desired node encoding for the value by its coding key. - case custom((Encodable.Type, Encoder) -> ((CodingKey) -> XMLEncoder.NodeEncoding)) + case custom(XMLEncodingClosure) + + func nodeEncodings(forType codableType: Encodable.Type, + with encoder: Encoder) -> ((CodingKey) -> XMLEncoder.NodeEncoding) { + return encoderClosure(codableType, encoder) + } - func nodeEncodings( - forType codableType: Encodable.Type, - with encoder: Encoder - ) -> ((CodingKey) -> XMLEncoder.NodeEncoding) { + var encoderClosure: XMLEncodingClosure { switch self { - case .deferredToEncoder: + case .deferredToEncoder: return NodeEncodingStrategy.defaultEncoder + case let .custom(closure): return closure + } + } + + static let defaultEncoder: XMLEncodingClosure = { codableType, _ in + guard let dynamicType = codableType as? DynamicNodeEncoding.Type else { return { _ in .default } - case let .custom(closure): - return closure(codableType, encoder) } + return dynamicType.nodeEncoding(forKey:) } } @@ -307,311 +342,3 @@ open class XMLEncoder { .data(using: .utf8, allowLossyConversion: true)! } } - -class XMLEncoderImplementation: Encoder { - // MARK: Properties - - /// The encoder's storage. - var storage: XMLEncodingStorage - - /// Options set on the top-level encoder. - let options: XMLEncoder.Options - - /// The path to the current point in encoding. - public var codingPath: [CodingKey] - - public var nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding] - - /// Contextual user-provided information for use during encoding. - public var userInfo: [CodingUserInfoKey: Any] { - return options.userInfo - } - - // MARK: - Initialization - - /// Initializes `self` with the given top-level encoder options. - init( - options: XMLEncoder.Options, - nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding], - codingPath: [CodingKey] = [] - ) { - self.options = options - storage = XMLEncodingStorage() - self.codingPath = codingPath - self.nodeEncodings = nodeEncodings - } - - /// Returns whether a new element can be encoded at this coding path. - /// - /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. - var canEncodeNewValue: Bool { - // Every time a new value gets encoded, the key it's encoded for is - // pushed onto the coding path (even if it's a nil key from an unkeyed container). - // At the same time, every time a container is requested, a new value - // gets pushed onto the storage stack. - // If there are more values on the storage stack than on the coding path, - // it means the value is requesting more than one container, which - // violates the precondition. - // - // This means that anytime something that can request a new container - // goes onto the stack, we MUST push a key onto the coding path. - // Things which will not request containers do not need to have the - // coding path extended for them (but it doesn't matter if it is, - // because they will not reach here). - return storage.count == codingPath.count - } - - // MARK: - Encoder Methods - - public func container(keyedBy _: Key.Type) -> KeyedEncodingContainer { - // If an existing keyed container was already requested, return that one. - let topContainer: SharedBox - if canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. - topContainer = storage.pushKeyedContainer() - } else { - guard let container = storage.lastContainer as? SharedBox else { - preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") - } - - topContainer = container - } - - let container = XMLKeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer) - return KeyedEncodingContainer(container) - } - - public func unkeyedContainer() -> UnkeyedEncodingContainer { - // If an existing unkeyed container was already requested, return that one. - let topContainer: SharedBox - if canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. - topContainer = storage.pushUnkeyedContainer() - } else { - guard let container = storage.lastContainer as? SharedBox else { - preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") - } - - topContainer = container - } - - return XMLUnkeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer) - } - - public func singleValueContainer() -> SingleValueEncodingContainer { - return self - } -} - -extension XMLEncoderImplementation: SingleValueEncodingContainer { - // MARK: - SingleValueEncodingContainer Methods - - func assertCanEncodeNewValue() { - precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") - } - - public func encodeNil() throws { - assertCanEncodeNewValue() - storage.push(container: box()) - } - - public func encode(_ value: Bool) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int8) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int16) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int32) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int64) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt8) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt16) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt32) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt64) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: String) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Float) throws { - assertCanEncodeNewValue() - try storage.push(container: box(value)) - } - - public func encode(_ value: Double) throws { - assertCanEncodeNewValue() - try storage.push(container: box(value)) - } - - public func encode(_ value: T) throws { - assertCanEncodeNewValue() - try storage.push(container: box(value)) - } -} - -extension XMLEncoderImplementation { - /// Returns the given value boxed in a container appropriate for pushing onto the container stack. - func box() -> SimpleBox { - return NullBox() - } - - func box(_ value: Bool) -> SimpleBox { - return BoolBox(value) - } - - func box(_ value: Decimal) -> SimpleBox { - return DecimalBox(value) - } - - func box(_ value: T) -> SimpleBox { - return IntBox(value) - } - - func box(_ value: T) -> SimpleBox { - return UIntBox(value) - } - - func box(_ value: T) throws -> SimpleBox { - guard value.isInfinite || value.isNaN else { - return FloatBox(value) - } - guard case let .convertToString(positiveInfinity: posInfString, - negativeInfinity: negInfString, - nan: nanString) = options.nonConformingFloatEncodingStrategy else { - throw EncodingError._invalidFloatingPointValue(value, at: codingPath) - } - if value == T.infinity { - return StringBox(posInfString) - } else if value == -T.infinity { - return StringBox(negInfString) - } else { - return StringBox(nanString) - } - } - - func box(_ value: String) -> SimpleBox { - return StringBox(value) - } - - func box(_ value: Date) throws -> Box { - switch options.dateEncodingStrategy { - case .deferredToDate: - try value.encode(to: self) - return storage.popContainer() - case .secondsSince1970: - return DateBox(value, format: .secondsSince1970) - case .millisecondsSince1970: - return DateBox(value, format: .millisecondsSince1970) - case .iso8601: - return DateBox(value, format: .iso8601) - case let .formatted(formatter): - return DateBox(value, format: .formatter(formatter)) - case let .custom(closure): - let depth = storage.count - try closure(value, self) - - guard storage.count > depth else { - return KeyedBox() - } - - return storage.popContainer() - } - } - - func box(_ value: Data) throws -> Box { - switch options.dataEncodingStrategy { - case .deferredToData: - try value.encode(to: self) - return storage.popContainer() - case .base64: - return DataBox(value, format: .base64) - case let .custom(closure): - let depth = storage.count - try closure(value, self) - - guard storage.count > depth else { - return KeyedBox() - } - - return storage.popContainer() - } - } - - func box(_ value: URL) -> SimpleBox { - return URLBox(value) - } - - func box(_ value: T) throws -> Box { - if T.self == Date.self || T.self == NSDate.self, - let value = value as? Date { - return try box(value) - } else if T.self == Data.self || T.self == NSData.self, - let value = value as? Data { - return try box(value) - } else if T.self == URL.self || T.self == NSURL.self, - let value = value as? URL { - return box(value) - } else if T.self == Decimal.self || T.self == NSDecimalNumber.self, - let value = value as? Decimal { - return box(value) - } - - let depth = storage.count - try value.encode(to: self) - - // The top container should be a new container. - guard storage.count > depth else { - return KeyedBox() - } - - let lastContainer = storage.popContainer() - - guard let sharedBox = lastContainer as? SharedBoxProtocol else { - return lastContainer - } - - return sharedBox.unbox() - } -} diff --git a/Sources/XMLCoder/Encoder/XMLEncoderImplementation+SingleValueEncodingContainer.swift b/Sources/XMLCoder/Encoder/XMLEncoderImplementation+SingleValueEncodingContainer.swift new file mode 100644 index 00000000..a10a4fe2 --- /dev/null +++ b/Sources/XMLCoder/Encoder/XMLEncoderImplementation+SingleValueEncodingContainer.swift @@ -0,0 +1,97 @@ +// +// XMLEncoder.swift +// XMLCoder +// +// Created by Shawn Moore on 11/22/17. +// Copyright © 2017 Shawn Moore. All rights reserved. +// + +import Foundation + +extension XMLEncoderImplementation: SingleValueEncodingContainer { + // MARK: - SingleValueEncodingContainer Methods + + func assertCanEncodeNewValue() { + precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") + } + + public func encodeNil() throws { + assertCanEncodeNewValue() + storage.push(container: box()) + } + + public func encode(_ value: Bool) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: String) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Float) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } + + public func encode(_ value: Double) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } + + public func encode(_ value: T) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } +} diff --git a/Sources/XMLCoder/Encoder/XMLEncoderImplementation.swift b/Sources/XMLCoder/Encoder/XMLEncoderImplementation.swift new file mode 100644 index 00000000..e0e710ee --- /dev/null +++ b/Sources/XMLCoder/Encoder/XMLEncoderImplementation.swift @@ -0,0 +1,229 @@ +// +// XMLEncoder.swift +// XMLCoder +// +// Created by Shawn Moore on 11/22/17. +// Copyright © 2017 Shawn Moore. All rights reserved. +// + +import Foundation + +class XMLEncoderImplementation: Encoder { + // MARK: Properties + + /// The encoder's storage. + var storage: XMLEncodingStorage + + /// Options set on the top-level encoder. + let options: XMLEncoder.Options + + /// The path to the current point in encoding. + public var codingPath: [CodingKey] + + public var nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding] + + /// Contextual user-provided information for use during encoding. + public var userInfo: [CodingUserInfoKey: Any] { + return options.userInfo + } + + // MARK: - Initialization + + /// Initializes `self` with the given top-level encoder options. + init( + options: XMLEncoder.Options, + nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding], + codingPath: [CodingKey] = [] + ) { + self.options = options + storage = XMLEncodingStorage() + self.codingPath = codingPath + self.nodeEncodings = nodeEncodings + } + + /// Returns whether a new element can be encoded at this coding path. + /// + /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. + var canEncodeNewValue: Bool { + // Every time a new value gets encoded, the key it's encoded for is + // pushed onto the coding path (even if it's a nil key from an unkeyed container). + // At the same time, every time a container is requested, a new value + // gets pushed onto the storage stack. + // If there are more values on the storage stack than on the coding path, + // it means the value is requesting more than one container, which + // violates the precondition. + // + // This means that anytime something that can request a new container + // goes onto the stack, we MUST push a key onto the coding path. + // Things which will not request containers do not need to have the + // coding path extended for them (but it doesn't matter if it is, + // because they will not reach here). + return storage.count == codingPath.count + } + + // MARK: - Encoder Methods + + public func container(keyedBy _: Key.Type) -> KeyedEncodingContainer { + // If an existing keyed container was already requested, return that one. + let topContainer: SharedBox + if canEncodeNewValue { + // We haven't yet pushed a container at this level; do so here. + topContainer = storage.pushKeyedContainer() + } else { + guard let container = storage.lastContainer as? SharedBox else { + preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") + } + + topContainer = container + } + + let container = XMLKeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer) + return KeyedEncodingContainer(container) + } + + public func unkeyedContainer() -> UnkeyedEncodingContainer { + // If an existing unkeyed container was already requested, return that one. + let topContainer: SharedBox + if canEncodeNewValue { + // We haven't yet pushed a container at this level; do so here. + topContainer = storage.pushUnkeyedContainer() + } else { + guard let container = storage.lastContainer as? SharedBox else { + preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") + } + + topContainer = container + } + + return XMLUnkeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer) + } + + public func singleValueContainer() -> SingleValueEncodingContainer { + return self + } +} + +extension XMLEncoderImplementation { + /// Returns the given value boxed in a container appropriate for pushing onto the container stack. + func box() -> SimpleBox { + return NullBox() + } + + func box(_ value: Bool) -> SimpleBox { + return BoolBox(value) + } + + func box(_ value: Decimal) -> SimpleBox { + return DecimalBox(value) + } + + func box(_ value: T) -> SimpleBox { + return IntBox(value) + } + + func box(_ value: T) -> SimpleBox { + return UIntBox(value) + } + + func box(_ value: T) throws -> SimpleBox { + guard value.isInfinite || value.isNaN else { + return FloatBox(value) + } + guard case let .convertToString(positiveInfinity: posInfString, + negativeInfinity: negInfString, + nan: nanString) = options.nonConformingFloatEncodingStrategy else { + throw EncodingError._invalidFloatingPointValue(value, at: codingPath) + } + if value == T.infinity { + return StringBox(posInfString) + } else if value == -T.infinity { + return StringBox(negInfString) + } else { + return StringBox(nanString) + } + } + + func box(_ value: String) -> SimpleBox { + return StringBox(value) + } + + func box(_ value: Date) throws -> Box { + switch options.dateEncodingStrategy { + case .deferredToDate: + try value.encode(to: self) + return storage.popContainer() + case .secondsSince1970: + return DateBox(value, format: .secondsSince1970) + case .millisecondsSince1970: + return DateBox(value, format: .millisecondsSince1970) + case .iso8601: + return DateBox(value, format: .iso8601) + case let .formatted(formatter): + return DateBox(value, format: .formatter(formatter)) + case let .custom(closure): + let depth = storage.count + try closure(value, self) + + guard storage.count > depth else { + return KeyedBox() + } + + return storage.popContainer() + } + } + + func box(_ value: Data) throws -> Box { + switch options.dataEncodingStrategy { + case .deferredToData: + try value.encode(to: self) + return storage.popContainer() + case .base64: + return DataBox(value, format: .base64) + case let .custom(closure): + let depth = storage.count + try closure(value, self) + + guard storage.count > depth else { + return KeyedBox() + } + + return storage.popContainer() + } + } + + func box(_ value: URL) -> SimpleBox { + return URLBox(value) + } + + func box(_ value: T) throws -> Box { + if T.self == Date.self || T.self == NSDate.self, + let value = value as? Date { + return try box(value) + } else if T.self == Data.self || T.self == NSData.self, + let value = value as? Data { + return try box(value) + } else if T.self == URL.self || T.self == NSURL.self, + let value = value as? URL { + return box(value) + } else if T.self == Decimal.self || T.self == NSDecimalNumber.self, + let value = value as? Decimal { + return box(value) + } + + let depth = storage.count + try value.encode(to: self) + + // The top container should be a new container. + guard storage.count > depth else { + return KeyedBox() + } + + let lastContainer = storage.popContainer() + + guard let sharedBox = lastContainer as? TypeErasedSharedBoxProtocol else { + return lastContainer + } + + return sharedBox.typeErasedUnbox() + } +} diff --git a/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift b/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift index 7279a6b7..f0e0a3b0 100644 --- a/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift +++ b/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift @@ -46,6 +46,18 @@ struct XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol { return XMLKey(stringValue: newKeyString, intValue: key.intValue) case let .custom(converter): return converter(codingPath + [key]) + case .capitalized: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToCapitalized(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .uppercased: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToUppercased(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .lowercased: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToLowercased(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) } } @@ -87,22 +99,39 @@ struct XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol { ) encoder.nodeEncodings.append(nodeEncodings) let box = try encode(encoder, value) - switch strategy(key) { - case .attribute: + + let mySelf = self + let attributeEncoder: (T, Key, Box) throws -> () = { value, key, box in guard let attribute = box as? SimpleBox else { throw EncodingError.invalidValue(value, EncodingError.Context( codingPath: [], debugDescription: "Complex values cannot be encoded as attributes." )) } - container.withShared { container in - container.attributes[_converted(key).stringValue] = attribute + mySelf.container.withShared { container in + container.attributes[mySelf._converted(key).stringValue] = attribute } - case .element: - container.withShared { container in - container.elements[_converted(key).stringValue] = box + } + + let elementEncoder: (T, Key, Box) throws -> () = { _, key, box in + mySelf.container.withShared { container in + container.elements[mySelf._converted(key).stringValue] = box } } + + defer { + self = mySelf + } + + switch strategy(key) { + case .attribute: + try attributeEncoder(value, key, box) + case .element: + try elementEncoder(value, key, box) + case .both: + try attributeEncoder(value, key, box) + try elementEncoder(value, key, box) + } } public mutating func nestedContainer( diff --git a/Tests/XMLCoderTests/Auxiliary/String+ExtensionsTests.swift b/Tests/XMLCoderTests/Auxiliary/String+ExtensionsTests.swift new file mode 100644 index 00000000..369778c7 --- /dev/null +++ b/Tests/XMLCoderTests/Auxiliary/String+ExtensionsTests.swift @@ -0,0 +1,43 @@ +// +// String+ExtensionsTests.swift +// XMLCoderTests +// +// Created by Joseph Mattiello on 1/31/19. +// + +import XCTest +@testable import XMLCoder + +class StringExtensionsTests: XCTestCase { + func testCapitalizingFirstLetter() { + let testStrings = ["lower", "UPPER", "snake_cased", "RaNdOm", ""] + let expected = ["Lower", "UPPER", "Snake_cased", "RaNdOm", ""] + let converted = testStrings.map { $0.capitalizingFirstLetter() } + + XCTAssertEqual(expected, converted) + + // Mutable version + let mutated: [String] = testStrings.map { s in + var s = s + s.capitalizeFirstLetter() + return s + } + XCTAssertEqual(expected, mutated) + } + + func testLowercasingFirstLetter() { + let testStrings = ["lower", "UPPER", "snake_cased", "RaNdOm", ""] + let expected = ["lower", "uPPER", "snake_cased", "raNdOm", ""] + let converted = testStrings.map { $0.lowercasingFirstLetter() } + + XCTAssertEqual(expected, converted) + + // Mutable version + let mutated: [String] = testStrings.map { s in + var s = s + s.lowercaseFirstLetter() + return s + } + XCTAssertEqual(expected, mutated) + } +} diff --git a/Tests/XMLCoderTests/BooksTest.swift b/Tests/XMLCoderTests/BooksTest.swift index d8e6c8d9..de1b5785 100644 --- a/Tests/XMLCoderTests/BooksTest.swift +++ b/Tests/XMLCoderTests/BooksTest.swift @@ -11,15 +11,15 @@ import XCTest @testable import XMLCoder private let bookXML = """ - + Gambardella, Matthew - XML Developer's Guide + An in-depth look at creating applications + with XML. Computer 44.95 2000-10-01 - An in-depth look at creating applications - with XML. + XML Developer's Guide """.data(using: .utf8)! @@ -155,7 +155,7 @@ private struct Catalog: Codable, Equatable { } } -private struct Book: Codable, Equatable { +private struct Book: Codable, Equatable, DynamicNodeEncoding { var id: String var author: String var title: String @@ -169,6 +169,15 @@ private struct Book: Codable, Equatable { case publishDate = "publish_date" } + + static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { + switch key { + case CodingKeys.id: + return .attribute + default: + return .element + } + } } private enum Genre: String, Codable { @@ -191,6 +200,8 @@ final class BooksTest: XCTestCase { let decoder = XMLDecoder() let encoder = XMLEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + decoder.dateDecodingStrategy = .formatted(formatter) encoder.dateEncodingStrategy = .formatted(formatter) @@ -198,11 +209,19 @@ final class BooksTest: XCTestCase { XCTAssertEqual(book1.publishDate, Date(timeIntervalSince1970: 970_358_400)) + XCTAssertEqual(book1.title, "XML Developer's Guide") + let data = try encoder.encode(book1, withRootKey: "book", header: XMLHeader(version: 1.0, encoding: "UTF-8")) let book2 = try decoder.decode(Book.self, from: data) + XCTAssertEqual(book1, book2) + + // Test string equivlancy + let encodedXML = String(data: data, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines) + let originalXML = String(data: bookXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines) + XCTAssertEqual(encodedXML, originalXML) } func testCatalogXML() throws { diff --git a/Tests/XMLCoderTests/BreakfastTest.swift b/Tests/XMLCoderTests/BreakfastTest.swift index 17ebea24..dba56019 100644 --- a/Tests/XMLCoderTests/BreakfastTest.swift +++ b/Tests/XMLCoderTests/BreakfastTest.swift @@ -17,7 +17,7 @@ private let xml = """ $5.95 Two of our famous Belgian Waffles with plenty of real maple syrup - /Users/vincent/Projects/Swift/XMLCoder/Tests/XMLCoderTests/NotesTest.swift + Strawberry Belgian Waffles $7.95 diff --git a/Tests/XMLCoderTests/DynamicNodeEncodingTest.swift b/Tests/XMLCoderTests/DynamicNodeEncodingTest.swift new file mode 100644 index 00000000..313ed4a5 --- /dev/null +++ b/Tests/XMLCoderTests/DynamicNodeEncodingTest.swift @@ -0,0 +1,210 @@ +// +// DynamicNodeEncodingTest.swift +// XMLCoderTests +// +// Created by Joseph Mattiello on 1/23/19. +// + +import Foundation +import XCTest +@testable import XMLCoder + +private let libraryXMLYN = """ + + + + 123 + Cat in the Hat + Kids + Wildlife + + + 789 + 1984 + Classics + News + + +""".data(using: .utf8)! + +private let libraryXMLTrueFalse = """ + + + + + Kids + + + Wildlife + + 123 + Cat in the Hat + + + + Classics + + + News + + 456 + 1984 + + 2 + +""" + +private struct Library: Codable, Equatable { + let count: Int + let books: [Book] + + private enum CodingKeys: String, CodingKey { + case count + case books = "book" + } +} + +private struct Book: Codable, Equatable, DynamicNodeEncoding { + let id: UInt + let title: String + let categories: [Category] + + private enum CodingKeys: String, CodingKey { + case id + case title + case categories = "category" + } + + static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { + switch key { + case Book.CodingKeys.id: return .both + default: return .element + } + } +} + +private struct Category: Codable, Equatable, DynamicNodeEncoding { + let main: Bool + let value: String + + private enum CodingKeys: String, CodingKey { + case main + case value + } + + static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding { + switch key { + case Category.CodingKeys.main: + return .attribute + default: + return .element + } + } +} + +final class DynamicNodeEncodingTest: XCTestCase { + func testEncode() throws { + let book1 = Book(id: 123, + title: "Cat in the Hat", + categories: [ + Category(main: true, value: "Kids"), + Category(main: false, value: "Wildlife"), + ]) + + let book2 = Book(id: 456, + title: "1984", + categories: [ + Category(main: true, value: "Classics"), + Category(main: false, value: "News"), + ]) + + let library = Library(count: 2, books: [book1, book2]) + let encoder = XMLEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + + let header = XMLHeader(version: 1.0, encoding: "UTF-8") + let encoded = try encoder.encode(library, withRootKey: "library", header: header) + let xmlString = String(data: encoded, encoding: .utf8) + print(xmlString!) + print(libraryXMLTrueFalse) + XCTAssertEqual(xmlString, libraryXMLTrueFalse) + } + + func testDecode() throws { + let decoder = XMLDecoder() + decoder.errorContextLength = 10 + + let library = try decoder.decode(Library.self, from: libraryXMLYN) + XCTAssertEqual(library.books.count, 2) + XCTAssertEqual(library.count, 2) + + let book1 = library.books[0] + XCTAssertEqual(book1.id, 123) + XCTAssertEqual(book1.title, "Cat in the Hat") + + let book1Categories = book1.categories + XCTAssertEqual(book1Categories.count, 2) + XCTAssertEqual(book1Categories[0].value, "Kids") + XCTAssertTrue(book1Categories[0].main) + XCTAssertEqual(book1Categories[1].value, "Wildlife") + XCTAssertFalse(book1Categories[1].main) + + let book2 = library.books[1] + // XCTAssertEqual(book2.id, 456) + XCTAssertEqual(book2.title, "1984") + + let book2Categories = book2.categories + XCTAssertEqual(book2Categories.count, 2) + XCTAssertEqual(book2Categories[0].value, "Classics") + XCTAssertTrue(book2Categories[0].main) + XCTAssertEqual(book2Categories[1].value, "News") + XCTAssertFalse(book2Categories[1].main) + } + + func testEncodeDecode() throws { + let decoder = XMLDecoder() + decoder.errorContextLength = 10 + + let encoder = XMLEncoder() + encoder.outputFormatting = [.prettyPrinted] + + let library = try decoder.decode(Library.self, from: libraryXMLYN) + XCTAssertEqual(library.books.count, 2) + XCTAssertEqual(library.count, 2) + + let book1 = library.books[0] + XCTAssertEqual(book1.id, 123) + XCTAssertEqual(book1.title, "Cat in the Hat") + + let book1Categories = book1.categories + XCTAssertEqual(book1Categories.count, 2) + XCTAssertEqual(book1Categories[0].value, "Kids") + XCTAssertTrue(book1Categories[0].main) + XCTAssertEqual(book1Categories[1].value, "Wildlife") + XCTAssertFalse(book1Categories[1].main) + + let book2 = library.books[1] + // XCTAssertEqual(book2.id, 456) + XCTAssertEqual(book2.title, "1984") + + let book2Categories = book2.categories + XCTAssertEqual(book2Categories.count, 2) + XCTAssertEqual(book2Categories[0].value, "Classics") + XCTAssertTrue(book2Categories[0].main) + XCTAssertEqual(book2Categories[1].value, "News") + XCTAssertFalse(book2Categories[1].main) + + let data = try encoder.encode(library, withRootKey: "library", + header: XMLHeader(version: 1.0, + encoding: "UTF-8")) + print(String(data: data, encoding: .utf8)!) + let library2 = try decoder.decode(Library.self, from: data) + XCTAssertEqual(library, library2) + } + + static var allTests = [ + ("testEncode", testEncode), + ("testDecode", testDecode), + ("testEncodeDecode", testEncodeDecode), + ] +} diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 7ce1a2dc..d9fb784a 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -24,6 +24,13 @@ A61DCCD821DF9CA200C0A19D /* ClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61DCCD621DF8DB300C0A19D /* ClassTests.swift */; }; A61FE03921E4D60B0015D993 /* UnkeyedIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61FE03721E4D4F10015D993 /* UnkeyedIntTests.swift */; }; A61FE03C21E4EAB10015D993 /* KeyedIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */; }; + B34B3C08220381AC00BCBA30 /* String+ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34B3C07220381AB00BCBA30 /* String+ExtensionsTests.swift */; }; + B35157CE21F986DD009CA0CC /* DynamicNodeEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */; }; + B3BE1D612202C1F600259831 /* DynamicNodeEncodingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */; }; + B3BE1D632202CB1400259831 /* XMLEncoderImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D622202CB1400259831 /* XMLEncoderImplementation.swift */; }; + B3BE1D652202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D642202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift */; }; + B3BE1D682202CBF800259831 /* XMLDecoderImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D662202CBF800259831 /* XMLDecoderImplementation.swift */; }; + B3BE1D692202CBF800259831 /* XMLDecoderImplementation+SingleValueDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D672202CBF800259831 /* XMLDecoderImplementation+SingleValueDecodingContainer.swift */; }; BF63EF0021CCDED2001D38C5 /* XMLStackParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EEFF21CCDED2001D38C5 /* XMLStackParserTests.swift */; }; BF63EF0621CD7A74001D38C5 /* URLBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0521CD7A74001D38C5 /* URLBox.swift */; }; BF63EF0821CD7AF8001D38C5 /* URLBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */; }; @@ -126,6 +133,13 @@ A61DCCD621DF8DB300C0A19D /* ClassTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassTests.swift; sourceTree = ""; }; A61FE03721E4D4F10015D993 /* UnkeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnkeyedIntTests.swift; sourceTree = ""; }; A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedIntTests.swift; sourceTree = ""; }; + B34B3C07220381AB00BCBA30 /* String+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+ExtensionsTests.swift"; sourceTree = ""; }; + B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncoding.swift; sourceTree = ""; }; + B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncodingTest.swift; sourceTree = ""; }; + B3BE1D622202CB1400259831 /* XMLEncoderImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLEncoderImplementation.swift; sourceTree = ""; }; + B3BE1D642202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XMLEncoderImplementation+SingleValueEncodingContainer.swift"; sourceTree = ""; }; + B3BE1D662202CBF800259831 /* XMLDecoderImplementation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLDecoderImplementation.swift; sourceTree = ""; }; + B3BE1D672202CBF800259831 /* XMLDecoderImplementation+SingleValueDecodingContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XMLDecoderImplementation+SingleValueDecodingContainer.swift"; sourceTree = ""; }; BF63EEFF21CCDED2001D38C5 /* XMLStackParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLStackParserTests.swift; sourceTree = ""; }; BF63EF0521CD7A74001D38C5 /* URLBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBox.swift; sourceTree = ""; }; BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBoxTests.swift; sourceTree = ""; }; @@ -237,6 +251,7 @@ BF63EEFF21CCDED2001D38C5 /* XMLStackParserTests.swift */, BF63EF6821D0FDB5001D38C5 /* XMLHeaderTests.swift */, BF63EF6A21D10284001D38C5 /* XMLElementTests.swift */, + B34B3C07220381AB00BCBA30 /* String+ExtensionsTests.swift */, ); path = Auxiliary; sourceTree = ""; @@ -325,10 +340,13 @@ children = ( OBJ_16 /* EncodingErrorExtension.swift */, OBJ_17 /* XMLEncoder.swift */, + B3BE1D622202CB1400259831 /* XMLEncoderImplementation.swift */, + B3BE1D642202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift */, OBJ_18 /* XMLEncodingStorage.swift */, OBJ_19 /* XMLKeyedEncodingContainer.swift */, OBJ_20 /* XMLReferencingEncoder.swift */, OBJ_21 /* XMLUnkeyedEncodingContainer.swift */, + B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */, ); path = Encoder; sourceTree = ""; @@ -360,6 +378,7 @@ OBJ_38 /* RelationshipsTest.swift */, BF63EF1D21CEC99A001D38C5 /* BenchmarkTests.swift */, D1FC040421C7EF8200065B43 /* RJISample.swift */, + B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */, A61DCCD621DF8DB300C0A19D /* ClassTests.swift */, D14D8A8521F1D6B300B0D31A /* SingleChildTests.swift */, ); @@ -415,6 +434,8 @@ children = ( OBJ_10 /* DecodingErrorExtension.swift */, OBJ_11 /* XMLDecoder.swift */, + B3BE1D662202CBF800259831 /* XMLDecoderImplementation.swift */, + B3BE1D672202CBF800259831 /* XMLDecoderImplementation+SingleValueDecodingContainer.swift */, OBJ_12 /* XMLDecodingStorage.swift */, OBJ_13 /* XMLKeyedDecodingContainer.swift */, OBJ_14 /* XMLUnkeyedDecodingContainer.swift */, @@ -534,21 +555,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( + B3BE1D632202CB1400259831 /* XMLEncoderImplementation.swift in Sources */, BF9457D621CBB59E005ACFDE /* FloatBox.swift in Sources */, BF9457B721CBB4DB005ACFDE /* XMLHeader.swift in Sources */, + B3BE1D692202CBF800259831 /* XMLDecoderImplementation+SingleValueDecodingContainer.swift in Sources */, BF9457BB21CBB4DB005ACFDE /* XMLKey.swift in Sources */, OBJ_48 /* DecodingErrorExtension.swift in Sources */, BF63EF1821CEB6BD001D38C5 /* SharedBox.swift in Sources */, + B3BE1D682202CBF800259831 /* XMLDecoderImplementation.swift in Sources */, BF9457DB21CBB5D2005ACFDE /* DateBox.swift in Sources */, BF63EF0621CD7A74001D38C5 /* URLBox.swift in Sources */, OBJ_49 /* XMLDecoder.swift in Sources */, OBJ_50 /* XMLDecodingStorage.swift in Sources */, + B3BE1D652202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift in Sources */, BF9457D521CBB59E005ACFDE /* UIntBox.swift in Sources */, OBJ_51 /* XMLKeyedDecodingContainer.swift in Sources */, OBJ_52 /* XMLUnkeyedDecodingContainer.swift in Sources */, OBJ_53 /* EncodingErrorExtension.swift in Sources */, BF9457B921CBB4DB005ACFDE /* XMLStackParser.swift in Sources */, OBJ_54 /* XMLEncoder.swift in Sources */, + B35157CE21F986DD009CA0CC /* DynamicNodeEncoding.swift in Sources */, BF9457BA21CBB4DB005ACFDE /* ISO8601DateFormatter.swift in Sources */, OBJ_55 /* XMLEncodingStorage.swift in Sources */, BF9457A921CBB498005ACFDE /* KeyedBox.swift in Sources */, @@ -611,6 +637,8 @@ OBJ_85 /* NodeEncodingStrategyTests.swift in Sources */, OBJ_86 /* NoteTest.swift in Sources */, BF63EF0C21CD7F28001D38C5 /* EmptyTests.swift in Sources */, + B34B3C08220381AC00BCBA30 /* String+ExtensionsTests.swift in Sources */, + B3BE1D612202C1F600259831 /* DynamicNodeEncodingTest.swift in Sources */, BF9457F721CBB6BC005ACFDE /* DataTests.swift in Sources */, BF9457EE21CBB6BC005ACFDE /* IntTests.swift in Sources */, BF8171F221D3D03E00901EB0 /* SharedBoxTests.swift in Sources */,