跳轉到內容

XML - 管理資料交換/解析 XML 檔案

來自 Wikibooks,開放世界的開放書籍



上一章 下一章
Cocoon XUL



學習目標

  • 理解解析 XML 檔案的概念
  • 使用不同的 API 處理 XML 檔案
  • 瞭解不同解析 XML 檔案方法之間的差異
  • 決定何時使用特定技術


在前面的章節中,我們學習瞭如何建立 XML 檔案 的詳細內容。這包括開發 XML 文件樣式表模式 以及它們的驗證。在本節中,我們將重點介紹解析 XML 檔案 的不同方法以及何時使用它們。

但首先,是時候回顧一下我們所學的關於解析的知識了。

解析 XML 檔案的過程

[編輯 | 編輯原始碼]

XML 格式的目標之一是透過包含對內容含義的詳細描述來增強純文字等原始資料格式。現在,為了能夠讀取 XML 檔案,我們使用一個解析器,它基本上透過一個稱為 API(應用程式程式設計介面)來公開文件的內容。換句話說,客戶端應用程式透過介面訪問 XML 文件的內容,而不是自行解釋 XML 程式碼!

簡單文字解析

[編輯 | 編輯原始碼]

從 XML 文件中提取資料的一種方法是簡單的文字解析 - 瀏覽文件中的所有字元並檢查所需的模式


<house>
<value><int>150,000</int></value>
</house>

假設我們對房子的價值感興趣。使用直接文字解析,我們將掃描檔案以查詢字元序列 <int> 並將其稱為起始模式。然後,我們將進一步掃描文件以查詢結束模式(即 </int></value>)。最後,我們將這兩個模式之間的文字字串宣告為周圍 <house>...</house> 標籤的價值。

為什麼不這樣工作

[編輯 | 編輯原始碼]

顯然,這種方法不適合從大型複雜的 XML 文件中提取資訊,因為我們必須確切地知道檔案的樣子以及所需資訊的位置。從更一般的角度來看,XML 檔案的結構和語義由文件的構成、其標籤和屬性決定 - 因此,我們需要一種能夠識別和理解這種結構並能夠指出其中的任何錯誤的裝置。此外,它必須透過介面提供文件的內容,以便其他應用程式可以輕鬆訪問它。這種裝置被稱為 XML 解析器。

解析器做什麼

[編輯 | 編輯原始碼]

幾乎所有需要處理 XML 文件的程式都使用 XML 解析器來提取儲存在 XML 文件中的資訊,以避免讀取和解釋原始 XML 資料時遇到的任何困難。解析器通常是一個類庫(例如一組 Java 類檔案),它讀取給定文件並檢查它是否符合 W3C 規範。然後,任何客戶端軟體都可以使用解析器 API 提供的介面方法來訪問解析器從 XML 檔案中檢索的資訊。

總而言之,解析器保護使用者免受處理 XML 的複雜細節的困擾,例如組裝分佈在多個 XML 檔案中的資訊、檢查格式良好的約束等等。

解析:一個例子

[編輯 | 編輯原始碼]

為了更清楚地說明解析 XML 檔案的真正含義,建立了以下示例,其中包含有關一些城市的資訊。它還跟蹤誰在度假,並使用當前最常見的解析方法演示解析過程。

示例:cities.xml

[編輯 | 編輯原始碼]
<?xml version="1.0" encoding="UTF-8" ?>
<cities>
<city vacation="Sam">
<cityName>Atlanta</cityName>
<cityCountry>USA</cityCountry> 
</city>
<city vacation="David">
<cityName>Sydney</cityName>
<cityCountry>Australia</cityCountry> 
</city>
<city vacation="Pune">
<cityName>Athens</cityName>
<cityCountry>Greece</cityCountry> 
</city>
</cities>

基於儲存在這個 XML 文件中的資訊,我們可以輕鬆地檢查誰在度假以及在哪裡。解析器將使用本章後面介紹的各種技術之一讀取檔案。

這個過程非常複雜,容易出現各種錯誤。幸運的是,我們永遠不必編寫程式碼,因為網上有大量免費的、功能齊全的解析器。我們所做的就是下載一個解析器類庫,並透過解析器軟體提供的介面訪問 XML 文件。隨著 Java 的更新版本釋出,大多數解析器甚至不需要下載。換句話說,我們使用類庫中包含的函式或方法來提取資訊。

基本上,解析器讀取 XML 文件,並嘗試識別檔案本身的結構,同時檢查錯誤。它只是檢查開始/結束標籤、屬性、名稱空間、字首等等。然後,客戶端軟體可以使用解析器軟體(即介面)提供的方

瞭解解析器功能的最佳方法是實際使用它們;因此,下一節將演示不同的解析方法。

解析器 API(應用程式程式設計介面)

[編輯 | 編輯原始碼]

目前市場上占主導地位的是兩種“傳統”方法:一種是基於事件的推模型,以 SAX (Simple API for XML) 為代表;另一種是基於樹的模型,使用 DOM (Document Object Model) 方法。

然而,現在正在興起一些新方法和技術,試圖克服這些傳統模型固有的缺陷——一種基於事件的拉模型和一種“遊標模型”,例如 VTD-XML,它允許我們像在基於樹的方法中一樣瀏覽 XML 文件,但更簡單、更容易使用。

SAX (Simple API for XML)

[編輯 | 編輯原始碼]

推模型,通常以 SAX (www.saxproject.org) 為例,是 XML 解析的“黃金標準”,因為它可能是迄今為止最完整、最準確的方法。SAX 類在 XML 文件讀取的輸入流和接收解析器提供的資料的客戶端軟體之間提供了一個介面。解析器瀏覽整個文件,並在每次識別 XML 結構時觸發事件(例如,它識別開始標籤並觸發事件——客戶端軟體收到通知並可以使用此資訊……或不使用)。

這種模型的優勢是我們不需要將整個 XML 文件儲存在記憶體中,因為我們一次只讀取一個資訊片段。如果您還記得 XML 結構是一組不同型別的節點(如元素節點)——使用 SAX 解析器解析文件意味著一次遍歷每個節點。這使得我們能夠以記憶體高效的方式讀取即使非常大的 XML 文件。但是,解析器只提供有關當前讀取的節點的資訊這一事實也意味著客戶端軟體的程式設計師負責將某些資訊儲存在單獨的資料結構中(例如,當前處理的節點的父節點或子節點)。此外,SAX 方法幾乎是只讀的,因為當我們沒有某種全域性檢視時,很難修改 XML 結構。

實際上,解析器控制著何時讀取。使用者只能等到某個事件發生,然後使用當前處理的節點中儲存的資訊。

示例:TGSAXParser.java

[編輯 | 編輯原始碼]

如前所述,完全理解解析過程的概念的最佳方法是實際使用它。在以下程式碼示例中,將顯示人們度假城市的名字和國家的資訊。SAX API 是 Xerces 解析器包的一部分,用於實現它((Xerces 2 首頁)


// import the basic SAX API classes
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;

public class TGSAXParser extends DefaultHandler
{
    public boolean onVacation = false;

    // what to do when a start-element event was triggered
    public void startElement(String uri, String name, String qName, Attributes atts)
    {
        // stores the string in the XML file          
        String vacationer = atts.getValue("vacation");
        String cityName = atts.getValue("cityName");
        String cityCountry = atts.getValue("cityCountry");

        // if the start tag is "city" set vacationer to true
        if (qName.equals("city") && (vacationer != null))
        {
            onVacation = true;
            System.out.print("\n" + vacationer + " is on vacation in ");
        }
        if (qName.equals("cityName") && onVacation)
            {                       
            }
        if (qName.equals("cityCountry") && onVacation)
        {                       
        }
    }

    /**This method is used to stop printing information once the element has
    *been read.  It will also reset the onVacation variable for the next
    *element.
    */
    public void endElement(String uri, String name, String qName)
    {
        //reset flag
        if (qName.equals("city"))
        {
            onVacation = false;
        }
    }

    /**This method is triggered to store and print the values between
    *the XML tags.  It will only print those values if onVacation == true.
    */
    public void characters(char[] ch, int start, int length)
    {
        if (onVacation)
        {
            for (int i = start; i < start + length; i++)
            System.out.print(ch[i]);
        }
    }

    public static void main(String[] args)
    {
        System.out.println("People on vacation in the following cities:");

        try
        {
            // create a SAX parser from the Xerces package
            XMLReader xml = XMLReaderFactory.createXMLReader();
            TGSAXParser handler = new TGSAXParser();
            xml.setContentHandler(handler);
            xml.setErrorHandler(handler);
            FileReader r = new FileReader("cities.xml");
            xml.parse(new InputSource(r));
        }
        catch (SAXException se)
        {
            System.out.println("XML Parsing Error: " + se);
        } 
        catch (IOException io) 
        {
            System.out.println("File I/O Error: " + io);
        }
    }
}

DefaultHandler:如前所述,SAX 完全是事件驅動的。因此,我們需要一個“監聽”來自輸入檔案(在本例中為cities.xml)的輸入流的處理程式。

SAX API 提供介面類,我們必須用我們自己的程式碼擴充套件它們來讀取我們自己的特定 XML 文件。為了將我們的程式碼包含在 SAX API 中,我們只需要用我們自己的類擴充套件 DefaultHandler 介面,並將內容處理程式設定為我們自定義的處理程式類(它包含三個方法:startElement、endElement 和 characters)

startElement()endElement() 方法:這些方法分別在 SAX 解析器找到開始標籤或結束標籤時被呼叫。SAX API 為這兩種方法提供了空白存根,我們必須用我們自己的程式碼填充它們。

在本例中,我們希望我們的程式在設定vacation 屬性時做一些事情,因此我們會在找到這樣的元素時將一個布林變數設定為 true,並透過列印開始和結束標籤之間的字元序列來處理該節點。character 方法會在觸發startElementendElement 事件時自動呼叫,但只會打印出字元字串,前提是onVacation 屬性已設定。

DOM (Document Object Model)

[編輯 | 編輯原始碼]

另一種流行的方法是基於樹的模型,以 DOM (Document Object Model,參見 W3C 建議) 為代表。這種方法的實際工作原理與 SAX 解析器類似,因為它透過瀏覽檔案並識別 XML 結構來從輸入流中讀取 XML 文件。

這一次,DOM 方法不是將文件的內容以一系列小片段的形式返回,而是將 XML 層次結構對映到一個 DOM 樹物件,該物件包含原始 XML 文件中的所有內容。從元素、註釋、文字資訊或處理指令到樹物件中的節點,從文件本身作為根節點開始,所有內容都儲存在樹物件中。

現在,由於我們所需的所有資訊都儲存在記憶體中,我們可以使用解析器軟體提供的方法來訪問資料,以讀取或修改樹中的物件。這便於隨機訪問 XML 文件的內容,並提供修改其中包含的資料甚至透過將 DOM 轉換回 XML 文件來建立新 XML 檔案的可能性。

但是,這種方法的主要缺點是它需要更多的記憶體,因此不適合使用大型 XML 檔案的情況。更重要的是,即使對於小型和簡單的問題,它也比簡單的 SAX 方法複雜得多。

示例:MyDOMParser.java

[編輯 | 編輯原始碼]

在以下程式碼示例中,使用基於樹的方法再次建立了一個包含度假人員的城市列表。

// import all necessary DOM API classes
import org.apache.xerces.parsers.*;
import org.apache.xerces.dom.*;
import org.w3c.dom.*;
public class MyDOMParser{
	public static void main(String[] args) {
		System.out.println("People on vacation in the following cities:");  
		try {
			// creates a DOM parser object
			DOMParser parser = new DOMParser();
			parser.parse("cities.xml"); 

			// stores the tree object in a variable
         		org.w3c.dom.Document doc  = parser.getDocument();

			// returns a list of all city elements in my city list
	 		NodeList list = doc.getElementsByTagName("city");

			// now, for every element in the city list, check if the
			// "vacation" attribute is set and if yes, print out the   
			// information about the vacationer.
			for(int i = 0, length = list.getLength(); i < length; i++){
				Element city  = (Element)list.item(i);
				Attr vacationer = city.getAttributeNode("vacation");
				if(vacationer!= null){
					String v = vacationer.getValue();
					System.out.print(v + " is vacationing in ");

					// grab information about city name and country
					// directly from the DOM tree object
					ParentNode cityname = (ParentNode)
					doc.getElementsByTagName("cityName").item(0);
					ParentNode country = (ParentNode)
					doc.getElementsByTagName("cityCountry").item(0);
					System.out.println(cityname.getTextContent() + ", " + country.getTextContent());
				}
			}
		} catch (Exception e) {         
			System.out.println(e.getMessage());
		}  
	}
}

parser.getDocument()解析 XML 文件後,樹物件臨時儲存在解析器變數中。為了使用 DOM 物件,我們必須建立一個包含它的變數(型別為org.w3c.dom.Document)。

然後,我們建立一個包含所有帶有標籤名稱<city>的元素的節點列表。解析器透過瀏覽 DOM 樹來查詢這些節點。然後,我們只需遍歷每個 city 元素並檢查vacation屬性是否設定,如果設定,則顯示有關度假者的所有資訊。

Xerces 提供了一個名為getTextContent()的有用方法,它允許我們直接訪問元素節點的文字節點,避免了來自不必要空格等的所有困難。

在 XML 專案開始時選擇 API 是一個非常重要的決定。一旦您決定使用哪一個,嘗試不同的供應商很容易,不會遇到太多麻煩,但切換到不同的 API 將是一個非常耗時且昂貴的過程,因為您將不得不重新設計整個程式程式碼。

SAX API 是一種廣泛接受且執行良好的解析器,易於實現,尤其適合處理流式內容(例如線上 XML 源)。由於它是一個只讀 API,因此無法修改底層 XML 資料來源。由於它一次只讀取一個節點,因此它非常節省記憶體且快速。但是,這意味著您的應用程式期望資訊緊密相連且有序。

如果您希望在任何時候隨機訪問整個文件,那麼 DOM 方法可能是更好的選擇。DOM API 更復雜,更難實現,但它可以讓您完全控制整個文件,並允許您修改資料。但是,它會將整個 XML 文件讀入記憶體,因此 DOM API 不適合處理非常大的 XML 檔案。

[編輯 | 編輯原始碼]

使用本章中 SAX 和 DOM 解析器的程式碼示例,並進行一些操作。您可能想要列印不同的節點或新增更多約束條件。這絕對是可選的,但它將讓您瞭解 SAX 和 DOM 之間的主要區別。

現在進行練習

[編輯 | 編輯原始碼]
  • 建立一個 SAX 解析器來解析檔案 movies.xml。輸出只需要來自您的 IDE,不需要傳送到網頁。


為了幫助您 下載這個,它提供了一個問題的結構,以便您更容易在 NetBeans 5.0 中執行應用程式。

如果您有興趣使用 Xerces - 只需下載以下檔案

           http://www.apache.org/dist/xml/xerces-j/Xerces-J-bin.2.8.0.zip

如果上面的連結失效了。請訪問 http://www.apache.org/dist/xml/xerces-j/ 下載最新的 zip 二進位制檔案。它應該以“Xerces-J-bin.#.#.#.zip”格式。

然後將內容放入 NetBeans 目錄的 \lib\ext 子資料夾中,並啟動 NetBeans IDE。現在,Xerces 包已成功安裝在您的機器上。

[編輯 | 編輯原始碼]
  1. http://www.cafeconleche.org
  2. http://www.xml.com
  3. http://www.xmlpull.org
  4. http://workshop.bea.com/xmlbeans/reference/com/bea/xml/XmlCursor.html
  5. http://workshop.bea.com/xmlbeans/reference/com/bea/xml/XmlCursor.html


如果這段文字顯示為藍色,則可以透過單擊 此處 找到本頁示例的 答案
華夏公益教科書