-
Notifications
You must be signed in to change notification settings - Fork 104
/
XMLDecoderImplementation.swift
493 lines (431 loc) · 16.9 KB
/
XMLDecoderImplementation.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
//
// XMLDecoder.swift
// XMLCoder
//
// Created by Shawn Moore on 11/20/17.
// Copyright © 2017 Shawn Moore. All rights reserved.
//
import Foundation
class XMLDecoderImplementation: Decoder {
// MARK: Properties
/// The decoder's storage.
var storage: XMLDecodingStorage = XMLDecodingStorage()
/// Options set on the top-level decoder.
let options: XMLDecoder.Options
/// The path to the current point in encoding.
public internal(set) var codingPath: [CodingKey]
public var nodeDecodings: [(CodingKey) -> XMLDecoder.NodeDecoding]
/// Contextual user-provided information for use during encoding.
public var userInfo: [CodingUserInfoKey: Any] {
return options.userInfo
}
// The error context length
open var errorContextLength: UInt = 0
// MARK: - Initialization
/// Initializes `self` with the given top-level container and options.
init(
referencing container: Box,
options: XMLDecoder.Options,
nodeDecodings: [(CodingKey) -> XMLDecoder.NodeDecoding],
codingPath: [CodingKey] = []
) {
storage.push(container: container)
self.codingPath = codingPath
self.nodeDecodings = nodeDecodings
self.options = options
}
// MARK: - Decoder Methods
internal func topContainer() throws -> Box {
guard let topContainer = storage.topContainer() else {
throw DecodingError.valueNotFound(Box.self, DecodingError.Context(
codingPath: codingPath,
debugDescription: "Cannot get decoding container -- empty container stack."
))
}
return topContainer
}
private func popContainer() throws -> Box {
guard let topContainer = storage.popContainer() else {
throw DecodingError.valueNotFound(Box.self, DecodingError.Context(
codingPath: codingPath,
debugDescription:
"""
Cannot get decoding container -- empty container stack.
"""
))
}
return topContainer
}
public func container<Key>(keyedBy keyType: Key.Type) throws -> KeyedDecodingContainer<Key> {
if let keyed = try self.topContainer() as? SharedBox<KeyedBox> {
return KeyedDecodingContainer(XMLKeyedDecodingContainer<Key>(
referencing: self,
wrapping: keyed
))
}
if Key.self is XMLChoiceCodingKey.Type {
return try choiceContainer(keyedBy: keyType)
} else {
return try keyedContainer(keyedBy: keyType)
}
}
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
let topContainer = try self.topContainer()
guard !topContainer.isNull else {
throw DecodingError.valueNotFound(
UnkeyedDecodingContainer.self,
DecodingError.Context(
codingPath: codingPath,
debugDescription:
"""
Cannot get unkeyed decoding container -- found null box instead.
"""
)
)
}
switch topContainer {
case let unkeyed as SharedBox<UnkeyedBox>:
return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed)
case let keyed as SharedBox<KeyedBox>:
return XMLUnkeyedDecodingContainer(
referencing: self,
wrapping: SharedBox(keyed.withShared { $0.elements.map(SingleKeyedBox.init) })
)
default:
throw DecodingError.typeMismatch(
at: codingPath,
expectation: [Any].self,
reality: topContainer
)
}
}
public func singleValueContainer() throws -> SingleValueDecodingContainer {
return self
}
private func keyedContainer<Key>(keyedBy _: Key.Type) throws -> KeyedDecodingContainer<Key> {
let topContainer = try self.topContainer()
let keyedBox: KeyedBox
switch topContainer {
case _ where topContainer.isNull:
throw DecodingError.valueNotFound(
KeyedDecodingContainer<Key>.self,
DecodingError.Context(
codingPath: codingPath,
debugDescription:
"""
Cannot get keyed decoding container -- found null box instead.
"""
)
)
case let string as StringBox:
keyedBox = KeyedBox(
elements: KeyedStorage([("", string)]),
attributes: KeyedStorage()
)
case let containsEmpty as SingleKeyedBox where containsEmpty.element is NullBox:
keyedBox = KeyedBox(
elements: KeyedStorage([("", StringBox(""))]),
attributes: KeyedStorage()
)
case let unkeyed as SharedBox<UnkeyedBox>:
guard let keyed = unkeyed.withShared({ $0.first }) as? KeyedBox else {
fallthrough
}
keyedBox = keyed
default:
throw DecodingError.typeMismatch(
at: codingPath,
expectation: [String: Any].self,
reality: topContainer
)
}
let container = XMLKeyedDecodingContainer<Key>(
referencing: self,
wrapping: SharedBox(keyedBox)
)
return KeyedDecodingContainer(container)
}
/// - Returns: A `KeyedDecodingContainer` for an XML choice element.
private func choiceContainer<Key>(keyedBy _: Key.Type) throws -> KeyedDecodingContainer<Key> {
let topContainer = try self.topContainer()
let choiceBox: ChoiceBox
switch topContainer {
case let choice as ChoiceBox:
choiceBox = choice
case let singleKeyed as SingleKeyedBox:
choiceBox = ChoiceBox(singleKeyed)
default:
throw DecodingError.typeMismatch(
at: codingPath,
expectation: [String: Any].self,
reality: topContainer
)
}
let container = XMLChoiceDecodingContainer<Key>(
referencing: self,
wrapping: SharedBox(choiceBox)
)
return KeyedDecodingContainer(container)
}
}
// MARK: - Concrete Value Representations
extension XMLDecoderImplementation {
/// Returns the given box unboxed from a container.
private func typedBox<T, B: Box>(_ box: Box, for valueType: T.Type) throws -> B {
let error = DecodingError.valueNotFound(valueType, DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected \(valueType) but found null instead."
))
switch box {
case let typedBox as B:
return typedBox
case let unkeyedBox as SharedBox<UnkeyedBox>:
guard let value = unkeyedBox.withShared({
$0.first as? B
}) else { throw error }
return value
case let keyedBox as SharedBox<KeyedBox>:
guard
let value = keyedBox.withShared({ $0.value as? B })
else { throw error }
return value
case let singleKeyedBox as SingleKeyedBox:
if let value = singleKeyedBox.element as? B {
return value
} else if let box = singleKeyedBox.element as? KeyedBox, let value = box.elements[""].first as? B {
return value
} else {
throw error
}
case is NullBox:
throw error
case let keyedBox as KeyedBox:
guard
let value = keyedBox.value as? B
else { fallthrough }
return value
default:
throw DecodingError.typeMismatch(
at: codingPath,
expectation: valueType,
reality: box
)
}
}
func unbox(_ box: Box) throws -> Bool {
let stringBox: StringBox = try typedBox(box, for: Bool.self)
let string = stringBox.unboxed
guard let boolBox = BoolBox(xmlString: string) else {
throw DecodingError.typeMismatch(at: codingPath, expectation: Bool.self, reality: box)
}
return boolBox.unboxed
}
func unbox(_ box: Box) throws -> Decimal {
let stringBox: StringBox = try typedBox(box, for: Decimal.self)
let string = stringBox.unboxed
guard let decimalBox = DecimalBox(xmlString: string) else {
throw DecodingError.typeMismatch(at: codingPath, expectation: Decimal.self, reality: box)
}
return decimalBox.unboxed
}
func unbox<T: BinaryInteger & SignedInteger & Decodable>(_ box: Box) throws -> T {
let stringBox: StringBox = try typedBox(box, for: T.self)
let string = stringBox.unboxed
guard let intBox = IntBox(xmlString: string) else {
throw DecodingError.typeMismatch(at: codingPath, expectation: T.self, reality: box)
}
guard let int: T = intBox.unbox() else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: codingPath,
debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)."
))
}
return int
}
func unbox<T: BinaryInteger & UnsignedInteger & Decodable>(_ box: Box) throws -> T {
let stringBox: StringBox = try typedBox(box, for: T.self)
let string = stringBox.unboxed
guard let uintBox = UIntBox(xmlString: string) else {
throw DecodingError.typeMismatch(at: codingPath, expectation: T.self, reality: box)
}
guard let uint: T = uintBox.unbox() else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: codingPath,
debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)."
))
}
return uint
}
func unbox(_ box: Box) throws -> Float {
let stringBox: StringBox = try typedBox(box, for: Float.self)
let string = stringBox.unboxed
guard let floatBox = FloatBox(xmlString: string) else {
throw DecodingError.typeMismatch(at: codingPath, expectation: Float.self, reality: box)
}
return floatBox.unboxed
}
func unbox(_ box: Box) throws -> Double {
let stringBox: StringBox = try typedBox(box, for: Double.self)
let string = stringBox.unboxed
guard let doubleBox = DoubleBox(xmlString: string) else {
throw DecodingError.typeMismatch(at: codingPath, expectation: Double.self, reality: box)
}
return doubleBox.unboxed
}
func unbox(_ box: Box) throws -> String {
do {
let stringBox: StringBox = try typedBox(box, for: String.self)
return stringBox.unboxed
} catch {
if box is NullBox {
return ""
}
}
return ""
}
func unbox(_ box: Box) throws -> Date {
switch options.dateDecodingStrategy {
case .deferredToDate:
storage.push(container: box)
defer { storage.popContainer() }
return try Date(from: self)
case .secondsSince1970:
let stringBox: StringBox = try typedBox(box, for: Date.self)
let string = stringBox.unboxed
guard let dateBox = DateBox(secondsSince1970: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected date string to be formatted in seconds since 1970."
))
}
return dateBox.unboxed
case .millisecondsSince1970:
let stringBox: StringBox = try typedBox(box, for: Date.self)
let string = stringBox.unboxed
guard let dateBox = DateBox(millisecondsSince1970: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected date string to be formatted in milliseconds since 1970."
))
}
return dateBox.unboxed
case .iso8601:
let stringBox: StringBox = try typedBox(box, for: Date.self)
let string = stringBox.unboxed
guard let dateBox = DateBox(iso8601: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected date string to be ISO8601-formatted."
))
}
return dateBox.unboxed
case let .formatted(formatter):
let stringBox: StringBox = try typedBox(box, for: Date.self)
let string = stringBox.unboxed
guard let dateBox = DateBox(xmlString: string, formatter: formatter) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: codingPath,
debugDescription: "Date string does not match format expected by formatter."
))
}
return dateBox.unboxed
case let .custom(closure):
storage.push(container: box)
defer { storage.popContainer() }
return try closure(self)
}
}
func unbox(_ box: Box) throws -> Data {
switch options.dataDecodingStrategy {
case .deferredToData:
storage.push(container: box)
defer { storage.popContainer() }
return try Data(from: self)
case .base64:
let stringBox: StringBox = try typedBox(box, for: Data.self)
let string = stringBox.unboxed
guard let dataBox = DataBox(base64: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: codingPath,
debugDescription: "Encountered Data is not valid Base64"
))
}
return dataBox.unboxed
case let .custom(closure):
storage.push(container: box)
defer { storage.popContainer() }
return try closure(self)
}
}
func unbox(_ box: Box) throws -> URL {
let stringBox: StringBox = try typedBox(box, for: URL.self)
let string = stringBox.unboxed
guard let urlBox = URLBox(xmlString: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: codingPath,
debugDescription: "Encountered Data is not valid Base64"
))
}
return urlBox.unboxed
}
func unbox<T: Decodable>(_ box: Box) throws -> T {
let decoded: T?
let type = T.self
if type == Date.self || type == NSDate.self {
let date: Date = try unbox(box)
decoded = date as? T
} else if type == Data.self || type == NSData.self {
let data: Data = try unbox(box)
decoded = data as? T
} else if type == URL.self || type == NSURL.self {
let data: URL = try unbox(box)
decoded = data as? T
} else if type == Decimal.self || type == NSDecimalNumber.self {
let decimal: Decimal = try unbox(box)
decoded = decimal as? T
} else if
type == String.self || type == NSString.self,
let value = (try unbox(box) as String) as? T {
decoded = value
} else {
storage.push(container: box)
defer {
storage.popContainer()
}
do {
decoded = try type.init(from: self)
} catch {
guard case DecodingError.valueNotFound = error,
let type = type as? AnyOptional.Type,
let result = type.init() as? T else {
throw error
}
return result
}
}
guard let result = decoded else {
throw DecodingError.typeMismatch(
at: codingPath, expectation: type, reality: box
)
}
return result
}
}
extension XMLDecoderImplementation {
var keyTransform: (String) -> String {
switch options.keyDecodingStrategy {
case .convertFromSnakeCase:
return XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase
case .convertFromCapitalized:
return XMLDecoder.KeyDecodingStrategy._convertFromCapitalized
case .convertFromKebabCase:
return XMLDecoder.KeyDecodingStrategy._convertFromKebabCase
case .useDefaultKeys:
return { key in key }
case let .custom(converter):
return { key in
converter(self.codingPath + [XMLKey(stringValue: key, intValue: nil)]).stringValue
}
}
}
}