Skip to content

Commit 749e0fc

Browse files
bwetherfieldjsbean
authored andcommitted
Add testing preliminaries for choice elements
Add benchmark baselines Pull in tests (#11) Add Decoding support for choice elements (#15) Fix indentation (#16) Replace usage of XCTUnrwap (#19) Add nested choice tests (#18) Add falsely passing double array roundtrip test (#17)
1 parent e17d335 commit 749e0fc

File tree

14 files changed

+2323
-1274
lines changed

14 files changed

+2323
-1274
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// SingleElementBox.swift
3+
// XMLCoder
4+
//
5+
// Created by James Bean on 7/15/19.
6+
//
7+
8+
/// A `Box` which contains a single `key` and `element` pair. This is useful for disambiguating elements which could either represent
9+
/// an element nested in a keyed or unkeyed container, or an choice between multiple known-typed values (implemented in Swift using
10+
/// enums with associated values).
11+
struct SingleElementBox: SimpleBox {
12+
let key: String
13+
let element: Box
14+
}
15+
16+
extension SingleElementBox: Box {
17+
var isNull: Bool {
18+
return false
19+
}
20+
21+
func xmlString() -> String? {
22+
return nil
23+
}
24+
}

Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ struct XMLCoderElement: Equatable {
5151
elements.append(element)
5252
}
5353

54-
func transformToBoxTree() -> KeyedBox {
54+
func transformToBoxTree() -> Box {
55+
if let value = value, self.attributes.isEmpty, self.elements.isEmpty {
56+
return SingleElementBox(key: key, element: StringBox(value))
57+
}
5558
let attributes = KeyedStorage(self.attributes.map { attribute in
5659
(key: attribute.key, value: StringBox(attribute.value) as SimpleBox)
5760
})
@@ -63,9 +66,7 @@ struct XMLCoderElement: Equatable {
6366
if elements.isEmpty, let value = value {
6467
elements.append(StringBox(value), at: "value")
6568
}
66-
let keyedBox = KeyedBox(elements: elements, attributes: attributes)
67-
68-
return keyedBox
69+
return KeyedBox(elements: elements, attributes: attributes)
6970
}
7071

7172
func toXMLString(with header: XMLHeader? = nil,
@@ -104,7 +105,7 @@ struct XMLCoderElement: Equatable {
104105
) -> String {
105106
var string = ""
106107
string += element._toXMLString(
107-
indented: level + 1, withCDATA: cdata, formatting: formatting
108+
indented: level, withCDATA: cdata, formatting: formatting
108109
)
109110
string += prettyPrinted ? "\n" : ""
110111
return string

Sources/XMLCoder/Auxiliaries/XMLStackParser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class XMLStackParser: NSObject {
2323
errorContextLength length: UInt,
2424
shouldProcessNamespaces: Bool,
2525
trimValueWhitespaces: Bool
26-
) throws -> KeyedBox {
26+
) throws -> Box {
2727
let parser = XMLStackParser(trimValueWhitespaces: trimValueWhitespaces)
2828

2929
let node = try parser.parse(

Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,10 @@ class XMLDecoderImplementation: Decoder {
138138
case let unkeyed as SharedBox<UnkeyedBox>:
139139
return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed)
140140
case let keyed as SharedBox<KeyedBox>:
141-
guard let firstKey = keyed.withShared({ $0.elements.keys.first }) else { fallthrough }
142-
143-
return XMLUnkeyedDecodingContainer(referencing: self, wrapping: SharedBox(keyed.withShared { $0.elements[firstKey] }))
141+
return XMLUnkeyedDecodingContainer(
142+
referencing: self,
143+
wrapping: SharedBox(keyed.withShared { $0.elements.map(SingleElementBox.init) })
144+
)
144145
default:
145146
throw DecodingError.typeMismatch(
146147
at: codingPath,
@@ -372,6 +373,20 @@ extension XMLDecoderImplementation {
372373
return urlBox.unboxed
373374
}
374375

376+
func unbox<T: Decodable>(_ box: SingleElementBox) throws -> T {
377+
do {
378+
return try unbox(box.element)
379+
} catch {
380+
// FIXME: Find a more economical way to unbox a `SingleElementBox` !
381+
return try unbox(
382+
KeyedBox(
383+
elements: KeyedStorage([(box.key, box.element)]),
384+
attributes: []
385+
)
386+
)
387+
}
388+
}
389+
375390
func unbox<T: Decodable>(_ box: Box) throws -> T {
376391
let decoded: T?
377392
let type = T.self
@@ -392,6 +407,8 @@ extension XMLDecoderImplementation {
392407
type == String.self || type == NSString.self,
393408
let value = (try unbox(box) as String) as? T {
394409
decoded = value
410+
} else if let singleElementBox = box as? SingleElementBox {
411+
decoded = try unbox(singleElementBox)
395412
} else {
396413
storage.push(container: box)
397414
defer {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//
2+
// CompositeChoiceTests.swift
3+
// XMLCoderTests
4+
//
5+
// Created by James Bean on 7/15/19.
6+
//
7+
8+
import XCTest
9+
import XMLCoder
10+
11+
private struct IntWrapper: Codable, Equatable {
12+
let wrapped: Int
13+
}
14+
15+
private struct StringWrapper: Codable, Equatable {
16+
let wrapped: String
17+
}
18+
19+
private enum IntOrStringWrapper: Equatable {
20+
case int(IntWrapper)
21+
case string(StringWrapper)
22+
}
23+
24+
extension IntOrStringWrapper: Codable {
25+
enum CodingKeys: String, CodingKey {
26+
case int
27+
case string
28+
}
29+
30+
init(from decoder: Decoder) throws {
31+
let container = try decoder.container(keyedBy: CodingKeys.self)
32+
do {
33+
self = .int(try container.decode(IntWrapper.self, forKey: .int))
34+
} catch {
35+
self = .string(try container.decode(StringWrapper.self, forKey: .string))
36+
}
37+
}
38+
39+
func encode(to encoder: Encoder) throws {
40+
var container = encoder.container(keyedBy: CodingKeys.self)
41+
switch self {
42+
case let .int(value):
43+
try container.encode(value, forKey: .int)
44+
case let .string(value):
45+
try container.encode(value, forKey: .string)
46+
}
47+
}
48+
}
49+
50+
class CompositeChoiceTests: XCTestCase {
51+
func testIntOrStringWrapper() throws {
52+
let xml = """
53+
<container>
54+
<string>
55+
<wrapped>A Word About Woke Times</wrapped>
56+
</string>
57+
</container>
58+
"""
59+
let result = try XMLDecoder().decode(IntOrStringWrapper.self, from: xml.data(using: .utf8)!)
60+
let expected = IntOrStringWrapper.string(StringWrapper(wrapped: "A Word About Woke Times"))
61+
XCTAssertEqual(result, expected)
62+
}
63+
64+
func testArrayOfIntOrStringWrappers() throws {
65+
let xml = """
66+
<container>
67+
<string>
68+
<wrapped>A Word About Woke Times</wrapped>
69+
</string>
70+
<int>
71+
<wrapped>9000</wrapped>
72+
</int>
73+
<string>
74+
<wrapped>A Word About Woke Tomes</wrapped>
75+
</string>
76+
</container>
77+
"""
78+
let result = try XMLDecoder().decode([IntOrStringWrapper].self, from: xml.data(using: .utf8)!)
79+
let expected: [IntOrStringWrapper] = [
80+
.string(StringWrapper(wrapped: "A Word About Woke Times")),
81+
.int(IntWrapper(wrapped: 9000)),
82+
.string(StringWrapper(wrapped: "A Word About Woke Tomes")),
83+
]
84+
XCTAssertEqual(result, expected)
85+
}
86+
}

Tests/XMLCoderTests/Minimal/BoxTreeTests.swift

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import XCTest
99
@testable import XMLCoder
1010

1111
class BoxTreeTests: XCTestCase {
12-
func testNestedValues() {
12+
func testNestedValues() throws {
1313
let e1 = XMLCoderElement(
1414
key: "foo",
1515
value: "456",
@@ -29,18 +29,11 @@ class BoxTreeTests: XCTestCase {
2929
attributes: []
3030
)
3131

32-
let boxTree = root.transformToBoxTree()
33-
34-
guard let foo = boxTree.elements["foo"] as? UnkeyedBox else {
35-
XCTAssert(
36-
false,
37-
"""
38-
flattened.elements["foo"] is not an UnkeyedBox
39-
"""
40-
)
32+
guard let boxTree = root.transformToBoxTree() as? KeyedBox else {
33+
XCTFail("boxtTree is not a KeyedBox")
4134
return
4235
}
43-
36+
let foo = boxTree.elements["foo"]
4437
XCTAssertEqual(foo.count, 2)
4538
}
4639
}

0 commit comments

Comments
 (0)