軽くて、素早くて、 Swift の XML/HTML パーサー。 [ドキュメント]
Fuzi は Mattt Thompson氏の Ono(斧) に参照し Swift 言語で実装した XML/HTML パーサーである。
Fuzi は漢字の
斧子の中国語発音で、 意味はOno(斧)と同じ。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)
}libxml2での素早いXMLパース- XPath と CSS クエリ
- 自動的にデータを日付や数字に変換する
- XML ネイムスペース
StringやNSDataや[CChar]からXMLDocumentをロードする- 全面的なユニットテスト
- 100%ドキュメント
- Swift 言語のネーミングやコーディングルールに沿って、クラスやメソッドを再設計した
- 日付や数字変換のフォマットを指定できる
- いくつかのバグ修正
- より多くのHTML便利メソッド
- 全種類のXMLノード取得可能(テキストノードやコメントノードなども含め)
- より多くのCSSクエリ対応 (これから)
- iOS 8.0+ / Mac OS X 10.9+
- Xcode 8.0+
Swift 2.3は0.4.0をご利用ください。
Cocoapods で簡単に Fuzi をインストールできます。 下記のようにPodfileを編集してください:
platform :ios, '8.0'
use_frameworks!
target 'MyApp' do
pod 'Fuzi', '~> 1.0.0'
endそして、下記のコマンドを実行してください:
$ pod installFuziフォルダの*.swiftファイルをプロジェクトに追加してください。- Xcode プロジェクトの
Build Settingsで:Search PathsのHeader Search Pathsに$(SDKROOT)/usr/include/libxml2を追加してください。LinkingのOther Linker Flagsに-lxml2を追加してください。
プロダクトのディレクトリにCartfile か Cartfile.privateのファイルを作成し、下記の行を追加してください:
github "cezheng/Fuzi" ~> 1.0.0
そして、下記のコマンドを実行してください:
$ carthage update
最後に、下記のようにXcodeのtargetを設定してください:
- ビルドターゲットの
General->Embedded Binariesに、CarthageがビルドしたFuzi.frameworkを追加してください。 Build SettingsでSearch PathsのHeader Search Pathsに$(SDKROOT)/usr/include/libxml2を追加してください。
import Fuzi
let xml = "..."
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let document = try XMLDocument(string: html, encoding: NSUTF8StringEncoding)
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)")
}
}HTMLDocument は XMLDocument サブクラス。
import Fuzi
let html = "<html>...</html>"
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let doc = try HTMLDocument(string: html, encoding: NSUTF8StringEncoding)
// 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 = ...
// すべてのエレメント、テキストとコメント子要素を取得する
document.root?.childNodes(ofTypes: [.Element, .Text, .Comment])
##Onoからの移行?
下記2つのサンプルコードを見たら、`Ono`と`Fuzi`の違いをわかる。
[Onoサンプル](https://github.com/mattt/Ono/blob/master/Example/main.m)
[Fuziサンプル](FuziDemo/FuziDemo/main.swift)
###子要素を取得
**Ono**
```objc
[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
Objective-Cの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; //NSStringFuzi
if let result = doc.eval(xpath: xpath) {
result.boolValue //Bool
result.doubleValue //Double
result.stringValue //String
}Fuzi のオープンソースライセンスは MIT です。 詳しくはこちら LICENSE 。