diff --git a/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift b/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift index 9d572993..ab92c55b 100644 --- a/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift +++ b/Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift @@ -104,6 +104,13 @@ 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..409b3505 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: [String: Box] = 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 valye 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/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index 37e48534..699c4067 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -308,7 +308,6 @@ extension XMLDecoderImplementation { if type == Date.self || type == NSDate.self { let date: Date = try unbox(box) - decoded = date as? T } else if type == Data.self || type == NSData.self { let data: Data = try unbox(box) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index b1f4d55d..5e7904b9 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 -> Bool 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.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..51b2990e --- /dev/null +++ b/Tests/XMLCoderTests/AttributedIntrinsicTest.swift @@ -0,0 +1,102 @@ +// +// 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() { + let encoder = XMLEncoder() + encoder.outputFormatting = [] + + let foo1 = FooEmptyKeyed(id: "123", unkeyedValue: 456) + + let header = XMLHeader(version: 1.0, encoding: "UTF-8") + do { + let encoded = try encoder.encode(foo1, withRootKey: "foo", header: header) + let xmlString = String(data: encoded, encoding: .utf8) + XCTAssertNotNil(xmlString) + print(xmlString!) + + // Test string equivlancy + let encodedXML = xmlString!.trimmingCharacters(in: .whitespacesAndNewlines) + let originalXML = String(data: fooXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines) + XCTAssertEqual(encodedXML, originalXML) + } catch { + print("Test threw error: " + error.localizedDescription) + XCTFail(error.localizedDescription) + } + } + + func testDecode() { + do { + 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) + } catch { + print("Test threw error: " + error.localizedDescription) + XCTFail(error.localizedDescription) + } + } + + static var allTests = [ + ("testEncode", testEncode), + ("testDecode", testDecode), + ] +} diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 48057411..d1fa530b 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 */,