mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-09 00:23:20 +01:00
petitlyrics
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Shawn Moore on 11/21/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Error Utilities
|
||||
|
||||
extension DecodingError {
|
||||
/// Returns a `.typeMismatch` error describing the expected type.
|
||||
///
|
||||
/// - parameter path: The path of `CodingKey`s taken to decode a value of this type.
|
||||
/// - parameter expectation: The type expected to be encountered.
|
||||
/// - parameter reality: The value that was encountered instead of the expected type.
|
||||
/// - returns: A `DecodingError` with the appropriate path and debug description.
|
||||
static func typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Box) -> DecodingError {
|
||||
let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead."
|
||||
return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description))
|
||||
}
|
||||
|
||||
/// Returns a description of the type of `value` appropriate for an error message.
|
||||
///
|
||||
/// - parameter value: The value whose type to describe.
|
||||
/// - returns: A string describing `value`.
|
||||
/// - precondition: `value` is one of the types below.
|
||||
static func _typeDescription(of box: Box) -> String {
|
||||
switch box {
|
||||
case is NullBox:
|
||||
return "a null value"
|
||||
case is BoolBox:
|
||||
return "a boolean value"
|
||||
case is DecimalBox:
|
||||
return "a decimal value"
|
||||
case is IntBox:
|
||||
return "a signed integer value"
|
||||
case is UIntBox:
|
||||
return "an unsigned integer value"
|
||||
case is FloatBox:
|
||||
return "a floating-point value"
|
||||
case is DoubleBox:
|
||||
return "a double floating-point value"
|
||||
case is UnkeyedBox:
|
||||
return "a array value"
|
||||
case is KeyedBox:
|
||||
return "a dictionary value"
|
||||
case _:
|
||||
return "\(type(of: box))"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2019-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Max Desiatov on 01/03/2019.
|
||||
//
|
||||
|
||||
/** Allows conforming types to specify how its properties will be decoded.
|
||||
|
||||
For example:
|
||||
```swift
|
||||
struct Book: Codable, Equatable, DynamicNodeDecoding {
|
||||
let id: UInt
|
||||
let title: String
|
||||
let categories: [Category]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case title
|
||||
case categories = "category"
|
||||
}
|
||||
|
||||
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
|
||||
switch key {
|
||||
case Book.CodingKeys.id: return .attribute
|
||||
default: return .element
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
allows XML of this form to be decoded into values of type `Book`:
|
||||
|
||||
```xml
|
||||
<book id="123">
|
||||
<title>Cat in the Hat</title>
|
||||
<category>Kids</category>
|
||||
<category>Wildlife</category>
|
||||
</book>
|
||||
```
|
||||
*/
|
||||
public protocol DynamicNodeDecoding: Decodable {
|
||||
static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Shawn Moore on 11/20/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension XMLDecoderImplementation: SingleValueDecodingContainer {
|
||||
// MARK: SingleValueDecodingContainer Methods
|
||||
|
||||
public func decodeNil() -> Bool {
|
||||
return (try? topContainer().isNull) ?? true
|
||||
}
|
||||
|
||||
public func decode(_: Bool.Type) throws -> Bool {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode(_: Decimal.Type) throws -> Decimal {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode<T: BinaryInteger & SignedInteger & Decodable>(_: T.Type) throws -> T {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode<T: BinaryInteger & UnsignedInteger & Decodable>(_: T.Type) throws -> T {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode(_: Float.Type) throws -> Float {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode(_: Double.Type) throws -> Double {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode(_: String.Type) throws -> String {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode(_: String.Type) throws -> Date {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode(_: String.Type) throws -> Data {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
|
||||
public func decode<T: Decodable>(_: T.Type) throws -> T {
|
||||
return try unbox(try topContainer())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2019-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by James Bean on 7/18/19.
|
||||
//
|
||||
|
||||
/// Container specialized for decoding XML choice elements.
|
||||
struct XMLChoiceDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
|
||||
typealias Key = K
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// A reference to the decoder we're reading from.
|
||||
private let decoder: XMLDecoderImplementation
|
||||
|
||||
/// A reference to the container we're reading from.
|
||||
private let container: SharedBox<ChoiceBox>
|
||||
|
||||
/// The path of coding keys taken to get to this point in decoding.
|
||||
public private(set) var codingPath: [CodingKey]
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` by referencing the given decoder and container.
|
||||
init(referencing decoder: XMLDecoderImplementation, wrapping container: SharedBox<ChoiceBox>) {
|
||||
self.decoder = decoder
|
||||
container.withShared { $0.key = decoder.keyTransform($0.key) }
|
||||
self.container = container
|
||||
codingPath = decoder.codingPath
|
||||
}
|
||||
|
||||
// MARK: - KeyedDecodingContainerProtocol Methods
|
||||
|
||||
public var allKeys: [Key] {
|
||||
return container.withShared { [Key(stringValue: $0.key)!] }
|
||||
}
|
||||
|
||||
public func contains(_ key: Key) -> Bool {
|
||||
return container.withShared { $0.key == key.stringValue }
|
||||
}
|
||||
|
||||
public func decodeNil(forKey key: Key) throws -> Bool {
|
||||
return container.withShared { $0.element.isNull }
|
||||
}
|
||||
|
||||
public func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
|
||||
guard container.withShared({ $0.key == key.stringValue }), key is XMLChoiceCodingKey else {
|
||||
throw DecodingError.typeMismatch(
|
||||
at: codingPath,
|
||||
expectation: type,
|
||||
reality: container
|
||||
)
|
||||
}
|
||||
return try decoder.unbox(container.withShared { $0.element })
|
||||
}
|
||||
|
||||
public func nestedContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type, forKey key: Key
|
||||
) throws -> KeyedDecodingContainer<NestedKey> {
|
||||
guard container.unboxed.key == key.stringValue else {
|
||||
throw DecodingError.typeMismatch(
|
||||
at: codingPath,
|
||||
expectation: NestedKey.self,
|
||||
reality: container
|
||||
)
|
||||
}
|
||||
|
||||
let value = container.unboxed.element
|
||||
guard let container = XMLKeyedDecodingContainer<NestedKey>(box: value, decoder: decoder) else {
|
||||
throw DecodingError.typeMismatch(
|
||||
at: codingPath,
|
||||
expectation: [String: Any].self,
|
||||
reality: value
|
||||
)
|
||||
}
|
||||
|
||||
return KeyedDecodingContainer(container)
|
||||
}
|
||||
|
||||
public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
|
||||
throw DecodingError.typeMismatch(
|
||||
at: codingPath,
|
||||
expectation: Key.self,
|
||||
reality: container
|
||||
)
|
||||
}
|
||||
|
||||
public func superDecoder() throws -> Decoder {
|
||||
throw DecodingError.typeMismatch(
|
||||
at: codingPath,
|
||||
expectation: Key.self,
|
||||
reality: container
|
||||
)
|
||||
}
|
||||
|
||||
public func superDecoder(forKey key: Key) throws -> Decoder {
|
||||
throw DecodingError.typeMismatch(
|
||||
at: codingPath,
|
||||
expectation: Key.self,
|
||||
reality: container
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Shawn Moore on 11/20/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - XML Decoder
|
||||
|
||||
/// `XMLDecoder` facilitates the decoding of XML into semantic `Decodable` types.
|
||||
open class XMLDecoder {
|
||||
// MARK: Options
|
||||
|
||||
/// The strategy to use for decoding `Date` values.
|
||||
public enum DateDecodingStrategy {
|
||||
/// Defer to `Date` for decoding. This is the default strategy.
|
||||
case deferredToDate
|
||||
|
||||
/// Decode the `Date` as a UNIX timestamp from a XML number. This is the default strategy.
|
||||
case secondsSince1970
|
||||
|
||||
/// Decode the `Date` as UNIX millisecond timestamp from a XML number.
|
||||
case millisecondsSince1970
|
||||
|
||||
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
|
||||
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
||||
case iso8601
|
||||
|
||||
/// Decode the `Date` as a string parsed by the given formatter.
|
||||
case formatted(DateFormatter)
|
||||
|
||||
/// Decode the `Date` as a custom box decoded by the given closure.
|
||||
case custom((_ decoder: Decoder) throws -> Date)
|
||||
|
||||
/// Decode the `Date` as a string parsed by the given formatter for the give key.
|
||||
static func keyFormatted(
|
||||
_ formatterForKey: @escaping (CodingKey) throws -> DateFormatter?
|
||||
) -> XMLDecoder.DateDecodingStrategy {
|
||||
return .custom { decoder -> Date in
|
||||
guard let codingKey = decoder.codingPath.last else {
|
||||
throw DecodingError.dataCorrupted(DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "No Coding Path Found"
|
||||
))
|
||||
}
|
||||
|
||||
guard let container = try? decoder.singleValueContainer(),
|
||||
let text = try? container.decode(String.self)
|
||||
else {
|
||||
throw DecodingError.dataCorrupted(DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Could not decode date text"
|
||||
))
|
||||
}
|
||||
|
||||
guard let dateFormatter = try formatterForKey(codingKey) else {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "No date formatter for date text"
|
||||
)
|
||||
}
|
||||
|
||||
if let date = dateFormatter.date(from: text) {
|
||||
return date
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Cannot decode date string \(text)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The strategy to use for decoding `Data` values.
|
||||
public enum DataDecodingStrategy {
|
||||
/// Defer to `Data` for decoding.
|
||||
case deferredToData
|
||||
|
||||
/// Decode the `Data` from a Base64-encoded string. This is the default strategy.
|
||||
case base64
|
||||
|
||||
/// Decode the `Data` as a custom box decoded by the given closure.
|
||||
case custom((_ decoder: Decoder) throws -> Data)
|
||||
|
||||
/// Decode the `Data` as a custom box by the given closure for the give key.
|
||||
static func keyFormatted(
|
||||
_ formatterForKey: @escaping (CodingKey) throws -> Data?
|
||||
) -> XMLDecoder.DataDecodingStrategy {
|
||||
return .custom { decoder -> Data in
|
||||
guard let codingKey = decoder.codingPath.last else {
|
||||
throw DecodingError.dataCorrupted(DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "No Coding Path Found"
|
||||
))
|
||||
}
|
||||
|
||||
guard let container = try? decoder.singleValueContainer(),
|
||||
let text = try? container.decode(String.self)
|
||||
else {
|
||||
throw DecodingError.dataCorrupted(DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Could not decode date text"
|
||||
))
|
||||
}
|
||||
|
||||
guard let data = try formatterForKey(codingKey) else {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Cannot decode data string \(text)"
|
||||
)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The strategy to use for non-XML-conforming floating-point values (IEEE 754 infinity and NaN).
|
||||
public enum NonConformingFloatDecodingStrategy {
|
||||
/// Throw upon encountering non-conforming values. This is the default strategy.
|
||||
case `throw`
|
||||
|
||||
/// Decode the values from the given representation strings.
|
||||
case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
|
||||
}
|
||||
|
||||
/// The strategy to use for automatically changing the box of keys before decoding.
|
||||
public enum KeyDecodingStrategy {
|
||||
/// Use the keys specified by each type. This is the default strategy.
|
||||
case useDefaultKeys
|
||||
|
||||
/// Convert from "snake_case_keys" to "camelCaseKeys" before attempting
|
||||
/// to match a key with the one specified by each type.
|
||||
///
|
||||
/// The conversion to upper case uses `Locale.system`, also known as
|
||||
/// the ICU "root" locale. This means the result is consistent
|
||||
/// regardless of the current user's locale and language preferences.
|
||||
///
|
||||
/// Converting from snake case to camel case:
|
||||
/// 1. Capitalizes the word starting after each `_`
|
||||
/// 2. Removes all `_`
|
||||
/// 3. Preserves starting and ending `_` (as these are often used to indicate private variables or other metadata).
|
||||
/// For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` becomes `_oneTwoThree_`.
|
||||
///
|
||||
/// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character.
|
||||
case convertFromSnakeCase
|
||||
|
||||
/// Convert from "kebab-case" to "kebabCase" before attempting
|
||||
/// to match a key with the one specified by each type.
|
||||
case convertFromKebabCase
|
||||
|
||||
/// Convert from "CodingKey" to "codingKey"
|
||||
case convertFromCapitalized
|
||||
|
||||
/// Convert from "CODING_KEY" to "codingKey"
|
||||
case convertFromUppercase
|
||||
|
||||
/// Provide a custom conversion from the key in the encoded XML to the
|
||||
/// keys specified by the decoded types.
|
||||
/// The full path to the current decoding position is provided for
|
||||
/// context (in case you need to locate this key within the payload).
|
||||
/// The returned key is used in place of the last component in the
|
||||
/// coding path before decoding.
|
||||
/// If the result of the conversion is a duplicate key, then only one
|
||||
/// box will be present in the container for the type to decode from.
|
||||
case custom((_ codingPath: [CodingKey]) -> CodingKey)
|
||||
|
||||
static func _convertFromCapitalized(_ stringKey: String) -> String {
|
||||
guard !stringKey.isEmpty else {
|
||||
return stringKey
|
||||
}
|
||||
let firstLetter = stringKey.prefix(1).lowercased()
|
||||
let result = firstLetter + stringKey.dropFirst()
|
||||
return result
|
||||
}
|
||||
|
||||
static func _convertFromUppercase(_ stringKey: String) -> String {
|
||||
_convert(stringKey.lowercased(), usingSeparator: "_")
|
||||
}
|
||||
|
||||
static func _convertFromSnakeCase(_ stringKey: String) -> String {
|
||||
return _convert(stringKey, usingSeparator: "_")
|
||||
}
|
||||
|
||||
static func _convertFromKebabCase(_ stringKey: String) -> String {
|
||||
return _convert(stringKey, usingSeparator: "-")
|
||||
}
|
||||
|
||||
static func _convert(_ stringKey: String, usingSeparator separator: Character) -> String {
|
||||
guard !stringKey.isEmpty else {
|
||||
return stringKey
|
||||
}
|
||||
|
||||
// Find the first non-separator character
|
||||
guard let firstNonSeparator = stringKey.firstIndex(where: { $0 != separator }) else {
|
||||
// Reached the end without finding a separator character
|
||||
return stringKey
|
||||
}
|
||||
|
||||
// Find the last non-separator character
|
||||
var lastNonSeparator = stringKey.index(before: stringKey.endIndex)
|
||||
while lastNonSeparator > firstNonSeparator, stringKey[lastNonSeparator] == separator {
|
||||
stringKey.formIndex(before: &lastNonSeparator)
|
||||
}
|
||||
|
||||
let keyRange = firstNonSeparator...lastNonSeparator
|
||||
let leadingSeparatorRange = stringKey.startIndex..<firstNonSeparator
|
||||
let trailingSeparatorRange = stringKey.index(after: lastNonSeparator)..<stringKey.endIndex
|
||||
|
||||
let components = stringKey[keyRange].split(separator: separator)
|
||||
let joinedString: String
|
||||
if components.count == 1 {
|
||||
// No separators in key, leave the word as is - maybe it is already good
|
||||
joinedString = String(stringKey[keyRange])
|
||||
} else {
|
||||
joinedString = ([components[0].lowercased()] + components[1...].map { $0.capitalized }).joined()
|
||||
}
|
||||
|
||||
// Do a cheap isEmpty check before creating and appending potentially empty strings
|
||||
let result: String
|
||||
if leadingSeparatorRange.isEmpty, trailingSeparatorRange.isEmpty {
|
||||
result = joinedString
|
||||
} else if !leadingSeparatorRange.isEmpty, !trailingSeparatorRange.isEmpty {
|
||||
// Both leading and trailing underscores
|
||||
result = String(stringKey[leadingSeparatorRange]) + joinedString + String(stringKey[trailingSeparatorRange])
|
||||
} else if !leadingSeparatorRange.isEmpty {
|
||||
// Just leading
|
||||
result = String(stringKey[leadingSeparatorRange]) + joinedString
|
||||
} else {
|
||||
// Just trailing
|
||||
result = joinedString + String(stringKey[trailingSeparatorRange])
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/// The strategy to use in decoding dates. Defaults to `.secondsSince1970`.
|
||||
open var dateDecodingStrategy: DateDecodingStrategy = .secondsSince1970
|
||||
|
||||
/// The strategy to use in decoding binary data. Defaults to `.base64`.
|
||||
open var dataDecodingStrategy: DataDecodingStrategy = .base64
|
||||
|
||||
/// The strategy to use in decoding non-conforming numbers. Defaults to `.throw`.
|
||||
open var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy = .throw
|
||||
|
||||
/// The strategy to use for decoding keys. Defaults to `.useDefaultKeys`.
|
||||
open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys
|
||||
|
||||
/// A node's decoding type
|
||||
public enum NodeDecoding {
|
||||
/// Decodes a node from attributes of form `nodeName="value"`.
|
||||
case attribute
|
||||
/// Decodes a node from elements of form `<nodeName>value</nodeName>`.
|
||||
case element
|
||||
/// Decodes a node from either elements of form `<nodeName>value</nodeName>` or attributes
|
||||
/// of form `nodeName="value"`, with elements taking priority.
|
||||
case elementOrAttribute
|
||||
}
|
||||
|
||||
/// The strategy to use in encoding encoding attributes. Defaults to `.deferredToEncoder`.
|
||||
open var nodeDecodingStrategy: NodeDecodingStrategy = .deferredToDecoder
|
||||
|
||||
/// Set of strategies to use for encoding of nodes.
|
||||
public enum NodeDecodingStrategy {
|
||||
/// Defer to `Encoder` for choosing an encoding. This is the default strategy.
|
||||
case deferredToDecoder
|
||||
|
||||
/// Return a closure computing the desired node encoding for the value by its coding key.
|
||||
case custom((Decodable.Type, Decoder) -> ((CodingKey) -> NodeDecoding))
|
||||
|
||||
func nodeDecodings(
|
||||
forType codableType: Decodable.Type,
|
||||
with decoder: Decoder
|
||||
) -> ((CodingKey) -> NodeDecoding?) {
|
||||
switch self {
|
||||
case .deferredToDecoder:
|
||||
guard let dynamicType = codableType as? DynamicNodeDecoding.Type else {
|
||||
return { _ in nil }
|
||||
}
|
||||
return dynamicType.nodeDecoding(for:)
|
||||
case let .custom(closure):
|
||||
return closure(codableType, decoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contextual user-provided information for use during decoding.
|
||||
open var userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
|
||||
/// The error context length. Non-zero length makes an error thrown from
|
||||
/// the XML parser with line/column location repackaged with a context
|
||||
/// around that location of specified length. For example, if an error was
|
||||
/// thrown indicating that there's an unexpected character at line 3, column
|
||||
/// 15 with `errorContextLength` set to 10, a new error type is rethrown
|
||||
/// containing 5 characters before column 15 and 5 characters after, all on
|
||||
/// line 3. Line wrapping should be handled correctly too as the context can
|
||||
/// span more than a few lines.
|
||||
open var errorContextLength: UInt = 0
|
||||
|
||||
/** A boolean value that determines whether the parser reports the
|
||||
namespaces and qualified names of elements. The default value is `false`.
|
||||
*/
|
||||
open var shouldProcessNamespaces: Bool = false
|
||||
|
||||
/** A boolean value that determines whether the parser trims whitespaces
|
||||
and newlines from the end and the beginning of string values. The default
|
||||
value is `true`.
|
||||
*/
|
||||
open var trimValueWhitespaces: Bool
|
||||
|
||||
/** A boolean value that determines whether to remove pure whitespace elements
|
||||
that have sibling elements that aren't pure whitespace. The default value
|
||||
is `false`.
|
||||
*/
|
||||
open var removeWhitespaceElements: Bool
|
||||
|
||||
/// Options set on the top-level encoder to pass down the decoding hierarchy.
|
||||
struct Options {
|
||||
let dateDecodingStrategy: DateDecodingStrategy
|
||||
let dataDecodingStrategy: DataDecodingStrategy
|
||||
let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
|
||||
let keyDecodingStrategy: KeyDecodingStrategy
|
||||
let nodeDecodingStrategy: NodeDecodingStrategy
|
||||
let userInfo: [CodingUserInfoKey: Any]
|
||||
}
|
||||
|
||||
/// The options set on the top-level decoder.
|
||||
var options: Options {
|
||||
return Options(
|
||||
dateDecodingStrategy: dateDecodingStrategy,
|
||||
dataDecodingStrategy: dataDecodingStrategy,
|
||||
nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy,
|
||||
keyDecodingStrategy: keyDecodingStrategy,
|
||||
nodeDecodingStrategy: nodeDecodingStrategy,
|
||||
userInfo: userInfo
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Constructing a XML Decoder
|
||||
|
||||
/// Initializes `self` with default strategies.
|
||||
public init(trimValueWhitespaces: Bool = true, removeWhitespaceElements: Bool = false) {
|
||||
self.trimValueWhitespaces = trimValueWhitespaces
|
||||
self.removeWhitespaceElements = removeWhitespaceElements
|
||||
}
|
||||
|
||||
// MARK: - Decoding Values
|
||||
|
||||
/// Decodes a top-level box of the given type from the given XML representation.
|
||||
///
|
||||
/// - parameter type: The type of the box to decode.
|
||||
/// - parameter data: The data to decode from.
|
||||
/// - returns: A box of the requested type.
|
||||
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid XML.
|
||||
/// - throws: An error if any box throws an error during decoding.
|
||||
open func decode<T: Decodable>(
|
||||
_ type: T.Type,
|
||||
from data: Data
|
||||
) throws -> T {
|
||||
let topLevel: Box = try XMLStackParser.parse(
|
||||
with: data,
|
||||
errorContextLength: errorContextLength,
|
||||
shouldProcessNamespaces: shouldProcessNamespaces,
|
||||
trimValueWhitespaces: trimValueWhitespaces,
|
||||
removeWhitespaceElements: removeWhitespaceElements
|
||||
)
|
||||
|
||||
let decoder = XMLDecoderImplementation(
|
||||
referencing: topLevel,
|
||||
options: options,
|
||||
nodeDecodings: []
|
||||
)
|
||||
decoder.nodeDecodings = [
|
||||
options.nodeDecodingStrategy.nodeDecodings(
|
||||
forType: T.self,
|
||||
with: decoder
|
||||
),
|
||||
]
|
||||
|
||||
defer {
|
||||
_ = decoder.nodeDecodings.removeLast()
|
||||
}
|
||||
|
||||
return try decoder.unbox(topLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: TopLevelDecoder
|
||||
|
||||
#if canImport(Combine)
|
||||
import protocol Combine.TopLevelDecoder
|
||||
import protocol Combine.TopLevelEncoder
|
||||
#elseif canImport(OpenCombine)
|
||||
import protocol OpenCombine.TopLevelDecoder
|
||||
import protocol OpenCombine.TopLevelEncoder
|
||||
#endif
|
||||
|
||||
#if canImport(Combine) || canImport(OpenCombine)
|
||||
extension XMLDecoder: TopLevelDecoder {}
|
||||
extension XMLEncoder: TopLevelEncoder {}
|
||||
#endif
|
||||
@@ -0,0 +1,497 @@
|
||||
// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Shawn Moore on 11/20/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class XMLDecoderImplementation: Decoder {
|
||||
// MARK: Properties
|
||||
|
||||
/// The decoder's storage.
|
||||
var storage = 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 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 .convertFromUppercase:
|
||||
return XMLDecoder.KeyDecodingStrategy._convertFromUppercase
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Shawn Moore on 11/20/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Decoding Storage
|
||||
|
||||
struct XMLDecodingStorage {
|
||||
// MARK: Properties
|
||||
|
||||
/// The container stack.
|
||||
/// Elements may be any one of the XML types (StringBox, KeyedBox).
|
||||
private var containers: [Box] = []
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` with no containers.
|
||||
init() {}
|
||||
|
||||
// MARK: - Modifying the Stack
|
||||
|
||||
var count: Int {
|
||||
return containers.count
|
||||
}
|
||||
|
||||
func topContainer() -> Box? {
|
||||
return containers.last
|
||||
}
|
||||
|
||||
mutating func push(container: Box) {
|
||||
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
|
||||
mutating func popContainer() -> Box? {
|
||||
guard !containers.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
return containers.removeLast()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
// Copyright (c) 2017-2021 Shawn Moore and XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Shawn Moore on 11/21/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: Decoding Containers
|
||||
|
||||
struct XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
|
||||
typealias Key = K
|
||||
typealias KeyedContainer = SharedBox<KeyedBox>
|
||||
typealias UnkeyedContainer = SharedBox<UnkeyedBox>
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// A reference to the decoder we're reading from.
|
||||
private let decoder: XMLDecoderImplementation
|
||||
|
||||
/// A reference to the container we're reading from.
|
||||
private let container: KeyedContainer
|
||||
|
||||
/// The path of coding keys taken to get to this point in decoding.
|
||||
public private(set) var codingPath: [CodingKey]
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` by referencing the given decoder and container.
|
||||
init(
|
||||
referencing decoder: XMLDecoderImplementation,
|
||||
wrapping container: KeyedContainer
|
||||
) {
|
||||
self.decoder = decoder
|
||||
container.withShared {
|
||||
$0.elements = .init($0.elements.map { (decoder.keyTransform($0), $1) })
|
||||
$0.attributes = .init($0.attributes.map { (decoder.keyTransform($0), $1) })
|
||||
}
|
||||
self.container = container
|
||||
codingPath = decoder.codingPath
|
||||
}
|
||||
|
||||
// MARK: - KeyedDecodingContainerProtocol Methods
|
||||
|
||||
public var allKeys: [Key] {
|
||||
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 elements = container.withShared { keyedBox in
|
||||
keyedBox.elements[key.stringValue]
|
||||
}
|
||||
|
||||
let attributes = container.withShared { keyedBox in
|
||||
keyedBox.attributes[key.stringValue]
|
||||
}
|
||||
|
||||
return !elements.isEmpty || !attributes.isEmpty
|
||||
}
|
||||
|
||||
public func decodeNil(forKey key: Key) throws -> Bool {
|
||||
let elements = container.withShared { keyedBox in
|
||||
keyedBox.elements[key.stringValue]
|
||||
}
|
||||
|
||||
let attributes = container.withShared { keyedBox in
|
||||
keyedBox.attributes[key.stringValue]
|
||||
}
|
||||
|
||||
let box = elements.first ?? attributes.first
|
||||
|
||||
if box is SingleKeyedBox {
|
||||
return false
|
||||
}
|
||||
|
||||
return box?.isNull ?? true
|
||||
}
|
||||
|
||||
public func decode<T: Decodable>(
|
||||
_ type: T.Type, forKey key: Key
|
||||
) throws -> T {
|
||||
let attributeFound = container.withShared { keyedBox in
|
||||
!keyedBox.attributes[key.stringValue].isEmpty
|
||||
}
|
||||
|
||||
let elementFound = container.withShared { keyedBox in
|
||||
!keyedBox.elements[key.stringValue].isEmpty || keyedBox.value != nil
|
||||
}
|
||||
|
||||
if let type = type as? XMLDecodableSequence.Type,
|
||||
!attributeFound,
|
||||
!elementFound,
|
||||
let result = type.init() as? T
|
||||
{
|
||||
return result
|
||||
}
|
||||
|
||||
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 elements = self.container.withShared { keyedBox in
|
||||
keyedBox.elements[key.stringValue]
|
||||
}
|
||||
|
||||
let attributes = self.container.withShared { keyedBox in
|
||||
keyedBox.attributes[key.stringValue]
|
||||
}
|
||||
|
||||
guard let value = elements.first ?? attributes.first else {
|
||||
throw DecodingError.keyNotFound(key, DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription:
|
||||
"""
|
||||
Cannot get \(KeyedDecodingContainer<NestedKey>.self) -- \
|
||||
no value found for key \"\(key.stringValue)\"
|
||||
"""
|
||||
))
|
||||
}
|
||||
|
||||
guard let container = XMLKeyedDecodingContainer<NestedKey>(box: value, decoder: decoder) 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() }
|
||||
|
||||
let elements = container.unboxed.elements[key.stringValue]
|
||||
|
||||
if let containsKeyed = elements as? [KeyedBox], containsKeyed.count == 1, let keyed = containsKeyed.first {
|
||||
return XMLUnkeyedDecodingContainer(
|
||||
referencing: decoder,
|
||||
wrapping: SharedBox(keyed.elements.map(SingleKeyedBox.init))
|
||||
)
|
||||
} else {
|
||||
return XMLUnkeyedDecodingContainer(
|
||||
referencing: decoder,
|
||||
wrapping: SharedBox(elements)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func superDecoder() throws -> Decoder {
|
||||
return try _superDecoder(forKey: XMLKey.super)
|
||||
}
|
||||
|
||||
public func superDecoder(forKey key: Key) throws -> Decoder {
|
||||
return try _superDecoder(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
extension XMLKeyedDecodingContainer {
|
||||
internal init?(box: Box, decoder: XMLDecoderImplementation) {
|
||||
switch box {
|
||||
case let keyedContainer as KeyedContainer:
|
||||
self.init(
|
||||
referencing: decoder,
|
||||
wrapping: keyedContainer
|
||||
)
|
||||
case let keyedBox as KeyedBox:
|
||||
self.init(
|
||||
referencing: decoder,
|
||||
wrapping: SharedBox(keyedBox)
|
||||
)
|
||||
case let singleBox as SingleKeyedBox:
|
||||
let element = (singleBox.key, singleBox.element)
|
||||
let keyedContainer = KeyedBox(elements: [element], attributes: [])
|
||||
self.init(
|
||||
referencing: decoder,
|
||||
wrapping: SharedBox(keyedContainer)
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Private functions
|
||||
extension XMLKeyedDecodingContainer {
|
||||
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)\")"
|
||||
}
|
||||
}
|
||||
|
||||
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 = decoder.nodeDecodings.last else {
|
||||
preconditionFailure(
|
||||
"""
|
||||
Attempt to access node decoding strategy from empty stack.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
let elements = container
|
||||
.withShared { keyedBox -> [KeyedBox.Element] in
|
||||
return (key.isInlined ? keyedBox.elements.values : keyedBox.elements[key.stringValue]).map {
|
||||
if let singleKeyed = $0 as? SingleKeyedBox {
|
||||
return singleKeyed.element.isNull ? singleKeyed : singleKeyed.element
|
||||
} else {
|
||||
return $0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let attributes = container.withShared { keyedBox in
|
||||
key.isInlined ? keyedBox.attributes.values : keyedBox.attributes[key.stringValue]
|
||||
}
|
||||
|
||||
decoder.codingPath.append(key)
|
||||
let nodeDecodings = decoder.options.nodeDecodingStrategy.nodeDecodings(
|
||||
forType: T.self,
|
||||
with: decoder
|
||||
)
|
||||
decoder.nodeDecodings.append(nodeDecodings)
|
||||
defer {
|
||||
_ = decoder.nodeDecodings.removeLast()
|
||||
decoder.codingPath.removeLast()
|
||||
}
|
||||
|
||||
// You can't decode sequences from attributes, but other strategies
|
||||
// need special handling for empty sequences.
|
||||
if strategy(key) != .attribute && elements.isEmpty,
|
||||
let empty = (type as? XMLDecodableSequence.Type)?.init() as? T
|
||||
{
|
||||
return empty
|
||||
}
|
||||
|
||||
// If we are looking at a coding key value intrinsic where the expected type is `String` and
|
||||
// the value is empty, return CDATA if present otherwise `""`.
|
||||
if strategy(key) != .attribute, elements.isEmpty, attributes.isEmpty, type == String.self, key.stringValue == "", let emptyString = "" as? T {
|
||||
let cdata = container.withShared { keyedBox in
|
||||
keyedBox.elements["#CDATA"].map {
|
||||
return ($0 as? KeyedBox)?.value ?? $0
|
||||
}
|
||||
}.first
|
||||
return ((cdata as? StringBox)?.unboxed as? T) ?? emptyString
|
||||
}
|
||||
|
||||
let box: Box
|
||||
if key.isInlined {
|
||||
box = container.typeErasedUnbox()
|
||||
} else {
|
||||
switch strategy(key) {
|
||||
case .attribute?:
|
||||
box = try getAttributeBox(for: type, attributes, key)
|
||||
case .element?:
|
||||
box = try getElementBox(for: type, elements, key)
|
||||
case .elementOrAttribute?:
|
||||
box = try getAttributeOrElementBox(attributes, elements, key)
|
||||
default:
|
||||
switch type {
|
||||
case is XMLAttributeProtocol.Type:
|
||||
box = try getAttributeBox(for: type, attributes, key)
|
||||
case is XMLElementProtocol.Type:
|
||||
box = try getElementBox(for: type, elements, key)
|
||||
default:
|
||||
box = try getAttributeOrElementBox(attributes, elements, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let value: T?
|
||||
if !(type is XMLDecodableSequence.Type), let unkeyedBox = box as? UnkeyedBox,
|
||||
let first = unkeyedBox.first
|
||||
{
|
||||
// Handle case where we have held onto a `SingleKeyedBox`
|
||||
if let singleKeyed = first as? SingleKeyedBox {
|
||||
if singleKeyed.element.isNull {
|
||||
value = try decoder.unbox(singleKeyed)
|
||||
} else {
|
||||
value = try decoder.unbox(singleKeyed.element)
|
||||
}
|
||||
} else {
|
||||
value = try decoder.unbox(first)
|
||||
}
|
||||
} else if box.isNull, let type = type as? XMLOptionalAttributeProtocol.Type, let nullAttribute = type.init() as? T {
|
||||
value = nullAttribute
|
||||
} else {
|
||||
value = try decoder.unbox(box)
|
||||
}
|
||||
|
||||
if value == nil, let type = type as? AnyOptional.Type,
|
||||
let result = type.init() as? T
|
||||
{
|
||||
return result
|
||||
}
|
||||
|
||||
guard let unwrapped = value else {
|
||||
throw DecodingError.valueNotFound(type, DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription:
|
||||
"Expected \(type) value but found null instead."
|
||||
))
|
||||
}
|
||||
return unwrapped
|
||||
}
|
||||
|
||||
private func getAttributeBox<T: Decodable>(for type: T.Type, _ attributes: [KeyedBox.Attribute], _ key: Key) throws -> Box {
|
||||
if let box = attributes.first { return box }
|
||||
if type is AnyOptional.Type || type is XMLOptionalAttributeProtocol.Type { return NullBox() }
|
||||
|
||||
throw DecodingError.keyNotFound(key, DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "No attribute found for key \(_errorDescription(of: key))."
|
||||
))
|
||||
}
|
||||
|
||||
private func getElementBox<T: Decodable>(for type: T.Type, _ elements: [KeyedBox.Element], _ key: Key) throws -> Box {
|
||||
guard elements.isEmpty else { return elements }
|
||||
if type is AnyOptional.Type || type is XMLDecodableSequence.Type { return elements }
|
||||
|
||||
throw DecodingError.keyNotFound(key, DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "No element found for key \(_errorDescription(of: key))."
|
||||
))
|
||||
}
|
||||
|
||||
private func getAttributeOrElementBox(_ attributes: [KeyedBox.Attribute], _ elements: [KeyedBox.Element], _ key: Key) throws -> Box {
|
||||
guard
|
||||
let anyBox = elements.isEmpty ? attributes.first : elements as Box?
|
||||
else {
|
||||
throw DecodingError.keyNotFound(key, DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription:
|
||||
"""
|
||||
No attribute or element found for key \
|
||||
\(_errorDescription(of: key)).
|
||||
"""
|
||||
))
|
||||
}
|
||||
return anyBox
|
||||
}
|
||||
|
||||
private func _superDecoder(forKey key: CodingKey) throws -> Decoder {
|
||||
decoder.codingPath.append(key)
|
||||
defer { decoder.codingPath.removeLast() }
|
||||
|
||||
let elements = container.withShared { keyedBox in
|
||||
keyedBox.elements[key.stringValue]
|
||||
}
|
||||
|
||||
let attributes = container.withShared { keyedBox in
|
||||
keyedBox.attributes[key.stringValue]
|
||||
}
|
||||
|
||||
let box: Box = elements.first ?? attributes.first ?? NullBox()
|
||||
return XMLDecoderImplementation(
|
||||
referencing: box,
|
||||
options: decoder.options,
|
||||
nodeDecodings: decoder.nodeDecodings,
|
||||
codingPath: decoder.codingPath
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Shawn Moore on 11/21/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer {
|
||||
// MARK: Properties
|
||||
|
||||
/// A reference to the decoder we're reading from.
|
||||
private let decoder: XMLDecoderImplementation
|
||||
|
||||
/// A reference to the container we're reading from.
|
||||
private let container: SharedBox<UnkeyedBox>
|
||||
|
||||
/// The path of coding keys taken to get to this point in decoding.
|
||||
public private(set) var codingPath: [CodingKey]
|
||||
|
||||
/// The index of the element we're about to decode.
|
||||
public private(set) var currentIndex: Int
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` by referencing the given decoder and container.
|
||||
init(referencing decoder: XMLDecoderImplementation, wrapping container: SharedBox<UnkeyedBox>) {
|
||||
self.decoder = decoder
|
||||
self.container = container
|
||||
codingPath = decoder.codingPath
|
||||
currentIndex = 0
|
||||
}
|
||||
|
||||
// MARK: - UnkeyedDecodingContainer Methods
|
||||
|
||||
public var count: Int? {
|
||||
return container.withShared { unkeyedBox in
|
||||
unkeyedBox.count
|
||||
}
|
||||
}
|
||||
|
||||
public var isAtEnd: Bool {
|
||||
return currentIndex >= count!
|
||||
}
|
||||
|
||||
public mutating func decodeNil() throws -> Bool {
|
||||
guard !isAtEnd else {
|
||||
throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(
|
||||
codingPath: decoder.codingPath + [XMLKey(index: currentIndex)],
|
||||
debugDescription: "Unkeyed container is at end."
|
||||
))
|
||||
}
|
||||
|
||||
let isNull = container.withShared { unkeyedBox in
|
||||
unkeyedBox[self.currentIndex].isNull
|
||||
}
|
||||
|
||||
if isNull {
|
||||
currentIndex += 1
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func decode<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
return try decode(type) { decoder, box in
|
||||
try decoder.unbox(box)
|
||||
}
|
||||
}
|
||||
|
||||
private mutating func decode<T: Decodable>(
|
||||
_ type: T.Type,
|
||||
decode: (XMLDecoderImplementation, Box) throws -> T?
|
||||
) throws -> T {
|
||||
decoder.codingPath.append(XMLKey(index: currentIndex))
|
||||
let nodeDecodings = decoder.options.nodeDecodingStrategy.nodeDecodings(
|
||||
forType: T.self,
|
||||
with: decoder
|
||||
)
|
||||
decoder.nodeDecodings.append(nodeDecodings)
|
||||
defer {
|
||||
_ = decoder.nodeDecodings.removeLast()
|
||||
_ = decoder.codingPath.removeLast()
|
||||
}
|
||||
guard !isAtEnd else {
|
||||
throw DecodingError.valueNotFound(type, DecodingError.Context(
|
||||
codingPath: decoder.codingPath + [XMLKey(index: currentIndex)],
|
||||
debugDescription: "Unkeyed container is at end."
|
||||
))
|
||||
}
|
||||
|
||||
decoder.codingPath.append(XMLKey(index: currentIndex))
|
||||
defer { self.decoder.codingPath.removeLast() }
|
||||
|
||||
let box = container.withShared { unkeyedBox in
|
||||
unkeyedBox[self.currentIndex]
|
||||
}
|
||||
|
||||
var value: T?
|
||||
if let singleKeyed = box as? SingleKeyedBox {
|
||||
do {
|
||||
value = try decode(decoder, singleKeyed)
|
||||
} catch {
|
||||
do {
|
||||
// Drill down to the element in the case of an nested unkeyed element
|
||||
value = try decode(decoder, singleKeyed.element)
|
||||
} catch {
|
||||
// Specialize for choice elements
|
||||
value = try decode(decoder, ChoiceBox(key: singleKeyed.key, element: singleKeyed.element))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value = try decode(decoder, box)
|
||||
}
|
||||
|
||||
defer { currentIndex += 1 }
|
||||
|
||||
if value == nil, let type = type as? AnyOptional.Type,
|
||||
let result = type.init() as? T
|
||||
{
|
||||
return result
|
||||
}
|
||||
|
||||
guard let decoded: T = value else {
|
||||
throw DecodingError.valueNotFound(type, DecodingError.Context(
|
||||
codingPath: decoder.codingPath + [XMLKey(index: currentIndex)],
|
||||
debugDescription: "Expected \(type) but found null instead."
|
||||
))
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
public mutating func nestedContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type
|
||||
) throws -> KeyedDecodingContainer<NestedKey> {
|
||||
decoder.codingPath.append(XMLKey(index: currentIndex))
|
||||
defer { self.decoder.codingPath.removeLast() }
|
||||
|
||||
guard !isAtEnd else {
|
||||
throw DecodingError.valueNotFound(
|
||||
KeyedDecodingContainer<NestedKey>.self, DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: "Cannot get nested keyed container -- unkeyed container is at end."
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let value = self.container.withShared { unkeyedBox in
|
||||
unkeyedBox[self.currentIndex]
|
||||
}
|
||||
guard !value.isNull else {
|
||||
throw DecodingError.valueNotFound(KeyedDecodingContainer<NestedKey>.self, DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: "Cannot get keyed decoding container -- found null value instead."
|
||||
))
|
||||
}
|
||||
|
||||
guard let keyedContainer = value as? SharedBox<KeyedBox> else {
|
||||
throw DecodingError.typeMismatch(at: codingPath,
|
||||
expectation: [String: Any].self,
|
||||
reality: value)
|
||||
}
|
||||
|
||||
currentIndex += 1
|
||||
let container = XMLKeyedDecodingContainer<NestedKey>(
|
||||
referencing: decoder,
|
||||
wrapping: keyedContainer
|
||||
)
|
||||
return KeyedDecodingContainer(container)
|
||||
}
|
||||
|
||||
public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
|
||||
decoder.codingPath.append(XMLKey(index: currentIndex))
|
||||
defer { self.decoder.codingPath.removeLast() }
|
||||
|
||||
guard !isAtEnd else {
|
||||
throw DecodingError.valueNotFound(
|
||||
UnkeyedDecodingContainer.self, DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: "Cannot get nested keyed container -- unkeyed container is at end."
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let value = container.withShared { unkeyedBox in
|
||||
unkeyedBox[self.currentIndex]
|
||||
}
|
||||
guard !value.isNull else {
|
||||
throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: "Cannot get keyed decoding container -- found null value instead."
|
||||
))
|
||||
}
|
||||
|
||||
guard let unkeyedContainer = value as? SharedBox<UnkeyedBox> else {
|
||||
throw DecodingError.typeMismatch(at: codingPath,
|
||||
expectation: UnkeyedBox.self,
|
||||
reality: value)
|
||||
}
|
||||
|
||||
currentIndex += 1
|
||||
return XMLUnkeyedDecodingContainer(referencing: decoder, wrapping: unkeyedContainer)
|
||||
}
|
||||
|
||||
public mutating func superDecoder() throws -> Decoder {
|
||||
decoder.codingPath.append(XMLKey(index: currentIndex))
|
||||
defer { self.decoder.codingPath.removeLast() }
|
||||
|
||||
guard !isAtEnd else {
|
||||
throw DecodingError.valueNotFound(Decoder.self, DecodingError.Context(
|
||||
codingPath: codingPath,
|
||||
debugDescription: "Cannot get superDecoder() -- unkeyed container is at end."
|
||||
))
|
||||
}
|
||||
|
||||
let value = container.withShared { unkeyedBox in
|
||||
unkeyedBox[self.currentIndex]
|
||||
}
|
||||
currentIndex += 1
|
||||
|
||||
return XMLDecoderImplementation(
|
||||
referencing: value,
|
||||
options: decoder.options,
|
||||
nodeDecodings: decoder.nodeDecodings,
|
||||
codingPath: decoder.codingPath
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user