Fuzi 3.1.3

Fuzi 3.1.3

测试已测试
Lang语言 SwiftSwift
许可证 MIT
Released最后发布2020年12月
SPM支持 SPM

Ce Zheng维护。



Fuzi 3.1.3

Fuzi (斧子)

Build Status CocoaPods Compatible License Carthage Compatible Platform Twitter

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 提供
  • 支持 XPathCSS 查询
  • 自动转换日期和数字值
  • 正确处理元素和属性的 XML 命名空间,符合常识
  • 能够从 StringNSData[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就应该能自动解析当前版本。

手动

  1. 将Fuzi目录中的所有*.swift文件添加到您的项目中。
  2. 在您的Xcode项目中构建设置
    1. 找到搜索路径,将$(SDKROOT)/usr/include/libxml2添加到头文件搜索路径中。
    2. 找到链接,将-lxml2添加到其他链接器标志中。

使用Carthage

在项目的根目录中创建一个CartfileCartfile.private,并添加以下行

github "cezheng/Fuzi" ~> 1.0.0

运行以下命令

$ carthage update

然后在Xcode中执行以下操作

  1. 将Carthage构建的Fuzi.framework拖到目标的通用 -> 嵌入的二进制文件中。
  2. 构建设置中,找到搜索路径,将$(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

HTMLDocumentXMLDocument的子类。

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 示例

Fuzi 示例

访问子节点

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 的 SequenceTypeIndexable

// 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