diff --git a/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift b/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift
index 408c2d9f..9d4d5156 100644
--- a/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift
+++ b/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift
@@ -106,6 +106,17 @@ extension KeyedBox: Box {
}
}
+extension KeyedBox {
+ var value: SimpleBox? {
+ guard
+ elements.count == 1,
+ let value = elements["value"] as? SimpleBox
+ ?? elements[""] as? SimpleBox,
+ !value.isNull else { return nil }
+ return value
+ }
+}
+
extension KeyedBox: CustomStringConvertible {
var description: String {
return "{attributes: \(attributes), elements: \(elements)}"
diff --git a/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift b/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
index fc1f5cd6..db0045c1 100644
--- a/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
+++ b/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
@@ -47,7 +47,7 @@ struct XMLCoderElement: Equatable {
func flatten() -> KeyedBox {
let attributes = self.attributes.mapValues { StringBox($0) }
- let keyedElements: [String: Box] = elements.reduce([String: Box]()) { (result, element) -> [String: Box] in
+ var keyedElements = elements.reduce([String: Box]()) { (result, element) -> [String: Box] in
var result = result
let key = element.key
@@ -93,6 +93,11 @@ struct XMLCoderElement: Equatable {
return result
}
+ // Handle attributed unkeyed value zap
+ // Value should be zap. Detect only when no other elements exist
+ if keyedElements.isEmpty, let value = value {
+ keyedElements["value"] = StringBox(value)
+ }
let keyedBox = KeyedBox(elements: keyedElements, attributes: attributes)
return keyedBox
diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
index b1f4d55d..5f5cf63d 100644
--- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
+++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
@@ -126,15 +126,18 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol {
public func decode(
_ type: T.Type, forKey key: Key
) throws -> T {
- let attributeNotFound = container.withShared { keyedBox in
- keyedBox.attributes[key.stringValue] == nil
+ let attributeFound = container.withShared { keyedBox in
+ keyedBox.attributes[key.stringValue] != nil
}
- let elementNotFound = container.withShared { keyedBox in
- keyedBox.elements[key.stringValue] == nil
+
+ let elementFound = container.withShared { keyedBox in
+ keyedBox.elements[key.stringValue] != nil || keyedBox.value != nil
}
- if let type = type as? AnyEmptySequence.Type, attributeNotFound,
- elementNotFound, let result = type.init() as? T {
+ if let type = type as? AnyEmptySequence.Type,
+ !attributeFound,
+ !elementFound,
+ let result = type.init() as? T {
return result
}
@@ -163,8 +166,12 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol {
_ type: T.Type,
forKey key: Key
) throws -> T {
- let elementOrNil = container.withShared { keyedBox in
- keyedBox.elements[key.stringValue]
+ let elementOrNil = container.withShared { keyedBox -> KeyedBox.Element? in
+ if ["value", ""].contains(key.stringValue) {
+ return keyedBox.elements[key.stringValue] ?? keyedBox.value
+ } else {
+ return keyedBox.elements[key.stringValue]
+ }
}
let attributeOrNil = container.withShared { keyedBox in
diff --git a/Tests/XMLCoderTests/AttributedIntrinsicTest.swift b/Tests/XMLCoderTests/AttributedIntrinsicTest.swift
new file mode 100644
index 00000000..b64ce0fd
--- /dev/null
+++ b/Tests/XMLCoderTests/AttributedIntrinsicTest.swift
@@ -0,0 +1,206 @@
+//
+// AttributedIntrinsicTest.swift
+// XMLCoderTests
+//
+// Created by Joseph Mattiello on 1/23/19.
+//
+
+import Foundation
+import XCTest
+@testable import XMLCoder
+
+let fooXML = """
+
+456
+""".data(using: .utf8)!
+
+private struct Foo: Codable, DynamicNodeEncoding {
+ let id: String
+ let value: String
+
+ enum CodingKeys: String, CodingKey {
+ case id
+ case value
+ }
+
+ static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
+ switch key {
+ case CodingKeys.id:
+ return .attribute
+ default:
+ return .element
+ }
+ }
+}
+
+private struct FooEmptyKeyed: Codable, DynamicNodeEncoding {
+ let id: String
+ let unkeyedValue: Int
+
+ enum CodingKeys: String, CodingKey {
+ case id
+ case unkeyedValue = ""
+ }
+
+ static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
+ switch key {
+ case CodingKeys.id:
+ return .attribute
+ default:
+ return .element
+ }
+ }
+}
+
+final class AttributedIntrinsicTest: XCTestCase {
+ func testEncode() throws {
+ let encoder = XMLEncoder()
+ encoder.outputFormatting = []
+
+ let foo1 = FooEmptyKeyed(id: "123", unkeyedValue: 456)
+
+ let header = XMLHeader(version: 1.0, encoding: "UTF-8")
+ let encoded = try encoder.encode(foo1, withRootKey: "foo", header: header)
+ let xmlString = String(data: encoded, encoding: .utf8)
+ XCTAssertNotNil(xmlString)
+
+ // Test string equivalency
+ let encodedXML = xmlString!.trimmingCharacters(in: .whitespacesAndNewlines)
+ let originalXML = String(data: fooXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
+ XCTAssertEqual(encodedXML, originalXML)
+ }
+
+ func testDecode() throws {
+ let decoder = XMLDecoder()
+ decoder.errorContextLength = 10
+
+ let foo1 = try decoder.decode(Foo.self, from: fooXML)
+ XCTAssertEqual(foo1.id, "123")
+ XCTAssertEqual(foo1.value, "456")
+
+ let foo2 = try decoder.decode(FooEmptyKeyed.self, from: fooXML)
+ XCTAssertEqual(foo2.id, "123")
+ XCTAssertEqual(foo2.unkeyedValue, 456)
+ }
+
+ static var allTests = [
+ ("testEncode", testEncode),
+ ("testDecode", testDecode),
+ ]
+}
+
+// MARK: - Enums
+
+let attributedEnumXML = """
+
+ABC123
+""".data(using: .utf8)!
+
+private struct Foo2: Codable {
+ let number: [FooNumber]
+}
+
+private struct FooNumber: Codable, DynamicNodeEncoding {
+ public let type: FooEnum
+
+ public init(type: FooEnum) {
+ self.type = type
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case type
+ case typeValue = ""
+ }
+
+ public static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
+ switch key {
+ case FooNumber.CodingKeys.type: return .attribute
+ default: return .element
+ }
+ }
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+
+ type = try container.decode(FooEnum.self, forKey: .type)
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ switch type {
+ case let .string(value):
+ try container.encode("string", forKey: .type)
+ try container.encode(value, forKey: .typeValue)
+ case let .int(value):
+ try container.encode("int", forKey: .type)
+ try container.encode(value, forKey: .typeValue)
+ }
+ }
+}
+
+private enum FooEnum: Equatable, Codable {
+ private enum CodingKeys: String, CodingKey {
+ case string
+ case int
+ }
+
+ public init(from decoder: Decoder) throws {
+ let values = try decoder.container(keyedBy: CodingKeys.self)
+ if let value = try values.decodeIfPresent(String.self, forKey: .string) {
+ self = .string(value)
+ return
+ } else if let value = try values.decodeIfPresent(Int.self, forKey: .int) {
+ self = .int(value)
+ return
+ } else {
+ throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
+ debugDescription: "No coded value for string or int"))
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ switch self {
+ case let .string(value):
+ try container.encode(value, forKey: .string)
+ case let .int(value):
+ try container.encode(value, forKey: .int)
+ }
+ }
+
+ case string(String)
+ case int(Int)
+}
+
+final class AttributedEnumIntrinsicTest: XCTestCase {
+ func testEncode() throws {
+ let encoder = XMLEncoder()
+ encoder.outputFormatting = []
+
+ let foo1 = Foo2(number: [FooNumber(type: FooEnum.string("ABC")), FooNumber(type: FooEnum.int(123))])
+
+ let header = XMLHeader(version: 1.0, encoding: "UTF-8")
+ let encoded = try encoder.encode(foo1, withRootKey: "foo", header: header)
+ let xmlString = String(data: encoded, encoding: .utf8)
+ XCTAssertNotNil(xmlString)
+ // Test string equivalency
+ let encodedXML = xmlString!.trimmingCharacters(in: .whitespacesAndNewlines)
+ let originalXML = String(data: attributedEnumXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
+ XCTAssertEqual(encodedXML, originalXML)
+ }
+
+ // TODO: Fix decoding
+// func testDecode() throws {
+// let decoder = XMLDecoder()
+// decoder.errorContextLength = 10
+//
+// let foo = try decoder.decode(Foo2.self, from: attributedEnumXML)
+// XCTAssertEqual(foo.number[0].type, FooEnum.string("ABC"))
+// XCTAssertEqual(foo.number[1].type, FooEnum.int(123))
+// }
+
+ static var allTests = [
+ ("testEncode", testEncode),
+// ("testDecode", testDecode),
+ ]
+}
diff --git a/Tests/XMLCoderTests/BooksTest.swift b/Tests/XMLCoderTests/BooksTest.swift
index de1b5785..f596a086 100644
--- a/Tests/XMLCoderTests/BooksTest.swift
+++ b/Tests/XMLCoderTests/BooksTest.swift
@@ -218,7 +218,7 @@ final class BooksTest: XCTestCase {
XCTAssertEqual(book1, book2)
- // Test string equivlancy
+ // Test string equivalency
let encodedXML = String(data: data, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
let originalXML = String(data: bookXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
XCTAssertEqual(encodedXML, originalXML)
diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj
index d9fb784a..33627d76 100644
--- a/XMLCoder.xcodeproj/project.pbxproj
+++ b/XMLCoder.xcodeproj/project.pbxproj
@@ -26,6 +26,7 @@
A61FE03C21E4EAB10015D993 /* KeyedIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */; };
B34B3C08220381AC00BCBA30 /* String+ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34B3C07220381AB00BCBA30 /* String+ExtensionsTests.swift */; };
B35157CE21F986DD009CA0CC /* DynamicNodeEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */; };
+ B3B6902E220A71DF0084D407 /* AttributedIntrinsicTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3B6902D220A71DF0084D407 /* AttributedIntrinsicTest.swift */; };
B3BE1D612202C1F600259831 /* DynamicNodeEncodingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */; };
B3BE1D632202CB1400259831 /* XMLEncoderImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D622202CB1400259831 /* XMLEncoderImplementation.swift */; };
B3BE1D652202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D642202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift */; };
@@ -135,6 +136,7 @@
A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedIntTests.swift; sourceTree = ""; };
B34B3C07220381AB00BCBA30 /* String+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+ExtensionsTests.swift"; sourceTree = ""; };
B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncoding.swift; sourceTree = ""; };
+ B3B6902D220A71DF0084D407 /* AttributedIntrinsicTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedIntrinsicTest.swift; sourceTree = ""; };
B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncodingTest.swift; sourceTree = ""; };
B3BE1D622202CB1400259831 /* XMLEncoderImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLEncoderImplementation.swift; sourceTree = ""; };
B3BE1D642202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XMLEncoderImplementation+SingleValueEncodingContainer.swift"; sourceTree = ""; };
@@ -378,6 +380,7 @@
OBJ_38 /* RelationshipsTest.swift */,
BF63EF1D21CEC99A001D38C5 /* BenchmarkTests.swift */,
D1FC040421C7EF8200065B43 /* RJISample.swift */,
+ B3B6902D220A71DF0084D407 /* AttributedIntrinsicTest.swift */,
B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */,
A61DCCD621DF8DB300C0A19D /* ClassTests.swift */,
D14D8A8521F1D6B300B0D31A /* SingleChildTests.swift */,
@@ -618,6 +621,7 @@
A61FE03921E4D60B0015D993 /* UnkeyedIntTests.swift in Sources */,
BF63EF6B21D10284001D38C5 /* XMLElementTests.swift in Sources */,
BF9457ED21CBB6BC005ACFDE /* BoolTests.swift in Sources */,
+ B3B6902E220A71DF0084D407 /* AttributedIntrinsicTest.swift in Sources */,
D1FC040521C7EF8200065B43 /* RJISample.swift in Sources */,
BF63EF0A21CD7C1A001D38C5 /* URLTests.swift in Sources */,
BF9457CE21CBB516005ACFDE /* StringBoxTests.swift in Sources */,