---
title: 'XML 處理 - SAX、DOM、PULL'
disqus: kyleAlien
---
XML 處理 - SAX、DOM、PULL
===
## Overview of Content
如有引用參考請詳註出處,感謝 :cat:
[TOC]
## XML 概述
XML(`eXtensible Markup Language`)可拓展標示語言,又稱為可拓展標記語言,**它與 HTML 一樣都是「通用標記語言」**(`SGML`, `Standard Generalized Markup Language`)
:::info
* 其實這種「標記」語言並不算程式語言,因為它無法通過圖靈測試;**主要算是 Open stardard 的結構領域,用來做不同裝置之間的通訊**
> XML 不依賴任何的平台,它是普遍公開的通訊協定
```mermaid
graph LR;
iOS --> XML
Android --> XML --> MacOS
```
:::
* **XML 是 W3C 制定的,它的特性如下**:
1. 主要目的在「**簡單並清晰的 ++展示(傳遞)數據++**」,它並不在意數據的索引、排序、查找… 等等的效率,其目的是為了展示,所以相對的也比較耗費空間
> 好的展示數據是為了給人們觀看
2. **重新定義 SGML** 的內部值、參數,只留下常用的功能,像是保留 **SGML 的結構化功能!**
* XML 的標記完全自由定義,不受約束(以下只有第一行為必須,其他標記可以用中文)
```xml=
<?xml version="1.0" encoding="UTF-8"?>
<學生資訊>
<名子>Kyle</名子>
<學號>456789</學號>
<性別>Man</性別>
</學生資訊>
```
* 對於**大小寫敏感**
* **標記成對出現**,每一行都需要有**結束標記的符號** `</>`
### XML 結構
* XML 擁有結構化的特性,開發時常見的 `pom.xml` 檔案就是 xml 結構… 範例如下
```xml=
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>JavaEEHelloWorld</artifactId>
<version>1.0-SNAPSHOT</version>
<name>JavaEEHelloWorld</name>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<junit.version>5.9.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
</project>
```
### 解析 XML 數據的方式
* 解析 XML 數據的方式有不同種,每種都有其特性、優缺點、解析 XML 的思考方式,像是 `SAX`、`DOM`、`PULL`
* 語法分析器一般來說會有兩種分析想法,然而 **想法並沒有對錯(哪個更好)**,只有在正確的情況下使用對的方式才是最好的
* 基於「**物件**」的界面,DOM 就是基於物件去開發
* 基於「**事件**」的界面,SAX 就是基於事件去開發
> ![image](https://hackmd.io/_uploads/SJLWk86w6.png)
## SAX 解析
**SAX 全名為 `Simple API for XML`**,它是指一套 API,同時它也是一個開源套件
> 最早由 Dacid Megginson 使用 Java 語言開發
:::danger
* **SAX 的不同之處**
它不同於其他大多數 XML 標準,**SAX 沒有語言開發商必須遵守的標準 SAX 參考版本**,因此 SAX 的不同實現可能採用區別很大的界面
:::
### SAX 原理 - 事件驅動
* SAX 軟體設計是「**事件驅動型**」:
簡單來說就是,它會邊依照順序讀取 XML 並 即時的反應給使用 SAX 的程序員
```mermaid
graph LR
程序員 --> |1. 使用 SAX| 解析_XML
解析_XML --> |解析| 數據 -.-> |2. 即時透過 XML 界面反應| 程序員
```
:::success
* SAX 是透過標準的解析 XML 的界面,即時反饋給使用者,這個解析 XML 的界面是標準界面(`Standard Interface`),不會改變
* SAX 也不會在記憶體中幫我們建立物件
:::
* 當掃描到文檔、元素的開始與結束都會透過 XML 的標準驅動 API 反饋給使用者,而 **SAX 大多數都有實現 5 種類型的事件**
> SAX 使用的事件驅動與用戶操作無關,事件只會與 XML 中的元素有關(元素驅動事件)
1. **文檔**(`Document`)的開始、結束
2. 每次觸發 XML 新的 **元素開始、結束**(標籤)
3. **MetaData** 通常有單獨的事件處理
4. 當處理文檔中的 **合法元素** `DTD`、`Schema`… 時,產生對應的事件
> DTD(`Document Type Definition`)是一種用來定義 XML 文件結構和合法元素的規範… 像是 `CDATA`、`ID`、`ID`、`IDREF`、`IDREFS`、`NMTOKEN`、`NMTOKENS`、`ENTITY`、`ENTITIES`、`NOTATION`、`DOCTYPE`
以下範例說明著
* bookstore 元素是一個包含至少一個 book 元素的元素
* book 元素包含 `title`、`author` 和 `price` 元素
* `title`、`author` 和 `price` 元素都包含字符數據(`PCDATA`)
```xml=
<!-- XML 文件(example.xml) -->
<!DOCTYPE bookstore [
<!ELEMENT bookstore (book+)>
<!ELEMENT book (title, author, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>
<bookstore>
<book>
<title>Introduction to XML</title>
<author>John Doe</author>
<price>29.95</price>
</book>
<book>
<title>Java Programming</title>
<author>Jane Smith</author>
<price>39.99</price>
</book>
</bookstore>
```
5. 每當產生錯誤事件時,會透過界面通知開發者
### SAX - XML 解析界面、類
> `org.xml.sax` 包 是 JavaSE 使用 SAX 的方式來解析 XML
* SAX 使用的常見 **XML 解析 ++界面++** 如下表
| XML 解析界面 | 概述 | 補充 |
| - | - | - |
| `ContentHandler` | **文檔本身** 相關的事件(像是開始、結束) | 其中也包括元素(`Element`)監聽 |
| `EntityResolver` | **實體關聯** 的事件 | 較少使用 |
| `DTDHandler` | XML 中的 **標準事件** | 然而它定義的事件不夠完整,要完整 DTD 事件的話,可以使用 `DeclHandler` 界面 |
| `ErrorHandler` | **錯誤事件** | 定義三種級別 ^1.^ warning(非 XML 規範)、^2.^ error(XML 定義的錯誤)、^3.^ fatalError(致命錯誤) |
大多數時候都會使用 `DefaultHandler` 類(有大部分的界面)
> ![image](https://hackmd.io/_uploads/H1IyfPpw6.png)
* SAX 使用的常見 **XML 解析 ++類++** 如下表
| XML 解析類 | 概述 | 補充 |
| - | - | - |
| `XMLReader` | 該類可以用 `parse` 方法,來啟動 XML 語法分析器,它可以接受 ^1.^ **文件名**、^2.^ **URL**、^3.^ **InputSource** | 我們可以以設定 XML 解析監聽器… |
| | `setFeature`(Boolean)用來控制語法分析器的工作 | |
| | `setProperty`(物件) 用來控制語法分析器的工作 | |
| `XMLReaderFactory` | 用來創建 XML 語法分析器物件 | 可創建 ^1.^ **系統預設**、^2.^ **指定類型**(透過設定類路徑) |
| `InputSource` | 控制 SAX 如何讀取文件 | |
| `SAXException` | SAX 定義的大多數方法都會拋出 `SAXException` | |
### SAX 解析 XML 範例
* 目標 XML 如下
```xml=
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE bookstore [
<!ELEMENT bookstore (book+)>
<!ELEMENT book (title, author, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>
<bookstore>
<book>
<title>Introduction to XML</title>
<author>John Doe</author>
<price>29.95</price>
</book>
<book>
<title>Java Programming</title>
<author>Jane Smith</author>
<price>39.99</price>
</book>
</bookstore>
```
* 使用 SAX API 來解析 XML 數據,並保存在記憶體中
```kotlin=
data class BookInformation(var bookTitle: String = "",
var authorName: String = "",
var bookPrice: Float = -1F) {
companion object {
const val XML_KEY_BOOK = "book"
const val XML_KEY_TITLE = "title"
const val XML_KEY_AUTHOR = "author"
const val XML_KEY_PRICE = "price"
}
}
class BookXmlSaxHandler: DefaultHandler() {
lateinit var bookList: MutableList<BookInformation>
var curBookInfo: BookInformation? = null
lateinit var curTag: String
override fun startDocument() {
bookList = mutableListOf()
println("---- Document start ----")
}
override fun startElement(
uri: String?, // 名稱空間
localName: String?, // 本地名,不帶前綴
qName: String?, // 帶前綴的限定名
attributes: Attributes?
) {
if (qName == null || attributes == null) {
return
}
when(qName) {
BookInformation.XML_KEY_BOOK -> {
println("create book instance")
curBookInfo = BookInformation()
}
BookInformation.XML_KEY_TITLE,
BookInformation.XML_KEY_AUTHOR,
BookInformation.XML_KEY_PRICE -> {
curTag = qName
}
}
}
override fun characters(ch: CharArray?, start: Int, length: Int) {
if (ch == null) {
return
}
val data = String(ch, start, length)
when(curTag) {
BookInformation.XML_KEY_TITLE -> {
curBookInfo!!.bookTitle = data
}
BookInformation.XML_KEY_AUTHOR -> {
curBookInfo!!.authorName = data
}
BookInformation.XML_KEY_PRICE -> {
curBookInfo!!.bookPrice = data.toFloat()
}
}
}
override fun endElement(uri: String?, localName: String?, qName: String?) {
if (BookInformation.XML_KEY_BOOK == qName) {
if (curBookInfo != null) {
bookList.add(curBookInfo!!)
}
curBookInfo = null
println("create book set to null")
}
}
override fun endDocument() {
println("---- Document end ----")
bookList.forEach {
println(it)
}
}
}
```
* 測試使用 SAX API 解析 XML:
```kotlin=
fun main() {
val xmlFile = File("/home/alien/AndroidStudioProjects/Network/app/src/main/res/xml/books.xml")
val bookXmlHandler = BookXmlSaxHandler()
// 創建 XMLReader
val xmlReader: XMLReader = XMLReaderFactory.createXMLReader().apply {
// 設置其中的 content Handler
contentHandler = bookXmlHandler
}
val fileInputStream = FileInputStream(xmlFile)
try {
xmlReader.parse(InputSource(fileInputStream))
} finally {
fileInputStream.close()
}
}
```
> ![image](https://hackmd.io/_uploads/BJjZi5pva.png)
## DOM 解析
DOM(`Document Object Model`)提供了一個方便的編程界面,因此可以被視為一種官方的W3C標準
W3C(`World Wide Web Consortium`)制定了DOM的標準,並且 **DOM 在瀏覽器中被廣泛使用**,**它允許 JavaScript 通過瀏覽器直接操作 HTML 和 XML 文檔**
### DOM 原理 - JavaScript
* DOM 會將 XML 完全解析完成後,在記憶體中建立一個 XML 副本(**又稱之為物件樹**),並且該副本中的所有元素皆以「物件的形式」儲存
```mermaid
graph LR
程序員 --> |1. 使用 DOM| 解析_XML
解析_XML --> |解析完成| 記憶體
subgraph 記憶體
XML_對應的物件樹
end
程序員 --> |2. 取物件| XML_對應的物件樹
```
:::warning
* DOM 會佔用,記憶體空間(建立 XML 對應的物件樹);但相對的… DOM 會比 SAX 還方便使用
:::
* **DOM & JavaScript 的關係**
早期人們會把 DOM 視為是一種讓 JavaScript 在遊覽器間可移植的方法!但其實 DOM 的應用是遠遠超出這個範圍的
* DOM 技術使的用戶界面可以「**動態變化**」,大大增加了交互性
> 像是 JavaScript 可以透過它動態操作 HTML 的元素的顯示、隱藏
* JavaScript 就是利用 DOM 來取得 HTML 文件的操縱入口,並獲得 HTML 對應的物件
### DOM 規範結構
* 根據 W3C DOM 規範,DOM 解析 HTML、XML 這兩類標記語言的界面… DOM 會將整個文件映射為一個由「層次節點」組成的物件
* **Node 節點**
根據 DOM 規範,HTML 文檔中的每一個成份都是節點;有分為「文檔節點」、「元素節點」、「屬性節點」、「文本節點」、「註釋節點」
> 除了 文檔節點 之外,所有節點都有父節點
```xml=
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
</head>
<body>
<form method="post"
enctype="multipart/form-data"
action="upload.handle">
<label>
<input type="file" name="upload" value="upload"/>
</label>
<button>Upload</button>
</form>
</body>
</html>
```
```mermaid
graph TB;
doc(HTML 文檔節點) --> html(< html > 元素節點)
html --> header(< header > 元素節點)
header --> titlew(< title > 元素節點)
titlew --> titleContext(Title 文本節點)
html --> body(< body > 元素節點)
body --> form(< form > 元素節點)
form --> formContext1(method 文本節點)
form --> formContext2(enctype 文本節點)
form --> formContext3(action 文本節點)
form --> label(< label > 元素節點)
label --> input(< input > 元素節點)
form --> button(< button > 元素節點)
```
* **DOM API 的分級**(共有 3 個分級)
1. **1 級 DOM**:
**由 `DOM 核心` 與 `DOM HTML` 兩個模塊組層**
* DOM 核心能映射以 XML 為基礎的文檔結構為物件
* DOM HTML(其實就是拓展核心)則通過添加 HTML 專用的物件與函數對 DOM 核心進行拓展
2. **2 級 DOM**:
鑑於 1 級 DOM 僅以映射文檔結構到記憶體中(物件樹),2 級 DOM 通過對於源有的 DOM 拓展,**針對 DOM 界面增加**
* DOM 視圖:描述跟蹤一個文檔的各種視圖(使用 CSS 樣式設計文檔前後的差異)
* DOM 事件:對於用戶界面事件的反應(像是滑鼠點擊)
* DOM 樣式:基於 CSS 樣式的反應
* DOM 遍歷:遍歷、操作文檔物件
* 支持 XML 的命名空間
:::info
* **XML 的命名空間**?
在 XML 中,命名空間(`Namespace`)是一種用來「區分元素、屬性名稱的機制」,以「**避免名稱衝突**」的問題
**命名空間在 XML 中通常以一個 URI**(`Uniform Resource Identifier`)或 URL 來標識
> 實際上這個 URL 並不要求是一個可訪問的網址
```xml=
<!-- 定義 books 的命名空間-->
<root xmlns:books="http://example.com/books">
<!-- 使用命名空間 + 標記 -->
<books:book>
<books:title>Introduction to XML</books:title>
<books:author>John Doe</books:author>
<books:price>29.95</books:price>
</books:book>
<!-- 無命名空間,這個 book 跟上面的 books:book 是不同標記 -->
<book>
<title>The Great Novel</books:title>
<author>Jane Smith</books:author>
<price>19.99</books:price>
</book>
</root>
```
:::
3. **3 級 DOM**:
對上一級 DOM 拓展,引入「統一載入」、「保存文件」和「文檔驗證方法」
DOM 核心再次拓展,支持 XML 1.0 的所有設計,像是 `XML Infoset`、`XPath`、`XML Base`… 等等
### DOM 解析 XML 範例
* 目標 XML 如下
```xml=
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE bookstore [
<!ELEMENT bookstore (book+)>
<!ELEMENT book (title, author, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>
<bookstore>
<book>
<title>Introduction to XML</title>
<author>John Doe</author>
<price>29.95</price>
</book>
<book>
<title>Java Programming</title>
<author>Jane Smith</author>
<price>39.99</price>
</book>
</bookstore>
```
* 使用 Javax 提供的 DOM API 來解析 XML 數據,使用 DOM API 的特點在於… 它會一次幫你處理好所有的物件映射關係(以 Node 的方式,儲存在記憶體中)
這樣我們在使用上就不用特別關心 XML 資料流的處理
```kotlin=
data class BookInformation(var bookTitle: String = "",
var authorName: String = "",
var bookPrice: Float = -1F) {
companion object {
const val XML_KEY_BOOK = "book"
const val XML_KEY_TITLE = "title"
const val XML_KEY_AUTHOR = "author"
const val XML_KEY_PRICE = "price"
}
}
class DomXml {
fun parseByDom(inputStream: InputStream) {
val list = mutableListOf<BookInformation>()
val docBuilder = DocumentBuilderFactory.newInstance().run {
newDocumentBuilder()
}
val doc = docBuilder.parse(inputStream)
val bookList = doc.documentElement.getElementsByTagName(BookInformation.XML_KEY_BOOK)
for (i in 0 until bookList.length) {
val element = bookList.item(i) as Element
val bookInformation = BookInformation()
val bookDetail = element.childNodes
for (j in 0 until bookDetail.length) {
val bookItem = bookDetail.item(j)
when(bookItem.nodeName) {
BookInformation.XML_KEY_TITLE -> {
bookInformation.bookTitle = bookItem.textContent
}
BookInformation.XML_KEY_AUTHOR -> {
bookInformation.authorName = bookItem.textContent
}
BookInformation.XML_KEY_PRICE -> {
bookInformation.bookPrice = bookItem.textContent.toFloat()
}
}
}
list.add(bookInformation)
}
list.forEach {
println(it)
}
}
}
```
* 測試使用 DOM API 解析 XML:
```kotlin=
fun main() {
val xmlFile = File("/home/alien/AndroidStudioProjects/Network/app/src/main/res/xml/books.xml")
val inputStream = FileInputStream(xmlFile)
try {
DomXml().parseByDom(inputStream)
} finally {
inputStream.close()
}
}
```
> ![image](https://hackmd.io/_uploads/SywVchTvT.png)
### DOM 創建 XML
* DOM 與 SAX 的令一個不同點在於,它也有提供對應創建(也可以覆蓋)XML 的對應 API;以下我們修改 DOM Parse XML 的範例
1. 首先我們先 Parse XML 得到物件樹,保存在記憶體 `List<BookInformation>` 內,並返回給 DOM 使用者
:::info
* 這裡返回的並不是 DOM 在記憶體內建構的 Node 節點,而是應用 DOM Parse 後,自己建立的物件
:::
```kotlin=
class DomXml {
private lateinit var curDocument: Document
fun parseByDom(inputStream: InputStream): List<BookInformation> {
val list = mutableListOf<BookInformation>()
val docBuilder = DocumentBuilderFactory.newInstance().run {
newDocumentBuilder()
}
val doc = docBuilder.parse(inputStream).apply {
curDocument = this
}
val bookList = doc.documentElement.getElementsByTagName(BookInformation.XML_KEY_BOOK)
for (i in 0 until bookList.length) {
val element = bookList.item(i) as Element
val bookInformation = BookInformation()
val bookDetail = element.childNodes
for (j in 0 until bookDetail.length) {
val bookItem = bookDetail.item(j)
when(bookItem.nodeName) {
BookInformation.XML_KEY_TITLE -> {
bookInformation.bookTitle = bookItem.textContent
}
BookInformation.XML_KEY_AUTHOR -> {
bookInformation.authorName = bookItem.textContent
}
BookInformation.XML_KEY_PRICE -> {
bookInformation.bookPrice = bookItem.textContent.toFloat()
}
}
}
list.add(bookInformation)
}
list.forEach {
println(it)
}
return list
}
}
```
2. 再來撰寫一個透過 DOM 提供的 API,來將改變後的數據寫入到新 XML 中…
將使用者設定的新數據,寫入到 DOM 的 Node 節點之中
> 透過 `element`#`getElementsByTagName` 取得節點,並修改節點數據
```kotlin=
class DomXml {
fun updateXmlPrice(book: BookInformation, outputStream: FileOutputStream) {
val bookList = curDocument.documentElement.getElementsByTagName(BookInformation.XML_KEY_BOOK)
// 找到要修改的書籍
for (i in 0 until bookList.length) {
val element = bookList.item(i) as Element
val title = element.getElementsByTagName(BookInformation.XML_KEY_TITLE).item(0).textContent
if (book.bookTitle != title) {
continue
}
// 取得 price 節點
val prizeNode = element.getElementsByTagName(BookInformation.XML_KEY_PRICE).item(0)
// 修改價格
prizeNode.textContent = book.bookPrice.toString()
// 將修改後的 XML 寫回文件
val transformerFactory = TransformerFactory.newInstance()
transformerFactory.newTransformer().apply {
// 設定縮排
setOutputProperty(OutputKeys.INDENT, "yes")
val streamResult = StreamResult(outputStream)
// 文件節點
val domSource = DOMSource(curDocument)
// 將修改後的物件,寫入目標文件
transform(domSource, streamResult)
}
println("XML updated successfully.")
break
}
}
}
```
* 使用 DOM 寫 XML 範例
```kotlin=
fun main() {
val xmlFile = File("/home/alien/AndroidStudioProjects/Network/app/src/main/res/xml/books.xml")
val xmlRefactorFile = File("/home/alien/AndroidStudioProjects/Network/app/src/main/res/xml/books_refactor.xml")
val inputStream = FileInputStream(xmlFile)
val outputStream = FileOutputStream(xmlRefactorFile)
try {
val domXml = DomXml()
val list = domXml.parseByDom(inputStream).apply {
this[0].bookPrice = 100.876f
}
domXml.updateXmlPrice(list[0], outputStream)
} finally {
inputStream.close()
}
}
```
> ![image](https://hackmd.io/_uploads/B1xP7FRPa.png)
## PULL 解析
如果是使用 Android 平台開發,除了 SAX、DOM 之外… Android 平台原生還提供了一個內置的 PULL 解析器!同樣可以用來解析 XML 文件
### PULL 原理
* PULL 採用與 SAX 類似的解析方式
**它也是採用「事件驅動」的方式解**析 XML,當遇到元素時會產生事件並驅動,讓使用者透過界面接收到事件
### PULL 解析 XML
* 目標 XML 如下(需要將檔案放置在 `assets` 目錄下,之後會利用 Android Context 取得目標檔案)
```xml=
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE bookstore [
<!ELEMENT bookstore (book+)>
<!ELEMENT book (title, author, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>
<bookstore>
<book>
<title>Introduction to XML</title>
<author>John Doe</author>
<price>29.95</price>
</book>
<book>
<title>Java Programming</title>
<author>Jane Smith</author>
<price>39.99</price>
</book>
</bookstore>
```
> ![image](https://hackmd.io/_uploads/SJTtJ9RP6.png)
* 以下使用 Android 系統內提供的 XmlPullParser 來解析 XML 檔案
```kotlin=
class PullXml {
fun parseByAndroidPull(inputStream: InputStream): List<BookInformation> {
val list = mutableListOf<BookInformation>()
var curBookInformation: BookInformation? = null
val xmlPullParser: XmlPullParser = Xml.newPullParser().apply {
// 設定 input stream 以及解碼方式
setInput(inputStream, Charsets.UTF_8.name())
}
var event = xmlPullParser.eventType
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
when(xmlPullParser.name) {
BookInformation.XML_KEY_BOOK -> {
curBookInformation = BookInformation()
}
BookInformation.XML_KEY_TITLE -> {
curBookInformation!!.bookTitle = xmlPullParser.nextText()
}
BookInformation.XML_KEY_AUTHOR -> {
curBookInformation!!.authorName = xmlPullParser.nextText()
}
BookInformation.XML_KEY_PRICE -> {
curBookInformation!!.bookPrice = xmlPullParser.nextText().toFloat()
}
}
} else if (event == XmlPullParser.END_TAG) {
if (curBookInformation != null) {
list.add(curBookInformation)
curBookInformation = null
}
}
event = xmlPullParser.next()
}
return list
}
}
```
使用 Android 的機器測試(整合測試)才能測試 Pull 的 Parse
:::warning
* Android XmlPullParser 服務必須運行在 Android Runtime 中
> ![image](https://hackmd.io/_uploads/H1-Xx5Rva.png)
:::
```kotlin=
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun pullWithAndroidPull() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
val inputStream = appContext.resources.assets.open("books.xml")
try {
val pullXml = PullXml()
val list = pullXml.parseByAndroidPull(inputStream)
list.forEach {
println("---- $it")
}
} finally {
inputStream.close()
}
}
}
```
> ![image](https://hackmd.io/_uploads/HyvJxqAvp.png)
### PULL 創建 XML
* Pull 雖然同 SAX 一樣不會在記憶體中建構物件樹,不過我們仍可以自己透過在記憶體中建立並輸出到目標檔案中(以下方法同樣也可以使用在 SAX)
```kotlin=
fun createByAndroidPull(outputStream: OutputStream, list: List<BookInformation>) {
val xmlNameSpace = null
val encodeType = Charsets.UTF_8.name()
val xmlSerializer = Xml.newSerializer().apply {
setOutput(outputStream, encodeType)
setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
}
xmlSerializer.startDocument(encodeType, true)
xmlSerializer.startTag(xmlNameSpace, "bookstore")
list.forEach {
xmlSerializer.startTag(xmlNameSpace, BookInformation.XML_KEY_BOOK)
xmlSerializer.startTag(xmlNameSpace, BookInformation.XML_KEY_TITLE)
xmlSerializer.text(it.bookTitle)
xmlSerializer.endTag(xmlNameSpace, BookInformation.XML_KEY_TITLE)
// Author
xmlSerializer.startTag(xmlNameSpace, BookInformation.XML_KEY_AUTHOR)
xmlSerializer.text(it.authorName)
xmlSerializer.endTag(xmlNameSpace, BookInformation.XML_KEY_AUTHOR)
// Price
xmlSerializer.startTag(xmlNameSpace, BookInformation.XML_KEY_PRICE)
xmlSerializer.text(it.bookPrice.toString())
xmlSerializer.endTag(xmlNameSpace, BookInformation.XML_KEY_PRICE)
xmlSerializer.endTag(xmlNameSpace, BookInformation.XML_KEY_BOOK)
}
xmlSerializer.endTag(xmlNameSpace, "bookstore")
xmlSerializer.endDocument()
}
```
:::info
* 如果是建立 attribute 的話,那數值會被寫在 tag 中
```kotlin=
xmlSerializer.attribute(xmlNameSpace, BookInformation.XML_KEY_AUTHOR, it.authorName)
```
建立出來的數據就會寫在 tag 中(如下)
```xml=
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<bookstore>
<book title="Introduction to XML"
author="Kyle"
price="29.95" />
<book title="Java Programming"
author="Jane Smith"
price="39.99" />
</bookstore>
```
:::
測試程式
> 以下測試運行在 Android App 中(非整合測試)
```kotlin=
fun testAndroidPull() {
val appContext = this
val inputStream = appContext.resources.assets.open("books.xml")
// 開啟或創建文件在內部存儲空間
val fileOutputStream: FileOutputStream = appContext.openFileOutput("hello.xml", Context.MODE_PRIVATE)
try {
val pullXml = PullXml()
val list = pullXml.parseByAndroidPull(inputStream).apply {
// 修改數據
this[0].authorName = "Kyle"
}
pullXml.createByAndroidPull(fileOutputStream, list)
} finally {
inputStream.close()
fileOutputStream.close()
}
}
```
> ![image](https://hackmd.io/_uploads/HyfOF50vT.png)
## 其他
### SAX vs DOM
* SAX、DOM 兩者都是對於解析 XML 的方案,不過特點、解決思路都不盡相同,兩者的比較表如下…
| \ | SAX | DOM |
| - | - | - |
| XML 物件建立 | 手動分析、建立 | 自動分析、建立 |
| XML 的限制 | 可以讀取不限大小 XML 檔 | 由於會在內存中自動幫我們建立物件樹,所以 XML 大小不能太大 |
| XML 讀取 | 以順序方式讀取(不能回頭) | 建立完物件樹後,可隨意讀取 |
| XML 修改 | 只能讀取 XML,不能修改! | 可以透過對物件樹的修改,來改動到 XML 檔案 |
| 技術的特點 | 較為自由(有很多地方可以設定),但相對的也較為麻煩 | 易於操作、開發 |
## Appendix & FAQ
:::info
:::
###### tags: `網路開發`