From 4ce74e2faf3ea960246f9b358359b7fc1fcf5137 Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Wed, 31 Jul 2019 14:15:11 -0700 Subject: [PATCH 01/19] Decoding choice elements that can hold empty structs (#120) ## Introduction In merging in #119, we fixed most but not quite all of #91! Decoding of _null_ choice elements (represented as enums with empty struct associated values on the Swift side) still results in errors. This PR adds a test to demonstrate and fixes this issue by wrapping each `NullBox` inside of a `SingleKeyedBox` at the `merge` phase (called by `transformToBoxTree`). ## Motivation One of the main lessons from #119 was that we have to wrap choice elements in the decoding phase to hold onto their keys. The keys are needed for directing us to the correct branch of the do-catch pyramid used for decoding. ```swift private enum Entry: Equatable { case run(Run) case properties(Properties) case br(Break) } extension Entry: Decodable { private enum CodingKeys: String, XMLChoiceCodingKey { case run, properties, br } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) do { self = .run(try container.decode(Run.self, forKey: .run)) } catch { do { self = .properties(try container.decode(Properties.self, forKey: .properties)) } catch { self = .br(try container.decode(Break.self, forKey: .br)) } } } } ``` where one of the associated values could be an empty struct (represented by null): ```swift private struct Break: Decodable {} ``` Although we _can_ throw out keys for non-choice null elements, a mechanism is needed for holding onto the keys while transforming from the `XMLCoderElement` tree to the `boxTree`. Only later will we know if the key is needed (if this wrapped element is transformed to a `ChoiceBox`); if not, we will be able to throw out the key. ## Proposed solution The Public API is unchanged. On the implementation side, we catch `NullBox` values in `merge` and wrap them in `SingleKeyedBox` instances. ## Detailed Design In `merge`, we wrap each `NullBox` in a `SingleKeyedBox` with the appropriate key bundled in. An `XMLChoiceDecodingContainer` can be constructed from the `SingleKeyedBox` by converting it to a `ChoiceBox` (just transferring over the contents) - as normal. In `XMLKeyedDecodingContainer`, when preparing the `elements` for concrete decoding, we unwrap all `SingleKeyedBox` values that may be contained therein, as any choice elements contained would have already been transformed to a `ChoiceBox` by this point in decoding: any stray `SingleKeyedBox` wrappers can thus be thrown out. ## Source compatibility This is purely an additive change. --- .../XMLCoder/Auxiliaries/KeyedStorage.swift | 2 +- .../Decoder/XMLKeyedDecodingContainer.swift | 20 ++++++++- Tests/XMLCoderTests/NestedChoiceTests.swift | 44 +++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/Sources/XMLCoder/Auxiliaries/KeyedStorage.swift b/Sources/XMLCoder/Auxiliaries/KeyedStorage.swift index 49a82d0d..740455fa 100644 --- a/Sources/XMLCoder/Auxiliaries/KeyedStorage.swift +++ b/Sources/XMLCoder/Auxiliaries/KeyedStorage.swift @@ -83,7 +83,7 @@ extension KeyedStorage where Key == String, Value == Box { } else if let value = element.value { result.append(StringBox(value), at: element.key) } else { - result.append(NullBox(), at: element.key) + result.append(SingleKeyedBox(key: element.key, element: NullBox()), at: element.key) } return result diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index cbf42eb2..e236db91 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -79,6 +79,10 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { let box = elements.first ?? attributes.first + if let singleKeyed = box as? SingleKeyedBox { + return singleKeyed.element.isNull + } + return box?.isNull ?? true } @@ -232,14 +236,26 @@ extension XMLKeyedDecodingContainer { let keyString = key.stringValue.isEmpty ? "value" : key.stringValue let value = keyedBox.elements[keyString] if !value.isEmpty { - return value + return value.map { + if let singleKeyed = $0 as? SingleKeyedBox { + return singleKeyed.element + } else { + return $0 + } + } } else if let value = keyedBox.value { return [value] } else { return [] } } else { - return keyedBox.elements[key.stringValue] + return keyedBox.elements[key.stringValue].map { + if let singleKeyed = $0 as? SingleKeyedBox { + return singleKeyed.element + } else { + return $0 + } + } } } diff --git a/Tests/XMLCoderTests/NestedChoiceTests.swift b/Tests/XMLCoderTests/NestedChoiceTests.swift index 4810c454..44344c49 100644 --- a/Tests/XMLCoderTests/NestedChoiceTests.swift +++ b/Tests/XMLCoderTests/NestedChoiceTests.swift @@ -227,6 +227,50 @@ class NestedChoiceTests: XCTestCase { XCTAssertEqual(result, expected) } + func testNestedEnumsWithEmptyStruct() throws { + let xml = """ + +

+

+ + 1518 + I am answering it again. + + + 431 + A Word About Wake Times + +

+

+ + 1519 + I am answering it again. + +
+

+
+ """ + let result = try XMLDecoder().decode(Container.self, from: xml.data(using: .utf8)!) + let expected = Container( + paragraphs: [ + Paragraph( + entries: [ + .br(Break()), + .run(Run(id: 1518, text: "I am answering it again.")), + .properties(Properties(id: 431, title: "A Word About Wake Times")), + ] + ), + Paragraph( + entries: [ + .run(Run(id: 1519, text: "I am answering it again.")), + .br(Break()), + ] + ), + ] + ) + XCTAssertEqual(result, expected) + } + func testNestedEnumsRoundTrip() throws { let original = Container( paragraphs: [ From 86ac6eff6258b18c50f755f217c7838f717d5f42 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 31 Jul 2019 22:16:53 +0100 Subject: [PATCH 02/19] Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 628d700e..f48aaa03 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ you can now set a property `trimValueWhitespaces` to `false` (the default value ### Choice element coding Starting with [version 0.8](https://github.com/MaxDesiatov/XMLCoder/releases/tag/0.8.0), you -now encode and decode union-type–like enums with associated values by conforming your +can encode and decode union-type–like enums with associated values by conforming your `CodingKey` type additionally to `XMLChoiceCodingKey`. For more information, see the [pull request](https://github.com/MaxDesiatov/XMLCoder/pull/119). From 823bd44e45f3b29b35bb358a5f95514a9157adf6 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 4 Aug 2019 13:19:09 +0100 Subject: [PATCH 03/19] Update README and CHANGELOG, bump version to 0.8.0 --- CHANGELOG.md | 109 ++++++++++++++++++----------- README.md | 67 ++++++++++++++++-- XMLCoder.podspec | 2 +- XMLCoder.xcodeproj/project.pbxproj | 7 +- 4 files changed, 133 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4da0922..4a186f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,32 @@ -# 0.7.0 (July 2, 2019) +# 0.8.0 (August 4, 2019) + +This release adds support for decoding and decoding ordered sequences of +different elements as enums with associated values. In addition, XMLCoder now +supports Linux. Many thanks to [@jsbean](https://github.com/jsbean), +[@bwetherfield](https://github.com/bwetherfield) and +[@drewag](https://github.com/drewag) for implementing this! + +**Closed issues:** + +- XML with autoclosed tags [\#116](https://github.com/MaxDesiatov/XMLCoder/issues/116) +- Arrays of enums [\#91](https://github.com/MaxDesiatov/XMLCoder/issues/91) +- Array of enums with associated values [\#25](https://github.com/MaxDesiatov/XMLCoder/issues/25) + +**Merged pull requests:** + +- Decoding choice elements that can hold empty structs + [\#120](https://github.com/MaxDesiatov/XMLCoder/pull/120) + ([@bwetherfield](https://github.com/bwetherfield)) +- `Encodable` and `Decodable` support for choice elements + [\#119](https://github.com/MaxDesiatov/XMLCoder/pull/119) + ([@jsbean](https://github.com/jsbean)) +- Add Linux support [\#117](https://github.com/MaxDesiatov/XMLCoder/pull/117) + ([@drewag](https://github.com/drewag)) +- Fix typo: `errorContextLenght` -\> `errorContextLength` + [\#114](https://github.com/MaxDesiatov/XMLCoder/pull/114) + ([@jsbean](https://github.com/jsbean)) + +# 0.7.0 (July 2, 2019) This release changes the behavior of attributes coding: now order of XML attributes is fully preserved. One of the benefits is that it improves unit @@ -9,19 +37,19 @@ Travis for CI with great improvements to overall CI stability, speed, and parallel builds. Thanks to [Andrés Cecilia Luque](https://github.com/acecilia) and [Jay Hickey](https://github.com/jayhickey) for the contributions! -## Merged pull requests +**Merged pull requests:** - Change components variable from var to let [\#107](https://github.com/MaxDesiatov/XMLCoder/pull/107) - ([jayhickey](https://github.com/jayhickey)) + ([@jayhickey](https://github.com/jayhickey)) - Keep the order of the attributes during encoding operations [\#110](https://github.com/MaxDesiatov/XMLCoder/pull/110) - ([acecilia](https://github.com/acecilia)) + ([@acecilia](https://github.com/acecilia)) - Migrate from Travis to Azure Pipelines [\#111](https://github.com/MaxDesiatov/XMLCoder/pull/111) - ([MaxDesiatov](https://github.com/MaxDesiatov)) + ([@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.6.0 (June 17, 2019) +# 0.6.0 (June 17, 2019) An improvement release that introduces `convertFromKebabCase` and `convertToKebabCase` key decoding strategies. There were a few changes that @@ -30,39 +58,38 @@ has changed and a few more tests added. Thanks to [Andrés Cecilia Luque](https://github.com/acecilia) and [Vincent Esche](https://github.com/regexident) for the contributions! -## Merged pull requests +**Merged pull requests:** - Add support for kebab-case KeyDecodingStrategy [\#105](https://github.com/MaxDesiatov/XMLCoder/pull/105) - ([acecilia](https://github.com/acecilia)) + ([@acecilia](https://github.com/acecilia)) - Replace UnkeyedBox with Array, refine KeyedStorage [\#102](https://github.com/MaxDesiatov/XMLCoder/pull/102) - ([MaxDesiatov](https://github.com/MaxDesiatov)) + ([@MaxDesiatov](https://github.com/MaxDesiatov)) - Add tests for nested keyed/unkeyed collections [\#38](https://github.com/MaxDesiatov/XMLCoder/pull/38) - ([regexident](https://github.com/regexident)) - + ([@regexident](https://github.com/regexident)) -# 0.5.1 (May 2, 2019) +# 0.5.1 (May 2, 2019) Bugfix release that restores decoding of empty sequences, which became broken in 0.5.0. -## Merged pull requests +**Merged pull requests:** - Fix decoding of empty sequences [\#98](https://github.com/MaxDesiatov/XMLCoder/pull/98) - ([MaxDesiatov](https://github.com/MaxDesiatov)) + ([@MaxDesiatov](https://github.com/MaxDesiatov)) - Rename `flatten` to `transformToBoxTree`, rename tests [\#97](https://github.com/MaxDesiatov/XMLCoder/pull/97) - ([MaxDesiatov](https://github.com/MaxDesiatov)) + ([@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.5.0 (May 2, 2019) +# 0.5.0 (May 2, 2019) A small improvement release tagged early to resolve blocking issues in [CoreXLSX](https://github.com/MaxDesiatov/CoreXLSX). -## Notable changes +**Notable changes:** * Empty value strings are no longer decoded as `nil` when a `String` is expected, but are decoded as empty strings, which represents the actual value. @@ -70,36 +97,36 @@ A small improvement release tagged early to resolve blocking issues in overriding the default behaviour, where starting and trailing whitespaces are trimmed from string values. -## Closed issues +**Closed issues:** - Trimmed whitespace on decoding `String` [\#94](https://github.com/MaxDesiatov/XMLCoder/issues/94) -## Merged pull requests +**Merged pull requests:** - Fixed a bug when decoding a key with one character only [\#96](https://github.com/MaxDesiatov/XMLCoder/pull/96) - ([TheFlow95](https://github.com/TheFlow95)) + ([@TheFlow95](https://github.com/TheFlow95)) - Add more cases to `AttributedIntrinsicTest` [\#95](https://github.com/MaxDesiatov/XMLCoder/pull/95) - ([MaxDesiatov](https://github.com/MaxDesiatov)) + ([@MaxDesiatov](https://github.com/MaxDesiatov)) - Use `map` instead of `mapValues`/`shuffle` in `XMLCoderElement.flatten` [\#93](https://github.com/MaxDesiatov/XMLCoder/pull/93) - ([jsbean](https://github.com/jsbean)) + ([@jsbean](https://github.com/jsbean)) - Fix decoding empty element as optional [\#92](https://github.com/MaxDesiatov/XMLCoder/pull/92) - ([MaxDesiatov](https://github.com/MaxDesiatov)) + ([@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.4.1 (April 12, 2019) +# 0.4.1 (April 12, 2019) A bugfix release removing unused Xcode project scheme to improve build time for Carthage users. -## Notable changes +**Notable changes:** -* Remove unused scheme in Xcode project, [@MaxDesiatov](https://github.com/MaxDesiatov)) +* Remove unused scheme in Xcode project ([@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.4.0 (April 8, 2019) +# 0.4.0 (April 8, 2019) This is a release with plenty of new features that allow you to parse many more XML variations than previously. Compatibility with Xcode 10.2 and Swift 5.0 is @@ -111,7 +138,7 @@ also improved. A huge thank you to [@JoeMatt](https://github.com/JoeMatt) and [@khoogheem](https://github.com/khoogheem) and [@thecb4](https://github.com/thecb4) for reporting issues during development! -## Notable changes +**Notable changes:** * Ordered encoding: this was one of the most requested changes and it's finally here! 🎉 Now both keyed and unkeyed elements are encoded in the exactly same @@ -139,7 +166,7 @@ also improved. A huge thank you to [@JoeMatt](https://github.com/JoeMatt) and (`"value"` or empty string `""` if you already have an XML attribute named `"value"`). -## Closed issues +**Closed issues:** - Crash: Range invalid bounds in XMLStackParser.swift [\#83](https://github.com/MaxDesiatov/XMLCoder/issues/83) - Document DynamicNodeEncoding and attributed intrinsic [\#80](https://github.com/MaxDesiatov/XMLCoder/issues/80) @@ -148,7 +175,7 @@ also improved. A huge thank you to [@JoeMatt](https://github.com/JoeMatt) and - XmlEncoder: ordering of elements [\#17](https://github.com/MaxDesiatov/XMLCoder/issues/17) - Can’t reach an XML value [\#12](https://github.com/MaxDesiatov/XMLCoder/issues/12) -## Merged pull requests +**Merged pull requests:** - Make value intrinsic smarter [\#89](https://github.com/MaxDesiatov/XMLCoder/pull/89) @@ -181,7 +208,7 @@ also improved. A huge thank you to [@JoeMatt](https://github.com/JoeMatt) and ([@MaxDesiatov](https://github.com/MaxDesiatov)) - Attributed Intrinsic \(value coding key\) [\#73](https://github.com/MaxDesiatov/XMLCoder/pull/73) - ([JoeMatt](https://github.com/JoeMatt)) + ([@JoeMatt](https://github.com/JoeMatt)) - Dynamic node encoding + new formatters + various fixes [\#70](https://github.com/MaxDesiatov/XMLCoder/pull/70) ([@JoeMatt](https://github.com/JoeMatt)) @@ -189,17 +216,17 @@ also improved. A huge thank you to [@JoeMatt](https://github.com/JoeMatt) and [\#45](https://github.com/MaxDesiatov/XMLCoder/pull/45) ([@regexident](https://github.com/regexident)) -# 0.3.1 (February 6, 2019) +# 0.3.1 (February 6, 2019) A bugfix release that adds missing `CFBundleVersion` in generated framework's `Info.plist` ([#72](https://github.com/MaxDesiatov/XMLCoder/issues/72) reported by [@stonedauwg](https://github.com/stonedauwg)). -## Changes +**Changes:** * Set `CURRENT_PROJECT_VERSION` in project file ([#74](https://github.com/MaxDesiatov/XMLCoder/pull/74), [@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.3.0 (January 22, 2019) +# 0.3.0 (January 22, 2019) A maintenance release focused on fixing bugs, improving error reporting and overall internal architecture of the library. For this release we've started @@ -208,7 +235,7 @@ Thanks to [@hodovani](https://github.com/hodovani) and [@regexident](https://github.com/regexident) for their work on improving test coverage in this release. -## Additions +**Additions:** You can now set `errorContextLength: UInt` property on `XMLDecoder` instance, which will make it add a snippet of XML of at most this length from parser state @@ -216,7 +243,7 @@ when a parsing error occurs. This change was provided by [@hodovani](https://github.com/hodovani) and can greatly help with attempts to parse invalid XML, where previously only a line and column number were reported. -## Deprecations +**Deprecations:** `NodeEncodingStrategies` was renamed to `NodeEncodingStrategy` for consistency. `NodeEncodingStrategies` is still available as a deprecated typealias, which @@ -224,7 +251,7 @@ will be removed in future versions. Thanks to [@regexident](https://github.com/regexident) for cleaning this up and providing many more changes in this release that make `XMLCoder` better and easier to use. -## Changes +**Changes:** * Add SwiftLint and fix linter errors ([#35](https://github.com/MaxDesiatov/XMLCoder/pull/35), @@ -365,19 +392,19 @@ many more changes in this release that make `XMLCoder` better and easier to use. * Run tests with coverage, upload to codecov.io ([@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.2.1 (November 18, 2018) +# 0.2.1 (November 18, 2018) * watchOS deployment target set to 2.0 for Carthage ([@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.2.0 (November 18, 2018) +# 0.2.0 (November 18, 2018) * Add watchOS 2.0 deployment target ([@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.1.1 (November 18, 2018) +# 0.1.1 (November 18, 2018) * Set iOS deployment target to 9.0 ([@MaxDesiatov](https://github.com/MaxDesiatov)) -# 0.1.0 (November 8, 2018) +# 0.1.0 (November 8, 2018) * Add support for decoupled, type-dependent node-encoding strategies ([@regexident](https://github.com/regexident)) diff --git a/README.md b/README.md index f48aaa03..4111d367 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,9 @@ works for this XML: ``` +Please refer to PR [\#70](https://github.com/MaxDesiatov/XMLCoder/pull/70) by +[@JoeMatt](https://github.com/JoeMatt) for more details. + ### Coding key value intrinsic Suppose that you need to decode an XML that looks similar to this: @@ -199,6 +202,9 @@ struct Foo: Codable, DynamicNodeEncoding { } ``` +Thanks to [@JoeMatt](https://github.com/JoeMatt) for implementing this in +in PR [\#73](https://github.com/MaxDesiatov/XMLCoder/pull/73). + ### Preserving whitespaces in element content By default whitespaces are trimmed in element content during decoding. This @@ -209,11 +215,58 @@ you can now set a property `trimValueWhitespaces` to `false` (the default value ### Choice element coding -Starting with [version 0.8](https://github.com/MaxDesiatov/XMLCoder/releases/tag/0.8.0), you -can encode and decode union-type–like enums with associated values by conforming your -`CodingKey` type additionally to `XMLChoiceCodingKey`. +Starting with [version 0.8](https://github.com/MaxDesiatov/XMLCoder/releases/tag/0.8.0), +you can encode and decode `enum`s with associated values by conforming your +`CodingKey` type additionally to `XMLChoiceCodingKey`. This allows decoding +XML elements similar in structure to this example: + +```xml + + 1 + two + three + 4 + 5 + +``` + +To decode these elements you can use this type: + +```swift +enum IntOrString: Equatable { + case int(Int) + case string(String) +} + +extension IntOrString: Codable { + enum CodingKeys: String, XMLChoiceCodingKey { + case int + case string + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .int(value): + try container.encode(value, forKey: .int) + case let .string(value): + try container.encode(value, forKey: .string) + } + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + do { + self = .int(try container.decode(Int.self, forKey: .int)) + } catch { + self = .string(try container.decode(String.self, forKey: .string)) + } + } +} +``` -For more information, see the [pull request](https://github.com/MaxDesiatov/XMLCoder/pull/119). +This is described in more details in PR [\#119](https://github.com/MaxDesiatov/XMLCoder/pull/119) +by [@jsbean](https://github.com/jsbean) and [@bwetherfield](https://github.com/bwetherfield). ## Installation @@ -240,7 +293,7 @@ easy as adding it to the `dependencies` value of your `Package.swift`. ```swift dependencies: [ - .package(url: "https://github.com/MaxDesiatov/XMLCoder.git", from: "0.7.0") + .package(url: "https://github.com/MaxDesiatov/XMLCoder.git", from: "0.8.0") ] ``` @@ -269,7 +322,7 @@ target 'YourApp' do use_frameworks! # Pods for Test - pod 'XMLCoder', '~> 0.7.0' + pod 'XMLCoder', '~> 0.8.0' end ``` @@ -297,7 +350,7 @@ $ brew install carthage Inside of your `Cartfile`, add GitHub path to `XMLCoder`: ```ogdl -github "MaxDesiatov/XMLCoder" ~> 0.7.0 +github "MaxDesiatov/XMLCoder" ~> 0.8.0 ``` Then, run the following command to build the framework: diff --git a/XMLCoder.podspec b/XMLCoder.podspec index 167f9db4..4fcc8404 100644 --- a/XMLCoder.podspec +++ b/XMLCoder.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "XMLCoder" - s.version = "0.7.0" + s.version = "0.8.0" s.summary = "XMLEncoder & XMLDecoder using the Codable protocol in Swift" s.description = "XMLCoder allows Swift Codable-conforming objects to be translated to and from XML" s.homepage = "https://github.com/MaxDesiatov/XMLCoder" diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index e6c05c36..76e0aed0 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -380,7 +380,7 @@ path = Encoder; sourceTree = ""; }; - OBJ_5 /* */ = { + OBJ_5 = { isa = PBXGroup; children = ( OBJ_6 /* Package.swift */, @@ -400,7 +400,6 @@ OBJ_141 /* docs.sh */, OBJ_142 /* pod.sh */, ); - name = ""; sourceTree = ""; }; OBJ_58 /* Tests */ = { @@ -595,7 +594,7 @@ English, en, ); - mainGroup = OBJ_5 /* */; + mainGroup = OBJ_5; productRefGroup = OBJ_128 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -898,6 +897,7 @@ CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0.8.0; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = YES; @@ -925,6 +925,7 @@ CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 0.8.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_OPTIMIZATION_LEVEL = s; From fd645f6b9f830b8ccd8799c5eb5ce536766c7bcc Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 4 Aug 2019 13:20:34 +0100 Subject: [PATCH 04/19] Fix formatting in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a186f3d..2b2726fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ This release adds support for decoding and decoding ordered sequences of different elements as enums with associated values. In addition, XMLCoder now supports Linux. Many thanks to [@jsbean](https://github.com/jsbean), -[@bwetherfield](https://github.com/bwetherfield) and +[@bwetherfield](https://github.com/bwetherfield) and [@drewag](https://github.com/drewag) for implementing this! **Closed issues:** From f68964b14c34f73f2a6fa56f30d8a355e21afc25 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 4 Aug 2019 13:25:17 +0100 Subject: [PATCH 05/19] Highlight breaking changes, fix typo in CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2726fd..da47bb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ # 0.8.0 (August 4, 2019) -This release adds support for decoding and decoding ordered sequences of +This release adds support for decoding and encoding ordered sequences of different elements as enums with associated values. In addition, XMLCoder now supports Linux. Many thanks to [@jsbean](https://github.com/jsbean), [@bwetherfield](https://github.com/bwetherfield) and [@drewag](https://github.com/drewag) for implementing this! +**Breaking changes:** + +- Fixed typo in `XMLDecoder` property: `errorContextLenght` has been renamed to `errorContextLength` in [\#114](https://github.com/MaxDesiatov/XMLCoder/pull/114). + **Closed issues:** - XML with autoclosed tags [\#116](https://github.com/MaxDesiatov/XMLCoder/issues/116) From 01dd2a5dfdcba14e2b77c21f4b1fb1e1fbfc068a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 4 Aug 2019 13:28:26 +0100 Subject: [PATCH 06/19] Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4111d367..4a18dd54 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ by [@jsbean](https://github.com/jsbean) and [@bwetherfield](https://github.com/b - iOS 9.0 / watchOS 2.0 / tvOS 9.0 / macOS 10.10 or later deployment targets **Linux** -- Ubuntu 14.04 or Later +- Ubuntu 14.04 or later - Swift 5.0.1 or later ### Swift Package Manager From 5bbac9278e4ced99eee4ea80fb0b75dbf767cfd6 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 4 Aug 2019 19:46:17 +0100 Subject: [PATCH 07/19] Clarify availability of features in README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4a18dd54..8f2f24bc 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,9 @@ let returnData = try? XMLEncoder().encode(note, withRootKey: "note") ## Advanced features -These features are available in [0.4.0 -release](https://github.com/MaxDesiatov/XMLCoder/releases/tag/0.4.0) or later: +The following features are available in [0.4.0 +release](https://github.com/MaxDesiatov/XMLCoder/releases/tag/0.4.0) or later +(unless stated otherwise): ### Stripping namespace prefix From b26fcfc9ba4c985de44c2c90484c4ea9cfafed3f Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Fri, 9 Aug 2019 22:41:12 -0700 Subject: [PATCH 08/19] Add nested choice unkeyed container decoding test --- .../XMLCoderTests/NestedChoiceArrayTest.swift | 116 ++++++++++++++++++ XMLCoder.xcodeproj/project.pbxproj | 4 + 2 files changed, 120 insertions(+) create mode 100644 Tests/XMLCoderTests/NestedChoiceArrayTest.swift diff --git a/Tests/XMLCoderTests/NestedChoiceArrayTest.swift b/Tests/XMLCoderTests/NestedChoiceArrayTest.swift new file mode 100644 index 00000000..b7e4c84d --- /dev/null +++ b/Tests/XMLCoderTests/NestedChoiceArrayTest.swift @@ -0,0 +1,116 @@ +// +// NestedChoiceArrayTest.swift +// XMLCoder +// +// Created by Benjamin Wetherfield on 8/22/19. +// + +import XCTest +@testable import XMLCoder + +private struct Book: Decodable { + let title: String + let chapters: [Chapter] + + enum CodingKeys: String, CodingKey { + case title + case chapters + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + title = try container.decode(String.self, forKey: .title) + + var chapters = [Chapter]() + + if var chapterContainer = try? container.nestedUnkeyedContainer(forKey: .chapters) { + while !chapterContainer.isAtEnd { + chapters.append(try chapterContainer.decode(Chapter.self)) + } + } + + self.chapters = chapters + } + + init(title: String, chapters: [Chapter]) { + self.title = title + self.chapters = chapters + } +} + +private enum Chapter { + struct Content { + let title: String + let content: String + } + + case intro(Content) + case body(Content) + case outro(Content) +} + +private enum BookError: Error { + case unknownChapterType +} + +extension Chapter: Decodable { + enum CodingKeys: String, XMLChoiceCodingKey { + case intro, body = "chapter", outro + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + do { + self = .body(try container.decode(Content.self, forKey: .body)) + } catch { + do { + self = .intro(try container.decode(Content.self, forKey: .intro)) + } catch { + self = .outro(try container.decode(Content.self, forKey: .outro)) + } + } + } +} + +extension Chapter.Content: Decodable { + enum CodingKeys: String, CodingKey { + case title + case value = "" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + title = try container.decode(String.self, forKey: .title) + content = try container.decode(String.self, forKey: .value) + } +} + +extension Book: Equatable {} +extension Chapter: Equatable {} +extension Chapter.Content: Equatable {} + +class NestedChoiceArrayTest: XCTestCase { + func testDecodingNestedChoiceArray() throws { + let xml = """ + + + + Content of first chapter + Content of chapter 1 + Content of chapter 2 + Content of last chapter + + + """ + let decoded = try XMLDecoder().decode(Book.self, from: xml.data(using: .utf8)!) + let expected = Book(title: "Example", + chapters: [ + .intro(.init(title: "Intro", content: "Content of first chapter")), + .body(.init(title: "Chapter 1", content: "Content of chapter 1")), + .body(.init(title: "Chapter 2", content: "Content of chapter 2")), + .outro(.init(title: "Epilogue", content: "Content of last chapter")), + ]) + XCTAssertEqual(decoded, expected) + } +} diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 76e0aed0..77f93f21 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + B5EA3BB6230F237800D8D69B /* NestedChoiceArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */; }; OBJ_148 /* BoolBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* BoolBox.swift */; }; OBJ_149 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Box.swift */; }; OBJ_150 /* ChoiceBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* ChoiceBox.swift */; }; @@ -152,6 +153,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedChoiceArrayTest.swift; sourceTree = ""; }; OBJ_100 /* DecimalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalTests.swift; sourceTree = ""; }; OBJ_101 /* EmptyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTests.swift; sourceTree = ""; }; OBJ_102 /* FloatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatTests.swift; sourceTree = ""; }; @@ -446,6 +448,7 @@ OBJ_125 /* SimpleChoiceTests.swift */, OBJ_126 /* SingleChildTests.swift */, OBJ_127 /* SpacePreserveTest.swift */, + B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */, ); name = XMLCoderTests; path = Tests/XMLCoderTests; @@ -717,6 +720,7 @@ OBJ_252 /* NullTests.swift in Sources */, OBJ_253 /* OptionalTests.swift in Sources */, OBJ_254 /* StringTests.swift in Sources */, + B5EA3BB6230F237800D8D69B /* NestedChoiceArrayTest.swift in Sources */, OBJ_255 /* UIntTests.swift in Sources */, OBJ_256 /* URLTests.swift in Sources */, OBJ_257 /* UnkeyedIntTests.swift in Sources */, From e1470331fcc2ce22a92aebb7c1e056f7938dd46b Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Thu, 22 Aug 2019 22:13:04 +0300 Subject: [PATCH 09/19] Fix nested unkeyed container implementation for nested keyed box --- .../Decoder/XMLKeyedDecodingContainer.swift | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index e236db91..275ed1b5 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -160,14 +160,19 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } - let elements = container.withShared { keyedBox in - keyedBox.elements[key.stringValue] - } + let elements = container.unboxed.elements[key.stringValue] - return XMLUnkeyedDecodingContainer( - referencing: decoder, - wrapping: SharedBox(elements) - ) + if let containsKeyed = elements as? [KeyedBox], !containsKeyed.isEmpty { + return XMLUnkeyedDecodingContainer( + referencing: decoder, + wrapping: SharedBox(containsKeyed.first!.elements.map(SingleKeyedBox.init)) + ) + } else { + return XMLUnkeyedDecodingContainer( + referencing: decoder, + wrapping: SharedBox(elements) + ) + } } public func superDecoder() throws -> Decoder { From 686a2a37a1663971851006604902c99239a227ef Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Sun, 25 Aug 2019 13:03:57 +0300 Subject: [PATCH 10/19] Clean up unwrapping syntax --- Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 275ed1b5..663a52a3 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -162,10 +162,10 @@ struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { let elements = container.unboxed.elements[key.stringValue] - if let containsKeyed = elements as? [KeyedBox], !containsKeyed.isEmpty { + if let containsKeyed = elements as? [KeyedBox], let keyed = containsKeyed.first { return XMLUnkeyedDecodingContainer( referencing: decoder, - wrapping: SharedBox(containsKeyed.first!.elements.map(SingleKeyedBox.init)) + wrapping: SharedBox(keyed.elements.map(SingleKeyedBox.init)) ) } else { return XMLUnkeyedDecodingContainer( From eb90eba715ea744dfd8641de6445750461b3050c Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 25 Sep 2019 14:09:16 +0100 Subject: [PATCH 11/19] Add Xcode 11, Swift 5.1 support (#133) * Add Xcode 11, Swift 5.1 to azure-pipelines.yml * Add FoundationXML import to XMLStackParser * Clean up xcodebuild.sh, use iPhone 8 for iOS 13 * Check for the Linux compiler version * Fix old compiler versions not handling comparisons --- .../XMLCoder/Auxiliaries/XMLStackParser.swift | 5 ++++- azure-pipelines.yml | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Sources/XMLCoder/Auxiliaries/XMLStackParser.swift b/Sources/XMLCoder/Auxiliaries/XMLStackParser.swift index 07ec9b51..8890b8c6 100644 --- a/Sources/XMLCoder/Auxiliaries/XMLStackParser.swift +++ b/Sources/XMLCoder/Auxiliaries/XMLStackParser.swift @@ -7,6 +7,9 @@ // import Foundation +#if canImport(FoundationXML) +import FoundationXML +#endif class XMLStackParser: NSObject { var root: XMLCoderElement? @@ -126,7 +129,7 @@ extension XMLStackParser: XMLParserDelegate { namespaceURI: String?, qualifiedName: String?, attributes attributeDict: [String: String] = [:]) { - #if os(Linux) + #if os(Linux) && !compiler(>=5.1) // For some reason, element names on linux are coming out with the namespace after the name // https://bugs.swift.org/browse/SR-11191 let elementName = elementName.components(separatedBy: ":").reversed().joined(separator: ":") diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1b090d11..a5aaae93 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -36,6 +36,14 @@ jobs: env: IOS_DEVICE: 'platform=iOS Simulator,OS=12.2,name=iPhone SE' TVOS_DEVICE: 'platform=tvOS Simulator,OS=12.2,name=Apple TV 4K' +- job: test_xcodebuild_11_0 + pool: + vmImage: 'macos-latest' + steps: + - bash: ./test_xcodebuild.sh Xcode_11 + env: + IOS_DEVICE: 'platform=iOS Simulator,OS=13.0,name=iPhone 8' + TVOS_DEVICE: 'platform=tvOS Simulator,OS=13.0,name=Apple TV 4K' CODECOV_JOB: 'true' CODECOV_TOKEN: $(codecovToken) - job: test_swiftpm @@ -43,9 +51,15 @@ jobs: vmImage: 'macos-latest' steps: - script: ./test_swiftpm.sh -- job: test_linux +- job: test_linux_5_0 + pool: + vmImage: 'Ubuntu 16.04' + container: norionomura/swift:503 + steps: + - script: ./test_linux.sh +- job: test_linux_5_1 pool: vmImage: 'Ubuntu 16.04' - container: norionomura/swift:501 + container: norionomura/swift:51 steps: - script: ./test_linux.sh From f2ce40644edf05025101f942bfbf3012571d79f7 Mon Sep 17 00:00:00 2001 From: James Bean Date: Sun, 29 Sep 2019 10:57:39 -0400 Subject: [PATCH 12/19] Treat corner case of empty string value intrinsic --- .../Decoder/XMLKeyedDecodingContainer.swift | 7 ++++ .../EmptyElementEmptyStringTests.swift | 35 +++++++++++++++++++ Tests/XMLCoderTests/Minimal/StringTests.swift | 9 ----- XMLCoder.xcodeproj/project.pbxproj | 4 +++ .../xcshareddata/xcschemes/XMLCoder.xcscheme | 9 ++--- 5 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index e236db91..e4cfd016 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -282,6 +282,13 @@ extension XMLKeyedDecodingContainer { return empty } + // If we are looking at a coding key value intrinsic where the expected type is `String` and + // the value is empty, return `""`. + if strategy(key) != .attribute, elements.isEmpty, attributes.isEmpty, type == String.self, + (key.stringValue == "value" || key.stringValue == "") { + return "" as! T + } + switch strategy(key) { case .attribute: guard diff --git a/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift new file mode 100644 index 00000000..54c1992f --- /dev/null +++ b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift @@ -0,0 +1,35 @@ +// +// EmptyElementEmptyStringTests.swift +// XMLCoderTests +// +// Created by James Bean on 9/29/19. +// + +import XCTest +import XMLCoder + +class EmptyElementEmptyStringTests: XCTestCase { + + struct Container: Equatable, Codable { + let attribute: String? + let value: String + } + + func testEmptyElementEmptyStringDecoding() throws { + let xml = """ + + """ + let expected = Container(attribute: nil, value: "") + let result = try XMLDecoder().decode(Container.self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } + + func testEmptyElementEmptyStringWithAttributeDecoding() throws { + let xml = """ + + """ + let expected = Container(attribute: "x", value: "") + let result = try XMLDecoder().decode(Container.self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } +} diff --git a/Tests/XMLCoderTests/Minimal/StringTests.swift b/Tests/XMLCoderTests/Minimal/StringTests.swift index ea5d6981..54ea2206 100644 --- a/Tests/XMLCoderTests/Minimal/StringTests.swift +++ b/Tests/XMLCoderTests/Minimal/StringTests.swift @@ -25,15 +25,6 @@ class StringTests: XCTestCase { ("foobar", "foobar"), ] - func testMissing() { - let decoder = XMLDecoder() - - let xmlString = "" - let xmlData = xmlString.data(using: .utf8)! - - XCTAssertThrowsError(try decoder.decode(Container.self, from: xmlData)) - } - func testAttribute() throws { let decoder = XMLDecoder() let encoder = XMLEncoder() diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 76e0aed0..a1c96ca6 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 07E441BA2340F14B00890F46 /* EmptyElementEmptyStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */; }; OBJ_148 /* BoolBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* BoolBox.swift */; }; OBJ_149 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Box.swift */; }; OBJ_150 /* ChoiceBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* ChoiceBox.swift */; }; @@ -152,6 +153,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyElementEmptyStringTests.swift; sourceTree = ""; }; OBJ_100 /* DecimalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalTests.swift; sourceTree = ""; }; OBJ_101 /* EmptyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTests.swift; sourceTree = ""; }; OBJ_102 /* FloatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatTests.swift; sourceTree = ""; }; @@ -440,6 +442,7 @@ OBJ_119 /* NoteTest.swift */, OBJ_120 /* PlantCatalog.swift */, OBJ_121 /* PlantTest.swift */, + 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */, OBJ_122 /* RJISample.swift */, OBJ_123 /* RJITest.swift */, OBJ_124 /* RelationshipsTest.swift */, @@ -716,6 +719,7 @@ OBJ_251 /* KeyedTests.swift in Sources */, OBJ_252 /* NullTests.swift in Sources */, OBJ_253 /* OptionalTests.swift in Sources */, + 07E441BA2340F14B00890F46 /* EmptyElementEmptyStringTests.swift in Sources */, OBJ_254 /* StringTests.swift in Sources */, OBJ_255 /* UIntTests.swift in Sources */, OBJ_256 /* URLTests.swift in Sources */, diff --git a/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme b/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme index c117e5d6..11fc7feb 100644 --- a/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme +++ b/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme @@ -37,10 +37,13 @@ BlueprintName = "XMLCoderTests" ReferencedContainer = "container:XMLCoder.xcodeproj"> + + + + - - - - Date: Sun, 29 Sep 2019 10:59:36 -0400 Subject: [PATCH 13/19] Remove xcodeproj junk --- XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme | 5 ----- 1 file changed, 5 deletions(-) diff --git a/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme b/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme index 11fc7feb..59df07f8 100644 --- a/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme +++ b/XMLCoder.xcodeproj/xcshareddata/xcschemes/XMLCoder.xcscheme @@ -37,11 +37,6 @@ BlueprintName = "XMLCoderTests" ReferencedContainer = "container:XMLCoder.xcodeproj"> - - - - From e544f76a7b692e5ae78c2e63ee9446bcca2c07b2 Mon Sep 17 00:00:00 2001 From: James Bean Date: Sun, 29 Sep 2019 13:32:47 -0400 Subject: [PATCH 14/19] Add some logging to assess where we're at --- Sources/XMLCoder/Decoder/XMLDecoder.swift | 2 + .../Decoder/XMLDecoderImplementation.swift | 4 ++ .../Decoder/XMLKeyedDecodingContainer.swift | 20 ++++++-- .../EmptyElementEmptyStringTests.swift | 50 ++++++++++++++++--- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLDecoder.swift b/Sources/XMLCoder/Decoder/XMLDecoder.swift index 024b9935..81c590ff 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoder.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoder.swift @@ -349,6 +349,8 @@ open class XMLDecoder { trimValueWhitespaces: trimValueWhitespaces ) + print("decoder.decode(\(type), from: \(data), top level: \(topLevel)") + let decoder = XMLDecoderImplementation( referencing: topLevel, options: options, diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index f3ae46f3..82ea6277 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -408,6 +408,7 @@ extension XMLDecoderImplementation { } func unbox(_ box: Box) throws -> T { + let decoded: T? let type = T.self @@ -433,7 +434,10 @@ extension XMLDecoderImplementation { storage.popContainer() } + print("type: \(type), box: \(box)") + do { + print("try decoding in init") decoded = try type.init(from: self) } catch { guard case DecodingError.valueNotFound = error, diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index e4cfd016..7ee1bae1 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -249,9 +249,12 @@ extension XMLKeyedDecodingContainer { return [] } } else { + #warning("TODO: just return keyedBox.elements[key.stringValue]") return keyedBox.elements[key.stringValue].map { if let singleKeyed = $0 as? SingleKeyedBox { - return singleKeyed.element + #warning("Don't get rid of key info just yet!") + // return singleKeyed.element + return singleKeyed } else { return $0 } @@ -321,10 +324,22 @@ extension XMLKeyedDecodingContainer { box = anyBox } + print(box) + let value: T? if !(type is AnySequence.Type), let unkeyedBox = box as? UnkeyedBox, let first = unkeyedBox.first { - value = try decoder.unbox(first) + // Handle case where we have held onto a `SingleKeyedBox` + if let singleKeyed = first as? SingleKeyedBox { + print("single keyed here?: \(singleKeyed.element)") + if singleKeyed.element.isNull { + value = try decoder.unbox(singleKeyed) + } else { + value = try decoder.unbox(singleKeyed.element) + } + } else { + value = try decoder.unbox(first) + } } else { value = try decoder.unbox(box) } @@ -341,7 +356,6 @@ extension XMLKeyedDecodingContainer { "Expected \(type) value but found null instead." )) } - return unwrapped } diff --git a/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift index 54c1992f..ea2683dc 100644 --- a/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift +++ b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift @@ -10,26 +10,62 @@ import XMLCoder class EmptyElementEmptyStringTests: XCTestCase { - struct Container: Equatable, Codable { + struct Parent: Equatable, Codable { + let thing: Thing + } + + struct Thing: Equatable, Codable { let attribute: String? let value: String } func testEmptyElementEmptyStringDecoding() throws { let xml = """ - + """ - let expected = Container(attribute: nil, value: "") - let result = try XMLDecoder().decode(Container.self, from: xml.data(using: .utf8)!) + let expected = Thing(attribute: nil, value: "") + let result = try XMLDecoder().decode(Thing.self, from: xml.data(using: .utf8)!) XCTAssertEqual(expected, result) } func testEmptyElementEmptyStringWithAttributeDecoding() throws { let xml = """ - + + """ + let expected = Thing(attribute: "x", value: "") + let result = try XMLDecoder().decode(Thing.self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } + + func testArrayOfEmptyElementStringDecoding() throws { + let xml = """ + + + + + """ - let expected = Container(attribute: "x", value: "") - let result = try XMLDecoder().decode(Container.self, from: xml.data(using: .utf8)!) + let expected = [ + Thing(attribute: nil, value: ""), + Thing(attribute: "x", value: ""), + Thing(attribute: nil, value: "") + ] + let result = try XMLDecoder().decode([Thing].self, from: xml.data(using: .utf8)!) XCTAssertEqual(expected, result) } + + func testNestedEmptyElementEmptyStringDecoding() throws { + let xml = """ + + + + """ + let expected = Parent(thing: Thing(attribute: nil, value: "")) + let result = try XMLDecoder().decode(Parent.self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } + + func testNestedArrayOfEmptyElementEmptyStringDecoding() throws { + + } } From 5ea4fa76d786aa2a3bd8d81258bd1911ed31cb99 Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Mon, 30 Sep 2019 10:08:36 -0700 Subject: [PATCH 15/19] Add cases for empty string as null element decoding --- .../XMLCoder/Decoder/XMLDecoderImplementation.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index 82ea6277..5aeb1885 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -101,6 +101,13 @@ class XMLDecoderImplementation: Decoder { attributes: KeyedStorage() )) )) + case let containsEmpty as SingleKeyedBox: + precondition(containsEmpty.element is NullBox) + return KeyedDecodingContainer(XMLKeyedDecodingContainer( + referencing: self, wrapping: SharedBox(KeyedBox( + elements: KeyedStorage([("value", StringBox(""))]), attributes: KeyedStorage() + )) + )) case let keyed as SharedBox: return KeyedDecodingContainer(XMLKeyedDecodingContainer( referencing: self, @@ -212,6 +219,10 @@ extension XMLDecoderImplementation { let value = keyedBox.withShared({ $0.value as? B }) else { throw error } return value + case let singleKeyedBox as SingleKeyedBox: + guard let value = singleKeyedBox.element as? B + else { throw error} + return value case is NullBox: throw error case let keyedBox as KeyedBox: From f9fc2f4a0a156befbc65209c436f9e9e393e83ec Mon Sep 17 00:00:00 2001 From: Ben Wetherfield Date: Mon, 30 Sep 2019 10:39:58 -0700 Subject: [PATCH 16/19] Swiftformat --- Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift | 3 +-- Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift | 2 +- Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift | 7 ++----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index 5aeb1885..b0284f9a 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -221,7 +221,7 @@ extension XMLDecoderImplementation { return value case let singleKeyedBox as SingleKeyedBox: guard let value = singleKeyedBox.element as? B - else { throw error} + else { throw error } return value case is NullBox: throw error @@ -419,7 +419,6 @@ extension XMLDecoderImplementation { } func unbox(_ box: Box) throws -> T { - let decoded: T? let type = T.self diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 7ee1bae1..9d937a0d 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -288,7 +288,7 @@ extension XMLKeyedDecodingContainer { // If we are looking at a coding key value intrinsic where the expected type is `String` and // the value is empty, return `""`. if strategy(key) != .attribute, elements.isEmpty, attributes.isEmpty, type == String.self, - (key.stringValue == "value" || key.stringValue == "") { + key.stringValue == "value" || key.stringValue == "" { return "" as! T } diff --git a/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift index ea2683dc..bdc57fbf 100644 --- a/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift +++ b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift @@ -9,7 +9,6 @@ import XCTest import XMLCoder class EmptyElementEmptyStringTests: XCTestCase { - struct Parent: Equatable, Codable { let thing: Thing } @@ -48,7 +47,7 @@ class EmptyElementEmptyStringTests: XCTestCase { let expected = [ Thing(attribute: nil, value: ""), Thing(attribute: "x", value: ""), - Thing(attribute: nil, value: "") + Thing(attribute: nil, value: ""), ] let result = try XMLDecoder().decode([Thing].self, from: xml.data(using: .utf8)!) XCTAssertEqual(expected, result) @@ -65,7 +64,5 @@ class EmptyElementEmptyStringTests: XCTestCase { XCTAssertEqual(expected, result) } - func testNestedArrayOfEmptyElementEmptyStringDecoding() throws { - - } + func testNestedArrayOfEmptyElementEmptyStringDecoding() throws {} } From 3ccbfb36afcbd63ad77e8c3d7c211f246d4d3e16 Mon Sep 17 00:00:00 2001 From: James Bean Date: Mon, 30 Sep 2019 13:52:39 -0400 Subject: [PATCH 17/19] Transform precondition to where clause in switch statement --- Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index b0284f9a..2e77d663 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -101,8 +101,7 @@ class XMLDecoderImplementation: Decoder { attributes: KeyedStorage() )) )) - case let containsEmpty as SingleKeyedBox: - precondition(containsEmpty.element is NullBox) + case let containsEmpty as SingleKeyedBox where containsEmpty.element is NullBox: return KeyedDecodingContainer(XMLKeyedDecodingContainer( referencing: self, wrapping: SharedBox(KeyedBox( elements: KeyedStorage([("value", StringBox(""))]), attributes: KeyedStorage() From 800a26a367a8eb045159250bc61571220d925707 Mon Sep 17 00:00:00 2001 From: James Bean Date: Mon, 30 Sep 2019 13:53:58 -0400 Subject: [PATCH 18/19] Remove print statements --- Sources/XMLCoder/Decoder/XMLDecoder.swift | 2 -- Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift | 3 --- Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift | 3 --- 3 files changed, 8 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLDecoder.swift b/Sources/XMLCoder/Decoder/XMLDecoder.swift index 81c590ff..024b9935 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoder.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoder.swift @@ -349,8 +349,6 @@ open class XMLDecoder { trimValueWhitespaces: trimValueWhitespaces ) - print("decoder.decode(\(type), from: \(data), top level: \(topLevel)") - let decoder = XMLDecoderImplementation( referencing: topLevel, options: options, diff --git a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift index 2e77d663..a88d3189 100644 --- a/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift +++ b/Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift @@ -443,10 +443,7 @@ extension XMLDecoderImplementation { storage.popContainer() } - print("type: \(type), box: \(box)") - do { - print("try decoding in init") decoded = try type.init(from: self) } catch { guard case DecodingError.valueNotFound = error, diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 9d937a0d..0c486114 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -324,14 +324,11 @@ extension XMLKeyedDecodingContainer { box = anyBox } - print(box) - let value: T? if !(type is AnySequence.Type), let unkeyedBox = box as? UnkeyedBox, let first = unkeyedBox.first { // Handle case where we have held onto a `SingleKeyedBox` if let singleKeyed = first as? SingleKeyedBox { - print("single keyed here?: \(singleKeyed.element)") if singleKeyed.element.isNull { value = try decoder.unbox(singleKeyed) } else { From d8fca56996ce155d3a9fddfd089a2935e9e63475 Mon Sep 17 00:00:00 2001 From: James Bean Date: Mon, 30 Sep 2019 14:03:45 -0400 Subject: [PATCH 19/19] Add failing test for a nested array of empty-string value intrinsic elements --- .../EmptyElementEmptyStringTests.swift | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift index bdc57fbf..c514c1ce 100644 --- a/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift +++ b/Tests/XMLCoderTests/EmptyElementEmptyStringTests.swift @@ -9,7 +9,12 @@ import XCTest import XMLCoder class EmptyElementEmptyStringTests: XCTestCase { - struct Parent: Equatable, Codable { + + struct ContainerMultiple: Equatable, Codable { + let things: [Thing] + } + + struct ContainerSingle: Equatable, Codable { let thing: Thing } @@ -55,14 +60,33 @@ class EmptyElementEmptyStringTests: XCTestCase { func testNestedEmptyElementEmptyStringDecoding() throws { let xml = """ - + - + """ - let expected = Parent(thing: Thing(attribute: nil, value: "")) - let result = try XMLDecoder().decode(Parent.self, from: xml.data(using: .utf8)!) + let expected = ContainerSingle(thing: Thing(attribute: nil, value: "")) + let result = try XMLDecoder().decode(ContainerSingle.self, from: xml.data(using: .utf8)!) XCTAssertEqual(expected, result) } - func testNestedArrayOfEmptyElementEmptyStringDecoding() throws {} + func testNestedArrayOfEmptyElementEmptyStringDecoding() throws { + let xml = """ + + + + + + + + """ + let expected = ContainerMultiple( + things: [ + Thing(attribute: nil, value: ""), + Thing(attribute: "x", value: ""), + Thing(attribute: nil, value: ""), + ] + ) + let result = try XMLDecoder().decode(ContainerMultiple.self, from: xml.data(using: .utf8)!) + XCTAssertEqual(expected, result) + } }