diff --git a/Sources/XMLCoder/Encoder/XMLEncoder.swift b/Sources/XMLCoder/Encoder/XMLEncoder.swift index 0f39ff2b..eb643a94 100644 --- a/Sources/XMLCoder/Encoder/XMLEncoder.swift +++ b/Sources/XMLCoder/Encoder/XMLEncoder.swift @@ -293,6 +293,7 @@ open class XMLEncoder { let keyEncodingStrategy: KeyEncodingStrategy let nodeEncodingStrategy: NodeEncodingStrategy let stringEncodingStrategy: StringEncodingStrategy + let rootAttributes: [String: String] let userInfo: [CodingUserInfoKey: Any] } @@ -304,9 +305,12 @@ open class XMLEncoder { keyEncodingStrategy: keyEncodingStrategy, nodeEncodingStrategy: nodeEncodingStrategy, stringEncodingStrategy: stringEncodingStrategy, + rootAttributes: rootAttributes, userInfo: userInfo) } + public var rootAttributes: [String: String] = [:] + // MARK: - Constructing a XML Encoder /// Initializes `self` with default strategies. @@ -326,7 +330,7 @@ open class XMLEncoder { /// - throws: An error if any value throws an error during encoding. open func encode(_ value: T, withRootKey rootKey: String? = nil, - rootAttributes: [String: String], + rootAttributes: [String: String]? = nil, header: XMLHeader? = nil) throws -> Data { let encoder = XMLEncoderImplementation( options: options, @@ -335,11 +339,11 @@ open class XMLEncoder { encoder.nodeEncodings.append(options.nodeEncodingStrategy.nodeEncodings(forType: T.self, with: encoder)) let topLevel = try encoder.box(value) - let attributes = rootAttributes.map(Attribute.init) + let attributes = rootAttributes?.map(Attribute.init) ?? [] let elementOrNone: XMLCoderElement? - let rootKey = rootKey ?? "\(T.self)" + let rootKey = rootKey ?? "\(T.self)".convert(for: keyEncodingStrategy) if let keyedBox = topLevel as? KeyedBox { elementOrNone = XMLCoderElement(key: rootKey, box: keyedBox, attributes: attributes) @@ -365,3 +369,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 let .custom(converter): + return self + case .capitalized: + return XMLEncoder.KeyEncodingStrategy._convertToCapitalized(self) + case .uppercased: + return XMLEncoder.KeyEncodingStrategy._convertToUppercased(self) + case .lowercased: + return XMLEncoder.KeyEncodingStrategy._convertToLowercased(self) + } + } +} diff --git a/Tests/XMLCoderTests/RootLevetExtraAttributesTests.swift b/Tests/XMLCoderTests/RootLevetExtraAttributesTests.swift new file mode 100644 index 00000000..a398c9e7 --- /dev/null +++ b/Tests/XMLCoderTests/RootLevetExtraAttributesTests.swift @@ -0,0 +1,61 @@ +// +// RootLevetExtraAttributesTests.swift +// XMLCoderTests +// +// Created by Luís Portela Afonso on 19/03/2020. +// + +import XCTest +@testable import XMLCoder + +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 = """ + + extra root attributes + + """ + + XCTAssertEqual(dataString!, expected, "") + } catch { + XCTAssertThrowsError(error) + } + } +} + +private struct Policy: Encodable, DynamicNodeEncoding { + var name: String + var initial: String + + enum CodingKeys: String, CodingKey { + case name, initial//, rule + } + + static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding { + switch key { + case Policy.CodingKeys.name: return .attribute + default: return .element + } + } +} diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 82099ba9..c3290d39 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -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 */; }; @@ -164,6 +165,7 @@ /* Begin PBXFileReference section */ 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyElementEmptyStringTests.swift; sourceTree = ""; }; 4A062D4E2341924E009BCAC1 /* CombineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineTests.swift; sourceTree = ""; }; + 970FA9DB2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootLevetExtraAttributesTests.swift; sourceTree = ""; }; B54555BB2343F5C1000D4128 /* EmptyArrayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyArrayTest.swift; sourceTree = ""; }; B5E67532238B47E5006C8548 /* MixedChoiceAndNonChoiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixedChoiceAndNonChoiceTests.swift; sourceTree = ""; }; B5E67534238B4960006C8548 /* IntOrString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntOrString.swift; sourceTree = ""; }; @@ -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; @@ -771,6 +774,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 */,