MiniDOM 1.0.3

MiniDOM 1.0.3

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

Paul Calnan 维护。



MiniDOM 1.0.3

  • 作者
  • Paul Calnan

MiniDOM: 最简 XML DOM for Swift

Platform License

CocoaPods Compatible Carthage Compatible Swift Package Manager Compatible

codecov.io Documentation Status

简介

MiniDOM 是 Document Object Model 接口的简化实现。它的目的是比完整的 DOM 更简单,但功能足够强大,足以在大多数应用程序中使用。

MiniDOM 已完全文档化单元测试。它可用于 iOS、macOS、watchOS 和 tvOS。库在 MIT 许可下发布。

要解析 XML 文档,只需创建一个 Parser 对象并调用 parse()

import Foundation
import MiniDOM

func parseXML(url: URL) -> Document? {
    let parser = Parser(contentsOf: url)
    let result = parser?.parse()
    return result?.document
}

结果结构是一个实现 Node 协议的对象树:DocumentElementTextProcessingInstructionCommentCDATASection。提供了类似于 DOM 规范中的访问器方法和属性。DOM 树可以使用搜索方法、路径评估方法或使用访问者设计模式进行遍历。以下将详细讨论这些方法。

安装

MiniDOM 支持 CocoaPods、Carthage 和 Swift Package Manager 的安装。

Cocoapods

请将以下内容添加到您的 Podfile

pod 'MiniDOM'

Carthage

将以下内容添加到您的 Cartfile

github "MiniDOM/MiniDOM"

Swift Package Manager

在你的 Package.swift 文件中添加以下依赖项

.package(url: "https://github.com/MiniDOM/MiniDOM/", from: "1.0.0")

依赖

MiniDOM 没有第三方依赖。它只使用 Foundation 类,包括 XMLParser。单元测试使用 XCUnit 编写。

路径评估

MiniDOM 提供了一种通过路径遍历文档的机制。调用 Document.evaluate(path:),传递一个表示元素节点名称的字符串数组 (Node.nodeName)。例如,考虑以下文档

<a id="1">
  <b id="2">
    <z id="3"/>
  </b>
  <c id="4">
    <z id="5"/>
  </c>
  <b id="6"/>
  <z id="7"/>
  <b id="8">
    <z id="9"/>
  </b>
</a>

评估路径 ["a", "b", "z"](通过调用 document.evaluate(path: ["a", "b", "z"]))将返回一个包含两个 Element 对象的数组,分别表示 ID 为 39<z> 元素。

访问者设计模式

在整个MiniDOM库中,使用访问者设计模式来实现涉及遍历DOM树的算法。它提供了一种方便的机制,将算法与其操作的对象结构分开。它允许向DOM结构添加操作而无需修改结构本身。

提供了一个用于启动遍历的Visitor对象,它是通过调用Node.accept(_:)实现的。在调用其子节点的Node.accept(_:)之前,Node对象会调用适当的Visitor对象方法,从而执行递归遍历。

Visitor协议定义了与DOM中的每种Node类型对应的方法。实现Visitor协议的类型不需要处理实际的遍历;其方法是由DOM类提供的遍历算法调用的。

关于访问者的简单示例,请参阅Search.swift中的ElementSearch类。关于访问者的更复杂示例,请参阅Formatter.swift中的PrettyPrinter类。

示例

以下内容来自项目根目录下的MiniDOM.playground。您可以随时打开它并自行实验。

解析文档

在沙盒的资源部分有一个XML文档。它包含EFF更新RSS源的一个快照。我们将从解析文档开始。

let url = Bundle.main.url(forResource: "eff-updates", withExtension: "rss")!
let parser = Parser(contentsOf: url)
let document = parser?.parse().document

遍历文档

文档的结构大致如下

<rss>
    <channel>
        <title>...</title>
        <link>...</link>
        <description>...</description>
        <item>
            <title>...</title>
            <link>...</link>
            <description>...</description>
        </item>
        <item>...</item>
        ...
    </channel>
</rss>

让我们首先获取文档元素或根节点。

let rss = document?.documentElement
rss?.nodeName

结果

"rss"

<rss>元素应该有一个子元素:一个<channel>元素。

let channel = rss?.firstChildElement
channel?.nodeName

结果

"channel"

<channel>元素应该有50个<item>子元素。

let items = channel?.childElements(withName: "item")
items?.count

结果

50

<item>元素应该有一个子元素:<title>

let itemTitles = items?.flatMap { itemElement -> String? in
    let titleElement = itemElement.childElements(withName: "title").first
    return titleElement?.textValue
}
itemTitles

结果

0 "Stupid Patent of the Month: Storing Files in Folders"
1 "NAFTA Renegotiation Will Resurrect Failed TPP Proposals"
2 "New Report Aims to Help Criminal Defense Attorneys Challenge Secretive Government Hacking"
3 "The Most Powerful Single Click in Your Facebook Privacy Settings"
4 "Repealing Broadband Privacy Rules, Congress Sides with the Cable and Telephone Industry"
...

还有<link>元素,它是<channel>元素的子元素,也是每个<item>元素的子元素。我们可以找到所有的这些元素。

let linkElementsFromDocument = document?.elements(withTagName: "link")
let linkURLsFromDocument = linkElementsFromDocument?.flatMap { $0.textValue }
linkURLsFromDocument

结果

0 "https://www.eff.org/rss/updates.xml"
1 "https://www.eff.org/deeplinks/2017/03/stupid-patent-month-storing-files-folders"
2 "https://www.eff.org/deeplinks/2017/03/nafta-renegotiation-will-resurrect-failed-tpp-proposals"
3 "https://www.eff.org/deeplinks/2017/03/eff-says-no-so-called-moral-rights-copyright-expansion"
4 "https://www.eff.org/deeplinks/2017/03/new-report-aims-help-criminal-defense-attorneys-challenge-secretive-government"
5 "https://www.eff.org/deeplinks/2017/03/most-powerful-single-click-your-facebook-privacy-settings"
...

路径评估

<channel>元素的子元素<item>每个都应该有一个<link>子元素。通过使用路径表达式,我们可以收集在<channel>元素下的所有<link>元素的文本子元素。

let linkTextNodesViaPath = document?.evaluate(path: ["rss", "channel", "item", "link", "#text"])
let linkURLsViaPath = linkTextNodesViaPath?.flatMap { $0.nodeValue }
linkURLsViaPath

结果

0 "https://www.eff.org/deeplinks/2017/03/stupid-patent-month-storing-files-folders"
1 "https://www.eff.org/deeplinks/2017/03/nafta-renegotiation-will-resurrect-failed-tpp-proposals"
2 "https://www.eff.org/deeplinks/2017/03/eff-says-no-so-called-moral-rights-copyright-expansion"
3 "https://www.eff.org/deeplinks/2017/03/new-report-aims-help-criminal-defense-attorneys-challenge-secretive-government"
4 "https://www.eff.org/deeplinks/2017/03/most-powerful-single-click-your-facebook-privacy-settings"
...

访客

我们可以通过访客收集文档中的所有<title>元素。

class TitleCollector: Visitor {
    var titles: [String] = []

    public func beginVisit(_ element: Element) {
        if element.tagName == "title", let title = element.textValue {
            titles.append(title)
        }
    }
}

let titleCollector = TitleCollector()
document?.accept(titleCollector)
titleCollector.titles

结果

0 "Deeplinks"
1 "Stupid Patent of the Month: Storing Files in Folders"
2 "NAFTA Renegotiation Will Resurrect Failed TPP Proposals"
3 "New Report Aims to Help Criminal Defense Attorneys Challenge Secretive Government Hacking"
4 "The Most Powerful Single Click in Your Facebook Privacy Settings"
5 "Repealing Broadband Privacy Rules, Congress Sides with the Cable and Telephone Industry"

问题与贡献

报告您找到的问题。

欢迎提交拉取请求。请确保任何添加文档并进行了单元测试。我们致力于保持100%的文档和测试覆盖率。