Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for root attributes propagation #160

Merged
merged 1 commit into from Apr 13, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 18 additions & 16 deletions Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Expand Up @@ -293,21 +293,23 @@ struct XMLCoderElement: Equatable {
// MARK: - Convenience Initializers

extension XMLCoderElement {
init(key: String, box: UnkeyedBox) {
init(key: String, box: UnkeyedBox, attributes: [Attribute] = []) {
if let containsChoice = box as? [ChoiceBox] {
self.init(key: key, elements: containsChoice.map {
XMLCoderElement(key: $0.key, box: $0.element)
})
self.init(
key: key,
elements: containsChoice.map { XMLCoderElement(key: $0.key, box: $0.element) },
attributes: attributes
)
} else {
self.init(key: key, elements: box.map { XMLCoderElement(key: key, box: $0) })
self.init(key: key, elements: box.map { XMLCoderElement(key: key, box: $0) }, attributes: attributes)
}
}

init(key: String, box: ChoiceBox) {
self.init(key: key, elements: [XMLCoderElement(key: box.key, box: box.element)])
init(key: String, box: ChoiceBox, attributes: [Attribute] = []) {
self.init(key: key, elements: [XMLCoderElement(key: box.key, box: box.element)], attributes: attributes)
}

init(key: String, box: KeyedBox) {
init(key: String, box: KeyedBox, attributes: [Attribute] = []) {
var elements: [XMLCoderElement] = []

for (key, box) in box.elements {
Expand Down Expand Up @@ -338,7 +340,7 @@ extension XMLCoderElement {
}
}

let attributes: [Attribute] = box.attributes.compactMap { key, box in
let attributes: [Attribute] = attributes + box.attributes.compactMap { key, box in
guard let value = box.xmlString else {
return nil
}
Expand All @@ -356,20 +358,20 @@ extension XMLCoderElement {
}
}

init(key: String, box: Box) {
init(key: String, box: Box, attributes: [Attribute] = []) {
switch box {
case let sharedUnkeyedBox as SharedBox<UnkeyedBox>:
self.init(key: key, box: sharedUnkeyedBox.unboxed)
self.init(key: key, box: sharedUnkeyedBox.unboxed, attributes: attributes)
case let sharedKeyedBox as SharedBox<KeyedBox>:
self.init(key: key, box: sharedKeyedBox.unboxed)
self.init(key: key, box: sharedKeyedBox.unboxed, attributes: attributes)
case let sharedChoiceBox as SharedBox<ChoiceBox>:
self.init(key: key, box: sharedChoiceBox.unboxed)
self.init(key: key, box: sharedChoiceBox.unboxed, attributes: attributes)
case let unkeyedBox as UnkeyedBox:
self.init(key: key, box: unkeyedBox)
self.init(key: key, box: unkeyedBox, attributes: attributes)
case let keyedBox as KeyedBox:
self.init(key: key, box: keyedBox)
self.init(key: key, box: keyedBox, attributes: attributes)
case let choiceBox as ChoiceBox:
self.init(key: key, box: choiceBox)
self.init(key: key, box: choiceBox, attributes: attributes)
case let simpleBox as SimpleBox:
self.init(key: key, box: simpleBox)
case let box:
Expand Down
36 changes: 32 additions & 4 deletions Sources/XMLCoder/Encoder/XMLEncoder.swift
Expand Up @@ -318,28 +318,35 @@ open class XMLEncoder {
///
/// - parameter value: The value to encode.
/// - parameter withRootKey: the key used to wrap the encoded values.
/// - parameter rootAttributes: the list of attributes to be added to the root node
/// - returns: A new `Data` value containing the encoded XML data.
/// - throws: `EncodingError.invalidValue` if a non-conforming
/// floating-point value is encountered during encoding, and the encoding
/// strategy is `.throw`.
/// - throws: An error if any value throws an error during encoding.
open func encode<T: Encodable>(_ value: T, withRootKey rootKey: String, header: XMLHeader? = nil) throws -> Data {
open func encode<T: Encodable>(_ value: T,
withRootKey rootKey: String? = nil,
rootAttributes: [String: String]? = nil,
header: XMLHeader? = nil) throws -> Data {
let encoder = XMLEncoderImplementation(
options: options,
nodeEncodings: []
)
encoder.nodeEncodings.append(options.nodeEncodingStrategy.nodeEncodings(forType: T.self, with: encoder))

let topLevel = try encoder.box(value)
let attributes = rootAttributes?.map(Attribute.init) ?? []

let elementOrNone: XMLCoderElement?

let rootKey = rootKey ?? "\(T.self)".convert(for: keyEncodingStrategy)

if let keyedBox = topLevel as? KeyedBox {
elementOrNone = XMLCoderElement(key: rootKey, box: keyedBox)
elementOrNone = XMLCoderElement(key: rootKey, box: keyedBox, attributes: attributes)
} else if let unkeyedBox = topLevel as? UnkeyedBox {
elementOrNone = XMLCoderElement(key: rootKey, box: unkeyedBox)
elementOrNone = XMLCoderElement(key: rootKey, box: unkeyedBox, attributes: attributes)
} else if let choiceBox = topLevel as? ChoiceBox {
elementOrNone = XMLCoderElement(key: rootKey, box: choiceBox)
elementOrNone = XMLCoderElement(key: rootKey, box: choiceBox, attributes: attributes)
} else {
fatalError("Unrecognized top-level element of type: \(type(of: topLevel))")
}
Expand All @@ -358,3 +365,24 @@ open class XMLEncoder {
.data(using: .utf8, allowLossyConversion: true)!
}
}

private extension String {
func convert(for encodingStrategy: XMLEncoder.KeyEncodingStrategy) -> String {
switch encodingStrategy {
case .useDefaultKeys:
return self
case .convertToSnakeCase:
return XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(self)
case .convertToKebabCase:
return XMLEncoder.KeyEncodingStrategy._convertToKebabCase(self)
case .custom:
return self
case .capitalized:
return XMLEncoder.KeyEncodingStrategy._convertToCapitalized(self)
case .uppercased:
return XMLEncoder.KeyEncodingStrategy._convertToUppercased(self)
case .lowercased:
return XMLEncoder.KeyEncodingStrategy._convertToLowercased(self)
}
}
}
56 changes: 56 additions & 0 deletions Tests/XMLCoderTests/RootLevetExtraAttributesTests.swift
@@ -0,0 +1,56 @@
import XCTest
@testable import XMLCoder

final class RootLevetExtraAttributesTests: XCTestCase {
private let encoder = XMLEncoder()

func testExtraAttributes() {
let policy = Policy(name: "test", initial: "extra root attributes")

let extraRootAttributes = [
"xmlns": "http://www.nrf-arts.org/IXRetail/namespace",
"xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
]

encoder.keyEncodingStrategy = .lowercased
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

do {
let data = try encoder.encode(policy,
rootAttributes: extraRootAttributes)

let dataString = String(data: data, encoding: .utf8)
XCTAssertNotNil(dataString, "failed to encode object")

let expected = """
<policy name="test" \
xmlns="http://www.nrf-arts.org/IXRetail/namespace" \
xmlns:xsd="http://www.w3.org/2001/XMLSchema" \
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<initial>extra root attributes</initial>
</policy>
"""

XCTAssertEqual(dataString!, expected, "")
} catch {
XCTAssertThrowsError(error)
}
}
}

private struct Policy: Encodable, DynamicNodeEncoding {
var name: String
var initial: String

enum CodingKeys: String, CodingKey {
case name, initial
}

static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
switch key {
case Policy.CodingKeys.name: return .attribute
default: return .element
}
}
}
14 changes: 9 additions & 5 deletions XMLCoder.xcodeproj/project.pbxproj
Expand Up @@ -23,6 +23,7 @@
/* Begin PBXBuildFile section */
07E441BA2340F14B00890F46 /* EmptyElementEmptyStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */; };
4A062D4F2341924E009BCAC1 /* CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A062D4E2341924E009BCAC1 /* CombineTests.swift */; };
970FA9DC2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970FA9DB2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift */; };
B54555BC2343F5C1000D4128 /* EmptyArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54555BB2343F5C1000D4128 /* EmptyArrayTest.swift */; };
B5E67533238B47E5006C8548 /* MixedChoiceAndNonChoiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E67532238B47E5006C8548 /* MixedChoiceAndNonChoiceTests.swift */; };
B5E67535238B4960006C8548 /* IntOrString.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E67534238B4960006C8548 /* IntOrString.swift */; };
Expand Down Expand Up @@ -164,6 +165,7 @@
/* Begin PBXFileReference section */
07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyElementEmptyStringTests.swift; sourceTree = "<group>"; };
4A062D4E2341924E009BCAC1 /* CombineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineTests.swift; sourceTree = "<group>"; };
970FA9DB2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootLevetExtraAttributesTests.swift; sourceTree = "<group>"; };
B54555BB2343F5C1000D4128 /* EmptyArrayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyArrayTest.swift; sourceTree = "<group>"; };
B5E67532238B47E5006C8548 /* MixedChoiceAndNonChoiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixedChoiceAndNonChoiceTests.swift; sourceTree = "<group>"; };
B5E67534238B4960006C8548 /* IntOrString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntOrString.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -452,30 +454,31 @@
OBJ_90 /* DecodingContainerTests.swift */,
OBJ_91 /* DynamicNodeDecodingTest.swift */,
OBJ_92 /* DynamicNodeEncodingTest.swift */,
B54555BB2343F5C1000D4128 /* EmptyArrayTest.swift */,
07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */,
OBJ_93 /* ErrorContextTest.swift */,
B5E67534238B4960006C8548 /* IntOrString.swift */,
OBJ_94 /* KeyDecodingAndEncodingStrategyTests.swift */,
B5E67532238B47E5006C8548 /* MixedChoiceAndNonChoiceTests.swift */,
OBJ_113 /* MixedContainerTest.swift */,
OBJ_114 /* NamespaceTest.swift */,
OBJ_115 /* NestedAttributeChoiceTests.swift */,
B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */,
OBJ_116 /* NestedChoiceTests.swift */,
OBJ_117 /* NestingTests.swift */,
OBJ_118 /* NodeEncodingStrategyTests.swift */,
OBJ_119 /* NoteTest.swift */,
OBJ_120 /* PlantCatalog.swift */,
OBJ_121 /* PlantTest.swift */,
07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */,
D18FBFB72348FAE500FA4F65 /* QuoteDecodingTest.swift */,
OBJ_124 /* RelationshipsTest.swift */,
OBJ_122 /* RJISample.swift */,
OBJ_123 /* RJITest.swift */,
B5F74471233F74E400BBDB15 /* RootLevelAttributeTest.swift */,
970FA9DB2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift */,
OBJ_125 /* SimpleChoiceTests.swift */,
OBJ_126 /* SingleChildTests.swift */,
OBJ_127 /* SpacePreserveTest.swift */,
B5EA3BB4230F235C00D8D69B /* NestedChoiceArrayTest.swift */,
B54555BB2343F5C1000D4128 /* EmptyArrayTest.swift */,
B5E67532238B47E5006C8548 /* MixedChoiceAndNonChoiceTests.swift */,
B5E67534238B4960006C8548 /* IntOrString.swift */,
);
name = XMLCoderTests;
path = Tests/XMLCoderTests;
Expand Down Expand Up @@ -792,6 +795,7 @@
OBJ_268 /* RJISample.swift in Sources */,
B5E67535238B4960006C8548 /* IntOrString.swift in Sources */,
OBJ_269 /* RJITest.swift in Sources */,
970FA9DC2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift in Sources */,
OBJ_270 /* RelationshipsTest.swift in Sources */,
OBJ_271 /* SimpleChoiceTests.swift in Sources */,
4A062D4F2341924E009BCAC1 /* CombineTests.swift in Sources */,
Expand Down