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,57 @@
|
||||
// Copyright (c) 2019-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Joseph Mattiello on 1/24/19.
|
||||
//
|
||||
|
||||
/** Allows conforming types to specify how its properties will be encoded.
|
||||
|
||||
For example:
|
||||
```swift
|
||||
struct Book: Codable, Equatable, DynamicNodeEncoding {
|
||||
let id: UInt
|
||||
let title: String
|
||||
let categories: [Category]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case title
|
||||
case categories = "category"
|
||||
}
|
||||
|
||||
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
switch key {
|
||||
case Book.CodingKeys.id: return .both
|
||||
default: return .element
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
produces XML of this form for values of type `Book`:
|
||||
|
||||
```xml
|
||||
<book id="123">
|
||||
<id>123</id>
|
||||
<title>Cat in the Hat</title>
|
||||
<category>Kids</category>
|
||||
<category>Wildlife</category>
|
||||
</book>
|
||||
```
|
||||
*/
|
||||
public protocol DynamicNodeEncoding: Encodable {
|
||||
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding
|
||||
}
|
||||
|
||||
extension Array: DynamicNodeEncoding where Element: DynamicNodeEncoding {
|
||||
public static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
return Element.nodeEncoding(for: key)
|
||||
}
|
||||
}
|
||||
|
||||
public extension DynamicNodeEncoding where Self: Collection, Self.Iterator.Element: DynamicNodeEncoding {
|
||||
static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
|
||||
return Element.nodeEncoding(for: key)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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
|
||||
|
||||
/// Error Utilities
|
||||
extension EncodingError {
|
||||
/// Returns a `.invalidValue` error describing the given invalid floating-point value.
|
||||
///
|
||||
///
|
||||
/// - parameter value: The value that was invalid to encode.
|
||||
/// - parameter path: The path of `CodingKey`s taken to encode this value.
|
||||
/// - returns: An `EncodingError` with the appropriate path and debug description.
|
||||
static func _invalidFloatingPointValue<T: FloatingPoint>(_ value: T, at codingPath: [CodingKey]) -> EncodingError {
|
||||
let valueDescription: String
|
||||
if value == T.infinity {
|
||||
valueDescription = "\(T.self).infinity"
|
||||
} else if value == -T.infinity {
|
||||
valueDescription = "-\(T.self).infinity"
|
||||
} else {
|
||||
valueDescription = "\(T.self).nan"
|
||||
}
|
||||
|
||||
let debugDescription = """
|
||||
Unable to encode \(valueDescription) directly in XML. \
|
||||
Use XMLEncoder.NonConformingFloatEncodingStrategy.convertToString \
|
||||
to specify how the value should be encoded.
|
||||
"""
|
||||
return .invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: debugDescription))
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// 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
|
||||
|
||||
extension XMLEncoderImplementation: SingleValueEncodingContainer {
|
||||
// MARK: - SingleValueEncodingContainer Methods
|
||||
|
||||
func assertCanEncodeNewValue() {
|
||||
precondition(
|
||||
canEncodeNewValue,
|
||||
"""
|
||||
Attempt to encode value through single value container when \
|
||||
previously value already encoded.
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
public func encodeNil() throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box())
|
||||
}
|
||||
|
||||
public func encode(_ value: Bool) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: Int) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: Int8) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: Int16) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: Int32) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: Int64) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: UInt) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: UInt8) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: UInt16) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: UInt32) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: UInt64) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: String) throws {
|
||||
assertCanEncodeNewValue()
|
||||
storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: Float) throws {
|
||||
assertCanEncodeNewValue()
|
||||
try storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode(_ value: Double) throws {
|
||||
assertCanEncodeNewValue()
|
||||
try storage.push(container: box(value))
|
||||
}
|
||||
|
||||
public func encode<T: Encodable>(_ value: T) throws {
|
||||
assertCanEncodeNewValue()
|
||||
try storage.push(container: box(value))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) 2019-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Benjamin Wetherfield on 7/17/19.
|
||||
//
|
||||
|
||||
struct XMLChoiceEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol {
|
||||
typealias Key = K
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// A reference to the encoder we're writing to.
|
||||
private let encoder: XMLEncoderImplementation
|
||||
|
||||
/// A reference to the container we're writing to.
|
||||
private var container: SharedBox<ChoiceBox>
|
||||
|
||||
/// The path of coding keys taken to get to this point in encoding.
|
||||
public private(set) var codingPath: [CodingKey]
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` with the given references.
|
||||
init(
|
||||
referencing encoder: XMLEncoderImplementation,
|
||||
codingPath: [CodingKey],
|
||||
wrapping container: SharedBox<ChoiceBox>
|
||||
) {
|
||||
self.encoder = encoder
|
||||
self.codingPath = codingPath
|
||||
self.container = container
|
||||
}
|
||||
|
||||
// MARK: - Coding Path Operations
|
||||
|
||||
private func converted(_ key: CodingKey) -> CodingKey {
|
||||
switch encoder.options.keyEncodingStrategy {
|
||||
case .useDefaultKeys:
|
||||
return key
|
||||
case .convertToSnakeCase:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToSnakeCase(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
case .convertToKebabCase:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToKebabCase(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
case let .custom(converter):
|
||||
return converter(codingPath + [key])
|
||||
case .capitalized:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToCapitalized(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
case .uppercased:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToUppercased(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
case .lowercased:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToLowercased(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - KeyedEncodingContainerProtocol Methods
|
||||
|
||||
public mutating func encodeNil(forKey key: Key) throws {
|
||||
container.withShared {
|
||||
$0.key = converted(key).stringValue
|
||||
$0.element = NullBox()
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func encode<T: Encodable>(
|
||||
_ value: T,
|
||||
forKey key: Key
|
||||
) throws {
|
||||
return try encode(value, forKey: key) { encoder, value in
|
||||
try encoder.box(value)
|
||||
}
|
||||
}
|
||||
|
||||
private mutating func encode<T: Encodable>(
|
||||
_ value: T,
|
||||
forKey key: Key,
|
||||
encode: (XMLEncoderImplementation, T) throws -> Box
|
||||
) throws {
|
||||
defer {
|
||||
_ = self.encoder.nodeEncodings.removeLast()
|
||||
self.encoder.codingPath.removeLast()
|
||||
}
|
||||
encoder.codingPath.append(key)
|
||||
let nodeEncodings = encoder.options.nodeEncodingStrategy.nodeEncodings(
|
||||
forType: T.self,
|
||||
with: encoder
|
||||
)
|
||||
encoder.nodeEncodings.append(nodeEncodings)
|
||||
let box = try encode(encoder, value)
|
||||
|
||||
let oldSelf = self
|
||||
let elementEncoder: (T, Key, Box) throws -> () = { _, key, box in
|
||||
oldSelf.container.withShared { container in
|
||||
container.element = box
|
||||
container.key = oldSelf.converted(key).stringValue
|
||||
}
|
||||
}
|
||||
|
||||
defer {
|
||||
self = oldSelf
|
||||
}
|
||||
|
||||
try elementEncoder(value, key, box)
|
||||
}
|
||||
|
||||
public mutating func nestedContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type,
|
||||
forKey key: Key
|
||||
) -> KeyedEncodingContainer<NestedKey> {
|
||||
if NestedKey.self is XMLChoiceCodingKey.Type {
|
||||
return nestedChoiceContainer(keyedBy: NestedKey.self, forKey: key)
|
||||
} else {
|
||||
return nestedKeyedContainer(keyedBy: NestedKey.self, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func nestedKeyedContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type,
|
||||
forKey key: Key
|
||||
) -> KeyedEncodingContainer<NestedKey> {
|
||||
let sharedKeyed = SharedBox(KeyedBox())
|
||||
|
||||
self.container.withShared { container in
|
||||
container.element = sharedKeyed
|
||||
container.key = converted(key).stringValue
|
||||
}
|
||||
|
||||
codingPath.append(key)
|
||||
defer { self.codingPath.removeLast() }
|
||||
|
||||
let container = XMLKeyedEncodingContainer<NestedKey>(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedKeyed
|
||||
)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
mutating func nestedChoiceContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type,
|
||||
forKey key: Key
|
||||
) -> KeyedEncodingContainer<NestedKey> {
|
||||
let sharedChoice = SharedBox(ChoiceBox())
|
||||
|
||||
self.container.withShared { container in
|
||||
container.element = sharedChoice
|
||||
container.key = converted(key).stringValue
|
||||
}
|
||||
|
||||
codingPath.append(key)
|
||||
defer { self.codingPath.removeLast() }
|
||||
|
||||
let container = XMLChoiceEncodingContainer<NestedKey>(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedChoice
|
||||
)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
public mutating func nestedUnkeyedContainer(
|
||||
forKey key: Key
|
||||
) -> UnkeyedEncodingContainer {
|
||||
let sharedUnkeyed = SharedBox(UnkeyedBox())
|
||||
|
||||
container.withShared { container in
|
||||
container.element = sharedUnkeyed
|
||||
container.key = converted(key).stringValue
|
||||
}
|
||||
|
||||
codingPath.append(key)
|
||||
defer { self.codingPath.removeLast() }
|
||||
return XMLUnkeyedEncodingContainer(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedUnkeyed
|
||||
)
|
||||
}
|
||||
|
||||
public mutating func superEncoder() -> Encoder {
|
||||
return XMLReferencingEncoder(
|
||||
referencing: encoder,
|
||||
key: XMLKey.super,
|
||||
convertedKey: converted(XMLKey.super),
|
||||
wrapping: container
|
||||
)
|
||||
}
|
||||
|
||||
public mutating func superEncoder(forKey key: Key) -> Encoder {
|
||||
return XMLReferencingEncoder(
|
||||
referencing: encoder,
|
||||
key: key,
|
||||
convertedKey: converted(key),
|
||||
wrapping: container
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
// Copyright © 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/22/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// `XMLEncoder` facilitates the encoding of `Encodable` values into XML.
|
||||
open class XMLEncoder {
|
||||
// MARK: Options
|
||||
|
||||
/// The formatting of the output XML data.
|
||||
public struct OutputFormatting: OptionSet {
|
||||
/// The format's default value.
|
||||
public let rawValue: UInt
|
||||
|
||||
/// Creates an OutputFormatting value with the given raw value.
|
||||
public init(rawValue: UInt) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// Produce human-readable XML with indented output.
|
||||
public static let prettyPrinted = OutputFormatting(rawValue: 1 << 0)
|
||||
|
||||
/// Produce XML with keys sorted in lexicographic order.
|
||||
public static let sortedKeys = OutputFormatting(rawValue: 1 << 1)
|
||||
|
||||
/// Produce XML with no short-hand annotation for empty elements, e.g., use `<p></p>` over `</p>`
|
||||
public static let noEmptyElements = OutputFormatting(rawValue: 1 << 2)
|
||||
}
|
||||
|
||||
/// The indentation to use when XML is pretty-printed.
|
||||
public enum PrettyPrintIndentation {
|
||||
case spaces(Int)
|
||||
case tabs(Int)
|
||||
}
|
||||
|
||||
/// A node's encoding type. Specifies how a node will be encoded.
|
||||
public enum NodeEncoding {
|
||||
case attribute
|
||||
case element
|
||||
case both
|
||||
|
||||
public static let `default`: NodeEncoding = .element
|
||||
}
|
||||
|
||||
/// The strategy to use for encoding `Date` values.
|
||||
public enum DateEncodingStrategy {
|
||||
/// Defer to `Date` for choosing an encoding. This is the default strategy.
|
||||
case deferredToDate
|
||||
|
||||
/// Encode the `Date` as a UNIX timestamp (as a XML number).
|
||||
case secondsSince1970
|
||||
|
||||
/// Encode the `Date` as UNIX millisecond timestamp (as a XML number).
|
||||
case millisecondsSince1970
|
||||
|
||||
/// Encode 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
|
||||
|
||||
/// Encode the `Date` as a string formatted by the given formatter.
|
||||
case formatted(DateFormatter)
|
||||
|
||||
/// Encode the `Date` as a custom value encoded by the given closure.
|
||||
///
|
||||
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
|
||||
case custom((Date, Encoder) throws -> ())
|
||||
}
|
||||
|
||||
/// The strategy to use for encoding `String` values.
|
||||
public enum StringEncodingStrategy {
|
||||
/// Defer to `String` for choosing an encoding. This is the default strategy.
|
||||
case deferredToString
|
||||
|
||||
/// Encode the `String` as a CData-encoded string.
|
||||
case cdata
|
||||
}
|
||||
|
||||
/// The strategy to use for encoding `Data` values.
|
||||
public enum DataEncodingStrategy {
|
||||
/// Defer to `Data` for choosing an encoding.
|
||||
case deferredToData
|
||||
|
||||
/// Encoded the `Data` as a Base64-encoded string. This is the default strategy.
|
||||
case base64
|
||||
|
||||
/// Encode the `Data` as a custom value encoded by the given closure.
|
||||
///
|
||||
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
|
||||
case custom((Data, Encoder) throws -> ())
|
||||
}
|
||||
|
||||
/// The strategy to use for non-XML-conforming floating-point values (IEEE 754 infinity and NaN).
|
||||
public enum NonConformingFloatEncodingStrategy {
|
||||
/// Throw upon encountering non-conforming values. This is the default strategy.
|
||||
case `throw`
|
||||
|
||||
/// Encode the values using the given representation strings.
|
||||
case convertToString(positiveInfinity: String, negativeInfinity: String, nan: String)
|
||||
}
|
||||
|
||||
/// The strategy to use for automatically changing the value of keys before encoding.
|
||||
public enum KeyEncodingStrategy {
|
||||
/// Use the keys specified by each type. This is the default strategy.
|
||||
case useDefaultKeys
|
||||
|
||||
/// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key to XML payload.
|
||||
///
|
||||
/// Capital characters are determined by testing membership in
|
||||
/// `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters`
|
||||
/// (Unicode General Categories Lu and Lt).
|
||||
/// The conversion to lower 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 camel case to snake case:
|
||||
/// 1. Splits words at the boundary of lower-case to upper-case
|
||||
/// 2. Inserts `_` between words
|
||||
/// 3. Lowercases the entire string
|
||||
/// 4. Preserves starting and ending `_`.
|
||||
///
|
||||
/// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`.
|
||||
///
|
||||
/// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted.
|
||||
case convertToSnakeCase
|
||||
|
||||
/// Same as convertToSnakeCase, but using `-` instead of `_`
|
||||
/// For example, `oneTwoThree` becomes `one-two-three`.
|
||||
case convertToKebabCase
|
||||
|
||||
/// Capitalize the first letter only
|
||||
/// `oneTwoThree` becomes `OneTwoThree`
|
||||
case capitalized
|
||||
|
||||
/// Uppercase ize all letters
|
||||
/// `oneTwoThree` becomes `ONETWOTHREE`
|
||||
case uppercased
|
||||
|
||||
/// Lowercase all letters
|
||||
/// `oneTwoThree` becomes `onetwothree`
|
||||
case lowercased
|
||||
|
||||
/// Provide a custom conversion to the key in the encoded XML from the
|
||||
/// keys specified by the encoded types.
|
||||
/// The full path to the current encoding 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 encoding.
|
||||
/// If the result of the conversion is a duplicate key, then only one
|
||||
/// value will be present in the result.
|
||||
case custom((_ codingPath: [CodingKey]) -> CodingKey)
|
||||
|
||||
static func _convertToSnakeCase(_ stringKey: String) -> String {
|
||||
return _convert(stringKey, usingSeparator: "_")
|
||||
}
|
||||
|
||||
static func _convertToKebabCase(_ stringKey: String) -> String {
|
||||
return _convert(stringKey, usingSeparator: "-")
|
||||
}
|
||||
|
||||
static func _convert(_ stringKey: String, usingSeparator separator: String) -> String {
|
||||
guard !stringKey.isEmpty else {
|
||||
return stringKey
|
||||
}
|
||||
|
||||
var words: [Range<String.Index>] = []
|
||||
// The general idea of this algorithm is to split words on
|
||||
// transition from lower to upper case, then on transition of >1
|
||||
// upper case characters to lowercase
|
||||
//
|
||||
// myProperty -> my_property
|
||||
// myURLProperty -> my_url_property
|
||||
//
|
||||
// We assume, per Swift naming conventions, that the first character of the key is lowercase.
|
||||
var wordStart = stringKey.startIndex
|
||||
var searchRange = stringKey.index(after: wordStart)..<stringKey.endIndex
|
||||
|
||||
// Find next uppercase character
|
||||
while let upperCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.uppercaseLetters, options: [], range: searchRange) {
|
||||
let untilUpperCase = wordStart..<upperCaseRange.lowerBound
|
||||
words.append(untilUpperCase)
|
||||
|
||||
// Find next lowercase character
|
||||
searchRange = upperCaseRange.lowerBound..<searchRange.upperBound
|
||||
guard let lowerCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.lowercaseLetters, options: [], range: searchRange) else {
|
||||
// There are no more lower case letters. Just end here.
|
||||
wordStart = searchRange.lowerBound
|
||||
break
|
||||
}
|
||||
|
||||
// Is the next lowercase letter more than 1 after the uppercase?
|
||||
// If so, we encountered a group of uppercase letters that we
|
||||
// should treat as its own word
|
||||
let nextCharacterAfterCapital = stringKey.index(after: upperCaseRange.lowerBound)
|
||||
if lowerCaseRange.lowerBound == nextCharacterAfterCapital {
|
||||
// The next character after capital is a lower case character and therefore not a word boundary.
|
||||
// Continue searching for the next upper case for the boundary.
|
||||
wordStart = upperCaseRange.lowerBound
|
||||
} else {
|
||||
// There was a range of >1 capital letters. Turn those into a word, stopping at the capital before the lower case character.
|
||||
let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound)
|
||||
words.append(upperCaseRange.lowerBound..<beforeLowerIndex)
|
||||
|
||||
// Next word starts at the capital before the lowercase we just found
|
||||
wordStart = beforeLowerIndex
|
||||
}
|
||||
searchRange = lowerCaseRange.upperBound..<searchRange.upperBound
|
||||
}
|
||||
words.append(wordStart..<searchRange.upperBound)
|
||||
let result = words.map { range in
|
||||
stringKey[range].lowercased()
|
||||
}.joined(separator: separator)
|
||||
return result
|
||||
}
|
||||
|
||||
static func _convertToCapitalized(_ stringKey: String) -> String {
|
||||
return stringKey.capitalizingFirstLetter()
|
||||
}
|
||||
|
||||
static func _convertToLowercased(_ stringKey: String) -> String {
|
||||
return stringKey.lowercased()
|
||||
}
|
||||
|
||||
static func _convertToUppercased(_ stringKey: String) -> String {
|
||||
return stringKey.uppercased()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "NodeEncodingStrategy")
|
||||
public typealias NodeEncodingStrategies = NodeEncodingStrategy
|
||||
|
||||
public typealias XMLNodeEncoderClosure = (CodingKey) -> NodeEncoding?
|
||||
public typealias XMLEncodingClosure = (Encodable.Type, Encoder) -> XMLNodeEncoderClosure
|
||||
|
||||
/// Set of strategies to use for encoding of nodes.
|
||||
public enum NodeEncodingStrategy {
|
||||
/// Defer to `Encoder` for choosing an encoding. This is the default strategy.
|
||||
case deferredToEncoder
|
||||
|
||||
/// Return a closure computing the desired node encoding for the value by its coding key.
|
||||
case custom(XMLEncodingClosure)
|
||||
|
||||
func nodeEncodings(
|
||||
forType codableType: Encodable.Type,
|
||||
with encoder: Encoder
|
||||
) -> ((CodingKey) -> NodeEncoding?) {
|
||||
return encoderClosure(codableType, encoder)
|
||||
}
|
||||
|
||||
var encoderClosure: XMLEncodingClosure {
|
||||
switch self {
|
||||
case .deferredToEncoder: return NodeEncodingStrategy.defaultEncoder
|
||||
case let .custom(closure): return closure
|
||||
}
|
||||
}
|
||||
|
||||
static let defaultEncoder: XMLEncodingClosure = { codableType, _ in
|
||||
guard let dynamicType = codableType as? DynamicNodeEncoding.Type else {
|
||||
return { _ in nil }
|
||||
}
|
||||
return dynamicType.nodeEncoding(for:)
|
||||
}
|
||||
}
|
||||
|
||||
/// Characters and their escaped representations to be escaped in attributes
|
||||
open var charactersEscapedInAttributes = [
|
||||
("&", "&"),
|
||||
("<", "<"),
|
||||
(">", ">"),
|
||||
("'", "'"),
|
||||
("\"", """),
|
||||
]
|
||||
|
||||
/// Characters and their escaped representations to be escaped in elements
|
||||
open var charactersEscapedInElements = [
|
||||
("&", "&"),
|
||||
("<", "<"),
|
||||
(">", ">"),
|
||||
("'", "'"),
|
||||
("\"", """),
|
||||
]
|
||||
|
||||
/// The output format to produce. Defaults to `[]`.
|
||||
open var outputFormatting: OutputFormatting = []
|
||||
|
||||
/// The indentation to use when XML is printed. Defaults to `.spaces(4)`.
|
||||
open var prettyPrintIndentation: PrettyPrintIndentation = .spaces(4)
|
||||
|
||||
/// The strategy to use in encoding dates. Defaults to `.deferredToDate`.
|
||||
open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate
|
||||
|
||||
/// The strategy to use in encoding binary data. Defaults to `.base64`.
|
||||
open var dataEncodingStrategy: DataEncodingStrategy = .base64
|
||||
|
||||
/// The strategy to use in encoding non-conforming numbers. Defaults to `.throw`.
|
||||
open var nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy = .throw
|
||||
|
||||
/// The strategy to use for encoding keys. Defaults to `.useDefaultKeys`.
|
||||
open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys
|
||||
|
||||
/// The strategy to use in encoding encoding attributes. Defaults to `.deferredToEncoder`.
|
||||
open var nodeEncodingStrategy: NodeEncodingStrategy = .deferredToEncoder
|
||||
|
||||
/// The strategy to use in encoding strings. Defaults to `.deferredToString`.
|
||||
open var stringEncodingStrategy: StringEncodingStrategy = .deferredToString
|
||||
|
||||
/// Contextual user-provided information for use during encoding.
|
||||
open var userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
|
||||
/// Options set on the top-level encoder to pass down the encoding hierarchy.
|
||||
struct Options {
|
||||
let dateEncodingStrategy: DateEncodingStrategy
|
||||
let dataEncodingStrategy: DataEncodingStrategy
|
||||
let nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy
|
||||
let keyEncodingStrategy: KeyEncodingStrategy
|
||||
let nodeEncodingStrategy: NodeEncodingStrategy
|
||||
let stringEncodingStrategy: StringEncodingStrategy
|
||||
let userInfo: [CodingUserInfoKey: Any]
|
||||
}
|
||||
|
||||
/// The options set on the top-level encoder.
|
||||
var options: Options {
|
||||
return Options(dateEncodingStrategy: dateEncodingStrategy,
|
||||
dataEncodingStrategy: dataEncodingStrategy,
|
||||
nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy,
|
||||
keyEncodingStrategy: keyEncodingStrategy,
|
||||
nodeEncodingStrategy: nodeEncodingStrategy,
|
||||
stringEncodingStrategy: stringEncodingStrategy,
|
||||
userInfo: userInfo)
|
||||
}
|
||||
|
||||
// MARK: - Constructing a XML Encoder
|
||||
|
||||
/// Initializes `self` with default strategies.
|
||||
public init() {}
|
||||
|
||||
// MARK: - Encoding Values
|
||||
|
||||
/// Encodes the given top-level value and returns its XML representation.
|
||||
///
|
||||
/// - parameter value: The value to encode.
|
||||
/// - parameter withRootKey: the key used to wrap the encoded values. The
|
||||
/// default value is inferred from the name of the root type.
|
||||
/// - parameter rootAttributes: the list of attributes to be added to the root node
|
||||
/// - parameter header: the XML header to start the encoded data with.
|
||||
/// - returns: A new `Data` value containing the encoded XML data.
|
||||
/// - throws: `EncodingError.invalidValue` if a non-conforming
|
||||
/// floating-point value is encountered during encoding, and the encoding
|
||||
/// strategy is `.throw`.
|
||||
/// - throws: An error if any value throws an error during encoding.
|
||||
open func encode<T: Encodable>(_ value: T,
|
||||
withRootKey rootKey: String? = nil,
|
||||
rootAttributes: [String: String]? = nil,
|
||||
header: XMLHeader? = nil,
|
||||
doctype: XMLDocumentType? = nil) throws -> Data
|
||||
{
|
||||
let encoder = XMLEncoderImplementation(options: options, nodeEncodings: [])
|
||||
encoder.nodeEncodings.append(options.nodeEncodingStrategy.nodeEncodings(forType: T.self, with: encoder))
|
||||
|
||||
let topLevel = try encoder.box(value)
|
||||
let attributes = rootAttributes?.map(XMLCoderElement.Attribute.init) ?? []
|
||||
|
||||
let elementOrNone: XMLCoderElement?
|
||||
|
||||
let rootKey = rootKey ?? "\(T.self)".convert(for: keyEncodingStrategy)
|
||||
|
||||
let isStringBoxCDATA = stringEncodingStrategy == .cdata
|
||||
|
||||
if let keyedBox = topLevel as? KeyedBox {
|
||||
elementOrNone = XMLCoderElement(
|
||||
key: rootKey,
|
||||
isStringBoxCDATA: isStringBoxCDATA,
|
||||
box: keyedBox,
|
||||
attributes: attributes
|
||||
)
|
||||
} else if let unkeyedBox = topLevel as? UnkeyedBox {
|
||||
elementOrNone = XMLCoderElement(
|
||||
key: rootKey,
|
||||
isStringBoxCDATA: isStringBoxCDATA,
|
||||
box: unkeyedBox,
|
||||
attributes: attributes
|
||||
)
|
||||
} else if let choiceBox = topLevel as? ChoiceBox {
|
||||
elementOrNone = XMLCoderElement(
|
||||
key: rootKey,
|
||||
isStringBoxCDATA: isStringBoxCDATA,
|
||||
box: choiceBox,
|
||||
attributes: attributes
|
||||
)
|
||||
} else {
|
||||
fatalError("Unrecognized top-level element of type: \(type(of: topLevel))")
|
||||
}
|
||||
|
||||
guard let element = elementOrNone else {
|
||||
throw EncodingError.invalidValue(value, EncodingError.Context(
|
||||
codingPath: [],
|
||||
debugDescription: "Unable to encode the given top-level value to XML."
|
||||
))
|
||||
}
|
||||
|
||||
return element.toXMLString(
|
||||
with: header,
|
||||
doctype: doctype,
|
||||
escapedCharacters: (
|
||||
elements: charactersEscapedInElements,
|
||||
attributes: charactersEscapedInAttributes
|
||||
),
|
||||
formatting: outputFormatting,
|
||||
indentation: prettyPrintIndentation
|
||||
).data(using: .utf8, allowLossyConversion: true)!
|
||||
}
|
||||
|
||||
// MARK: - TopLevelEncoder
|
||||
|
||||
#if canImport(Combine) || canImport(OpenCombine)
|
||||
open func encode<T>(_ value: T) throws -> Data where T: Encodable {
|
||||
return try encode(value, withRootKey: nil, rootAttributes: nil, header: nil)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private extension String {
|
||||
func convert(for encodingStrategy: XMLEncoder.KeyEncodingStrategy) -> String {
|
||||
switch encodingStrategy {
|
||||
case .useDefaultKeys:
|
||||
return self
|
||||
case .convertToSnakeCase:
|
||||
return XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(self)
|
||||
case .convertToKebabCase:
|
||||
return XMLEncoder.KeyEncodingStrategy._convertToKebabCase(self)
|
||||
case .custom:
|
||||
return self
|
||||
case .capitalized:
|
||||
return XMLEncoder.KeyEncodingStrategy._convertToCapitalized(self)
|
||||
case .uppercased:
|
||||
return XMLEncoder.KeyEncodingStrategy._convertToUppercased(self)
|
||||
case .lowercased:
|
||||
return XMLEncoder.KeyEncodingStrategy._convertToLowercased(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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
|
||||
|
||||
// MARK: - Encoding Storage and Containers
|
||||
|
||||
struct XMLEncodingStorage {
|
||||
// MARK: Properties
|
||||
|
||||
/// The container stack.
|
||||
private var containers: [Box] = []
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` with no containers.
|
||||
init() {}
|
||||
|
||||
// MARK: - Modifying the Stack
|
||||
|
||||
var count: Int {
|
||||
return containers.count
|
||||
}
|
||||
|
||||
var lastContainer: Box? {
|
||||
return containers.last
|
||||
}
|
||||
|
||||
mutating func pushKeyedContainer(_ keyedBox: KeyedBox = KeyedBox()) -> SharedBox<KeyedBox> {
|
||||
let container = SharedBox(keyedBox)
|
||||
containers.append(container)
|
||||
return container
|
||||
}
|
||||
|
||||
mutating func pushChoiceContainer() -> SharedBox<ChoiceBox> {
|
||||
let container = SharedBox(ChoiceBox())
|
||||
containers.append(container)
|
||||
return container
|
||||
}
|
||||
|
||||
mutating func pushUnkeyedContainer() -> SharedBox<UnkeyedBox> {
|
||||
let container = SharedBox(UnkeyedBox())
|
||||
containers.append(container)
|
||||
return container
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func popContainer() -> Box {
|
||||
precondition(!containers.isEmpty, "Empty container stack.")
|
||||
return containers.popLast()!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// Copyright (c) 2018-2021 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 11/20/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct XMLKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol {
|
||||
typealias Key = K
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// A reference to the encoder we're writing to.
|
||||
private let encoder: XMLEncoderImplementation
|
||||
|
||||
/// A reference to the container we're writing to.
|
||||
private var container: SharedBox<KeyedBox>
|
||||
|
||||
/// The path of coding keys taken to get to this point in encoding.
|
||||
public private(set) var codingPath: [CodingKey]
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` with the given references.
|
||||
init(
|
||||
referencing encoder: XMLEncoderImplementation,
|
||||
codingPath: [CodingKey],
|
||||
wrapping container: SharedBox<KeyedBox>
|
||||
) {
|
||||
self.encoder = encoder
|
||||
self.codingPath = codingPath
|
||||
self.container = container
|
||||
}
|
||||
|
||||
// MARK: - Coding Path Operations
|
||||
|
||||
private func converted(_ key: CodingKey) -> CodingKey {
|
||||
switch encoder.options.keyEncodingStrategy {
|
||||
case .useDefaultKeys:
|
||||
return key
|
||||
case .convertToSnakeCase:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToSnakeCase(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
case .convertToKebabCase:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToKebabCase(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
case let .custom(converter):
|
||||
return converter(codingPath + [key])
|
||||
case .capitalized:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToCapitalized(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
case .uppercased:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToUppercased(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
case .lowercased:
|
||||
let newKeyString = XMLEncoder.KeyEncodingStrategy
|
||||
._convertToLowercased(key.stringValue)
|
||||
return XMLKey(stringValue: newKeyString, intValue: key.intValue)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - KeyedEncodingContainerProtocol Methods
|
||||
|
||||
public mutating func encodeNil(forKey key: Key) throws {
|
||||
container.withShared {
|
||||
$0.elements.append(NullBox(), at: converted(key).stringValue)
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func encode<T: Encodable>(
|
||||
_ value: T,
|
||||
forKey key: Key
|
||||
) throws {
|
||||
return try encode(value, forKey: key) { encoder, value in
|
||||
try encoder.box(value)
|
||||
}
|
||||
}
|
||||
|
||||
private mutating func encode<T: Encodable>(
|
||||
_ value: T,
|
||||
forKey key: Key,
|
||||
encode: (XMLEncoderImplementation, T) throws -> Box
|
||||
) throws {
|
||||
defer {
|
||||
_ = self.encoder.nodeEncodings.removeLast()
|
||||
self.encoder.codingPath.removeLast()
|
||||
}
|
||||
guard let strategy = encoder.nodeEncodings.last else {
|
||||
preconditionFailure(
|
||||
"Attempt to access node encoding strategy from empty stack."
|
||||
)
|
||||
}
|
||||
encoder.codingPath.append(key)
|
||||
let nodeEncodings = encoder.options.nodeEncodingStrategy.nodeEncodings(
|
||||
forType: T.self,
|
||||
with: encoder
|
||||
)
|
||||
encoder.nodeEncodings.append(nodeEncodings)
|
||||
let box = try encode(encoder, value)
|
||||
|
||||
let oldSelf = self
|
||||
let attributeEncoder: (T, Key, Box) throws -> () = { value, key, box in
|
||||
guard let attribute = box as? SimpleBox else {
|
||||
throw EncodingError.invalidValue(value, EncodingError.Context(
|
||||
codingPath: [],
|
||||
debugDescription: "Complex values cannot be encoded as attributes."
|
||||
))
|
||||
}
|
||||
oldSelf.container.withShared { container in
|
||||
container.attributes.append(attribute, at: oldSelf.converted(key).stringValue)
|
||||
}
|
||||
}
|
||||
|
||||
let elementEncoder: (T, Key, Box) throws -> () = { _, key, box in
|
||||
oldSelf.container.withShared { container in
|
||||
container.elements.append(box, at: oldSelf.converted(key).stringValue)
|
||||
}
|
||||
}
|
||||
|
||||
defer {
|
||||
self = oldSelf
|
||||
}
|
||||
|
||||
switch strategy(key) {
|
||||
case .attribute?:
|
||||
try attributeEncoder(value, key, box)
|
||||
case .element?:
|
||||
try elementEncoder(value, key, box)
|
||||
case .both?:
|
||||
try attributeEncoder(value, key, box)
|
||||
try elementEncoder(value, key, box)
|
||||
default:
|
||||
switch value {
|
||||
case is XMLElementProtocol:
|
||||
encodeElement(forKey: key, box: box)
|
||||
case is XMLAttributeProtocol:
|
||||
try encodeAttribute(value, forKey: key, box: box)
|
||||
case is XMLElementAndAttributeProtocol:
|
||||
try encodeAttribute(value, forKey: key, box: box)
|
||||
encodeElement(forKey: key, box: box)
|
||||
default:
|
||||
encodeElement(forKey: key, box: box)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private mutating func encodeAttribute<T: Encodable>(
|
||||
_ value: T,
|
||||
forKey key: Key,
|
||||
box: Box
|
||||
) throws {
|
||||
guard let attribute = box as? SimpleBox else {
|
||||
throw EncodingError.invalidValue(value, EncodingError.Context(
|
||||
codingPath: [],
|
||||
debugDescription: "Complex values cannot be encoded as attributes."
|
||||
))
|
||||
}
|
||||
container.withShared { container in
|
||||
container.attributes.append(attribute, at: self.converted(key).stringValue)
|
||||
}
|
||||
}
|
||||
|
||||
private mutating func encodeElement(
|
||||
forKey key: Key,
|
||||
box: Box
|
||||
) {
|
||||
container.withShared { container in
|
||||
container.elements.append(box, at: self.converted(key).stringValue)
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func nestedContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type,
|
||||
forKey key: Key
|
||||
) -> KeyedEncodingContainer<NestedKey> {
|
||||
if NestedKey.self is XMLChoiceCodingKey.Type {
|
||||
return nestedChoiceContainer(keyedBy: NestedKey.self, forKey: key)
|
||||
} else {
|
||||
return nestedKeyedContainer(keyedBy: NestedKey.self, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func nestedKeyedContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type,
|
||||
forKey key: Key
|
||||
) -> KeyedEncodingContainer<NestedKey> {
|
||||
let sharedKeyed = SharedBox(KeyedBox())
|
||||
|
||||
self.container.withShared { container in
|
||||
container.elements.append(sharedKeyed, at: converted(key).stringValue)
|
||||
}
|
||||
|
||||
codingPath.append(key)
|
||||
defer { self.codingPath.removeLast() }
|
||||
|
||||
let container = XMLKeyedEncodingContainer<NestedKey>(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedKeyed
|
||||
)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
mutating func nestedChoiceContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type,
|
||||
forKey key: Key
|
||||
) -> KeyedEncodingContainer<NestedKey> {
|
||||
let sharedChoice = SharedBox(ChoiceBox())
|
||||
|
||||
self.container.withShared { container in
|
||||
container.elements.append(sharedChoice, at: converted(key).stringValue)
|
||||
}
|
||||
|
||||
codingPath.append(key)
|
||||
defer { self.codingPath.removeLast() }
|
||||
|
||||
let container = XMLChoiceEncodingContainer<NestedKey>(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedChoice
|
||||
)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
public mutating func nestedUnkeyedContainer(
|
||||
forKey key: Key
|
||||
) -> UnkeyedEncodingContainer {
|
||||
let sharedUnkeyed = SharedBox(UnkeyedBox())
|
||||
|
||||
container.withShared { container in
|
||||
container.elements.append(sharedUnkeyed, at: converted(key).stringValue)
|
||||
}
|
||||
|
||||
codingPath.append(key)
|
||||
defer { self.codingPath.removeLast() }
|
||||
return XMLUnkeyedEncodingContainer(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedUnkeyed
|
||||
)
|
||||
}
|
||||
|
||||
public mutating func superEncoder() -> Encoder {
|
||||
return XMLReferencingEncoder(
|
||||
referencing: encoder,
|
||||
key: XMLKey.super,
|
||||
convertedKey: converted(XMLKey.super),
|
||||
wrapping: container
|
||||
)
|
||||
}
|
||||
|
||||
public mutating func superEncoder(forKey key: Key) -> Encoder {
|
||||
return XMLReferencingEncoder(
|
||||
referencing: encoder,
|
||||
key: key,
|
||||
convertedKey: converted(key),
|
||||
wrapping: container
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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/25/17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// XMLReferencingEncoder is a special subclass of _XMLEncoder which has its
|
||||
/// own storage, but references the contents of a different encoder.
|
||||
/// It's used in superEncoder(), which returns a new encoder for encoding a
|
||||
// superclass -- the lifetime of the encoder should not escape the scope it's
|
||||
/// created in, but it doesn't necessarily know when it's done being used
|
||||
/// (to write to the original container).
|
||||
class XMLReferencingEncoder: XMLEncoderImplementation {
|
||||
// MARK: Reference types.
|
||||
|
||||
/// The type of container we're referencing.
|
||||
private enum Reference {
|
||||
/// Referencing a specific index in an unkeyed container.
|
||||
case unkeyed(SharedBox<UnkeyedBox>, Int)
|
||||
|
||||
/// Referencing a specific key in a keyed container.
|
||||
case keyed(SharedBox<KeyedBox>, String)
|
||||
|
||||
/// Referencing a specific key in a keyed container.
|
||||
case choice(SharedBox<ChoiceBox>, String)
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The encoder we're referencing.
|
||||
let encoder: XMLEncoderImplementation
|
||||
|
||||
/// The container reference itself.
|
||||
private let reference: Reference
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` by referencing the given array container in the given encoder.
|
||||
init(
|
||||
referencing encoder: XMLEncoderImplementation,
|
||||
at index: Int,
|
||||
wrapping sharedUnkeyed: SharedBox<UnkeyedBox>
|
||||
) {
|
||||
self.encoder = encoder
|
||||
reference = .unkeyed(sharedUnkeyed, index)
|
||||
super.init(
|
||||
options: encoder.options,
|
||||
nodeEncodings: encoder.nodeEncodings,
|
||||
codingPath: encoder.codingPath
|
||||
)
|
||||
|
||||
codingPath.append(XMLKey(index: index))
|
||||
}
|
||||
|
||||
/// Initializes `self` by referencing the given dictionary container in the given encoder.
|
||||
init(
|
||||
referencing encoder: XMLEncoderImplementation,
|
||||
key: CodingKey,
|
||||
convertedKey: CodingKey,
|
||||
wrapping sharedKeyed: SharedBox<KeyedBox>
|
||||
) {
|
||||
self.encoder = encoder
|
||||
reference = .keyed(sharedKeyed, convertedKey.stringValue)
|
||||
super.init(
|
||||
options: encoder.options,
|
||||
nodeEncodings: encoder.nodeEncodings,
|
||||
codingPath: encoder.codingPath
|
||||
)
|
||||
|
||||
codingPath.append(key)
|
||||
}
|
||||
|
||||
init(
|
||||
referencing encoder: XMLEncoderImplementation,
|
||||
key: CodingKey,
|
||||
convertedKey: CodingKey,
|
||||
wrapping sharedKeyed: SharedBox<ChoiceBox>
|
||||
) {
|
||||
self.encoder = encoder
|
||||
reference = .choice(sharedKeyed, convertedKey.stringValue)
|
||||
super.init(
|
||||
options: encoder.options,
|
||||
nodeEncodings: encoder.nodeEncodings,
|
||||
codingPath: encoder.codingPath
|
||||
)
|
||||
|
||||
codingPath.append(key)
|
||||
}
|
||||
|
||||
// MARK: - Coding Path Operations
|
||||
|
||||
override var canEncodeNewValue: Bool {
|
||||
// With a regular encoder, the storage and coding path grow together.
|
||||
// A referencing encoder, however, inherits its parents coding path, as well as the key it was created for.
|
||||
// We have to take this into account.
|
||||
return storage.count == codingPath.count - encoder.codingPath.count - 1
|
||||
}
|
||||
|
||||
// MARK: - Deinitialization
|
||||
|
||||
// Finalizes `self` by writing the contents of our storage to the referenced encoder's storage.
|
||||
deinit {
|
||||
let box: Box
|
||||
switch self.storage.count {
|
||||
case 0: box = KeyedBox()
|
||||
case 1: box = self.storage.popContainer()
|
||||
default: fatalError("Referencing encoder deallocated with multiple containers on stack.")
|
||||
}
|
||||
|
||||
switch self.reference {
|
||||
case let .unkeyed(sharedUnkeyedBox, index):
|
||||
sharedUnkeyedBox.withShared { unkeyedBox in
|
||||
unkeyedBox.insert(box, at: index)
|
||||
}
|
||||
case let .keyed(sharedKeyedBox, key):
|
||||
sharedKeyedBox.withShared { keyedBox in
|
||||
keyedBox.elements.append(box, at: key)
|
||||
}
|
||||
case let .choice(sharedChoiceBox, key):
|
||||
sharedChoiceBox.withShared { choiceBox in
|
||||
choiceBox.element = box
|
||||
choiceBox.key = key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2018-2020 XMLCoder contributors
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// Created by Vincent Esche on 11/20/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct XMLUnkeyedEncodingContainer: UnkeyedEncodingContainer {
|
||||
// MARK: Properties
|
||||
|
||||
/// A reference to the encoder we're writing to.
|
||||
private let encoder: XMLEncoderImplementation
|
||||
|
||||
/// A reference to the container we're writing to.
|
||||
private let container: SharedBox<UnkeyedBox>
|
||||
|
||||
/// The path of coding keys taken to get to this point in encoding.
|
||||
public private(set) var codingPath: [CodingKey]
|
||||
|
||||
/// The number of elements encoded into the container.
|
||||
public var count: Int {
|
||||
return container.withShared { $0.count }
|
||||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initializes `self` with the given references.
|
||||
init(
|
||||
referencing encoder: XMLEncoderImplementation,
|
||||
codingPath: [CodingKey],
|
||||
wrapping container: SharedBox<UnkeyedBox>
|
||||
) {
|
||||
self.encoder = encoder
|
||||
self.codingPath = codingPath
|
||||
self.container = container
|
||||
}
|
||||
|
||||
// MARK: - UnkeyedEncodingContainer Methods
|
||||
|
||||
public mutating func encodeNil() throws {
|
||||
container.withShared { container in
|
||||
container.append(encoder.box())
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func encode<T: Encodable>(_ value: T) throws {
|
||||
try encode(value) { encoder, value in
|
||||
try encoder.box(value)
|
||||
}
|
||||
}
|
||||
|
||||
private mutating func encode<T: Encodable>(
|
||||
_ value: T,
|
||||
encode: (XMLEncoderImplementation, T) throws -> Box
|
||||
) rethrows {
|
||||
encoder.codingPath.append(XMLKey(index: count))
|
||||
defer { self.encoder.codingPath.removeLast() }
|
||||
|
||||
try container.withShared { container in
|
||||
container.append(try encode(encoder, value))
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func nestedContainer<NestedKey>(
|
||||
keyedBy _: NestedKey.Type
|
||||
) -> KeyedEncodingContainer<NestedKey> {
|
||||
if NestedKey.self is XMLChoiceCodingKey.Type {
|
||||
return nestedChoiceContainer(keyedBy: NestedKey.self)
|
||||
} else {
|
||||
return nestedKeyedContainer(keyedBy: NestedKey.self)
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func nestedKeyedContainer<NestedKey>(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
|
||||
codingPath.append(XMLKey(index: count))
|
||||
defer { self.codingPath.removeLast() }
|
||||
|
||||
let sharedKeyed = SharedBox(KeyedBox())
|
||||
self.container.withShared { container in
|
||||
container.append(sharedKeyed)
|
||||
}
|
||||
|
||||
let container = XMLKeyedEncodingContainer<NestedKey>(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedKeyed
|
||||
)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
public mutating func nestedChoiceContainer<NestedKey>(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
|
||||
codingPath.append(XMLKey(index: count))
|
||||
defer { self.codingPath.removeLast() }
|
||||
|
||||
let sharedChoice = SharedBox(ChoiceBox())
|
||||
self.container.withShared { container in
|
||||
container.append(sharedChoice)
|
||||
}
|
||||
|
||||
let container = XMLChoiceEncodingContainer<NestedKey>(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedChoice
|
||||
)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
|
||||
codingPath.append(XMLKey(index: count))
|
||||
defer { self.codingPath.removeLast() }
|
||||
|
||||
let sharedUnkeyed = SharedBox(UnkeyedBox())
|
||||
container.withShared { container in
|
||||
container.append(sharedUnkeyed)
|
||||
}
|
||||
|
||||
return XMLUnkeyedEncodingContainer(
|
||||
referencing: encoder,
|
||||
codingPath: codingPath,
|
||||
wrapping: sharedUnkeyed
|
||||
)
|
||||
}
|
||||
|
||||
public mutating func superEncoder() -> Encoder {
|
||||
return XMLReferencingEncoder(
|
||||
referencing: encoder,
|
||||
at: count,
|
||||
wrapping: container
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user