XMLCoder
使用 Swift 的 Codable
协议对 XML 进行编码和解码。
该软件包是原始 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 提供了两个辅助协议,允许您自定义节点是编码为属性还是元素: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>
请参阅 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()
}
此外,从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 完成。
安装
需求
苹果平台
- 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] 提交不可接受的行为报告。
赞助
如果您从这个库中节省了任何时间或金钱,请考虑 赞助维护者的工作。虽然一些赞助层级提供优先支持甚至咨询时间,但任何金额都备受感激,有助于维持项目。
编码风格
该项目使用 SwiftFormat 和 SwiftLint 来强制执行格式和编码风格。我们鼓励您以您最自在的方式在本地克隆的仓库中运行 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 被合并的几率非常低。如果您添加任何新功能,请确保添加测试,同样适用于对现有代码的更改和重构。