Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
749e0fc
Add testing preliminaries for choice elements
bwetherfield Jul 15, 2019
ffbb395
Add ChoiceKey protocol conforming to CodingKey
jsbean Jul 26, 2019
77f9699
Implement choice element encoding
bwetherfield Jul 26, 2019
c128f28
Implement choice element decoding
jsbean Jul 27, 2019
312c4f5
Refactor clean up choice coding implementation
jsbean Jul 27, 2019
91693e9
Rename XMLChoiceKey -> XMLChoiceCodingKey
jsbean Jul 27, 2019
b5684f3
Rename SingleElementBox to SingleKeyedBox
jsbean Jul 26, 2019
5fd8d8f
Rename nestedSingleElementContainer -> nestedChoiceContainer
jsbean Jul 27, 2019
5bda791
Cull redundancies
jsbean Jul 27, 2019
045e07c
Add enum with associated value encoding tests
bwetherfield Jul 27, 2019
a7fb985
Fix usage to one key in the XMLChoiceDecodingContainer
jsbean Jul 26, 2019
b1b6c27
Factor out mapKeys to XMLDecoderImplementation.transformKeyedContainer
jsbean Jul 26, 2019
048d0c3
Be more assertive in NestingTests (#44)
jsbean Jul 27, 2019
5748eee
Merge branch 'master' into choice-implementation
jsbean Jul 27, 2019
c6ee065
Use KeyedBox like we used to (#46)
jsbean Jul 27, 2019
e6467d5
Rename scheme XMLCoder-Package -> XMLCoder
jsbean Jul 28, 2019
ce09102
Share scheme
jsbean Jul 28, 2019
f09c79d
Use Swift 4.2
jsbean Jul 28, 2019
0414fd8
Use Swift 4.2 everywhere
jsbean Jul 28, 2019
e1f0c45
Bring back old performance testing baseline
jsbean Jul 29, 2019
0b9c5cc
Whitespace
jsbean Jul 29, 2019
a8125e2
Bring back scheme management plist
jsbean Jul 29, 2019
bf52ca8
Bring back in empty AdditionalOptions
jsbean Jul 29, 2019
fd594fd
Whitespace
jsbean Jul 29, 2019
a930d00
Remove print statement
jsbean Jul 29, 2019
5a7a64a
Merge early exits in ChoiceBox.init?(_: KeyedBox)
jsbean Jul 30, 2019
c000573
Tighten up SharedBox init callsite
jsbean Jul 30, 2019
d4bd9f4
Rename _converted -> converted
jsbean Jul 30, 2019
4a99e95
Beef up XMLChoiceCodingKey doc comment
jsbean Jul 30, 2019
7920b72
Rename local variable mySelf -> oldSelf
jsbean Jul 30, 2019
683cb34
Wrangle long preconditionFailure messages
jsbean Jul 30, 2019
7db9627
Reword Implement -> Implementing in doc comment
jsbean Jul 30, 2019
c213808
Throw errors instead of fatallyErroring
jsbean Jul 30, 2019
32195c5
Add brief description to README
jsbean Jul 30, 2019
8149ead
Keep README in tag-ological order
jsbean Jul 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor clean up choice coding implementation
Rename singleElementBox -> choiceBox

Create privileged path for Choices

Sweep away commented-out code

Add comment

Don't treat top-level choice

Tighten up impl

Rename singleElementContainer method -> choiceContainer

Whoops that was the Encoder

Add unkeyed single element container et al.

Add messages to fatal errors

Omit type label

Switch to ChoiceBox based implementation

Revert pretty printing special casing

Add passing encode tests for choice elements with attributes

Add xcodeproj debris

Remove use of XMLUnkeyedSingleElementDecodingContainer

Remove unreached code in XMLChoiceDecodingContainer

Remove superDecoder methods because enums ain't classes

Put all the decode impl in one place

Whitespace
  • Loading branch information
jsbean committed Jul 27, 2019
commit 312c4f5d07fc3cbdd71ff9f65740a563e64704e1
6 changes: 5 additions & 1 deletion Sources/XMLCoder/Auxiliaries/Box/ChoiceBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ extension ChoiceBox: SimpleBox {}
extension ChoiceBox {
init?(_ keyedBox: KeyedBox) {
guard let firstKey = keyedBox.elements.keys.first else { return nil }
let firstElement = keyedBox.elements[firstKey]
guard let firstElement = keyedBox.elements[firstKey].first else { return nil }
self.init(key: firstKey, element: firstElement)
}

init(_ singleElementBox: SingleElementBox) {
self.init(key: singleElementBox.key, element: singleElementBox.element)
}
}
23 changes: 11 additions & 12 deletions Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ struct XMLCoderElement: Equatable {
}

func transformToBoxTree() -> Box {
if let value = value, self.attributes.isEmpty, self.elements.isEmpty {
return SingleElementBox(attributes: SingleElementBox.Attributes(), key: key, element: StringBox(value))
}
let attributes = KeyedStorage(self.attributes.map { attribute in
(key: attribute.key, value: StringBox(attribute.value) as SimpleBox)
})
Expand Down Expand Up @@ -105,7 +102,7 @@ struct XMLCoderElement: Equatable {
) -> String {
var string = ""
string += element._toXMLString(
indented: level, withCDATA: cdata, formatting: formatting
indented: level + 1, withCDATA: cdata, formatting: formatting
)
string += prettyPrinted ? "\n" : ""
return string
Expand Down Expand Up @@ -246,14 +243,16 @@ struct XMLCoderElement: Equatable {

extension XMLCoderElement {
init(key: String, box: UnkeyedBox) {
if box is [SingleElementBox] {
self.init(key: key, elements: box.map { XMLCoderElement(key: "", box: $0) })
if let containsChoice = box as? [ChoiceBox] {
self.init(key: key, elements: containsChoice.map {
return XMLCoderElement(key: $0.key, box: $0.element)
})
} else {
self.init(key: key, elements: box.map { XMLCoderElement(key: key, box: $0) })
}
}

init(key: String, box: SingleElementBox) {
init(key: String, box: ChoiceBox) {
self.init(key: key, elements: [XMLCoderElement(key: box.key, box: box.element)])
}

Expand Down Expand Up @@ -309,14 +308,14 @@ extension XMLCoderElement {
self.init(key: key, box: sharedUnkeyedBox.unboxed)
case let sharedKeyedBox as SharedBox<KeyedBox>:
self.init(key: key, box: sharedKeyedBox.unboxed)
case let sharedSingleElementBox as SharedBox<SingleElementBox>:
self.init(key: key, box: sharedSingleElementBox.unboxed)
case let sharedChoiceBox as SharedBox<ChoiceBox>:
self.init(key: key, box: sharedChoiceBox.unboxed)
case let unkeyedBox as UnkeyedBox:
self.init(key: key, box: unkeyedBox)
case let keyedBox as KeyedBox:
self.init(key: key, box: keyedBox)
case let singleElementBox as SingleElementBox:
self.init(key: key, box: singleElementBox)
case let choiceBox as ChoiceBox:
self.init(key: key, box: choiceBox)
case let simpleBox as SimpleBox:
self.init(key: key, box: simpleBox)
case let box:
Expand Down
170 changes: 19 additions & 151 deletions Sources/XMLCoder/Decoder/XMLChoiceDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation

/// Container specialized for decoding XML choice elements.
struct XMLChoiceDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
typealias Key = K

Expand Down Expand Up @@ -82,130 +83,12 @@ struct XMLChoiceDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
}

public func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
guard container.withShared({ $0.key == key.stringValue }) else {
throw DecodingError.typeMismatch(
at: codingPath,
expectation: type,
reality: container
)
}
return try decodeConcrete(type, forKey: key)
}

public func nestedContainer<NestedKey>(
keyedBy _: NestedKey.Type, forKey key: Key
) throws -> KeyedDecodingContainer<NestedKey> {
decoder.codingPath.append(key)
defer { decoder.codingPath.removeLast() }

let value = container.withShared { $0.element }
let container: XMLKeyedDecodingContainer<NestedKey>

if let keyedContainer = value as? SharedBox<KeyedBox> {
container = XMLKeyedDecodingContainer<NestedKey>(
referencing: decoder,
wrapping: keyedContainer
)
} else if let keyedContainer = value as? KeyedBox {
container = XMLKeyedDecodingContainer<NestedKey>(
referencing: decoder,
wrapping: SharedBox(keyedContainer)
)
} else {
throw DecodingError.typeMismatch(
at: codingPath,
expectation: [String: Any].self,
reality: value
)
}
return KeyedDecodingContainer(container)
}

public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
decoder.codingPath.append(key)
defer { decoder.codingPath.removeLast() }
guard let unkeyedElement = container.withShared({ $0.element }) as? UnkeyedBox else {
throw DecodingError.typeMismatch(
at: codingPath,
expectation: UnkeyedBox.self,
reality: container
)
}
return XMLUnkeyedDecodingContainer(
referencing: decoder,
wrapping: SharedBox(unkeyedElement)
)
}

public func superDecoder() throws -> Decoder {
return try _superDecoder(forKey: XMLKey.super)
}

public func superDecoder(forKey key: Key) throws -> Decoder {
return try _superDecoder(forKey: key)
}
}

/// Private functions
extension XMLChoiceDecodingContainer {
private func _errorDescription(of key: CodingKey) -> String {
switch decoder.options.keyDecodingStrategy {
case .convertFromSnakeCase:
// In this case we can attempt to recover the original value by
// reversing the transform
let original = key.stringValue
let converted = XMLEncoder.KeyEncodingStrategy
._convertToSnakeCase(original)
if converted == original {
return "\(key) (\"\(original)\")"
} else {
return "\(key) (\"\(original)\"), converted to \(converted)"
}
default:
// Otherwise, just report the converted string
return "\(key) (\"\(key.stringValue)\")"
guard container.withShared({ $0.key == key.stringValue }), key is XMLChoiceKey else {
throw DecodingError.typeMismatch(at: codingPath, expectation: type, reality: container)
}
}

private func decodeSignedInteger<T>(_ type: T.Type,
forKey key: Key) throws -> T
where T: BinaryInteger & SignedInteger & Decodable {
return try decodeConcrete(type, forKey: key)
}

private func decodeUnsignedInteger<T>(_ type: T.Type,
forKey key: Key) throws -> T
where T: BinaryInteger & UnsignedInteger & Decodable {
return try decodeConcrete(type, forKey: key)
}

private func decodeFloatingPoint<T>(_ type: T.Type,
forKey key: Key) throws -> T
where T: BinaryFloatingPoint & Decodable {
return try decodeConcrete(type, forKey: key)
}

private func decodeConcrete<T: Decodable>(
_ type: T.Type,
forKey key: Key
) throws -> T {
guard let strategy = self.decoder.nodeDecodings.last else {
preconditionFailure(
"""
Attempt to access node decoding strategy from empty stack.
"""
)
preconditionFailure("Attempt to access node decoding strategy from empty stack.")
}
let elements = container
.withShared { singleElementBox -> [KeyedBox.Element] in
if let unkeyed = singleElementBox.element as? UnkeyedBox {
return unkeyed
} else if let keyed = singleElementBox.element as? KeyedBox {
return keyed.elements[key.stringValue]
} else {
return []
}
}
decoder.codingPath.append(key)
let nodeDecodings = decoder.options.nodeDecodingStrategy.nodeDecodings(
forType: T.self,
Expand All @@ -216,39 +99,24 @@ extension XMLChoiceDecodingContainer {
_ = decoder.nodeDecodings.removeLast()
decoder.codingPath.removeLast()
}
let box: Box = elements
let value: T?
if !(type is AnySequence.Type), let unkeyedBox = box as? UnkeyedBox,
let first = unkeyedBox.first {
value = try decoder.unbox(first)
} else {
value = try decoder.unbox(box)
}
return try decoder.unbox(container.withShared { $0.element })
}

if value == nil, let type = type as? AnyOptional.Type,
let result = type.init() as? T {
return result
}
public func nestedContainer<NestedKey>(
keyedBy _: NestedKey.Type, forKey key: Key
) throws -> KeyedDecodingContainer<NestedKey> {
fatalError("Choice elements cannot produce a nested container.")
}

guard let unwrapped = value else {
throw DecodingError.valueNotFound(type, DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription:
"Expected \(type) value but found null instead."
))
}
return unwrapped
public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
fatalError("Choice elements cannot produce a unkeyed nested container.")
}

private func _superDecoder(forKey key: CodingKey) throws -> Decoder {
decoder.codingPath.append(key)
defer { decoder.codingPath.removeLast() }
let box: Box = container.withShared { $0.element }
return XMLDecoderImplementation(
referencing: box,
options: decoder.options,
nodeDecodings: decoder.nodeDecodings,
codingPath: decoder.codingPath
)
public func superDecoder() throws -> Decoder {
fatalError("XMLChoiceDecodingContainer cannot produce a super decoder.")
}

public func superDecoder(forKey key: Key) throws -> Decoder {
fatalError("XMLChoiceDecodingContainer cannot produce a super decoder.")
}
}
33 changes: 12 additions & 21 deletions Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,16 @@ class XMLDecoderImplementation: Decoder {
/// - Returns: A `KeyedDecodingContainer` for an XML choice element.
public func choiceContainer<Key>(keyedBy _: Key.Type) throws -> KeyedDecodingContainer<Key> {
let topContainer = try self.topContainer()
guard
let keyed = topContainer as? SharedBox<KeyedBox>,
let choiceBox = ChoiceBox(keyed.withShared { $0 })
else {
let choiceBox: ChoiceBox?
switch topContainer {
case let choice as ChoiceBox:
choiceBox = choice
case let keyed as SharedBox<KeyedBox>:
choiceBox = ChoiceBox(keyed.withShared { $0 })
default:
choiceBox = nil
}
guard let box = choiceBox else {
throw DecodingError.typeMismatch(
at: codingPath,
expectation: [String: Any].self,
Expand All @@ -139,7 +145,7 @@ class XMLDecoderImplementation: Decoder {
}
let container = XMLChoiceDecodingContainer<Key>(
referencing: self,
wrapping: SharedBox(choiceBox)
wrapping: SharedBox(box)
)
return KeyedDecodingContainer(container)
}
Expand Down Expand Up @@ -404,21 +410,8 @@ extension XMLDecoderImplementation {
return urlBox.unboxed
}

func unbox<T: Decodable>(_ box: SingleElementBox) throws -> T {
do {
return try unbox(box.element)
} catch {
// FIXME: Find a more economical way to unbox a `SingleElementBox` !
return try unbox(
KeyedBox(
elements: KeyedStorage([(box.key, box.element)]),
attributes: []
)
)
}
}

func unbox<T: Decodable>(_ box: Box) throws -> T {

let decoded: T?
let type = T.self

Expand All @@ -438,8 +431,6 @@ extension XMLDecoderImplementation {
type == String.self || type == NSString.self,
let value = (try unbox(box) as String) as? T {
decoded = value
} else if let singleElementBox = box as? SingleElementBox {
decoded = try unbox(singleElementBox)
} else {
storage.push(container: box)
defer {
Expand Down
14 changes: 13 additions & 1 deletion Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,19 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer {
unkeyedBox[self.currentIndex]
}

let value = try decode(decoder, box)
var value: T?
if let singleElement = box as? SingleElementBox {
do {
// Drill down to the element in the case of an nested unkeyed element
value = try decode(decoder, singleElement.element)
} catch {
// Specialize for choice elements
value = try decode(decoder, ChoiceBox(key: singleElement.key, element: singleElement.element))
}
} else {
value = try decode(decoder, box)
}


defer { currentIndex += 1 }

Expand Down
Loading