petitlyrics

This commit is contained in:
eevee
2024-07-17 01:25:40 +03:00
parent 828f8d36bc
commit 4e6bf27c44
63 changed files with 5962 additions and 33 deletions
@@ -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
)
}
}