Fuzi (斧子)
Fuzi 是一个快速且轻量级的 Swift XML/HTML 解析器,使得您的代码更加轻松。 [文档]
Fuzi 是基于 Mattt Thompson 的 Ono(斧) 的 Swift 端实现,使用了其大部分底层实现,并根据 Swift 的标准约定适度地重设计了类和接口,并包括了几个错误修复。
Fuzi(斧子) 的含义是“斧头”,以纪念 Ono(斧),而它又受到了 Nokogiri (锯) 的启发,其含义为“锯子”。
快速浏览
let xml = "..."
// or
// let xmlData = <some NSData or Data>
do {
let document = try XMLDocument(string: xml)
// or
// let document = try XMLDocument(data: xmlData)
if let root = document.root {
// Accessing all child nodes of root element
for element in root.children {
print("\(element.tag): \(element.attributes)")
}
// Getting child element by tag & accessing attributes
if let length = root.firstChild(tag:"Length", inNamespace: "dc") {
print(length["unit"]) // `unit` attribute
print(length.attributes) // all attributes
}
}
// XPath & CSS queries
for element in document.xpath("//element") {
print("\(element.tag): \(element.attributes)")
}
if let firstLink = document.firstChild(css: "a, link") {
print(firstLink["href"])
}
} catch let error {
print(error)
}
特性
继承自 Ono
- 极高的文档解析和遍历性能,由
libxml2
提供 - 支持 XPath 和 CSS 查询
- 自动转换日期和数字值
- 正确处理元素和属性的 XML 命名空间,符合常识
- 能够从
String
、NSData
或[CChar]
加载 HTML 和 XML 文档 - 全面的测试套件
- 完善的文档
Fuzi 中的改进
- 遵循 Swift 标准约定的简单、现代 API,没有了像
AnyObject!
这样的不必要的类型转换 - 可自定义日期和数字格式化程序
- 修复了一些错误
- 更多 HTML 文档的便捷方法
- 访问所有类型的 XML 节点(包括文本、注释等)
- 支持更多 CSS 选择器(待补充)
要求
- iOS 8.0+ / Mac OS X 10.9+
- Xcode 8.0+
对于 Swift 2.3,请使用 0.4.0 版本。
安装
有四种方法可以将 Fuzi 添加到您的项目中。
CocoaPods
使用您可以通过将 Fuzi
添加到您的 Podfile
中来使用 CocoaPods 安装 CocoaPods
platform :ios, '8.0'
use_frameworks!
target 'MyApp' do
pod 'Fuzi', '~> 1.0.0'
end
然后,运行以下命令
$ pod install
使用 Swift Package Manager
Swift包管理器现已成为Xcode 11(目前处于beta测试阶段)的内置功能。您可以通过选择文件 > Swift包 > 添加包依赖...
或在项目文件的Swift包标签中点击+
轻松地将Fuzi添加为依赖项。只需将https://github.com/cezheng/Fuzi
用作存储库,Xcode就应该能自动解析当前版本。
手动
- 将Fuzi目录中的所有
*.swift
文件添加到您的项目中。 - 在您的Xcode项目中
构建设置
- 找到
搜索路径
,将$(SDKROOT)/usr/include/libxml2
添加到头文件搜索路径
中。 - 找到
链接
,将-lxml2
添加到其他链接器标志
中。
- 找到
Carthage
使用在项目的根目录中创建一个Cartfile
或Cartfile.private
,并添加以下行
github "cezheng/Fuzi" ~> 1.0.0
运行以下命令
$ carthage update
然后在Xcode中执行以下操作
- 将Carthage构建的
Fuzi.framework
拖到目标的通用
->嵌入的二进制文件
中。 - 在
构建设置
中,找到搜索路径
,将$(SDKROOT)/usr/include/libxml2
添加到头文件搜索路径
中。
用法
XML
import Fuzi
let xml = "..."
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let document = try XMLDocument(string: html, encoding: String.Encoding.utf8)
if let root = document.root {
print(root.tag)
// define a prefix for a namespace
document.definePrefix("atom", defaultNamespace: "http://www.w3.org/2005/Atom")
// get first child element with given tag in namespace(optional)
print(root.firstChild(tag: "title", inNamespace: "atom"))
// iterate through all children
for element in root.children {
print("\(index) \(element.tag): \(element.attributes)")
}
}
// you can also use CSS selector against XMLDocument when you feels it makes sense
} catch let error as XMLError {
switch error {
case .noError: print("wth this should not appear")
case .parserFailure, .invalidData: print(error)
case .libXMLError(let code, let message):
print("libxml error code: \(code), message: \(message)")
}
}
HTML
HTMLDocument
是XMLDocument
的子类。
import Fuzi
let html = "<html>...</html>"
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let doc = try HTMLDocument(string: html, encoding: String.Encoding.utf8)
// CSS queries
if let elementById = doc.firstChild(css: "#id") {
print(elementById.stringValue)
}
for link in doc.css("a, link") {
print(link.rawXML)
print(link["href"])
}
// XPath queries
if let firstAnchor = doc.firstChild(xpath: "//body/a") {
print(firstAnchor["href"])
}
for script in doc.xpath("//head/script") {
print(script["src"])
}
// Evaluate XPath functions
if let result = doc.eval(xpath: "count(/*/a)") {
print("anchor count : \(result.doubleValue)")
}
// Convenient HTML methods
print(doc.title) // gets <title>'s innerHTML in <head>
print(doc.head) // gets <head> element
print(doc.body) // gets <body> element
} catch let error {
print(error)
}
我不在乎错误处理
import Fuzi
let xml = "..."
// Don't show me the errors, just don't crash
if let doc1 = try? XMLDocument(string: xml) {
//...
}
let html = "<html>...</html>"
// I'm sure this won't crash
let doc2 = try! HTMLDocument(string: html)
//...
想要访问文本节点
不仅限于文本节点,您可以指定想访问的节点类型。
let document = ...
// Get all child nodes that are Element nodes, Text nodes, or Comment nodes
document.root?.childNodes(ofTypes: [.Element, .Text, .Comment])
是否从 Ono 迁移而来?
通过查看示例程序是了解差异最快的方式。以下两个示例做了完全相同的事情。
访问子节点
Ono
[doc firstChildWithTag:tag inNamespace:namespace];
[doc firstChildWithXPath:xpath];
[doc firstChildWithXPath:css];
for (ONOXMLElement *element in parent.children) {
//...
}
[doc childrenWithTag:tag inNamespace:namespace];
Fuzi
doc.firstChild(tag: tag, inNamespace: namespace)
doc.firstChild(xpath: xpath)
doc.firstChild(css: css)
for element in parent.children {
//...
}
doc.children(tag: tag, inNamespace:namespace)
遍历查询结果
Ono
符合 NSFastEnumeration
。
// simply iterating through the results
// mark `__unused` to unused params `idx` and `stop`
[doc enumerateElementsWithXPath:xpath usingBlock:^(ONOXMLElement *element, __unused NSUInteger idx, __unused BOOL *stop) {
NSLog(@"%@", element);
}];
// stop the iteration at second element
[doc enumerateElementsWithXPath:XPath usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) {
*stop = (idx == 1);
}];
// getting element by index
ONOXMLDocument *nthElement = [(NSEnumerator*)[doc CSS:css] allObjects][n];
// total element count
NSUInteger count = [(NSEnumerator*)[document XPath:xpath] allObjects].count;
Fuzi
符合 Swift 的 SequenceType
和 Indexable
。
// simply iterating through the results
// no need to write the unused `idx` or `stop` params
for element in doc.xpath(xpath) {
print(element)
}
// stop the iteration at second element
for (index, element) in doc.xpath(xpath).enumerate() {
if idx == 1 {
break
}
}
// getting element by index
if let nthElement = doc.css(css)[n] {
//...
}
// total element count
let count = doc.xpath(xpath).count
评估 XPath 函数
Ono
ONOXPathFunctionResult *result = [doc functionResultByEvaluatingXPath:xpath];
result.boolValue; //BOOL
result.numericValue; //double
result.stringValue; //NSString
Fuzi
if let result = doc.eval(xpath: xpath) {
result.boolValue //Bool
result.doubleValue //Double
result.stringValue //String
}
授权
Fuzi
项目依据 MIT 许可发布。详细信息请见 LICENSE。