From 125f8df105b7d35dd326147425acb98d25768972 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Sat, 22 Dec 2018 19:22:57 +0100 Subject: [PATCH 1/4] Added `SharedBox` type for on-demand reference semantics --- Sources/XMLCoder/Auxiliaries/XMLElement.swift | 59 ++++--- Sources/XMLCoder/Box/Box.swift | 8 +- Sources/XMLCoder/Box/KeyedBox.swift | 7 +- Sources/XMLCoder/Box/SharedBox.swift | 36 +++++ Sources/XMLCoder/Box/UnkeyedBox.swift | 6 +- Sources/XMLCoder/Decoder/XMLDecoder.swift | 4 +- .../XMLCoder/Decoder/XMLDecodingStorage.swift | 8 +- .../Decoder/XMLKeyedDecodingContainer.swift | 150 ++++++++++++------ .../Decoder/XMLUnkeyedDecodingContainer.swift | 41 +++-- Sources/XMLCoder/Encoder/XMLEncoder.swift | 34 ++-- .../XMLCoder/Encoder/XMLEncodingStorage.swift | 24 +-- .../Encoder/XMLKeyedEncodingContainer.swift | 34 ++-- .../Encoder/XMLReferencingEncoder.swift | 24 +-- .../Encoder/XMLUnkeyedEncodingContainer.swift | 32 ++-- Tests/XMLCoderTests/Box/KeyedBoxTests.swift | 2 +- Tests/XMLCoderTests/Box/SharedBoxTests.swift | 63 ++++++++ Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift | 4 +- XMLCoder.xcodeproj/project.pbxproj | 10 +- 18 files changed, 394 insertions(+), 152 deletions(-) create mode 100644 Sources/XMLCoder/Box/SharedBox.swift create mode 100644 Tests/XMLCoderTests/Box/SharedBoxTests.swift diff --git a/Sources/XMLCoder/Auxiliaries/XMLElement.swift b/Sources/XMLCoder/Auxiliaries/XMLElement.swift index 30e82fb5..a33a92a6 100644 --- a/Sources/XMLCoder/Auxiliaries/XMLElement.swift +++ b/Sources/XMLCoder/Auxiliaries/XMLElement.swift @@ -66,6 +66,10 @@ struct _XMLElement { 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: @@ -93,36 +97,41 @@ struct _XMLElement { var elements: [String: Box] = [:] for (key, value) in self.elements { for child in value { - if let content = child.value { - switch elements[key] { - case let unkeyedBox as UnkeyedBox: - unkeyedBox.append(StringBox(content)) - elements[key] = unkeyedBox - case let keyedBox as StringBox: - elements[key] = UnkeyedBox([keyedBox, StringBox(content)]) - default: - elements[key] = StringBox(content) + let hasValue = child.value != nil + let hasElements = !child.elements.isEmpty + let hasAttributes = !child.attributes.isEmpty + + if hasValue || hasElements || hasAttributes { + if let content = child.value { + switch elements[key] { + case var unkeyedBox as UnkeyedBox: + unkeyedBox.append(StringBox(content)) + elements[key] = unkeyedBox + case let stringBox as StringBox: + elements[key] = UnkeyedBox([stringBox, StringBox(content)]) + default: + elements[key] = StringBox(content) + } } - } else if !child.elements.isEmpty || !child.attributes.isEmpty { - let content = child.flatten() - if let existingValue = elements[key] { - if let unkeyedBox = existingValue as? UnkeyedBox { - var boxes = unkeyedBox.unbox() - boxes.append(content) - elements[key] = UnkeyedBox(boxes) - } else { - elements[key] = UnkeyedBox([existingValue, content]) + if hasElements || hasAttributes { + let content = child.flatten() + switch elements[key] { + case var unkeyedBox as UnkeyedBox: + unkeyedBox.append(content) + elements[key] = unkeyedBox + case let box?: + elements[key] = UnkeyedBox([box, content]) + default: + elements[key] = content } - } else { - elements[key] = content } } else { switch elements[key] { - case let unkeyedBox as UnkeyedBox: + case var unkeyedBox as UnkeyedBox: unkeyedBox.append(NullBox()) elements[key] = unkeyedBox - case let keyedBox as StringBox: - elements[key] = UnkeyedBox([keyedBox, NullBox()]) + case let box?: + elements[key] = UnkeyedBox([box, NullBox()]) default: elements[key] = NullBox() } @@ -130,7 +139,9 @@ struct _XMLElement { } } - return KeyedBox(elements: elements, attributes: attributes) + let keyedBox = KeyedBox(elements: elements, attributes: attributes) + + return keyedBox } func toXMLString(with header: XMLHeader? = nil, withCDATA cdata: Bool, formatting: XMLEncoder.OutputFormatting, ignoreEscaping _: Bool = false) -> String { diff --git a/Sources/XMLCoder/Box/Box.swift b/Sources/XMLCoder/Box/Box.swift index 70ce4a8a..8dfdb829 100644 --- a/Sources/XMLCoder/Box/Box.swift +++ b/Sources/XMLCoder/Box/Box.swift @@ -13,4 +13,10 @@ protocol Box { } /// A box that only describes a single atomic value. -protocol SimpleBox: Box {} +protocol SimpleBox: Box { + // A simple tagging protocol, for now. +} + +protocol SharedBoxProtocol { + func unbox() -> Box +} diff --git a/Sources/XMLCoder/Box/KeyedBox.swift b/Sources/XMLCoder/Box/KeyedBox.swift index a6aa1691..1475c259 100644 --- a/Sources/XMLCoder/Box/KeyedBox.swift +++ b/Sources/XMLCoder/Box/KeyedBox.swift @@ -63,7 +63,7 @@ extension KeyedStorage: CustomStringConvertible } } -class KeyedBox { +struct KeyedBox { typealias Key = String typealias Attribute = SimpleBox typealias Element = Box @@ -90,6 +90,11 @@ class KeyedBox { self.attributes = Attributes(attributes) } + init(elements: Elements, attributes: Attributes) { + self.elements = elements + self.attributes = attributes + } + func unbox() -> (elements: Elements, attributes: Attributes) { return ( elements: elements, diff --git a/Sources/XMLCoder/Box/SharedBox.swift b/Sources/XMLCoder/Box/SharedBox.swift new file mode 100644 index 00000000..8106a15c --- /dev/null +++ b/Sources/XMLCoder/Box/SharedBox.swift @@ -0,0 +1,36 @@ +// +// SharedBox.swift +// XMLCoder +// +// Created by Vincent Esche on 12/22/18. +// + +import Foundation + +class SharedBox { + fileprivate var unboxed: Unboxed + + init(_ wrapped: Unboxed) { + unboxed = wrapped + } + + func withShared(_ body: (inout Unboxed) throws -> Result) rethrows -> Result { + return try body(&unboxed) + } +} + +extension SharedBox: Box { + var isNull: Bool { + return unboxed.isNull + } + + func xmlString() -> String? { + return unboxed.xmlString() + } +} + +extension SharedBox: SharedBoxProtocol { + func unbox() -> Box { + return unboxed + } +} diff --git a/Sources/XMLCoder/Box/UnkeyedBox.swift b/Sources/XMLCoder/Box/UnkeyedBox.swift index b931934d..f5862822 100644 --- a/Sources/XMLCoder/Box/UnkeyedBox.swift +++ b/Sources/XMLCoder/Box/UnkeyedBox.swift @@ -8,7 +8,7 @@ import Foundation // Minimalist implementation of an order-preserving unkeyed box: -class UnkeyedBox { +struct UnkeyedBox { typealias Element = Box typealias Unboxed = [Element] @@ -35,11 +35,11 @@ class UnkeyedBox { return unboxed } - func append(_ newElement: Element) { + mutating func append(_ newElement: Element) { unboxed.append(newElement) } - func insert(_ newElement: Element, at index: Int) { + mutating func insert(_ newElement: Element, at index: Int) { unboxed.insert(newElement, at: index) } } diff --git a/Sources/XMLCoder/Decoder/XMLDecoder.swift b/Sources/XMLCoder/Decoder/XMLDecoder.swift index c6e83262..a3ff939c 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoder.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoder.swift @@ -332,7 +332,7 @@ class _XMLDecoder: Decoder { )) } - guard let keyed = topContainer as? KeyedBox else { + guard let keyed = topContainer as? SharedBox else { throw DecodingError._typeMismatch(at: codingPath, expectation: [String: Any].self, reality: topContainer) } @@ -350,7 +350,7 @@ class _XMLDecoder: Decoder { )) } - let unkeyed = (topContainer as? UnkeyedBox) ?? UnkeyedBox([topContainer]) + let unkeyed = (topContainer as? SharedBox) ?? SharedBox(UnkeyedBox([topContainer])) return _XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed) } diff --git a/Sources/XMLCoder/Decoder/XMLDecodingStorage.swift b/Sources/XMLCoder/Decoder/XMLDecodingStorage.swift index 839cfb07..69fde464 100644 --- a/Sources/XMLCoder/Decoder/XMLDecodingStorage.swift +++ b/Sources/XMLCoder/Decoder/XMLDecodingStorage.swift @@ -33,7 +33,13 @@ struct _XMLDecodingStorage { } mutating func push(container: Box) { - containers.append(container) + if let keyedBox = container as? KeyedBox { + containers.append(SharedBox(keyedBox)) + } else if let unkeyedBox = container as? UnkeyedBox { + containers.append(SharedBox(unkeyedBox)) + } else { + containers.append(container) + } } @discardableResult diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 73f97cb7..16a4ba14 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -12,6 +12,8 @@ import Foundation struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { typealias Key = K + typealias KeyedContainer = SharedBox + typealias UnkeyedContainer = SharedBox // MARK: Properties @@ -19,7 +21,7 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol private let decoder: _XMLDecoder /// A reference to the container we're reading from. - private let container: KeyedBox + private let container: KeyedContainer /// The path of coding keys taken to get to this point in decoding. public private(set) var codingPath: [CodingKey] @@ -27,8 +29,20 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol // MARK: - Initialization /// Initializes `self` by referencing the given decoder and container. - init(referencing decoder: _XMLDecoder, wrapping container: KeyedBox) { + init(referencing decoder: _XMLDecoder, wrapping container: KeyedContainer) { self.decoder = decoder + + func mapKeys(_ container: KeyedContainer, closure: (String) -> String) -> KeyedContainer { + let attributes = container.withShared { keyedBox in + keyedBox.attributes.map { (closure($0), $1) } + } + let elements = container.withShared { keyedBox in + keyedBox.elements.map { (closure($0), $1) } + } + let keyedBox = KeyedBox(elements: elements, attributes: attributes) + return SharedBox(keyedBox) + } + switch decoder.options.keyDecodingStrategy { case .useDefaultKeys: self.container = container @@ -36,33 +50,18 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol // Convert the snake case keys in the container to camel case. // If we hit a duplicate key after conversion, then we'll use the // first one we saw. Effectively an undefined behavior with dictionaries. - let attributes = container.attributes.map { key, value in - (XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value) + self.container = mapKeys(container) { key in + XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key) } - let elements = container.elements.map { key, value in - (XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value) - } - self.container = KeyedBox(elements: elements, attributes: attributes) case .convertFromCapitalized: - let attributes = container.attributes.map { key, value in - (XMLDecoder.KeyDecodingStrategy._convertFromCapitalized(key), value) - } - let elements = container.elements.map { key, value in - (XMLDecoder.KeyDecodingStrategy._convertFromCapitalized(key), value) + self.container = mapKeys(container) { key in + XMLDecoder.KeyDecodingStrategy._convertFromCapitalized(key) } - self.container = KeyedBox(elements: elements, attributes: attributes) case let .custom(converter): - let attributes = container.attributes.map { key, value in - (converter(decoder.codingPath + - [_XMLKey(stringValue: key, intValue: nil)]).stringValue, - value) - } - let elements = container.elements.map { key, value in - (converter(decoder.codingPath + - [_XMLKey(stringValue: key, intValue: nil)]).stringValue, - value) + self.container = mapKeys(container) { key in + let codingPath = decoder.codingPath + [_XMLKey(stringValue: key, intValue: nil)] + return converter(codingPath).stringValue } - self.container = KeyedBox(elements: elements, attributes: attributes) } codingPath = decoder.codingPath } @@ -70,14 +69,27 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol // MARK: - KeyedDecodingContainerProtocol Methods public var allKeys: [Key] { - let attributeKeys = Array(container.attributes.keys.compactMap { Key(stringValue: $0) }) - let elementKeys = Array(container.elements.keys.compactMap { Key(stringValue: $0) }) + let elementKeys = container.withShared { keyedBox in + keyedBox.elements.keys.compactMap { Key(stringValue: $0) } + } + + let attributeKeys = container.withShared { keyedBox in + keyedBox.attributes.keys.compactMap { Key(stringValue: $0) } + } + return attributeKeys + elementKeys } public func contains(_ key: Key) -> Bool { - let keyString = key.stringValue - return (container.attributes[keyString] != nil) || (container.elements[keyString] != nil) + let elementOrNil = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributeOrNil = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + return (elementOrNil ?? attributeOrNil) != nil } private func _errorDescription(of key: CodingKey) -> String { @@ -98,14 +110,17 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol } public func decodeNil(forKey key: Key) throws -> Bool { - let keyString = key.stringValue - if let entry = container.attributes[keyString] { - return entry.isNull - } else if let entry = container.elements[key.stringValue] { - return entry.isNull - } else { - return true + let elementOrNil = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributeOrNil = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] } + + let box = elementOrNil ?? attributeOrNil + + return box?.isNull ?? true } public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { @@ -177,11 +192,20 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol } public func decode(_ type: T.Type, forKey key: Key) throws -> T { - let attributeNotFound = (container.attributes[key.stringValue] == nil) - let elementNotFound = (container.elements[key.stringValue] == nil) + print(#function, type, key) + container.withShared { keyedBox in + print("attributes: ", keyedBox.attributes.keys) + print("elements: ", keyedBox.elements.keys) + } - if let type = type as? AnyEmptySequence.Type, - attributeNotFound, elementNotFound { + let attributeNotFound = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] == nil + } + let elementNotFound = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] == nil + } + + if let type = type as? AnyEmptySequence.Type, attributeNotFound, elementNotFound { return type.init() as! T } @@ -210,7 +234,15 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol _ type: T.Type, forKey key: Key ) throws -> T { - guard let entry = container.elements[key.stringValue] ?? container.attributes[key.stringValue] else { + let elementOrNil = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributeOrNil = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + guard let entry = elementOrNil ?? attributeOrNil else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))." @@ -242,18 +274,26 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - guard let value = self.container.elements[key.stringValue] ?? self.container.attributes[key.stringValue] else { + let elementOrNil = self.container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributeOrNil = self.container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + guard let value = elementOrNil ?? attributeOrNil else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: codingPath, debugDescription: "Cannot get \(KeyedDecodingContainer.self) -- no value found for key \"\(key.stringValue)\"" )) } - guard let keyed = value as? KeyedBox else { + guard let keyedContainer = value as? KeyedContainer else { throw DecodingError._typeMismatch(at: codingPath, expectation: [String: Any].self, reality: value) } - let container = _XMLKeyedDecodingContainer(referencing: decoder, wrapping: keyed) + let container = _XMLKeyedDecodingContainer(referencing: decoder, wrapping: keyedContainer) return KeyedDecodingContainer(container) } @@ -261,25 +301,41 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - guard let value = container.elements[key.stringValue] ?? container.attributes[key.stringValue] else { + let elementOrNil = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributeOrNil = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + guard let value = elementOrNil ?? attributeOrNil else { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: codingPath, debugDescription: "Cannot get UnkeyedDecodingContainer -- no value found for key \"\(key.stringValue)\"" )) } - guard let unkeyed = value as? UnkeyedBox else { + guard let unkeyedContainer = value as? UnkeyedContainer else { throw DecodingError._typeMismatch(at: codingPath, expectation: [Any].self, reality: value) } - return _XMLUnkeyedDecodingContainer(referencing: decoder, wrapping: unkeyed) + return _XMLUnkeyedDecodingContainer(referencing: decoder, wrapping: unkeyedContainer) } private func _superDecoder(forKey key: CodingKey) throws -> Decoder { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - let box: Box = container.elements[key.stringValue] ?? container.attributes[key.stringValue] ?? NullBox() + let elementOrNil = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributeOrNil = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + let box: Box = elementOrNil ?? attributeOrNil ?? NullBox() return _XMLDecoder(referencing: box, at: decoder.codingPath, options: decoder.options) } diff --git a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift index 26154701..a2640289 100644 --- a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift @@ -9,13 +9,16 @@ import Foundation struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { + typealias KeyedContainer = SharedBox + typealias UnkeyedContainer = SharedBox + // MARK: Properties /// A reference to the decoder we're reading from. private let decoder: _XMLDecoder /// A reference to the container we're reading from. - private let container: UnkeyedBox + private let container: UnkeyedContainer /// The path of coding keys taken to get to this point in decoding. public private(set) var codingPath: [CodingKey] @@ -26,7 +29,7 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { // MARK: - Initialization /// Initializes `self` by referencing the given decoder and container. - init(referencing decoder: _XMLDecoder, wrapping container: UnkeyedBox) { + init(referencing decoder: _XMLDecoder, wrapping container: UnkeyedContainer) { self.decoder = decoder self.container = container codingPath = decoder.codingPath @@ -36,7 +39,9 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { // MARK: - UnkeyedDecodingContainer Methods public var count: Int? { - return container.count + return container.withShared { unkeyedBox in + unkeyedBox.count + } } public var isAtEnd: Bool { @@ -51,7 +56,11 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { )) } - if container[self.currentIndex].isNull { + let isNull = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex].isNull + } + + if isNull { currentIndex += 1 return true } else { @@ -160,7 +169,9 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { decoder.codingPath.append(_XMLKey(index: currentIndex)) defer { self.decoder.codingPath.removeLast() } - let box = container[self.currentIndex] + let box = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] + } let value = try decode(decoder, box) defer { currentIndex += 1 } @@ -194,7 +205,9 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { ) } - let value = self.container[self.currentIndex] + let value = self.container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] + } guard !value.isNull else { throw DecodingError.valueNotFound(KeyedDecodingContainer.self, DecodingError.Context( codingPath: codingPath, @@ -202,7 +215,7 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { )) } - guard let keyed = value as? KeyedBox else { + guard let keyedContainer = value as? KeyedContainer else { throw DecodingError._typeMismatch(at: codingPath, expectation: [String: Any].self, reality: value) @@ -211,7 +224,7 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { currentIndex += 1 let container = _XMLKeyedDecodingContainer( referencing: decoder, - wrapping: keyed + wrapping: keyedContainer ) return KeyedDecodingContainer(container) } @@ -229,7 +242,9 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { ) } - let value = container[self.currentIndex] + let value = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] + } guard !value.isNull else { throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, DecodingError.Context( codingPath: codingPath, @@ -237,14 +252,14 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { )) } - guard let unkeyed = value as? UnkeyedBox else { + guard let unkeyedContainer = value as? UnkeyedContainer else { throw DecodingError._typeMismatch(at: codingPath, expectation: UnkeyedBox.self, reality: value) } currentIndex += 1 - return _XMLUnkeyedDecodingContainer(referencing: decoder, wrapping: unkeyed) + return _XMLUnkeyedDecodingContainer(referencing: decoder, wrapping: unkeyedContainer) } public mutating func superDecoder() throws -> Decoder { @@ -258,7 +273,9 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { )) } - let value = container[self.currentIndex] + let value = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] + } currentIndex += 1 return _XMLDecoder(referencing: value, at: decoder.codingPath, diff --git a/Sources/XMLCoder/Encoder/XMLEncoder.swift b/Sources/XMLCoder/Encoder/XMLEncoder.swift index 211acb3a..64787f3b 100644 --- a/Sources/XMLCoder/Encoder/XMLEncoder.swift +++ b/Sources/XMLCoder/Encoder/XMLEncoder.swift @@ -270,12 +270,12 @@ open class XMLEncoder { let elementOrNone: _XMLElement? - if let keyed = topLevel as? KeyedBox { - elementOrNone = _XMLElement(key: rootKey, box: keyed) - } else if let unkeyed = topLevel as? UnkeyedBox { - elementOrNone = _XMLElement(key: rootKey, box: unkeyed) + if let keyedBox = topLevel as? KeyedBox { + elementOrNone = _XMLElement(key: rootKey, box: keyedBox) + } else if let unkeyedBox = topLevel as? UnkeyedBox { + elementOrNone = _XMLElement(key: rootKey, box: unkeyedBox) } else { - fatalError("Unrecognized top-level element.") + fatalError("Unrecognized top-level element of type: \(type(of: topLevel))") } guard let element = elementOrNone else { @@ -340,12 +340,12 @@ class _XMLEncoder: Encoder { public func container(keyedBy _: Key.Type) -> KeyedEncodingContainer { // If an existing keyed container was already requested, return that one. - let topContainer: KeyedBox + 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? KeyedBox else { + guard let container = storage.lastContainer as? SharedBox else { preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") } @@ -358,12 +358,12 @@ class _XMLEncoder: Encoder { public func unkeyedContainer() -> UnkeyedEncodingContainer { // If an existing unkeyed container was already requested, return that one. - let topContainer: UnkeyedBox + 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? UnkeyedBox else { + guard let container = storage.lastContainer as? SharedBox else { preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") } @@ -558,13 +558,13 @@ extension _XMLEncoder { internal func box(_ value: T) throws -> Box { if T.self == Date.self || T.self == NSDate.self { - return try box(value as! Date) + return try self.box(value as! Date) } else if T.self == Data.self || T.self == NSData.self { - return try box(value as! Data) + return try self.box(value as! Data) } else if T.self == URL.self || T.self == NSURL.self { - return box(value as! URL) + return self.box(value as! URL) } else if T.self == Decimal.self || T.self == NSDecimalNumber.self { - return box(value as! Decimal) + return self.box(value as! Decimal) } let depth = storage.count @@ -575,6 +575,12 @@ extension _XMLEncoder { return KeyedBox() } - return storage.popContainer() + let box = storage.popContainer() + + guard let sharedBox = box as? SharedBoxProtocol else { + return box + } + + return sharedBox.unbox() } } diff --git a/Sources/XMLCoder/Encoder/XMLEncodingStorage.swift b/Sources/XMLCoder/Encoder/XMLEncodingStorage.swift index 1870ec12..78ec2a56 100644 --- a/Sources/XMLCoder/Encoder/XMLEncodingStorage.swift +++ b/Sources/XMLCoder/Encoder/XMLEncodingStorage.swift @@ -32,20 +32,26 @@ struct _XMLEncodingStorage { return containers.last } - mutating func pushKeyedContainer() -> KeyedBox { - let keyed = KeyedBox() - containers.append(keyed) - return keyed + mutating func pushKeyedContainer() -> SharedBox { + let container = SharedBox(KeyedBox()) + containers.append(container) + return container } - mutating func pushUnkeyedContainer() -> UnkeyedBox { - let unkeyed = UnkeyedBox() - containers.append(unkeyed) - return unkeyed + mutating func pushUnkeyedContainer() -> SharedBox { + let container = SharedBox(UnkeyedBox()) + containers.append(container) + return container } mutating func push(container: Box) { - containers.append(container) + if let keyedBox = container as? KeyedBox { + containers.append(SharedBox(keyedBox)) + } else if let unkeyedBox = container as? UnkeyedBox { + containers.append(SharedBox(unkeyedBox)) + } else { + containers.append(container) + } } mutating func popContainer() -> Box { diff --git a/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift b/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift index b0ab654f..4c0a806c 100644 --- a/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift +++ b/Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift @@ -16,7 +16,7 @@ struct _XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol private let encoder: _XMLEncoder /// A reference to the container we're writing to. - private let container: KeyedBox + private var container: SharedBox /// The path of coding keys taken to get to this point in encoding. public private(set) var codingPath: [CodingKey] @@ -24,7 +24,7 @@ struct _XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol // MARK: - Initialization /// Initializes `self` with the given references. - init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: KeyedBox) { + init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: SharedBox) { self.encoder = encoder self.codingPath = codingPath self.container = container @@ -47,7 +47,9 @@ struct _XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol // MARK: - KeyedEncodingContainerProtocol Methods public mutating func encodeNil(forKey key: Key) throws { - container.elements[_converted(key).stringValue] = NullBox() + container.withShared { container in + container.elements[_converted(key).stringValue] = NullBox() + } } public mutating func encode(_ value: Bool, forKey key: Key) throws { @@ -179,30 +181,40 @@ struct _XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol debugDescription: "Complex values cannot be encoded as attributes." )) } - container.attributes[_converted(key).stringValue] = attribute + container.withShared { container in + container.attributes[_converted(key).stringValue] = attribute + } case .element: - container.elements[_converted(key).stringValue] = box + container.withShared { container in + container.elements[_converted(key).stringValue] = box + } } } public mutating func nestedContainer(keyedBy _: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { - let keyed = KeyedBox() - self.container.elements[_converted(key).stringValue] = keyed + let sharedKeyed = SharedBox(KeyedBox()) + + self.container.withShared { container in + container.elements[_converted(key).stringValue] = sharedKeyed + } codingPath.append(key) defer { self.codingPath.removeLast() } - let container = _XMLKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: keyed) + let container = _XMLKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: sharedKeyed) return KeyedEncodingContainer(container) } public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let unkeyed = UnkeyedBox() - container.elements[_converted(key).stringValue] = unkeyed + let sharedUnkeyed = SharedBox(UnkeyedBox()) + + container.withShared { container in + container.elements[_converted(key).stringValue] = sharedUnkeyed + } codingPath.append(key) defer { self.codingPath.removeLast() } - return _XMLUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: unkeyed) + return _XMLUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: sharedUnkeyed) } public mutating func superEncoder() -> Encoder { diff --git a/Sources/XMLCoder/Encoder/XMLReferencingEncoder.swift b/Sources/XMLCoder/Encoder/XMLReferencingEncoder.swift index c73a72b8..096ac26d 100644 --- a/Sources/XMLCoder/Encoder/XMLReferencingEncoder.swift +++ b/Sources/XMLCoder/Encoder/XMLReferencingEncoder.swift @@ -18,10 +18,10 @@ class _XMLReferencingEncoder: _XMLEncoder { /// The type of container we're referencing. private enum Reference { /// Referencing a specific index in an unkeyed container. - case unkeyed(UnkeyedBox, Int) + case unkeyed(SharedBox, Int) /// Referencing a specific key in a keyed container. - case keyed(KeyedBox, String) + case keyed(SharedBox, String) } // MARK: - Properties @@ -38,10 +38,10 @@ class _XMLReferencingEncoder: _XMLEncoder { init( referencing encoder: _XMLEncoder, at index: Int, - wrapping unkeyed: UnkeyedBox + wrapping sharedUnkeyed: SharedBox ) { self.encoder = encoder - reference = .unkeyed(unkeyed, index) + reference = .unkeyed(sharedUnkeyed, index) super.init( options: encoder.options, nodeEncodings: encoder.nodeEncodings, @@ -56,10 +56,10 @@ class _XMLReferencingEncoder: _XMLEncoder { referencing encoder: _XMLEncoder, key: CodingKey, convertedKey: CodingKey, - wrapping keyed: KeyedBox + wrapping sharedKeyed: SharedBox ) { self.encoder = encoder - reference = .keyed(keyed, convertedKey.stringValue) + reference = .keyed(sharedKeyed, convertedKey.stringValue) super.init( options: encoder.options, nodeEncodings: encoder.nodeEncodings, @@ -90,10 +90,14 @@ class _XMLReferencingEncoder: _XMLEncoder { } switch self.reference { - case let .unkeyed(unkeyed, index): - unkeyed.insert(box, at: index) - case let .keyed(keyed, key): - keyed.elements[key] = box + case let .unkeyed(sharedUnkeyedBox, index): + sharedUnkeyedBox.withShared { unkeyedBox in + unkeyedBox.insert(box, at: index) + } + case let .keyed(sharedKeyedBox, key): + sharedKeyedBox.withShared { keyedBox in + keyedBox.elements[key] = box + } } } } diff --git a/Sources/XMLCoder/Encoder/XMLUnkeyedEncodingContainer.swift b/Sources/XMLCoder/Encoder/XMLUnkeyedEncodingContainer.swift index fe5eeec6..1a088d8d 100644 --- a/Sources/XMLCoder/Encoder/XMLUnkeyedEncodingContainer.swift +++ b/Sources/XMLCoder/Encoder/XMLUnkeyedEncodingContainer.swift @@ -14,20 +14,20 @@ struct _XMLUnkeyedEncodingContainer: UnkeyedEncodingContainer { private let encoder: _XMLEncoder /// A reference to the container we're writing to. - private let container: UnkeyedBox + private let container: SharedBox /// 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 + return container.withShared { $0.count } } // MARK: - Initialization /// Initializes `self` with the given references. - init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: UnkeyedBox) { + init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: SharedBox) { self.encoder = encoder self.codingPath = codingPath self.container = container @@ -36,7 +36,9 @@ struct _XMLUnkeyedEncodingContainer: UnkeyedEncodingContainer { // MARK: - UnkeyedEncodingContainer Methods public mutating func encodeNil() throws { - container.append(encoder.box()) + container.withShared { container in + container.append(encoder.box()) + } } public mutating func encode(_ value: Bool) throws { @@ -147,17 +149,21 @@ struct _XMLUnkeyedEncodingContainer: UnkeyedEncodingContainer { ) rethrows { encoder.codingPath.append(_XMLKey(index: count)) defer { self.encoder.codingPath.removeLast() } - container.append(try encode(encoder, value)) + try container.withShared { container in + container.append(try encode(encoder, value)) + } } public mutating func nestedContainer(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer { codingPath.append(_XMLKey(index: count)) defer { self.codingPath.removeLast() } - let keyed = KeyedBox() - self.container.append(keyed) + let sharedKeyed = SharedBox(KeyedBox()) + self.container.withShared { container in + container.append(sharedKeyed) + } - let container = _XMLKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: keyed) + let container = _XMLKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: sharedKeyed) return KeyedEncodingContainer(container) } @@ -165,13 +171,15 @@ struct _XMLUnkeyedEncodingContainer: UnkeyedEncodingContainer { codingPath.append(_XMLKey(index: count)) defer { self.codingPath.removeLast() } - let unkeyed = UnkeyedBox() - container.append(unkeyed) + let sharedUnkeyed = SharedBox(UnkeyedBox()) + container.withShared { container in + container.append(sharedUnkeyed) + } - return _XMLUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: unkeyed) + return _XMLUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: sharedUnkeyed) } public mutating func superEncoder() -> Encoder { - return _XMLReferencingEncoder(referencing: encoder, at: container.count, wrapping: container) + return _XMLReferencingEncoder(referencing: encoder, at: count, wrapping: container) } } diff --git a/Tests/XMLCoderTests/Box/KeyedBoxTests.swift b/Tests/XMLCoderTests/Box/KeyedBoxTests.swift index 262c5646..ca5efb7d 100644 --- a/Tests/XMLCoderTests/Box/KeyedBoxTests.swift +++ b/Tests/XMLCoderTests/Box/KeyedBoxTests.swift @@ -53,7 +53,7 @@ class KeyedBoxTests: XCTestCase { } func testSubscript() { - let box = Boxed( + var box = Boxed( elements: ["foo": StringBox("bar"), "baz": IntBox(42)], attributes: ["baz": StringBox("blee")] ) diff --git a/Tests/XMLCoderTests/Box/SharedBoxTests.swift b/Tests/XMLCoderTests/Box/SharedBoxTests.swift new file mode 100644 index 00000000..c80c8248 --- /dev/null +++ b/Tests/XMLCoderTests/Box/SharedBoxTests.swift @@ -0,0 +1,63 @@ +// +// SharedBoxTests.swift +// XMLCoderTests +// +// Created by Vincent Esche on 12/26/18. +// + +import XCTest +@testable import XMLCoder + +class SharedBoxTests: XCTestCase { + func testInit() { + let box = SharedBox(BoolBox(false)) + box.withShared { shared in + XCTAssertFalse(shared.unbox()) + } + } + + func testIsNull() { + let box = SharedBox(BoolBox(false)) + XCTAssertEqual(box.isNull, false) + } + + func testXMLString() { + let nullBox = NullBox() + let sharedNullBox = SharedBox(nullBox) + XCTAssertEqual(sharedNullBox.xmlString(), nullBox.xmlString()) + + let boolBox = BoolBox(false) + let sharedBoolBox = SharedBox(boolBox) + XCTAssertEqual(sharedBoolBox.xmlString(), boolBox.xmlString()) + + let intBox = IntBox(42) + let sharedIntBox = SharedBox(intBox) + XCTAssertEqual(sharedIntBox.xmlString(), intBox.xmlString()) + + let stringBox = StringBox("lorem ipsum") + let sharedStringBox = SharedBox(stringBox) + XCTAssertEqual(sharedStringBox.xmlString(), stringBox.xmlString()) + } + + func testWithShared() { + let sharedBox = SharedBox(UnkeyedBox()) + let sharedBoxAlias = sharedBox + + XCTAssertEqual(sharedBox.withShared { $0.count }, 0) + XCTAssertEqual(sharedBoxAlias.withShared { $0.count }, 0) + + sharedBox.withShared { unkeyedBox in + unkeyedBox.append(NullBox()) + } + + XCTAssertEqual(sharedBox.withShared { $0.count }, 1) + XCTAssertEqual(sharedBoxAlias.withShared { $0.count }, 1) + + sharedBoxAlias.withShared { unkeyedBox in + unkeyedBox.append(NullBox()) + } + + XCTAssertEqual(sharedBox.withShared { $0.count }, 2) + XCTAssertEqual(sharedBoxAlias.withShared { $0.count }, 2) + } +} diff --git a/Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift b/Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift index 7daebf1b..16e50217 100644 --- a/Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift +++ b/Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift @@ -41,7 +41,7 @@ class UnkeyedBoxTests: XCTestCase { } func testSubscript() { - let box = Boxed([StringBox("foo"), IntBox(42)]) + var box = Boxed([StringBox("foo"), IntBox(42)]) box[0] = NullBox() XCTAssertEqual(box.count, 2) XCTAssertEqual(box[0] as? NullBox, NullBox()) @@ -49,7 +49,7 @@ class UnkeyedBoxTests: XCTestCase { } func testInsertAt() { - let box = Boxed([StringBox("foo"), IntBox(42)]) + var box = Boxed([StringBox("foo"), IntBox(42)]) box.insert(NullBox(), at: 1) XCTAssertEqual(box.count, 3) diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 5c8bb7f2..0df9a1a0 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -26,9 +26,11 @@ BF63EF0821CD7AF8001D38C5 /* URLBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */; }; BF63EF0A21CD7C1A001D38C5 /* URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0921CD7C1A001D38C5 /* URLTests.swift */; }; BF63EF0C21CD7F28001D38C5 /* EmptyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0B21CD7F28001D38C5 /* EmptyTests.swift */; }; + BF63EF1821CEB6BD001D38C5 /* SharedBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF1721CEB6BD001D38C5 /* SharedBox.swift */; }; BF63EF6721D0F874001D38C5 /* XMLKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF6621D0F874001D38C5 /* XMLKeyTests.swift */; }; BF63EF6921D0FDB5001D38C5 /* XMLHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF6821D0FDB5001D38C5 /* XMLHeaderTests.swift */; }; BF63EF6B21D10284001D38C5 /* XMLElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF6A21D10284001D38C5 /* XMLElementTests.swift */; }; + BF8171F221D3D03E00901EB0 /* SharedBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8171F121D3D03E00901EB0 /* SharedBoxTests.swift */; }; BF9457A821CBB498005ACFDE /* NullBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF94579E21CBB497005ACFDE /* NullBox.swift */; }; BF9457A921CBB498005ACFDE /* KeyedBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF94579F21CBB497005ACFDE /* KeyedBox.swift */; }; BF9457AA21CBB498005ACFDE /* UnkeyedBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9457A021CBB497005ACFDE /* UnkeyedBox.swift */; }; @@ -92,7 +94,6 @@ OBJ_86 /* NoteTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* NoteTest.swift */; }; OBJ_87 /* PlantCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* PlantCatalog.swift */; }; OBJ_88 /* PlantTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* PlantTest.swift */; }; - OBJ_89 /* RJITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* RJITest.swift */; }; OBJ_90 /* RelationshipsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* RelationshipsTest.swift */; }; OBJ_92 /* XMLCoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "XMLCoder::XMLCoder::Product" /* XMLCoder.framework */; }; /* End PBXBuildFile section */ @@ -120,9 +121,11 @@ BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBoxTests.swift; sourceTree = ""; }; BF63EF0921CD7C1A001D38C5 /* URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTests.swift; sourceTree = ""; }; BF63EF0B21CD7F28001D38C5 /* EmptyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTests.swift; sourceTree = ""; }; + BF63EF1721CEB6BD001D38C5 /* SharedBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedBox.swift; sourceTree = ""; }; BF63EF6621D0F874001D38C5 /* XMLKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLKeyTests.swift; sourceTree = ""; }; BF63EF6821D0FDB5001D38C5 /* XMLHeaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLHeaderTests.swift; sourceTree = ""; }; BF63EF6A21D10284001D38C5 /* XMLElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLElementTests.swift; sourceTree = ""; }; + BF8171F121D3D03E00901EB0 /* SharedBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedBoxTests.swift; sourceTree = ""; }; BF94579E21CBB497005ACFDE /* NullBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NullBox.swift; sourceTree = ""; }; BF94579F21CBB497005ACFDE /* KeyedBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyedBox.swift; sourceTree = ""; }; BF9457A021CBB497005ACFDE /* UnkeyedBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnkeyedBox.swift; sourceTree = ""; }; @@ -239,6 +242,7 @@ BF63EF0521CD7A74001D38C5 /* URLBox.swift */, BF9457A021CBB497005ACFDE /* UnkeyedBox.swift */, BF94579F21CBB497005ACFDE /* KeyedBox.swift */, + BF63EF1721CEB6BD001D38C5 /* SharedBox.swift */, ); path = Box; sourceTree = ""; @@ -272,6 +276,7 @@ BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */, BF9457C721CBB516005ACFDE /* UnkeyedBoxTests.swift */, BF9457BF21CBB516005ACFDE /* KeyedBoxTests.swift */, + BF8171F121D3D03E00901EB0 /* SharedBoxTests.swift */, ); path = Box; sourceTree = ""; @@ -481,6 +486,7 @@ BF9457B721CBB4DB005ACFDE /* XMLHeader.swift in Sources */, BF9457BB21CBB4DB005ACFDE /* XMLKey.swift in Sources */, OBJ_48 /* DecodingErrorExtension.swift in Sources */, + BF63EF1821CEB6BD001D38C5 /* SharedBox.swift in Sources */, BF9457DB21CBB5D2005ACFDE /* DateBox.swift in Sources */, BF63EF0621CD7A74001D38C5 /* URLBox.swift in Sources */, OBJ_49 /* XMLDecoder.swift in Sources */, @@ -550,6 +556,7 @@ BF63EF0C21CD7F28001D38C5 /* EmptyTests.swift in Sources */, BF9457F721CBB6BC005ACFDE /* DataTests.swift in Sources */, BF9457EE21CBB6BC005ACFDE /* IntTests.swift in Sources */, + BF8171F221D3D03E00901EB0 /* SharedBoxTests.swift in Sources */, OBJ_87 /* PlantCatalog.swift in Sources */, BF9457C921CBB516005ACFDE /* KeyedBoxTests.swift in Sources */, OBJ_88 /* PlantTest.swift in Sources */, @@ -560,7 +567,6 @@ BF9457C821CBB516005ACFDE /* BoolBoxTests.swift in Sources */, D1E0C85321D8E65E0042A261 /* ErrorContextTest.swift in Sources */, BF9457F421CBB6BC005ACFDE /* UIntTests.swift in Sources */, - OBJ_89 /* RJITest.swift in Sources */, BF9457F121CBB6BC005ACFDE /* FloatTests.swift in Sources */, BF9457EF21CBB6BC005ACFDE /* NullTests.swift in Sources */, OBJ_90 /* RelationshipsTest.swift in Sources */, From 83ff89c8969dac028f3368c0c4ee91f6f609e767 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Sat, 22 Dec 2018 19:22:57 +0100 Subject: [PATCH 2/4] Added `SharedBox` type for on-demand reference semantics --- Tests/XMLCoderTests/RelationshipsTest.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/XMLCoderTests/RelationshipsTest.swift b/Tests/XMLCoderTests/RelationshipsTest.swift index 550da4fd..febcf0e9 100644 --- a/Tests/XMLCoderTests/RelationshipsTest.swift +++ b/Tests/XMLCoderTests/RelationshipsTest.swift @@ -49,11 +49,11 @@ final class RelationshipsTest: XCTestCase { let decoder = XMLDecoder() decoder.keyDecodingStrategy = .convertFromCapitalized - let rels = try decoder.decode(Relationships.self, from: xml) + let relationships = try decoder.decode(Relationships.self, from: xml) - XCTAssertEqual(rels.items.count, 3) + XCTAssertEqual(relationships.items.count, 3) - guard let relationship = rels.items.first else { + guard let relationship = relationships.items.first else { return } From 9d73f5a0ff85d7ec70f44739f31752e643b8a7eb Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 3 Jan 2019 00:10:47 +0100 Subject: [PATCH 3/4] =?UTF-8?q?Change=20`XMLElement`=E2=80=99s=20internal?= =?UTF-8?q?=20representation=20to=20ordered=20array=20of=20children?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/XMLCoder/Auxiliaries/XMLElement.swift | 123 +++++++++--------- .../Auxiliary/XMLElementTests.swift | 8 +- .../Auxiliary/XMLStackParserTests.swift | 20 ++- 3 files changed, 73 insertions(+), 78 deletions(-) diff --git a/Sources/XMLCoder/Auxiliaries/XMLElement.swift b/Sources/XMLCoder/Auxiliaries/XMLElement.swift index a33a92a6..f9c28a7f 100644 --- a/Sources/XMLCoder/Auxiliaries/XMLElement.swift +++ b/Sources/XMLCoder/Auxiliaries/XMLElement.swift @@ -13,50 +13,51 @@ struct _XMLElement { var key: String var value: String? + var elements: [_XMLElement] = [] var attributes: [String: String] = [:] - var elements: [String: [_XMLElement]] = [:] - init(key: String, value: String? = nil, attributes: [String: String] = [:], elements: [String: [_XMLElement]] = [:]) { + init(key: String, value: String? = nil, elements: [_XMLElement] = [], attributes: [String: String] = [:]) { self.key = key self.value = value - self.attributes = attributes self.elements = elements + self.attributes = attributes } init(key: String, box: UnkeyedBox) { - self.init(key: key) - - elements[key] = box.map { box in + let elements = box.map { box in _XMLElement(key: key, box: box) } + + self.init(key: key, elements: elements) } init(key: String, box: KeyedBox) { - self.init(key: key) + var elements: [_XMLElement] = [] - attributes = Dictionary(uniqueKeysWithValues: box.attributes.compactMap { key, box in - guard let value = box.xmlString() else { - return nil - } - return (key, value) - }) - - elements = Dictionary(uniqueKeysWithValues: box.elements.map { key, box in + for (key, box) in box.elements { switch box { case let unkeyedBox as UnkeyedBox: // This basically injects the unkeyed children directly into self: - let elements = unkeyedBox.map { _XMLElement(key: key, box: $0) } - return (key, elements) + elements.append(contentsOf: unkeyedBox.map { + _XMLElement(key: key, box: $0) + }) case let keyedBox as KeyedBox: - let elements = [_XMLElement(key: key, box: keyedBox)] - return (key, elements) + elements.append(_XMLElement(key: key, box: keyedBox)) case let simpleBox as SimpleBox: - let elements = [_XMLElement(key: key, box: simpleBox)] - return (key, elements) + elements.append(_XMLElement(key: key, box: simpleBox)) case let box: preconditionFailure("Unclassified box: \(type(of: box))") } + } + + 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) { @@ -88,54 +89,54 @@ struct _XMLElement { } mutating func append(element: _XMLElement, forKey key: String) { - elements[key, default: []].append(element) + elements.append(element) } func flatten() -> KeyedBox { let attributes = self.attributes.mapValues { StringBox($0) } var elements: [String: Box] = [:] - for (key, value) in self.elements { - for child in value { - let hasValue = child.value != nil - let hasElements = !child.elements.isEmpty - let hasAttributes = !child.attributes.isEmpty - - if hasValue || hasElements || hasAttributes { - if let content = child.value { - switch elements[key] { - case var unkeyedBox as UnkeyedBox: - unkeyedBox.append(StringBox(content)) - elements[key] = unkeyedBox - case let stringBox as StringBox: - elements[key] = UnkeyedBox([stringBox, StringBox(content)]) - default: - elements[key] = StringBox(content) - } - } - if hasElements || hasAttributes { - let content = child.flatten() - switch elements[key] { - case var unkeyedBox as UnkeyedBox: - unkeyedBox.append(content) - elements[key] = unkeyedBox - case let box?: - elements[key] = UnkeyedBox([box, content]) - default: - elements[key] = content - } + for element in self.elements { + let key = element.key + + let hasValue = element.value != nil + let hasElements = !element.elements.isEmpty + let hasAttributes = !element.attributes.isEmpty + + if hasValue || hasElements || hasAttributes { + if let content = element.value { + switch elements[key] { + case var unkeyedBox as UnkeyedBox: + unkeyedBox.append(StringBox(content)) + elements[key] = unkeyedBox + case let stringBox as StringBox: + elements[key] = UnkeyedBox([stringBox, StringBox(content)]) + default: + elements[key] = StringBox(content) } - } else { + } + if hasElements || hasAttributes { + let content = element.flatten() switch elements[key] { case var unkeyedBox as UnkeyedBox: - unkeyedBox.append(NullBox()) + unkeyedBox.append(content) elements[key] = unkeyedBox case let box?: - elements[key] = UnkeyedBox([box, NullBox()]) + elements[key] = UnkeyedBox([box, content]) default: - elements[key] = NullBox() + elements[key] = content } } + } else { + switch elements[key] { + case var unkeyedBox as UnkeyedBox: + unkeyedBox.append(NullBox()) + elements[key] = unkeyedBox + case let box?: + elements[key] = UnkeyedBox([box, NullBox()]) + default: + elements[key] = NullBox() + } } } @@ -152,15 +153,13 @@ struct _XMLElement { } fileprivate func formatUnsortedXMLElements(_ string: inout String, _ level: Int, _ cdata: Bool, _ formatting: XMLEncoder.OutputFormatting, _ prettyPrinted: Bool) { - formatXMLElements(from: elements.map { (key: $0, value: $1) }, into: &string, at: level, cdata: cdata, formatting: formatting, prettyPrinted: prettyPrinted) + formatXMLElements(from: elements, into: &string, at: level, cdata: cdata, formatting: formatting, prettyPrinted: prettyPrinted) } - fileprivate func elementString(for element: (key: String, value: [_XMLElement]), at level: Int, cdata: Bool, formatting: XMLEncoder.OutputFormatting, prettyPrinted: Bool) -> String { + fileprivate func elementString(for element: _XMLElement, at level: Int, cdata: Bool, formatting: XMLEncoder.OutputFormatting, prettyPrinted: Bool) -> String { var string = "" - for child in element.value { - string += child._toXMLString(indented: level + 1, withCDATA: cdata, formatting: formatting) - string += prettyPrinted ? "\n" : "" - } + string += element._toXMLString(indented: level + 1, withCDATA: cdata, formatting: formatting) + string += prettyPrinted ? "\n" : "" return string } @@ -178,7 +177,7 @@ struct _XMLElement { } } - fileprivate func formatXMLElements(from elements: [(key: String, value: [_XMLElement])], into string: inout String, at level: Int, cdata: Bool, formatting: XMLEncoder.OutputFormatting, prettyPrinted: Bool) { + fileprivate func formatXMLElements(from elements: [_XMLElement], into string: inout String, at level: Int, cdata: Bool, formatting: XMLEncoder.OutputFormatting, prettyPrinted: Bool) { for element in elements { string += elementString(for: element, at: level, cdata: cdata, formatting: formatting, prettyPrinted: prettyPrinted) } diff --git a/Tests/XMLCoderTests/Auxiliary/XMLElementTests.swift b/Tests/XMLCoderTests/Auxiliary/XMLElementTests.swift index e4972ec0..fff99f9b 100644 --- a/Tests/XMLCoderTests/Auxiliary/XMLElementTests.swift +++ b/Tests/XMLCoderTests/Auxiliary/XMLElementTests.swift @@ -14,7 +14,7 @@ class XMLElementTests: XCTestCase { XCTAssertEqual(null.key, "foo") XCTAssertNil(null.value) - XCTAssertEqual(null.elements, [:]) + XCTAssertEqual(null.elements, []) XCTAssertEqual(null.attributes, [:]) } @@ -23,7 +23,7 @@ class XMLElementTests: XCTestCase { XCTAssertEqual(keyed.key, "foo") XCTAssertNil(keyed.value) - XCTAssertEqual(keyed.elements, ["foo": []]) + XCTAssertEqual(keyed.elements, []) XCTAssertEqual(keyed.attributes, [:]) } @@ -35,7 +35,7 @@ class XMLElementTests: XCTestCase { XCTAssertEqual(keyed.key, "foo") XCTAssertNil(keyed.value) - XCTAssertEqual(keyed.elements, [:]) + XCTAssertEqual(keyed.elements, []) XCTAssertEqual(keyed.attributes, ["blee": "42"]) } @@ -44,7 +44,7 @@ class XMLElementTests: XCTestCase { XCTAssertEqual(keyed.key, "foo") XCTAssertEqual(keyed.value, "bar") - XCTAssertEqual(keyed.elements, [:]) + XCTAssertEqual(keyed.elements, []) XCTAssertEqual(keyed.attributes, [:]) } } diff --git a/Tests/XMLCoderTests/Auxiliary/XMLStackParserTests.swift b/Tests/XMLCoderTests/Auxiliary/XMLStackParserTests.swift index 82b697d0..1b8a2879 100644 --- a/Tests/XMLCoderTests/Auxiliary/XMLStackParserTests.swift +++ b/Tests/XMLCoderTests/Auxiliary/XMLStackParserTests.swift @@ -27,18 +27,14 @@ class XMLStackParserTests: XCTestCase { let expected = _XMLElement( key: "container", elements: [ - "value": [ - _XMLElement( - key: "value", - value: "42" - ), - ], - "data": [ - _XMLElement( - key: "data", - value: "lorem ipsum" - ), - ], + _XMLElement( + key: "value", + value: "42" + ), + _XMLElement( + key: "data", + value: "lorem ipsum" + ), ] ) XCTAssertEqual(root, expected) From 6064d4787609df8837c94f1e7cc9fdc6a502fcd6 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 13 Jan 2019 12:19:45 +0000 Subject: [PATCH 4/4] Remove unused internal initializer --- Sources/XMLCoder/Box/KeyedBox.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sources/XMLCoder/Box/KeyedBox.swift b/Sources/XMLCoder/Box/KeyedBox.swift index 484a52a8..3a94f6dd 100644 --- a/Sources/XMLCoder/Box/KeyedBox.swift +++ b/Sources/XMLCoder/Box/KeyedBox.swift @@ -89,11 +89,6 @@ struct KeyedBox { self.attributes = Attributes(attributes) } - init(elements: Elements, attributes: Attributes) { - self.elements = elements - self.attributes = attributes - } - func unbox() -> (elements: Elements, attributes: Attributes) { return ( elements: elements,