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,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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user