使用 Swift 的 Codable
协议进行 XML 编码和解码。
此软件包是原始 ShawnMoore/XMLParsing 的一个分支,具有更多功能和改进的测试覆盖率。 自动生成的文档可在 我们的 GitHub Pages 上找到。
加入我们的 Discord,提出任何问题和进行友好交流。
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 命名空间前缀,例如在下面的 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 提供了两个辅助协议,允许您自定义节点是否作为属性或元素进行编码和解码:DynamicNodeEncoding
和 DynamicNodeDecoding
。
协议的声明非常简单
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>
请参阅 #70 由 @JoeMatt 提供的 PR 获取更多详细信息。
假设您需要解码一个类似于以下的 XML
<?xml version="1.0" encoding="UTF-8"?>
<foo id="123">456</foo>
默认情况下,您可以将 foo
解码为一个元素,但随后无法解码 id
属性。 XMLCoder
以特殊方式处理某些 CodingKey
值,以允许为此 XML 进行适当的编码。 只需添加一个 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
}
}
}
默认情况下,在解码期间会修剪元素内容中的空格。 这包括使用 值内在键 解码的字符串值。 从 0.5 版本 开始,您现在可以在 XMLDecoder
实例上设置属性 trimValueWhitespaces
为 false
(默认值为 true
),以保留解码字符串中的所有空格。
当解码漂亮的 XML 时,如果 trimValueWhitespaces
设置为 false
,则可能会将空白元素作为子元素添加到 XMLCoderElement
的实例上。 这些空白元素使得解码需要自定义 Decodable
逻辑的数据结构成为不可能。 从 0.13.0 版本 开始,您可以将 removeWhitespaceElements
属性设置为 true
(默认值为 false
)在 XMLDecoder
上以删除这些空白元素。
从 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: Codable {
case int(Int)
case string(String)
enum CodingKeys: String, XMLChoiceCodingKey {
case int
case string
}
enum IntCodingKeys: String, CodingKey { case _0 = "" }
enum StringCodingKeys: String, CodingKey { case _0 = "" }
}
这在 PR #119 由 @jsbean 和 @bwetherfield 中有更详细的描述。
让我们扩展之前的示例,用关联值中的复杂类型替换简单类型。 此示例将涵盖类似于以下的 XML
<container>
<nested attr="n1_a1">
<val>n1_v1</val>
<labeled>
<val>n2_val</val>
</labeled>
</nested>
<simple attr="n1_a1">
<val>n1_v1</val>
</simple>
</container>
enum InlineChoice: Equatable, Codable {
case simple(Nested1)
case nested(Nested1, labeled: Nested2)
enum CodingKeys: String, CodingKey, XMLChoiceCodingKey {
case simple, nested
}
enum SimpleCodingKeys: String, CodingKey { case _0 = "" }
enum NestedCodingKeys: String, CodingKey {
case _0 = ""
case labeled
}
struct Nested1: Equatable, Codable, DynamicNodeEncoding {
var attr = "n1_a1"
var val = "n1_v1"
public static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
switch key {
case CodingKeys.attr: return .attribute
default: return .element
}
}
}
struct Nested2: Equatable, Codable {
var val = "n2_val"
}
}
从 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()
}
此外,从 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 开始,XMLEncoder
上的 encode
函数接受一个新的 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 实现的。
如果您的 Swift 版本允许使用属性包装器,您可能更喜欢此 API 而不是更详细的 动态节点编码。
例如,此类型
struct Book: Codable {
@Element var id: Int
}
会将值 Book(id: 42)
编码为 <Book><id>42</id></Book>
。 反之亦然,它会将后者解码为前者。
同样,
struct Book: Codable {
@Attribute var id: Int
}
会将值 Book(id: 42)
编码为 <Book id="42"></Book>
,反之亦然进行解码。
如果您事先不知道属性在解码期间将作为元素还是属性存在,请使用 @ElementAndAttribute
struct Book: Codable {
@ElementAndAttribute var id: Int
}
这会将值 Book(id: 42)
编码为 <Book id="42"><id>42</id></Book>
。 它会将 <Book><id>42</id></Book>
和 <Book id="42"></Book>
都解码为 Book(id: 42)
。
此功能从 XMLCoder 0.13.0 开始可用,由 @bwetherfield 实现。
您可以通过将 XML 标头和/或 doctype 提供给 encode
函数,在编码对象时添加它们。 这些参数都是可选的,并且仅在显式提供时才会呈现。
struct User: Codable {
@Element var username: String
}
let data = try encoder.encode(
User(username: "Joanis"),
withRootKey: "user",
header: XMLHeader(version: 1.0, encoding: "UTF-8"),
doctype: .system(
rootElement: "user",
dtdLocation: "http://example.com/myUser_v1.dtd"
)
)
Apple 平台
Linux
Windows
Swift Package Manager 是一种用于管理 Swift 代码分发的工具。 它与 Swift 构建系统集成,以自动执行下载、编译和链接所有平台上依赖项的过程。
设置好 Swift 包后,将 XMLCoder
添加为依赖项就像将其添加到 Package.swift
的 dependencies
值一样容易。
dependencies: [
.package(url: "https://github.com/CoreOffice/XMLCoder.git", from: "0.15.0")
]
如果您在 Xcode 构建的应用程序中使用 XMLCoder,您也可以 使用 Xcode 的 GUI 将其添加为直接依赖项。
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.14.0'
end
然后,运行以下命令
$ pod install
打开创建的 YourApp.xcworkspace
文件。 这应该是您每天用来创建应用程序的文件,而不是 YourApp.xcodeproj
文件。
Carthage 是 Apple 平台的依赖管理器,它构建您的依赖项并为您提供二进制框架。
可以使用 Homebrew 使用以下命令安装 Carthage
$ brew update
$ brew install carthage
在您的 Cartfile
中,添加 GitHub 路径到 XMLCoder
github "CoreOffice/XMLCoder" ~> 0.15.0
然后,运行以下命令来构建框架
$ carthage update
将构建的框架拖到您的 Xcode 项目中。
extension XMLEncoder: ContentEncoder {
public func encode<E: Encodable>(
_ encodable: E,
to body: inout ByteBuffer,
headers: inout HTTPHeaders
) throws {
headers.contentType = .xml
// Note: You can provide an XMLHeader or DocType if necessary
let data = try self.encode(encodable)
body.writeData(data)
}
}
extension XMLDecoder: ContentDecoder {
public func decode<D: Decodable>(
_ decodable: D.Type,
from body: ByteBuffer,
headers: HTTPHeaders
) throws -> D {
// Force wrap is acceptable, as we're guaranteed these bytes exist through `readableBytes`
let body = body.readData(length: body.readableBytes)!
return try self.decode(D.self, from: body)
}
}
本项目遵守 贡献者盟约行为准则。 通过参与,您需要遵守此代码。 请向 coreoffice@desiatov.com 报告不可接受的行为。
本项目使用 SwiftFormat 和 SwiftLint 来强制执行格式和编码风格。 我们鼓励您以最适合您的任何方式在存储库的本地克隆中运行 SwiftFormat,无论是手动还是通过 Xcode 源代码编辑器扩展,构建阶段 或 git pre-commit 钩子 等自动运行。
为了确保这些工具能在您提交 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 也会在 CI 上为每个 PR 运行,因此 CI 构建可能会因格式或样式不一致而失败。我们要求所有 PR 在合并之前都必须通过 CI 构建。
我们的目标是保持 XMLCoder 的稳定性,并根据 XML 1.0 标准 正确地序列化任何 XML。 所有这些都可以很容易地自动测试,我们正在逐步提高 XMLCoder 的测试覆盖率,并且不希望它降低。 降低测试覆盖率的 PR 被合并的可能性要低得多。 如果您添加任何新功能,请务必添加测试,对于现有代码中的更改和任何重构也是如此。