Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Sources/XMLCoder/Auxiliaries/Metatypes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Metatypes.swift
// XMLCoder
//
// Created by Max Desiatov on 30/12/2018.
//

/// Type-erased protocol helper for a metatype check in generic `decode`
/// overload.
protocol AnyEmptySequence {
init()
}

protocol AnyArray {
static var elementType: Any.Type { get }
}

extension Array: AnyEmptySequence, AnyArray {
static var elementType: Any.Type {
return Element.self
}
}

extension Dictionary: AnyEmptySequence {}

/// Type-erased protocol helper for a metatype check in generic `decode`
/// overload.
protocol AnyOptional {
init()
}

extension Optional: AnyOptional {
init() {
self = nil
}
}
17 changes: 13 additions & 4 deletions Sources/XMLCoder/Auxiliaries/XMLElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,11 @@ struct _XMLElement {
if let content = child.value {
switch elements[key] {
case let unkeyedBox as UnkeyedBox:
var boxes = unkeyedBox.unbox()
boxes.append(StringBox(content))
elements[key] = UnkeyedBox(boxes)
unkeyedBox.append(StringBox(content))
elements[key] = unkeyedBox
case let keyedBox as StringBox:
elements[key] = UnkeyedBox([keyedBox, StringBox(content)])
case _:
default:
elements[key] = StringBox(content)
}
} else if !child.elements.isEmpty || !child.attributes.isEmpty {
Expand All @@ -117,6 +116,16 @@ struct _XMLElement {
} else {
elements[key] = content
}
} else {
switch elements[key] {
case let unkeyedBox as UnkeyedBox:
unkeyedBox.append(NullBox())
elements[key] = unkeyedBox
case let keyedBox as StringBox:
elements[key] = UnkeyedBox([keyedBox, NullBox()])
default:
elements[key] = NullBox()
}
}
}
}
Expand Down
17 changes: 13 additions & 4 deletions Sources/XMLCoder/Auxiliaries/XMLStackParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ class _XMLStackParser: NSObject {
var root: _XMLElement?
private var stack: [_XMLElement] = []

static func parse(with data: Data, errorContextLength length: UInt) throws -> KeyedBox {
static func parse(with data: Data,
errorContextLength length: UInt) throws -> KeyedBox {
let parser = _XMLStackParser()

let node = try parser.parse(with: data, errorContextLength: length)

return node.flatten()
}

func parse(with data: Data, errorContextLength: UInt) throws -> _XMLElement {
func parse(with data: Data,
errorContextLength: UInt) throws -> _XMLElement {
let xmlParser = XMLParser(data: data)
xmlParser.delegate = self

Expand Down Expand Up @@ -79,12 +81,19 @@ extension _XMLStackParser: XMLParserDelegate {
stack = []
}

func parser(_: XMLParser, didStartElement elementName: String, namespaceURI _: String?, qualifiedName _: String?, attributes attributeDict: [String: String] = [:]) {
func parser(_: XMLParser,
didStartElement elementName: String,
namespaceURI _: String?,
qualifiedName _: String?,
attributes attributeDict: [String: String] = [:]) {
let element = _XMLElement(key: elementName, attributes: attributeDict)
stack.append(element)
}

func parser(_: XMLParser, didEndElement _: String, namespaceURI _: String?, qualifiedName _: String?) {
func parser(_: XMLParser,
didEndElement _: String,
namespaceURI _: String?,
qualifiedName _: String?) {
guard var element = stack.popLast() else {
return
}
Expand Down
6 changes: 4 additions & 2 deletions Sources/XMLCoder/Decoder/XMLDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ extension _XMLDecoder {
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
Expand All @@ -620,9 +621,10 @@ extension _XMLDecoder {
decoded = decimal as! T
} else {
storage.push(container: box)
defer { storage.popContainer() }
return try type.init(from: self)
decoded = try type.init(from: self)
storage.popContainer()
}

return decoded
}
}
90 changes: 41 additions & 49 deletions Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@

import Foundation

/// Type-erased protocol helper for a metatype check in generic `decode`
/// overload.
private protocol AnyEmptySequence {
init()
}

extension Array: AnyEmptySequence {}
extension Dictionary: AnyEmptySequence {}

// MARK: Decoding Containers

struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
Expand All @@ -43,7 +34,8 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
self.container = container
case .convertFromSnakeCase:
// Convert the snake case keys in the container to camel case.
// If we hit a duplicate key after conversion, then we'll use the first one we saw. Effectively an undefined behavior with dictionaries.
// If we hit a duplicate key after conversion, then we'll use the
// first one we saw. Effectively an undefined behavior with dictionaries.
let attributes = container.attributes.map { key, value in
(XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value)
}
Expand All @@ -61,10 +53,14 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
self.container = KeyedBox(elements: elements, attributes: attributes)
case let .custom(converter):
let attributes = container.attributes.map { key, value in
(converter(decoder.codingPath + [_XMLKey(stringValue: key, intValue: nil)]).stringValue, value)
(converter(decoder.codingPath +
[_XMLKey(stringValue: key, intValue: nil)]).stringValue,
value)
}
let elements = container.elements.map { key, value in
(converter(decoder.codingPath + [_XMLKey(stringValue: key, intValue: nil)]).stringValue, value)
(converter(decoder.codingPath +
[_XMLKey(stringValue: key, intValue: nil)]).stringValue,
value)
}
self.container = KeyedBox(elements: elements, attributes: attributes)
}
Expand Down Expand Up @@ -113,15 +109,11 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
}

public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode(_ type: Decimal.Type, forKey key: Key) throws -> Decimal {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
Expand Down Expand Up @@ -173,58 +165,50 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
}

public func decode(_ type: String.Type, forKey key: Key) throws -> String {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode(_ type: Date.Type, forKey key: Key) throws -> Date {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode(_ type: Data.Type, forKey key: Key) throws -> Data {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
let attributeNotFound = (container.attributes[key.stringValue] == nil)
let elementNotFound = (container.elements[key.stringValue] == nil)

if type is AnyEmptySequence.Type, attributeNotFound, elementNotFound {
return (type as! AnyEmptySequence.Type).init() as! T
if let type = type as? AnyEmptySequence.Type,
attributeNotFound, elementNotFound {
return type.init() as! T
}

return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

private func decodeSignedInteger<T: BinaryInteger & SignedInteger & Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
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: BinaryInteger & UnsignedInteger & Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
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: BinaryFloatingPoint & Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
private func decodeFloatingPoint<T>(_ type: T.Type,
forKey key: Key) throws -> T
where T: BinaryFloatingPoint & Decodable {
return try decodeConcrete(type, forKey: key)
}

private func decode<T: Decodable>(
private func decodeConcrete<T: Decodable>(
_ type: T.Type,
forKey key: Key,
decode _: (_XMLDecoder, Box) throws -> T?
forKey key: Key
) throws -> T {
guard let entry = container.elements[key.stringValue] ?? container.attributes[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(
Expand All @@ -236,14 +220,22 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
decoder.codingPath.append(key)
defer { decoder.codingPath.removeLast() }

guard let value: T = try decoder.unbox(entry) else {
let value: T? = try decoder.unbox(entry)

if value == nil,
let type = type as? AnyArray.Type,
type.elementType is AnyOptional.Type {
return [nil] as! T
}

guard let unwrapped = value else {
throw DecodingError.valueNotFound(type, DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected \(type) value but found null instead."
))
}

return value
return unwrapped
}

public func nestedContainer<NestedKey>(keyedBy _: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> {
Expand Down
Loading