Skip to content

Commit

Permalink
closes CoreOffice#12, support attributed intrinsic values
Browse files Browse the repository at this point in the history
Add intrinsic encoding decoding with attributes support
  • Loading branch information
JoeMatt committed Feb 6, 2019
1 parent a110b83 commit 559f35a
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 10 deletions.
7 changes: 7 additions & 0 deletions Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift
Expand Up @@ -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)}"
Expand Down
7 changes: 6 additions & 1 deletion Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Expand Up @@ -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

Expand Down Expand Up @@ -93,6 +93,11 @@ struct XMLCoderElement: Equatable {
return result
}

// Handle attributed unkeyed valye <foo attr="bar">zap</foo>
// 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
Expand Down
1 change: 0 additions & 1 deletion Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift
Expand Up @@ -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)
Expand Down
23 changes: 15 additions & 8 deletions Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Expand Up @@ -126,15 +126,18 @@ struct XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
public func decode<T: Decodable>(
_ 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
}

Expand Down Expand Up @@ -163,8 +166,12 @@ struct XMLKeyedDecodingContainer<K: CodingKey>: 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
Expand Down
102 changes: 102 additions & 0 deletions 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 = """
<?xml version="1.0" encoding="UTF-8"?>
<foo id="123">456</foo>
""".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),
]
}
4 changes: 4 additions & 0 deletions XMLCoder.xcodeproj/project.pbxproj
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -135,6 +136,7 @@
A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedIntTests.swift; sourceTree = "<group>"; };
B34B3C07220381AB00BCBA30 /* String+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+ExtensionsTests.swift"; sourceTree = "<group>"; };
B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncoding.swift; sourceTree = "<group>"; };
B3B6902D220A71DF0084D407 /* AttributedIntrinsicTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedIntrinsicTest.swift; sourceTree = "<group>"; };
B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncodingTest.swift; sourceTree = "<group>"; };
B3BE1D622202CB1400259831 /* XMLEncoderImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLEncoderImplementation.swift; sourceTree = "<group>"; };
B3BE1D642202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XMLEncoderImplementation+SingleValueEncodingContainer.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down

0 comments on commit 559f35a

Please sign in to comment.