--- 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: `網路開發`