Issue #869

We can use PropertyListEncoder from Swift 4

let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let data = try encoder.encode(model)

Or we can manually do with resultBuilder style

Declare @resultBuilder for PlistBuilder that can support if else check

import Foundation

@resultBuilder
enum PlistBuilder {
    static func buildBlock(_ components: PlistNode...) -> [PlistNode] {
        components
    }

    static func buildBlock(_ components: [PlistNode]...) -> [PlistNode] {
        components.flatMap { $0 }
    }


    static func buildEither(first component: [PlistNode]) -> [PlistNode] {
        component
    }

    static func buildEither(second component: [PlistNode]) -> [PlistNode] {
        component
    }

    static func buildOptional(_ component: [PlistNode]?) -> [PlistNode] {
        component ?? []
    }

    static func buildExpression(_ expression: PlistNode) -> [PlistNode] {
        [expression]
    }

    static func buildExpression(_ expression: [PlistNode]) -> [PlistNode] {
        expression
    }

    static func buildArray(_ components: [[PlistNode]]) -> [PlistNode] {
        components.flatMap { $0 }
    }
}

protocol PlistNode {
    func toLines() -> [String]
}

struct PlistString: PlistNode {
    let key: String
    let value: String

    init(key: String, value: String) {
        self.key = key
        self.value = value
    }

    func toLines() -> [String] {
        [
            "<key>\(key)</key>",
            "<string>\(value)</string>"
        ]
    }
}

struct PlistBool: PlistNode {
    let key: String
    let value: Bool

    init(key: String, value: Bool) {
        self.key = key
        self.value = value
    }

    func toLines() -> [String] {
        let string = value ? "<true/>" : "<false/>"
        return [
            "<key>\(key)</key>",
            "\(string)"
        ]
    }
}

struct PlistDict: PlistNode {
    var key: String?
    var nodes: [PlistNode]

    init(key: String? = nil, @PlistBuilder _ builder: () -> [PlistNode]) {
        self.key = key
        self.nodes = builder()
    }

    func toLines() -> [String] {
        var lines = [String]()
        if let key = key {
            lines.append("<key>\(key)</key>")
        }

        lines.append("<dict>")
        lines.append(
            contentsOf: nodes
                .flatMap { $0.toLines() }
                .map { Array(repeating: " ", count: 4) + $0 }
        )
        lines.append("</dict>")

        return lines
    }

    func toPlistString() -> String {
        let content = toLines().joined(separator: "\n")
        return
"""
<?Plist version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
\(content)
</plist>
"""
    }
}

Then we can generate like

let dict = PlistDict {
    PlistBool(key: "uploadSymbols", value: exportOptions.uploadSymbols)
    PlistBool(key: "uploadBitcode", value: exportOptions.uploadBitcode)
    PlistBool(key: "compileBitcode", value: exportOptions.compileBitcode)
    PlistString(key: "signingStyle", value: exportOptions.signingStyle.rawValue)

    if let teamId = exportOptions.teamId {
        PlistString(key: "teamID", value: teamId)
    }

    if let signingCertificate = exportOptions.signingCertificate {
        PlistString(key: "signingCertificate", value: signingCertificate)
    }

    if let profiles = exportOptions.provisioningProfiles {
        PlistDict(key: "provisioningProfiles") {
            for profile in profiles {
                PlistString(key: profile.bundleId, value: profile.profileName)
            }
        }
    }
}