Files
EeveeSpotifyReborn/Sources/EeveeSpotify/Dependencies/XMLCoder/Encoder/XMLEncoderImplementation.swift
2024-07-17 01:25:40 +03:00

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()
}
}