SWXMLHash
SWXMLHash 是在 Swift 中解析 XML 的相对简单方法。如果您熟悉 NSXMLParser
,这个库是其简单封装。从概念上来说,它提供 XML 到数组和字典(即散列)的转换。
API 从 SwiftyJSON 中汲取了很多灵感。
内容
要求
- iOS 8.0+/ Mac OS X 10.9+/ tvOS 9.0+/ watchOS 2.0+
- Xcode 8.0+
安装
SWXMLHash 可以使用 CocoaPods、Carthage、Swift 包管理器 或手动方式安装。
CocoaPods
要安装 CocoaPods,请运行
$ gem install cocoapods
然后创建一个具有以下内容的 Podfile
platform :ios, '10.0'
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'SWXMLHash', '~> 7.0.0'
end
最后,运行以下命令进行安装
$ pod install
Carthage
要安装 Carthage,请运行(使用 Homebrew)
$ brew update
$ brew install carthage
然后在你的 Cartfile
中添加以下行
github "drmohundro/SWXMLHash" ~> 7.0
Swift 包管理器
Swift 包管理器需要 Swift 版本 4.0 或更高。首先,创建一个 Package.swift
文件。它应如下所示
dependencies: [
.package(url: "https://github.com/drmohundro/SWXMLHash.git", from: "7.0.0")
]
然后运行 swift build
,这将为您下载并编译 SWXMLHash,以便您开始使用。
手动安装
要手动安装,您需要克隆SWXMLHash存储库。您可以在单独的目录中执行此操作,或者可以使用git子模块——在这种情况下,建议使用git子模块,这样您的存储库将包含有关您正在使用的SWXMLHash的哪个提交的详细信息。一旦完成,您只需将所有相关的swift文件放入项目中即可。
如果您正在使用工作区,则可以仅包含整个SWXMLHash.xcodeproj
。
入门指南
如果你刚开始使用SWXMLHash,我建议克隆存储库并打开工作区。我在工作区中包括了一个Swift playground,这使得在API和调用上进行实验变得容易。
配置
SWXMLHash允许在解析方法方面进行有限的配置。要设置任何配置选项,您使用如下的configure
方法
let xml = XMLHash.config {
config in
// set any config options here
}.parse(xmlToParse)
目前可用的选项包括
shouldProcessLazily
- 此选项确定是否使用XML的懒加载。如果您的大型XML,则这可以显着提高解析性能。
- 默认为
false
shouldProcessNamespaces
- 此设置将传递给内部
NSXMLParser
实例。它将返回不带其命名空间部分的任何XML元素(即,“<h:table>”将返回为“<table>”) - 默认为
false
- 此设置将传递给内部
caseInsensitive
- 此设置允许进行不区分大小写的键查找。通常,XML是一种区分大小写的语言,但此选项可以在必要时绕过此限制。
- 默认为
false
encoding
- 此设置允许在将XML字符串传递给
parse
时显式指定字符编码。 - 默认为
String.encoding.utf8
- 此设置允许在将XML字符串传递给
userInfo
- 此设置模仿
Codable
的userInfo
属性,使用户能够添加将用于反序列化的上下文信息。 - 参阅 Codable的userInfo文档
- 默认为 [:]
- 此设置模仿
detectParsingErrors
- 此设置尝试检测XML解析错误。如果在解析过程中发现任何问题,
parse
将返回一个XMLIndexer.parsingError
。 - 默认为
false
(因为向后兼容性,以及许多用户尝试使用此库解析HTML)
- 此设置尝试检测XML解析错误。如果在解析过程中发现任何问题,
示例
下面所有的示例都可以在包含的规范中找到。
初始化
let xml = XMLHash.parse(xmlToParse)
如果正在解析一个大型XML文件并且需要最佳性能,可以选择将解析配置为懒加载。懒加载 avoidsloading the entire XML document into memory, 因此在性能方面可能更可取。有关有关懒加载的一个注意事项,请参阅错误处理。
let xml = XMLHash.config {
config in
config.shouldProcessLazily = true
}.parse(xmlToParse)
上述方法使用新的config方法,但还有XMLHash
的直接lazy
方法。
let xml = XMLHash.lazy(xmlToParse)
单个元素查找
给定
<root>
<header>
<title>Foo</title>
</header>
...
</root>
将返回 "Foo"。
xml["root"]["header"]["title"].element?.text
多个元素查找
给定
<root>
...
<catalog>
<book><author>Bob</author></book>
<book><author>John</author></book>
<book><author>Mark</author></book>
</catalog>
...
</root>
下面将返回 "John"。
xml["root"]["catalog"]["book"][1]["author"].element?.text
属性使用
给定
<root>
...
<catalog>
<book id="1"><author>Bob</author></book>
<book id="123"><author>John</author></book>
<book id="456"><author>Mark</author></book>
</catalog>
...
</root>
以下将返回 "123"。
xml["root"]["catalog"]["book"][1].element?.attribute(by: "id")?.text
或者,您可以查找具有特定属性的事件。以下将返回 "John"。
xml["root"]["catalog"]["book"].withAttribute("id", "123")["author"].element?.text
在当前级别返回所有元素
给定
<root>
...
<catalog>
<book><genre>Fiction</genre></book>
<book><genre>Non-fiction</genre></book>
<book><genre>Technical</genre></book>
</catalog>
...
</root>
使用 all
方法将遍历索引级别的所有节点。以下将返回 "Fiction, Non-fiction, Technical"。
", ".join(xml["root"]["catalog"]["book"].all.map { elem in
elem["genre"].element!.text!
})
您还可以迭代all
方法
for elem in xml["root"]["catalog"]["book"].all {
print(elem["genre"].element!.text!)
}
在当前级别返回所有子元素
给定
<root>
<catalog>
<book>
<genre>Fiction</genre>
<title>Book</title>
<date>1/1/2015</date>
</book>
</catalog>
</root>
以下将 print
"root", "catalog", "book", "genre", "title", 和 "date"(注意children
方法)。
func enumerate(indexer: XMLIndexer) {
for child in indexer.children {
print(child.element!.name)
enumerate(child)
}
}
enumerate(indexer: xml)
筛选元素
给定
<root>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre><price>44.95</price>
<publish_date>2000-10-01</publish_date>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
</book>
<book id="bk103">
<author>Corets, Eva</author>
<title>Maeve Ascendant</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-11-17</publish_date>
</book>
</catalog>
</root>
以下将返回 "Midnight Rain"。筛选可以基于XMLElement
类的任何部分或索引。
let subIndexer = xml!["root"]["catalog"]["book"]
.filterAll { elem, _ in elem.attribute(by: "id")!.text == "bk102" }
.filterChildren { _, index in index >= 1 && index <= 3 }
print(subIndexer.children[0].element?.text)
错误处理
使用 Swift 2.0 的新错误处理功能
do {
try xml!.byKey("root").byKey("what").byKey("header").byKey("foo")
} catch let error as IndexingError {
// error is an IndexingError instance that you can deal with
}
或者 使用现有的索引功能
switch xml["root"]["what"]["header"]["foo"] {
case .element(let elem):
// everything is good, code away!
case .xmlError(let error):
// error is an IndexingError instance that you can deal with
}
请注意,如上所示的错误处理方式不适用于懒加载的 XML。懒解析实际上在调用 element
或 all
方法时才会发生 - 因此,在请求元素之前,无法知道元素是否已存在。
XML 对象反序列化
更频繁的情况下,您可能希望将 XML 树反序列化为自定义类型的数组。这就是 XMLObjectDeserialization
发挥作用的地方。
给定
<root>
<books>
<book isbn="0000000001">
<title>Book A</title>
<price>12.5</price>
<year>2015</year>
<categories>
<category>C1</category>
<category>C2</category>
</categories>
</book>
<book isbn="0000000002">
<title>Book B</title>
<price>10</price>
<year>1988</year>
<categories>
<category>C2</category>
<category>C3</category>
</categories>
</book>
<book isbn="0000000003">
<title>Book C</title>
<price>8.33</price>
<year>1990</year>
<amount>10</amount>
<categories>
<category>C1</category>
<category>C3</category>
</categories>
</book>
</books>
</root>
使用实现了 XMLObjectDeserialization
的 Book
结构体
struct Book: XMLObjectDeserialization {
let title: String
let price: Double
let year: Int
let amount: Int?
let isbn: Int
let category: [String]
static func deserialize(_ node: XMLIndexer) throws -> Book {
return try Book(
title: node["title"].value(),
price: node["price"].value(),
year: node["year"].value(),
amount: node["amount"].value(),
isbn: node.value(ofAttribute: "isbn"),
category : node["categories"]["category"].value()
)
}
}
以下将返回 Book
结构体的数组
let books: [Book] = try xml["root"]["books"]["book"].value()
您可以通过为任何非叶节点(例如上面示例中的 `XMLObjectDeserialization
来将任何 XML 转换为您自定义的类型。
对于叶节点(例如上面的 `
Int
、Double
、Float
、Bool
和 String
值(均为非可选和可选变体)。可以通过实现 XMLElementDeserializable
添加自定义转换器。
对于属性(例如上面的 `isbn=`),内建的转换器支持与上面相同的类型,并且可以通过实现 XMLAttributeDeserializable
添加额外的转换器。
类型转换支持错误处理、可选和数组。有关更多示例,请参阅 SWXMLHashTests.swift
或直接在 Swift playground 中玩转类型转换。
自定义值转换
值反序列化是将特定字符串值反序列化到自定义类型的地方。所以,日期是一个很好的例子 - 你宁愿处理日期类型而不愿意进行字符串解析,对吧?这就是 XMLValueDeserialization
属性的作用。
给定
<root>
<elem>Monday, 23 January 2016 12:01:12 111</elem>
</root>
以下为 Date
值反序列化的实现
extension Date: XMLValueDeserialization {
public static func deserialize(_ element: XMLHash.XMLElement) throws -> Date {
let date = stringToDate(element.text)
guard let validDate = date else {
throw XMLDeserializationError.typeConversionFailed(type: "Date", element: element)
}
return validDate
}
public static func deserialize(_ attribute: XMLAttribute) throws -> Date {
let date = stringToDate(attribute.text)
guard let validDate = date else {
throw XMLDeserializationError.attributeDeserializationFailed(type: "Date", attribute: attribute)
}
return validDate
}
public func validate() throws {
// empty validate... only necessary for custom validation logic after parsing
}
private static func stringToDate(_ dateAsString: String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE, dd MMMM yyyy HH:mm:ss SSS"
return dateFormatter.date(from: dateAsString)
}
}
以下将返回日期值
let dt: Date = try xml["root"]["elem"].value()
常见问题解答
SWXMLHash是否为我处理URL?
不是的 - SWXMLHash只处理XML的解析。如果你有一个包含XML内容的URL,我建议使用例如AlamoFire这样的库将内容下载为一个字符串,然后对其进行解析。
SWXMLHash支持写入XML内容吗?
目前不支持 - SWXMLHash只支持通过索引、反序列化等方式解析XML。
.value()
时,我遇到了"对于成员'subscript'的引用不明确"的错误。
当我调用.value()
用于反序列化 - 你必须有一个实现了XMLObjectDeserialization
(如果是单个元素而非元素组,则为XMLElementDeserializable
)并能够处理对表达式左侧的反序列化的东西。
例如,给定以下内容
let dateValue: Date = try! xml["root"]["date"].value()
你会得到错误,因为没有内置的用于 Date
的反序列化程序。请参阅上面关于添加您自己的序列化支持的文档。在这种情况下,你需要为 Date
创建自己的 XMLElementDeserializable
实现。请查看如何添加自己的 Date
反序列化支持的示例。
parse()
时遇到了 EXC_BAD_ACCESS (SIGSEGV)
我在调用 你的 XML 内容可能含有所谓的 "字节顺序标记" 或 BOM。SWXMLHash 使用 NSXMLParser
进行解析逻辑,并且存在处理 BOM 字符的问题。有关详细信息,请参阅问题 #65。遇到此问题的其他人已经在解析之前删除了内容中的 BOM。
NSDate
)的反序列化?
如何处理类与结构体(如使用 在类上使用扩展而不是结构体会导致一些不太常见的问题,这些可能会给你带来一些麻烦。例如,请参阅 StackOverflow 上的这个问题,其中有人尝试为类 NSDate
(它不是结构体)编写自己的 XMLElementDeserializable
。 XMLElementDeserializable
协议期望一个返回 Self
的方法 - 这部分可能有点复杂。
以下是一些代码片段,可以完成此操作,并请注意,重点在于 private static func value<T>() -> T
这一行 - 这才是关键。
extension NSDate: XMLElementDeserializable {
public static func deserialize(_ element: XMLElement) throws -> Self {
guard let dateAsString = element.text else {
throw XMLDeserializationError.nodeHasNoValue
}
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
let date = dateFormatter.dateFromString(dateAsString)
guard let validDate = date else {
throw XMLDeserializationError.typeConversionFailed(type: "Date", element: element)
}
// NOTE THIS
return value(validDate)
}
// AND THIS
private static func value<T>(date: NSDate) -> T {
return date as! T
}
}
如何处理枚举的反序列化?
查看来自 @woolie 的这个出色的建议/示例,他(她)在 #245 的 GitHub 讨论页。
我遇到了一个 "''XMLElement' 是模糊的" 错误
这与 #256 有关 - XMLElement
实际上已经被重命名多次以避免冲突,但最简单的方法是使用 XMLHash.XMLElement
进行范围限定。
SWXMLHash 在 Web 上下文(例如 Vapor)中工作吗?
请参阅 #264 以了解讨论内容。所需的唯一更改是添加以下导入逻辑
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
有不同的问题吗?
请随时给我发邮件,在 StackOverflow 上提问,或者如果您认为发现了错误,可以打开一个问题。我将很乐意尝试帮助!
另一种选择是在 讨论 中提问。
更新日志
查看更新日志来了解所有变更及其对应版本。
贡献
查看贡献指南了解如何向SWXMLHash做出贡献。
许可
SWXMLHash遵循MIT许可协议发布。详情请见许可。