mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-08 23:23:20 +00:00
293 lines
9.5 KiB
Swift
293 lines
9.5 KiB
Swift
// 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/22/17.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
class XMLEncoderImplementation: Encoder {
|
|
// MARK: Properties
|
|
|
|
/// The encoder's storage.
|
|
var storage: XMLEncodingStorage
|
|
|
|
/// Options set on the top-level encoder.
|
|
let options: XMLEncoder.Options
|
|
|
|
/// The path to the current point in encoding.
|
|
public var codingPath: [CodingKey]
|
|
|
|
public var nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding?]
|
|
|
|
/// Contextual user-provided information for use during encoding.
|
|
public var userInfo: [CodingUserInfoKey: Any] {
|
|
return options.userInfo
|
|
}
|
|
|
|
// MARK: - Initialization
|
|
|
|
/// Initializes `self` with the given top-level encoder options.
|
|
init(
|
|
options: XMLEncoder.Options,
|
|
nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding?],
|
|
codingPath: [CodingKey] = []
|
|
) {
|
|
self.options = options
|
|
storage = XMLEncodingStorage()
|
|
self.codingPath = codingPath
|
|
self.nodeEncodings = nodeEncodings
|
|
}
|
|
|
|
/// Returns whether a new element can be encoded at this coding path.
|
|
///
|
|
/// `true` if an element has not yet been encoded at this coding path; `false` otherwise.
|
|
var canEncodeNewValue: Bool {
|
|
// Every time a new value gets encoded, the key it's encoded for is
|
|
// pushed onto the coding path (even if it's a nil key from an unkeyed container).
|
|
// At the same time, every time a container is requested, a new value
|
|
// gets pushed onto the storage stack.
|
|
// If there are more values on the storage stack than on the coding path,
|
|
// it means the value is requesting more than one container, which
|
|
// violates the precondition.
|
|
//
|
|
// This means that anytime something that can request a new container
|
|
// goes onto the stack, we MUST push a key onto the coding path.
|
|
// Things which will not request containers do not need to have the
|
|
// coding path extended for them (but it doesn't matter if it is,
|
|
// because they will not reach here).
|
|
return storage.count == codingPath.count
|
|
}
|
|
|
|
// MARK: - Encoder Methods
|
|
|
|
public func container<Key>(keyedBy _: Key.Type) -> KeyedEncodingContainer<Key> {
|
|
guard canEncodeNewValue else {
|
|
return mergeWithExistingKeyedContainer(keyedBy: Key.self)
|
|
}
|
|
if Key.self is XMLChoiceCodingKey.Type {
|
|
return choiceContainer(keyedBy: Key.self)
|
|
} else {
|
|
return keyedContainer(keyedBy: Key.self)
|
|
}
|
|
}
|
|
|
|
public func unkeyedContainer() -> UnkeyedEncodingContainer {
|
|
// If an existing unkeyed container was already requested, return that one.
|
|
let topContainer: SharedBox<UnkeyedBox>
|
|
if canEncodeNewValue {
|
|
// We haven't yet pushed a container at this level; do so here.
|
|
topContainer = storage.pushUnkeyedContainer()
|
|
} else {
|
|
guard let container = storage.lastContainer as? SharedBox<UnkeyedBox> else {
|
|
preconditionFailure(
|
|
"""
|
|
Attempt to push new unkeyed encoding container when already previously encoded \
|
|
at this path.
|
|
"""
|
|
)
|
|
}
|
|
|
|
topContainer = container
|
|
}
|
|
|
|
return XMLUnkeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer)
|
|
}
|
|
|
|
public func singleValueContainer() -> SingleValueEncodingContainer {
|
|
return self
|
|
}
|
|
|
|
private func keyedContainer<Key>(keyedBy _: Key.Type) -> KeyedEncodingContainer<Key> {
|
|
let container = XMLKeyedEncodingContainer<Key>(
|
|
referencing: self,
|
|
codingPath: codingPath,
|
|
wrapping: storage.pushKeyedContainer()
|
|
)
|
|
return KeyedEncodingContainer(container)
|
|
}
|
|
|
|
private func choiceContainer<Key>(keyedBy _: Key.Type) -> KeyedEncodingContainer<Key> {
|
|
let container = XMLChoiceEncodingContainer<Key>(
|
|
referencing: self,
|
|
codingPath: codingPath,
|
|
wrapping: storage.pushChoiceContainer()
|
|
)
|
|
return KeyedEncodingContainer(container)
|
|
}
|
|
|
|
private func mergeWithExistingKeyedContainer<Key>(keyedBy _: Key.Type) -> KeyedEncodingContainer<Key> {
|
|
switch storage.lastContainer {
|
|
case let keyed as SharedBox<KeyedBox>:
|
|
let container = XMLKeyedEncodingContainer<Key>(
|
|
referencing: self,
|
|
codingPath: codingPath,
|
|
wrapping: keyed
|
|
)
|
|
return KeyedEncodingContainer(container)
|
|
case let choice as SharedBox<ChoiceBox>:
|
|
_ = storage.popContainer()
|
|
let keyed = KeyedBox(
|
|
elements: KeyedBox.Elements([choice.withShared { ($0.key, $0.element) }]),
|
|
attributes: []
|
|
)
|
|
let container = XMLKeyedEncodingContainer<Key>(
|
|
referencing: self,
|
|
codingPath: codingPath,
|
|
wrapping: storage.pushKeyedContainer(keyed)
|
|
)
|
|
return KeyedEncodingContainer(container)
|
|
default:
|
|
preconditionFailure(
|
|
"""
|
|
No existing keyed encoding container to merge with.
|
|
"""
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension XMLEncoderImplementation {
|
|
/// Returns the given value boxed in a container appropriate for pushing onto the container stack.
|
|
func box() -> SimpleBox {
|
|
return NullBox()
|
|
}
|
|
|
|
func box(_ value: Bool) -> SimpleBox {
|
|
return BoolBox(value)
|
|
}
|
|
|
|
func box(_ value: Decimal) -> SimpleBox {
|
|
return DecimalBox(value)
|
|
}
|
|
|
|
func box<T: BinaryInteger & SignedInteger & Encodable>(_ value: T) -> SimpleBox {
|
|
return IntBox(value)
|
|
}
|
|
|
|
func box<T: BinaryInteger & UnsignedInteger & Encodable>(_ value: T) -> SimpleBox {
|
|
return UIntBox(value)
|
|
}
|
|
|
|
func box(_ value: Float) throws -> SimpleBox {
|
|
return try box(value, FloatBox.self)
|
|
}
|
|
|
|
func box(_ value: Double) throws -> SimpleBox {
|
|
return try box(value, DoubleBox.self)
|
|
}
|
|
|
|
func box<T: BinaryFloatingPoint & Encodable, B: ValueBox>(
|
|
_ value: T,
|
|
_: B.Type
|
|
) throws -> SimpleBox where B.Unboxed == T {
|
|
guard value.isInfinite || value.isNaN else {
|
|
return B(value)
|
|
}
|
|
guard case let .convertToString(
|
|
positiveInfinity: posInfString,
|
|
negativeInfinity: negInfString,
|
|
nan: nanString
|
|
) = options.nonConformingFloatEncodingStrategy else {
|
|
throw EncodingError._invalidFloatingPointValue(value, at: codingPath)
|
|
}
|
|
if value == T.infinity {
|
|
return StringBox(posInfString)
|
|
} else if value == -T.infinity {
|
|
return StringBox(negInfString)
|
|
} else {
|
|
return StringBox(nanString)
|
|
}
|
|
}
|
|
|
|
func box(_ value: String) -> SimpleBox {
|
|
return StringBox(value)
|
|
}
|
|
|
|
func box(_ value: Date) throws -> Box {
|
|
switch options.dateEncodingStrategy {
|
|
case .deferredToDate:
|
|
try value.encode(to: self)
|
|
return storage.popContainer()
|
|
case .secondsSince1970:
|
|
return DateBox(value, format: .secondsSince1970)
|
|
case .millisecondsSince1970:
|
|
return DateBox(value, format: .millisecondsSince1970)
|
|
case .iso8601:
|
|
return DateBox(value, format: .iso8601)
|
|
case let .formatted(formatter):
|
|
return DateBox(value, format: .formatter(formatter))
|
|
case let .custom(closure):
|
|
let depth = storage.count
|
|
try closure(value, self)
|
|
|
|
guard storage.count > depth else {
|
|
return KeyedBox()
|
|
}
|
|
|
|
return storage.popContainer()
|
|
}
|
|
}
|
|
|
|
func box(_ value: Data) throws -> Box {
|
|
switch options.dataEncodingStrategy {
|
|
case .deferredToData:
|
|
try value.encode(to: self)
|
|
return storage.popContainer()
|
|
case .base64:
|
|
return DataBox(value, format: .base64)
|
|
case let .custom(closure):
|
|
let depth = storage.count
|
|
try closure(value, self)
|
|
|
|
guard storage.count > depth else {
|
|
return KeyedBox()
|
|
}
|
|
|
|
return storage.popContainer()
|
|
}
|
|
}
|
|
|
|
func box(_ value: URL) -> SimpleBox {
|
|
return URLBox(value)
|
|
}
|
|
|
|
func box<T: Encodable>(_ value: T) throws -> Box {
|
|
if T.self == Date.self || T.self == NSDate.self,
|
|
let value = value as? Date
|
|
{
|
|
return try box(value)
|
|
} else if T.self == Data.self || T.self == NSData.self,
|
|
let value = value as? Data
|
|
{
|
|
return try box(value)
|
|
} else if T.self == URL.self || T.self == NSURL.self,
|
|
let value = value as? URL
|
|
{
|
|
return box(value)
|
|
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self,
|
|
let value = value as? Decimal
|
|
{
|
|
return box(value)
|
|
}
|
|
|
|
let depth = storage.count
|
|
try value.encode(to: self)
|
|
|
|
// The top container should be a new container.
|
|
guard storage.count > depth else {
|
|
return KeyedBox()
|
|
}
|
|
|
|
let lastContainer = storage.popContainer()
|
|
|
|
guard let sharedBox = lastContainer as? TypeErasedSharedBoxProtocol else {
|
|
return lastContainer
|
|
}
|
|
|
|
return sharedBox.typeErasedUnbox()
|
|
}
|
|
}
|