XMLCoder 0.14.0

XMLCoder 0.14.0

Max DesiatovJoannis Orlandos 维护。



XMLCoder 0.14.0

  • Shawn Moore 和 Max Desiatov

SWUbanner

XMLCoder

使用 Swift 的 Codable 协议对 XML 进行编码和解码。

Build Status Version License Platform Coverage

该软件包是原始 ShawnMoore/XMLParsing 的分支,具有更多功能和改进的测试覆盖率。自动生成的文档可在我们的 GitHub Pages(https://coreoffice.github.io/XMLCoder/)上查看。

示例

import XMLCoder
import Foundation

let sourceXML = """
<note>
    <to>Bob</to>
    <from>Jane</from>
    <heading>Reminder</heading>
    <body>Don't forget to use XMLCoder!</body>
</note>
"""

struct Note: Codable {
    let to: String
    let from: String
    let heading: String
    let body: String
}

let note = try! XMLDecoder().decode(Note.self, from: Data(sourceXML.utf8))

let encodedXML = try! XMLEncoder().encode(note, withRootKey: "note")

高级功能

以下功能在 0.4.0 版本 或更高版本中提供(除非另有说明)

删除命名空间前缀

有时您需要处理 XML 命名空间前缀,如下所示

<h:table xmlns:h="http://www.w3.org/TR/html4/">
  <h:tr>
    <h:td>Apples</h:td>
    <h:td>Bananas</h:td>
  </h:tr>
</h:table>

通过设置 shouldProcessNamespaces 属性,启用从元素名称中删除前缀

struct Table: Codable, Equatable {
    struct TR: Codable, Equatable {
        let td: [String]
    }

    let tr: [TR]
}


let decoder = XMLDecoder()

// Setting this property to `true` for the namespace prefix to be stripped
// during decoding so that key names could match.
decoder.shouldProcessNamespaces = true

let decoded = try decoder.decode(Table.self, from: xmlData)

动态节点编码

XMLCoder 提供了两个辅助协议,允许您自定义节点是编码为属性还是元素:DynamicNodeEncodingDynamicNodeDecoding

协议的声明非常简单

protocol DynamicNodeEncoding: Encodable {
    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding
}

protocol DynamicNodeDecoding: Decodable {
    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding
}

相应 static 函数返回的值看起来像这样

enum NodeDecoding {
    // decodes a value from an attribute
    case attribute

    // decodes a value from an element
    case element

    // the default, attempts to decode as an element first,
    // otherwise reads from an attribute
    case elementOrAttribute
}

enum NodeEncoding {
    // encodes a value in an attribute
    case attribute

    // the default, encodes a value in an element
    case element

    // encodes a value in both attribute and element
    case both
}

为您想定制的类型添加适当协议的遵从性。相应地,下面这段示例代码

struct Book: Codable, Equatable, DynamicNodeEncoding {
    let id: UInt
    let title: String
    let categories: [Category]

    enum CodingKeys: String, CodingKey {
        case id
        case title
        case categories = "category"
    }

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case Book.CodingKeys.id: return .both
        default: return .element
        }
    }
}

适用于此 XML

<book id="123">
    <id>123</id>
    <title>Cat in the Hat</title>
    <category>Kids</category>
    <category>Wildlife</category>
</book>

请参阅 JoeMatt 提交的 PR #70 获取更多详细信息。

编码键值固有

假设您需要解码以下类似的 XML

<?xml version="1.0" encoding="UTF-8"?>
<foo id="123">456</foo>

默认情况下,您可以将 foo 解码为一个元素,但无法将 id 属性解码。为了对这种 XML 进行适当编码,XMLCoder 以一种特殊的方式处理某些 CodingKey 值。只需添加一个 stringValue 等于 “” (空字符串)的编码键即可。下面是一个示例类型声明,用于编码上述 XML,但具有相应值的编码键的特殊处理对于编码和解码都适用。

struct Foo: Codable, DynamicNodeEncoding {
    let id: String
    let value: String

    enum CodingKeys: String, CodingKey {
        case id
        case value = ""
    }

    static func nodeEncoding(forKey key: CodingKey)
    -> XMLEncoder.NodeEncoding {
        switch key {
        case CodingKeys.id:
            return .attribute
        default:
            return .element
        }
    }
}

感谢 JoeMatt 在 PR #73 中实现此功能。

保留元素内容中的空白字符

默认情况下,解码元素内容时会在空白字符。这包括使用 值固有键 解码的字符串值。从 版本 0.5 开始,您现在可以在 XMLDecoder 实例上将属性 trimValueWhitespaces 设置为 false(默认值为 true)以保留解码的字符串中的所有空白字符。

移除空白元素

当在关闭trimValueWhitespaces时解码美化打印的XML,可能会在XMLCoderElement的实例中添加空白元素作为子元素。这些空白元素使得解码需要自定义Decodable逻辑的数据结构变得不可能。从版本0.13.0开始,您可以在XMLDecoder上设置removeWhitespaceElements属性为true(默认值是false)来删除这些空白元素。

选择元素编码

版本0.8开始,您可以通过将您的CodingKey类型进一步符合XMLChoiceCodingKey来编码和解码具有关联值的enum。这允许像这个示例一样解码结构相似的XML元素

<container>
    <int>1</int>
    <string>two</string>
    <string>three</string>
    <int>4</int>
    <int>5</int>
</container>

您可以使用以下类型来解码这些元素

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))
        }
    }
}

此内容在#119中由@jsbean@bwetherfield详细描述。

Combine集成

从XMLCoder 版本0.9开始,当Apple的Combine框架可用时,XMLDecoder符合TopLevelDecoder协议,允许它与decode(type:decoder:)运算符一起使用

import Combine
import Foundation
import XMLCoder

func fetchBook(from url: URL) -> AnyPublisher<Book, Error> {
    return URLSession.shared.dataTaskPublisher(for: url)
        .map(\.data)
        .decode(type: Book.self, decoder: XMLDecoder())
        .eraseToAnyPublisher()
}

这是由@sharplet实现,在PR #132中。

此外,从XMLCoder 0.11开始,XMLEncoder符合TopLevelEncoder协议

import Combine
import XMLCoder

func encode(book: Book) -> AnyPublisher<Data, Error> {
    return Just(book)
        .encode(encoder: XMLEncoder())
        .eraseToAnyPublisher()
}

上述示例中的XML结果将始于<book,为了自定义根元素的字母大小写(例如<Book),您必须在编码器上设置适当的keyEncoding策略。如果您想完全更改元素名称,您必须更改类型名称,这是TopLevelEncoder API的一个不幸的限制。

根元素属性

有时需要在根元素上设置与您的模型类型无关的属性。从XMLCoder 0.11开始,XMLEncoderencode函数接受新的rootAttributes参数以帮助实现此功能

struct Policy: Encodable {
    var name: String
}

let encoder = XMLEncoder()
let data = try encoder.encode(Policy(name: "test"), rootAttributes: [
    "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",
])

结果XML将如下所示

<policy 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">
    <name>test</name>
</policy>

该功能已通过PR #160 实现,由 @portellaa 完成。

安装

需求

苹果平台

  • Xcode 11.0 或更高版本
    • 重要:由于编译过程中可能会出现 EXC_BAD_ACCESS 导致的崩溃,因此不建议使用 Xcode 11.2.0(11B52)和 11.2.1(11B500)编译 XMLCoder,请使用 Xcode 11.3 或更新版。更多详情请参考 #150
  • Swift 5.1 或更高版本
  • iOS 9.0 / watchOS 2.0 / tvOS 9.0 / macOS 10.10 或更高版本的部署目标

Linux

  • Ubuntu 18.04 或更高版本
  • Swift 5.1 或更高版本

Windows

  • Swift 5.5 或更高版本。

Swift 包管理工具

Swift 包管理工具 是用于管理 Swift 代码分发的工具。它与 Swift 构建系统集成,用于自动执行跨所有平台下载、编译和链接依赖项的过程。

配置好 Swift 包之后,将 XMLCoder 添加为依赖项只需将其添加到 Package.swift 文件中的 dependencies 值。

dependencies: [
    .package(url: "https://github.com/MaxDesiatov/XMLCoder.git", from: "0.13.1")
]

如果使用 Xcode 构建应用程序并使用 XMLCoder,您也可以使用 Xcode 的图形界面将其作为直接依赖项添加 add

CocoaPods

CocoaPods 是用于 Apple 平台 Swift 和 Objective-C Cocoa 项目的依赖项管理器。您可以使用以下命令安装它

$ gem install cocoapods

导航到项目目录,并使用以下命令创建 Podfile

$ pod install

Podfile 文件中指定 XMLCoder pod

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'YourApp' do
  # Comment the next line if you're not using Swift or don't want
  # to use dynamic frameworks
  use_frameworks!

  # Pods for YourApp
  pod 'XMLCoder', '~> 0.13.1'
end

然后,运行以下命令

$ pod install

打开您创建的 YourApp.xcworkspace 文件。这应该是您平常用来创建应用的文件,而不是 YourApp.xcodeproj 文件。

Carthage

Carthage 是 Apple 平台的依赖管理器,它构建您的依赖并提供二进制框架。

您可以使用以下命令通过 Homebrew 安装 Carthage

$ brew update
$ brew install carthage

在您的 Cartfile 中,添加 XMLCoder 的 GitHub 路径

github "MaxDesiatov/XMLCoder" ~> 0.13.1

然后,运行以下命令来构建框架

$ carthage update

将构建好的框架拖入您的 Xcode 项目中。

贡献

该项目遵守 贡献者行为准则。通过参与,您期望遵守此准则。请向 [email protected] 提交不可接受的行为报告。

赞助

如果您从这个库中节省了任何时间或金钱,请考虑 赞助维护者的工作。虽然一些赞助层级提供优先支持甚至咨询时间,但任何金额都备受感激,有助于维持项目。

编码风格

该项目使用 SwiftFormatSwiftLint 来强制执行格式和编码风格。我们鼓励您以您最自在的方式在本地克隆的仓库中运行 SwiftFormat,手动或自动通过 Xcode 扩展构建阶段git 预提交钩子 等。

为了确保在 macOS 上在您提交更改之前运行这些工具,我们鼓励您运行以下命令来设置 pre-commit 钩子

brew bundle # installs SwiftLint, SwiftFormat and pre-commit
pre-commit install # installs pre-commit hook to run checks before you commit

请参阅pre-commit 文档页面以获取更多详细信息和其他平台的安装说明。

SwiftFormat 和 SwiftLint 也在每次 PR 的 CI 上运行,因此 CI 编译可能会因为不一致的格式或样式而失败。在合并之前,我们需要所有 PR 的 CI 编译通过。

测试覆盖率

我们的目标是保持 XMLCoder 稳定并根据XML 1.0 标准正确序列化任何 XML。所有这些都可以通过自动测试轻松实现,我们正在逐步提高 XMLCoder 的测试覆盖率,并期待它不会降低。减少测试覆盖率的 PR 被合并的几率非常低。如果您添加任何新功能,请确保添加测试,同样适用于对现有代码的更改和重构。