XMLCoder

这个包允许通过 Swift 4 中引入的 Codable 协议来编码/解码任意 XML 文档。

免责声明: 开发这个包是为了在我自己的项目中使用。我将其作为开源发布,希望它对其他开发者有用。虽然我设计的这个实现具有合理的通用性,但我并不声称它能满足所有需求。但是,如果您发现 bug 或缺少某个功能,欢迎提交 issue 或 pull request,最好是提供一个失败的测试(或者更好,一个 bug 修复或可用的功能)。我接受所有建议。

支持的平台: macOS 10.12+, Ubuntu 18.04, Ubuntu 20.04.

支持的 Swift 版本 5.1 ... 5.6.

用法

集成 (Package.swift)

CodableXML 依赖于 Swift Package Manager 来集成到您的项目中。

.package(url: "https://github.com/franklefebvre/XMLCoder.git", equal: "0.3.3"),
.target(
    name: <your_target_here>,
    dependencies: ["XMLCoder", ...]),

集成 (Xcode 11+)

您可以通过选择 File > Swift Packages > Add Package Dependency... 并提供仓库的 URL:https://github.com/franklefebvre/XMLCoder,将 XMLCoder 添加到现有项目中。当前版本仍在开发中,API 的某些部分可能会更改,因此我建议在 Version 菜单中选择“Up to Next Minor”或“Exact”。

输入/输出

该库编码到/解码自 Foundation 提供的 XMLDocument 对象。

我选择不处理 XML 的文本(或数据)的原因有很多:

缺点是 Linux 上使用的 Foundation 的开源实现与 macOS 版本不同。编码器生成的 XML 结构可能略有不同。

编码

假设 Document 符合 Encodable,编码可以像这样简单:

func encode(doc: Document) throws -> String {
    let encoder = XMLEncoder(documentRootTag: "root")
    let xmlDocument = try encoder.encode(doc)
    // here xmlDocument is of type XMLDocument
    guard let result = String(data: xmlDocument.xmlData, encoding: .utf8) else {
        throw SomeError()
    }
    return result
}

解码

假设 Document 符合 Decodable,可以使用以下方法解码 XML 字符串:

func decode(xmlString: String) throws -> Document {
    let xmlDocument = XMLDocument(...)
    let decoder = XMLDecoder()
    let document = try decoder.decode(Document.self, from: xmlDocument)
    return document
}

XML 特性

XMLCoder 支持大多数 XML 特定的特性,例如命名空间、属性等。这些特性通过对 CodingKeys 协议的扩展来实现。

可以通过利用编译器为您的 Codable 类型合成的代码来编码/解码简单的 XML 文档。但是,当 XML 表示形式更复杂时,必须在 codable 类型旁边提供显式的 CodingKeys 声明。

命名空间

为了支持命名空间,CodingKeys 类型必须符合 XMLQualifiedKey 协议。

该协议声明如下:

protocol XMLQualifiedKey {
    var namespace: String? { get }
}

因此,对于 CodingKeys 类型中定义的每个键,如果该键属于默认命名空间,则 namespace 可以返回命名空间 URI,或者返回 nil

命名空间示例

struct NamespaceStruct: Codable {
    var key1: String
    var key2: String
    
    private enum CodingKeys: String, CodingKey, XMLQualifiedKey {
        case key1
        case key2
        
        var namespace: String? {
            switch self {
            case .key1:
                return "http://namespace.example.com"
            default:
                return nil
            }
        }
    }
}

let value = NamespaceStruct(key1: "element1", key2: "element2")
let encoder = XMLEncoder(documentRootTag: "root")
let xmlDocument = try encoder.encode(value)
let result = String(data: xmlDocument.xmlData, encoding: .utf8)

结果字符串将包含类似以下内容:

<root xmlns:ns1="http://namespace.example.com">
    <ns1:key1>element1</ns1:key1>
    <key2>element2</key2>
</root>

节点类型

通过将 XMLTypedKey 协议添加到 CodingKeys 来实现属性和数组。

protocol XMLTypedKey {
    var nodeType: XMLNodeType { get }
}

enum XMLNodeType {
    case element
    case attribute
    case inline
    case array(String?)
}

属性示例

使用此定义:

struct AttributeStruct: Codable {
    var key1: String
    var key2: String
    
    private enum CodingKeys: String, CodingKey, XMLTypedKey {
        case key1
        case key2
        
        var nodeType: XMLNodeType {
            switch self {
            case .key1:
                return .attribute
            case .key2:
                return .element
            }
        }
    }
}

let value = AttributeStruct(key1: "value1", key2: "value2")
let encoder = XMLEncoder(documentRootTag: "root")
let xmlDocument = try encoder.encode(value)
let result = String(data: xmlDocument.xmlData, encoding: .utf8)

结果字符串将包含类似以下内容:

<root key1="value1">
    <key2>value2</key2>
</root>

数组示例

使用此定义:

struct ArrayStruct: Codable {
    var key3: String
    var key4: [String]
    
    private enum CodingKeys: String, CodingKey, XMLNodeType {
        case key3
        case key4
        
        var nodeType: XMLNodeType {
            switch(self) {
            case .key3:
                return .element
            case .key4:
                return .array("child")
            }
        }
    }
}

let value = ArrayStruct(key3: "value3", key4: ["one", "two", "three"])
let encoder = XMLEncoder(documentRootTag: "root")
let xmlDocument = try encoder.encode(value)
let result = String(data: xmlDocument.xmlData, encoding: .utf8)

结果字符串将包含类似以下内容:

<root>
    <key3>value3</key3>
    <key4>
        <child>one</child>
        <child>two</child>
        <child>three</child>
    </key4>
</root>

这可能会在未来的实现中发生变化。

根元素

必须使用表示文档根的标签名称初始化 XMLEncoder

默认情况下,XMLDecoder 会忽略 XML 文档中的根标签。但是,可以使用 documentRootTag 属性指定根标签的预期名称。

编码策略

键转换

默认情况下,Swift 类型中的键被编码和解码为具有相同名称的 XML 标签,无需转换。但是,可以使用 XMLEncoderXMLDecoder 上的 keyCodingStrategyelementNameCodingStrategyattributeNameCodingStrategy 属性全局更改此行为。

keyCodingStrategy 定义元素和属性的行为,除非被 elementNameCodingStrategyattributeNameCodingStrategy 覆盖。

可能的设置是:

除了这种全局机制之外,仍然可以在个案基础上进行键和标签之间的转换,例如,通过为 CodingKeys 枚举中的枚举 case 显式提供字符串。

值转换

某些类型在 XML 中没有原生表示形式,可能需要额外的转换才能在 XML 元素或属性中表示为字符串。

Nil

XMLEncodernilEncodingStrategyXMLDecodernilDecodingStrategy 确定如何在 XML 文档中表示可选值。

可能的设置是:

Bool

XMLEncoder 公开了一个 boolEncodingStrategy 属性,XMLDecoder 公开了一个对称的 boolDecodingStrategy 属性。这些属性被定义为包含用于表示 falsetrue 值的字符串的双元素结构。

默认情况下,false 表示为 "0"true 表示为 "1"

Data

XMLEncoder 公开了一个 dataEncodingStrategy 属性,XMLDecoder 公开了一个对称的 dataDecodingStrategy 属性。这些属性允许将 Data 属性表示为 Base64 字符串(默认),十六进制字符串,或提供自定义实现。

Date

XMLEncoder 公开了一个 dateEncodingStrategy 属性,XMLDecoder 公开了一个对称的 dateDecodingStrategy 属性。默认行为是将日期编码和解码为 ISO 8601 字符串;其他策略允许提供 DateFormatter 或自定义实现。

URL

URL 被转换为/从字符串转换。不提供自定义选项。