diff --git a/Sources/XMLCoder/Encoder/EncodingErrorExtension.swift b/Sources/XMLCoder/Encoder/EncodingErrorExtension.swift index f2b7eebe..077f0218 100644 --- a/Sources/XMLCoder/Encoder/EncodingErrorExtension.swift +++ b/Sources/XMLCoder/Encoder/EncodingErrorExtension.swift @@ -8,9 +8,7 @@ import Foundation -//===----------------------------------------------------------------------===// -// Error Utilities -//===----------------------------------------------------------------------===// +/// Error Utilities internal extension EncodingError { /// Returns a `.invalidValue` error describing the given invalid floating-point value. /// diff --git a/Sources/XMLCoder/Encoder/XMLEncoder.swift b/Sources/XMLCoder/Encoder/XMLEncoder.swift index 879f0fb3..2343539f 100644 --- a/Sources/XMLCoder/Encoder/XMLEncoder.swift +++ b/Sources/XMLCoder/Encoder/XMLEncoder.swift @@ -374,476 +374,11 @@ internal class _XMLEncoder: Encoder { } } -// MARK: - Encoding Containers - -fileprivate struct _XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol { - typealias Key = K - - // MARK: Properties - - /// A reference to the encoder we're writing to. - private let encoder: _XMLEncoder - - /// A reference to the container we're writing to. - private let container: NSMutableDictionary - - /// The path of coding keys taken to get to this point in encoding. - public private(set) var codingPath: [CodingKey] - - // MARK: - Initialization - - /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { - self.encoder = encoder - self.codingPath = codingPath - self.container = container - } - - // MARK: - Coding Path Operations - - private func _converted(_ key: CodingKey) -> CodingKey { - switch encoder.options.keyEncodingStrategy { - case .useDefaultKeys: - return key - case .convertToSnakeCase: - let newKeyString = XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue) - return _XMLKey(stringValue: newKeyString, intValue: key.intValue) - case let .custom(converter): - return converter(codingPath + [key]) - } - } - - // MARK: - KeyedEncodingContainerProtocol Methods - - public mutating func encodeNil(forKey key: Key) throws { - container[_converted(key).stringValue] = NSNull() - } - - public mutating func encode(_ value: Bool, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: Int, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: Int8, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: Int16, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: Int32, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: Int64, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: UInt, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: UInt8, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: UInt16, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: UInt32, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: UInt64, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: String, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = encoder.box(value) - } - } - - public mutating func encode(_ value: Float, forKey key: Key) throws { - // Since the float may be invalid and throw, the coding path needs to contain this key. - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - container[_converted(key).stringValue] = try encoder.box(value) - } - - public mutating func encode(_ value: Double, forKey key: Key) throws { - // Since the double may be invalid and throw, the coding path needs to contain this key. - encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = try encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = try encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = try encoder.box(value) - } - } - - public mutating func encode(_ value: T, forKey key: Key) throws { - guard let strategy = self.encoder.nodeEncodings.last else { - preconditionFailure("Attempt to access node encoding strategy from empty stack.") - } - encoder.codingPath.append(key) - let nodeEncodings = encoder.options.nodeEncodingStrategy.nodeEncodings( - forType: T.self, - with: encoder - ) - encoder.nodeEncodings.append(nodeEncodings) - defer { - _ = self.encoder.nodeEncodings.removeLast() - self.encoder.codingPath.removeLast() - } - switch strategy(key) { - case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = try encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = try encoder.box(value) - container[_XMLElement.attributesKey] = attributesContainer - } - case .element: - container[_converted(key).stringValue] = try encoder.box(value) - } - } - - public mutating func nestedContainer(keyedBy _: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { - let dictionary = NSMutableDictionary() - self.container[_converted(key).stringValue] = dictionary - - codingPath.append(key) - defer { self.codingPath.removeLast() } - - let container = _XMLKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) - return KeyedEncodingContainer(container) - } - - public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let array = NSMutableArray() - container[_converted(key).stringValue] = array - - codingPath.append(key) - defer { self.codingPath.removeLast() } - return _XMLUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) - } - - public mutating func superEncoder() -> Encoder { - return _XMLReferencingEncoder(referencing: encoder, key: _XMLKey.super, convertedKey: _converted(_XMLKey.super), wrapping: container) - } - - public mutating func superEncoder(forKey key: Key) -> Encoder { - return _XMLReferencingEncoder(referencing: encoder, key: key, convertedKey: _converted(key), wrapping: container) - } -} - -fileprivate struct _XMLUnkeyedEncodingContainer: UnkeyedEncodingContainer { - // MARK: Properties - - /// A reference to the encoder we're writing to. - private let encoder: _XMLEncoder - - /// A reference to the container we're writing to. - private let container: NSMutableArray - - /// The path of coding keys taken to get to this point in encoding. - public private(set) var codingPath: [CodingKey] - - /// The number of elements encoded into the container. - public var count: Int { - return container.count - } - - // MARK: - Initialization - - /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { - self.encoder = encoder - self.codingPath = codingPath - self.container = container - } - - // MARK: - UnkeyedEncodingContainer Methods - - public mutating func encodeNil() throws { container.add(NSNull()) } - public mutating func encode(_ value: Bool) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: Int) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: Int8) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: Int16) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: Int32) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: Int64) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: UInt) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: UInt8) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: UInt16) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: UInt32) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: UInt64) throws { container.add(encoder.box(value)) } - public mutating func encode(_ value: String) throws { container.add(encoder.box(value)) } - - public mutating func encode(_ value: Float) throws { - // Since the float may be invalid and throw, the coding path needs to contain this key. - encoder.codingPath.append(_XMLKey(index: count)) - defer { self.encoder.codingPath.removeLast() } - container.add(try encoder.box(value)) - } - - public mutating func encode(_ value: Double) throws { - // Since the double may be invalid and throw, the coding path needs to contain this key. - encoder.codingPath.append(_XMLKey(index: count)) - defer { self.encoder.codingPath.removeLast() } - container.add(try encoder.box(value)) - } - - public mutating func encode(_ value: T) throws { - encoder.codingPath.append(_XMLKey(index: count)) - let nodeEncodings = encoder.options.nodeEncodingStrategy.nodeEncodings( - forType: T.self, - with: encoder - ) - encoder.nodeEncodings.append(nodeEncodings) - defer { - _ = self.encoder.nodeEncodings.removeLast() - self.encoder.codingPath.removeLast() - } - container.add(try encoder.box(value)) - } - - public mutating func nestedContainer(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer { - codingPath.append(_XMLKey(index: count)) - defer { self.codingPath.removeLast() } - - let dictionary = NSMutableDictionary() - self.container.add(dictionary) - - let container = _XMLKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) - return KeyedEncodingContainer(container) - } - - public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - codingPath.append(_XMLKey(index: count)) - defer { self.codingPath.removeLast() } - - let array = NSMutableArray() - container.add(array) - return _XMLUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) - } - - public mutating func superEncoder() -> Encoder { - return _XMLReferencingEncoder(referencing: encoder, at: container.count, wrapping: container) - } -} - extension _XMLEncoder: SingleValueEncodingContainer { // MARK: - SingleValueEncodingContainer Methods - - fileprivate func assertCanEncodeNewValue() { - precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") + + internal func assertCanEncodeNewValue() { + precondition(self.canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") } public func encodeNil() throws { @@ -929,19 +464,19 @@ extension _XMLEncoder: SingleValueEncodingContainer { extension _XMLEncoder { /// Returns the given value boxed in a container appropriate for pushing onto the container stack. - fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) } - + internal func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: Int) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } + internal func box(_ value: String) -> NSObject { return NSString(string: value) } + internal func box(_ value: Float) throws -> NSObject { if value.isInfinite || value.isNaN { guard case let .convertToString(positiveInfinity: posInfString, negativeInfinity: negInfString, nan: nanString) = options.nonConformingFloatEncodingStrategy else { @@ -1021,13 +556,13 @@ extension _XMLEncoder { return storage.popContainer() as NSObject } } - - fileprivate func box(_ value: T) throws -> NSObject { - return try box_(value) ?? NSDictionary() + + internal func box(_ value: T) throws -> NSObject { + return try self.box_(value) ?? NSDictionary() } // This method is called "box_" instead of "box" to disambiguate it from the overloads. Because the return type here is different from all of the "box" overloads (and is more general), any "box" calls in here would call back into "box" recursively instead of calling the appropriate overload, which is not what we want. - fileprivate func box_(_ value: T) throws -> NSObject? { + internal func box_(_ value: T) throws -> NSObject? { if T.self == Date.self || T.self == NSDate.self { return try box((value as! Date)) } else if T.self == Data.self || T.self == NSData.self { diff --git a/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift b/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift new file mode 100644 index 00000000..6de04ac7 --- /dev/null +++ b/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift @@ -0,0 +1,196 @@ +// +// XMLKeyedEncodingContainer.swift +// XMLCoder +// +// Created by Vincent Esche on 11/20/18. +// + +import Foundation + +internal struct _XMLKeyedEncodingContainer : KeyedEncodingContainerProtocol { + typealias Key = K + + // MARK: Properties + + /// A reference to the encoder we're writing to. + private let encoder: _XMLEncoder + + /// A reference to the container we're writing to. + private let container: NSMutableDictionary + + /// The path of coding keys taken to get to this point in encoding. + private(set) public var codingPath: [CodingKey] + + // MARK: - Initialization + + /// Initializes `self` with the given references. + internal init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - Coding Path Operations + + private func _converted(_ key: CodingKey) -> CodingKey { + switch encoder.options.keyEncodingStrategy { + case .useDefaultKeys: + return key + case .convertToSnakeCase: + let newKeyString = XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue) + return _XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .custom(let converter): + return converter(codingPath + [key]) + } + } + + // MARK: - KeyedEncodingContainerProtocol Methods + + public mutating func encodeNil(forKey key: Key) throws { + self.container[_converted(key).stringValue] = NSNull() + } + + public mutating func encode(_ value: Bool, forKey key: Key) throws { + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: + if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + attributesContainer[_converted(key).stringValue] = self.encoder.box(value) + } else { + let attributesContainer = NSMutableDictionary() + attributesContainer[_converted(key).stringValue] = self.encoder.box(value) + self.container[_XMLElement.attributesKey] = attributesContainer + } + case .element: + self.container[_converted(key).stringValue] = self.encoder.box(value) + } + } + + public mutating func encode(_ value: T, forKey key: Key) throws { + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: + if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) + } else { + let attributesContainer = NSMutableDictionary() + attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) + self.container[_XMLElement.attributesKey] = attributesContainer + } + case .element: + self.container[_converted(key).stringValue] = try self.encoder.box(value) + } + } + + public mutating func encode(_ value: String, forKey key: Key) throws { + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: + if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + attributesContainer[_converted(key).stringValue] = self.encoder.box(value) + } else { + let attributesContainer = NSMutableDictionary() + attributesContainer[_converted(key).stringValue] = self.encoder.box(value) + self.container[_XMLElement.attributesKey] = attributesContainer + } + case .element: + self.container[_converted(key).stringValue] = self.encoder.box(value) + } + } + + public mutating func encode(_ value: Float, forKey key: Key) throws { + // Since the float may be invalid and throw, the coding path needs to contain this key. + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + self.container[_converted(key).stringValue] = try self.encoder.box(value) + } + + public mutating func encode(_ value: Double, forKey key: Key) throws { + // Since the double may be invalid and throw, the coding path needs to contain this key. + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: + if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) + } else { + let attributesContainer = NSMutableDictionary() + attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) + self.container[_XMLElement.attributesKey] = attributesContainer + } + case .element: + self.container[_converted(key).stringValue] = try self.encoder.box(value) + } + } + + public mutating func encode(_ value: T, forKey key: Key) throws { + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + self.encoder.codingPath.append(key) + let nodeEncodings = self.encoder.options.nodeEncodingStrategy.nodeEncodings( + forType: T.self, + with: self.encoder + ) + self.encoder.nodeEncodings.append(nodeEncodings) + defer { + let _ = self.encoder.nodeEncodings.removeLast() + self.encoder.codingPath.removeLast() + } + switch strategy(key) { + case .attribute: + if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) + } else { + let attributesContainer = NSMutableDictionary() + attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) + self.container[_XMLElement.attributesKey] = attributesContainer + } + case .element: + self.container[_converted(key).stringValue] = try self.encoder.box(value) + } + } + + public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { + let dictionary = NSMutableDictionary() + self.container[_converted(key).stringValue] = dictionary + + self.codingPath.append(key) + defer { self.codingPath.removeLast() } + + let container = _XMLKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + let array = NSMutableArray() + self.container[_converted(key).stringValue] = array + + self.codingPath.append(key) + defer { self.codingPath.removeLast() } + return _XMLUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) + } + + public mutating func superEncoder() -> Encoder { + return _XMLReferencingEncoder(referencing: self.encoder, key: _XMLKey.super, convertedKey: _converted(_XMLKey.super), wrapping: self.container) + } + + public mutating func superEncoder(forKey key: Key) -> Encoder { + return _XMLReferencingEncoder(referencing: self.encoder, key: key, convertedKey: _converted(key), wrapping: self.container) + } +} diff --git a/Sources/XMLCoder/Encoder/XMLUnkeyedEncodingContainer.swift b/Sources/XMLCoder/Encoder/XMLUnkeyedEncodingContainer.swift new file mode 100644 index 00000000..22c24c65 --- /dev/null +++ b/Sources/XMLCoder/Encoder/XMLUnkeyedEncodingContainer.swift @@ -0,0 +1,90 @@ +// +// XMLUnkeyedEncodingContainer.swift +// XMLCoder +// +// Created by Vincent Esche on 11/20/18. +// + +import Foundation + +internal struct _XMLUnkeyedEncodingContainer : UnkeyedEncodingContainer { + // MARK: Properties + + /// A reference to the encoder we're writing to. + private let encoder: _XMLEncoder + + /// A reference to the container we're writing to. + private let container: NSMutableArray + + /// The path of coding keys taken to get to this point in encoding. + private(set) public var codingPath: [CodingKey] + + /// The number of elements encoded into the container. + public var count: Int { + return self.container.count + } + + // MARK: - Initialization + + /// Initializes `self` with the given references. + internal init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - UnkeyedEncodingContainer Methods + + public mutating func encodeNil() throws { self.container.add(NSNull()) } + public mutating func encode(_ value: Bool) throws { self.container.add(self.encoder.box(value)) } + + public mutating func encode(_ value: T) throws { + try self.container.add(self.encoder.box(value)) + } + + public mutating func encode(_ value: String) throws { self.container.add(self.encoder.box(value)) } + + public mutating func encode(_ value: Float) throws { + // Since the float may be invalid and throw, the coding path needs to contain this key. + self.encoder.codingPath.append(_XMLKey(index: self.count)) + defer { self.encoder.codingPath.removeLast() } + self.container.add(try self.encoder.box(value)) + } + + public mutating func encode(_ value: Double) throws { + // Since the double may be invalid and throw, the coding path needs to contain this key. + self.encoder.codingPath.append(_XMLKey(index: self.count)) + defer { self.encoder.codingPath.removeLast() } + self.container.add(try self.encoder.box(value)) + } + + public mutating func encode(_ value: T) throws { + self.encoder.codingPath.append(_XMLKey(index: self.count)) + defer { self.encoder.codingPath.removeLast() } + self.container.add(try self.encoder.box(value)) + } + + public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { + self.codingPath.append(_XMLKey(index: self.count)) + defer { self.codingPath.removeLast() } + + let dictionary = NSMutableDictionary() + self.container.add(dictionary) + + let container = _XMLKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + self.codingPath.append(_XMLKey(index: self.count)) + defer { self.codingPath.removeLast() } + + let array = NSMutableArray() + self.container.add(array) + return _XMLUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) + } + + public mutating func superEncoder() -> Encoder { + return _XMLReferencingEncoder(referencing: self.encoder, at: self.container.count, wrapping: self.container) + } +} diff --git a/Sources/XMLCoder/ISO8601DateFormatter.swift b/Sources/XMLCoder/ISO8601DateFormatter.swift index f8e05ae5..023c88ef 100644 --- a/Sources/XMLCoder/ISO8601DateFormatter.swift +++ b/Sources/XMLCoder/ISO8601DateFormatter.swift @@ -8,11 +8,11 @@ import Foundation -//===----------------------------------------------------------------------===// -// Shared ISO8601 Date Formatter -//===----------------------------------------------------------------------===// - -// NOTE: This value is implicitly lazy and _must_ be lazy. We're compiled against the latest SDK (w/ ISO8601DateFormatter), but linked against whichever Foundation the user has. ISO8601DateFormatter might not exist, so we better not hit this code path on an older OS. +/// Shared ISO8601 Date Formatter +/// NOTE: This value is implicitly lazy and _must_ be lazy. We're compiled +/// against the latest SDK (w/ ISO8601DateFormatter), but linked against +/// whichever Foundation the user has. ISO8601DateFormatter might not exist, so +/// we better not hit this code path on an older OS. @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) internal var _iso8601Formatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() diff --git a/Sources/XMLCoder/XMLKey.swift b/Sources/XMLCoder/XMLKey.swift index 0030ebd6..44bf33d1 100644 --- a/Sources/XMLCoder/XMLKey.swift +++ b/Sources/XMLCoder/XMLKey.swift @@ -8,13 +8,10 @@ import Foundation -//===----------------------------------------------------------------------===// -// Shared Key Types -//===----------------------------------------------------------------------===// - +/// Shared Key Types internal struct _XMLKey: CodingKey { - public var stringValue: String - public var intValue: Int? + public let stringValue: String + public let intValue: Int? public init?(stringValue: String) { self.stringValue = stringValue diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 2dd62498..da995d32 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -6,6 +6,6 @@ XCTMain([ testCase(BreakfastTest.allTests), testCase(NodeEncodingStrategyTests.allTests), testCase(BooksTest.allTests), - testCase(NoteTest.allTests) + testCase(NoteTest.allTests), testCase(PlantTest.allTests), ]) diff --git a/Tests/XMLCoderTests/NodeEncodingStrategyTests.swift b/Tests/XMLCoderTests/NodeEncodingStrategyTests.swift index 0b273ad0..f5ad503d 100644 --- a/Tests/XMLCoderTests/NodeEncodingStrategyTests.swift +++ b/Tests/XMLCoderTests/NodeEncodingStrategyTests.swift @@ -1,72 +1,83 @@ import XCTest @testable import XMLCoder -final class NodeEncodingStrategyTests: XCTestCase { - fileprivate struct SingleContainer: Encodable { - let element: Element +fileprivate struct SingleContainer: Encodable { + let element: Element - enum CodingKeys: String, CodingKey { - case element - } + enum CodingKeys: String, CodingKey { + case element } +} - fileprivate struct KeyedContainer: Encodable { - let elements: [String: Element] +fileprivate struct KeyedContainer: Encodable { + let elements: [String: Element] - enum CodingKeys: String, CodingKey { - case elements = "element" - } + enum CodingKeys: String, CodingKey { + case elements = "element" } +} - fileprivate struct UnkeyedContainer: Encodable { - let elements: [Element] +fileprivate struct UnkeyedContainer: Encodable { + let elements: [Element] - enum CodingKeys: String, CodingKey { - case elements = "element" - } + enum CodingKeys: String, CodingKey { + case elements = "element" } +} - fileprivate struct Element: Encodable { - let key: String = "value" - - enum CodingKeys: CodingKey { - case key - } +fileprivate struct Element: Encodable { + let key: String = "value" + let intKey: Int = 42 + let int8Key: Int8 = 42 + let doubleKey: Double = 42.42 + + enum CodingKeys: CodingKey { + case key + case intKey + case int8Key + case doubleKey + } - static func nodeEncoding(forKey _: CodingKey) -> XMLEncoder.NodeEncoding { - return .attribute - } + static func nodeEncoding(forKey _: CodingKey) -> XMLEncoder.NodeEncoding { + return .attribute } +} - fileprivate struct ComplexUnkeyedContainer: Encodable { - let elements: [ComplexElement] +fileprivate struct ComplexUnkeyedContainer: Encodable { + let elements: [ComplexElement] - enum CodingKeys: String, CodingKey { - case elements = "element" - } + enum CodingKeys: String, CodingKey { + case elements = "element" } +} - fileprivate struct ComplexElement: Encodable { - struct Key: Encodable { - let a: String - let b: String - let c: String - } +fileprivate struct ComplexElement: Encodable { + struct Key: Encodable { + let a: String + let b: String + let c: String + } - var key: Key = Key(a: "C", b: "B", c: "A") + var key: Key = Key(a: "C", b: "B", c: "A") - enum CodingKeys: CodingKey { - case key - } + enum CodingKeys: CodingKey { + case key + } - static func nodeEncoding(forKey _: CodingKey) -> XMLEncoder.NodeEncoding { - return .attribute - } + static func nodeEncoding(forKey _: CodingKey) -> XMLEncoder.NodeEncoding { + return .attribute } +} +final class NodeEncodingStrategyTests: XCTestCase { func testSingleContainer() { + guard #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) else { + return + } + let encoder = XMLEncoder() - encoder.outputFormatting = .prettyPrinted + + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] do { let container = SingleContainer(element: Element()) @@ -77,6 +88,9 @@ final class NodeEncodingStrategyTests: XCTestCase { """ + 42.42 + 42 + 42 value @@ -101,7 +115,7 @@ final class NodeEncodingStrategyTests: XCTestCase { let expected = """ - + """ XCTAssertEqual(xml, expected) @@ -111,8 +125,12 @@ final class NodeEncodingStrategyTests: XCTestCase { } func testKeyedContainer() { + guard #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) else { + return + } + let encoder = XMLEncoder() - encoder.outputFormatting = .prettyPrinted + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] do { let container = KeyedContainer(elements: ["first": Element()]) @@ -124,6 +142,9 @@ final class NodeEncodingStrategyTests: XCTestCase { + 42.42 + 42 + 42 value @@ -150,7 +171,7 @@ final class NodeEncodingStrategyTests: XCTestCase { """ - + """ @@ -161,8 +182,12 @@ final class NodeEncodingStrategyTests: XCTestCase { } func testUnkeyedContainer() { + guard #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) else { + return + } + let encoder = XMLEncoder() - encoder.outputFormatting = .prettyPrinted + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] do { let container = UnkeyedContainer(elements: [Element(), Element()]) @@ -173,9 +198,15 @@ final class NodeEncodingStrategyTests: XCTestCase { """ + 42.42 + 42 + 42 value + 42.42 + 42 + 42 value @@ -186,10 +217,10 @@ final class NodeEncodingStrategyTests: XCTestCase { } encoder.nodeEncodingStrategy = .custom { codableType, _ in - guard let barType = codableType as? Element.Type else { + guard codableType is [Element].Type else { return { _ in .default } } - return barType.nodeEncoding(forKey:) + return Element.nodeEncoding(forKey:) } do { @@ -200,8 +231,8 @@ final class NodeEncodingStrategyTests: XCTestCase { let expected = """ - - + + """ XCTAssertEqual(xml, expected) @@ -217,13 +248,13 @@ final class NodeEncodingStrategyTests: XCTestCase { ] func testItSortsKeysWhenEncodingAsElements() { - let encoder = XMLEncoder() - if #available(macOS 10.13, *) { - encoder.outputFormatting = [.sortedKeys, .prettyPrinted] - } else { + guard #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) else { return } + let encoder = XMLEncoder() + encoder.outputFormatting = [.sortedKeys, .prettyPrinted] + do { let container = ComplexUnkeyedContainer(elements: [ComplexElement()]) let data = try encoder.encode(container, withRootKey: "container") diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 1163a757..fb86b3b6 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -24,6 +24,8 @@ BF191DE621A467F600EAB791 /* RJITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF191DE421A467F600EAB791 /* RJITest.swift */; }; BF191DE821A467FF00EAB791 /* RJITest.xml in Resources */ = {isa = PBXBuildFile; fileRef = BF191DE521A467F600EAB791 /* RJITest.xml */; }; BFE1C58E21A4203200EA0458 /* NoteTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE1C58D21A4203200EA0458 /* NoteTest.swift */; }; + BFE1C58521A41AFB00EA0458 /* XMLUnkeyedEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE1C58421A41AFA00EA0458 /* XMLUnkeyedEncodingContainer.swift */; }; + BFE1C58721A41B3200EA0458 /* XMLKeyedEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE1C58621A41B3200EA0458 /* XMLKeyedEncodingContainer.swift */; }; BFE1C59121A4242300EA0458 /* NodeEncodingStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE1C58F21A4232100EA0458 /* NodeEncodingStrategyTests.swift */; }; D15ACF3F21A2C97F003238FD /* BreakfastTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15ACF3E21A2C97F003238FD /* BreakfastTest.swift */; }; D15ACF4221A407AA003238FD /* BooksTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15ACF3321A2C7F2003238FD /* BooksTest.swift */; }; @@ -69,6 +71,8 @@ BF191DE421A467F600EAB791 /* RJITest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RJITest.swift; sourceTree = ""; }; BF191DE521A467F600EAB791 /* RJITest.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = RJITest.xml; sourceTree = ""; }; BFE1C58D21A4203200EA0458 /* NoteTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteTest.swift; sourceTree = ""; }; + BFE1C58421A41AFA00EA0458 /* XMLUnkeyedEncodingContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = XMLUnkeyedEncodingContainer.swift; sourceTree = ""; tabWidth = 4; }; + BFE1C58621A41B3200EA0458 /* XMLKeyedEncodingContainer.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = XMLKeyedEncodingContainer.swift; sourceTree = ""; tabWidth = 4; }; BFE1C58F21A4232100EA0458 /* NodeEncodingStrategyTests.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = NodeEncodingStrategyTests.swift; sourceTree = ""; tabWidth = 4; }; D15ACF3321A2C7F2003238FD /* BooksTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BooksTest.swift; sourceTree = ""; }; D15ACF3E21A2C97F003238FD /* BreakfastTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreakfastTest.swift; sourceTree = ""; }; @@ -125,6 +129,8 @@ OBJ_16 /* EncodingErrorExtension.swift */, OBJ_17 /* XMLEncoder.swift */, OBJ_18 /* XMLEncodingStorage.swift */, + BFE1C58621A41B3200EA0458 /* XMLKeyedEncodingContainer.swift */, + BFE1C58421A41AFA00EA0458 /* XMLUnkeyedEncodingContainer.swift */, OBJ_19 /* XMLReferencingEncoder.swift */, ); path = Encoder; @@ -350,6 +356,7 @@ OBJ_50 /* DecodingErrorExtension.swift in Sources */, OBJ_51 /* XMLDecoder.swift in Sources */, OBJ_52 /* XMLDecodingStorage.swift in Sources */, + BFE1C58521A41AFB00EA0458 /* XMLUnkeyedEncodingContainer.swift in Sources */, OBJ_53 /* XMLKeyedDecodingContainer.swift in Sources */, OBJ_54 /* XMLUnkeyedDecodingContainer.swift in Sources */, OBJ_55 /* EncodingErrorExtension.swift in Sources */, @@ -357,6 +364,7 @@ OBJ_57 /* XMLEncodingStorage.swift in Sources */, OBJ_58 /* XMLReferencingEncoder.swift in Sources */, OBJ_59 /* ISO8601DateFormatter.swift in Sources */, + BFE1C58721A41B3200EA0458 /* XMLKeyedEncodingContainer.swift in Sources */, OBJ_60 /* XMLKey.swift in Sources */, OBJ_61 /* XMLStackParser.swift in Sources */, );