Vincent.Chan‘s Blog

常用链接

统计

积分与排名

网站

最新评论

SAX 的基本概要

SAX和DOM一樣也是一個存取XML文件的介面。SAX是Simple API for XML的縮寫。它不像DOM那樣是W3C的認可標準。它是由XML-DEV信件清單的成員開發維護,由David Megginson領導(david@megginson.com)的一個Public Domain軟體。SAX是一個徹底的自由軟體,它的作者放棄了對它的所有權利,它也被許可用於任何目的(在文章最後附錄了它的版權聲明)。

到現在為止SAX的版本已經發展到2.0。在這個最新版本中增加了對命名空間(Namespaces)的支援,而且可以透過對features以及 properties的設定來對剖析器做全面的配置,這其中包括設定剖析器是否對文件進行有效性驗證,以及怎樣來處理帶有命名空間的元素名稱等。SAX1 中的介面已經不再使用了,這裡只會討論有關SAX2的開發。在本文中提到SAX只是指SAX 2。另外,本文的所有例子都是用java編寫,SAX剖析器也使用的是JAVA版本。

像DOM一樣,SAX並不是一個可以實際使用的XML文件剖析器,而是其它相容SAX的剖析器所要實作的介面和幫助性類別的集合。如果你想使用SAX的話,你必須滿足下面的要求︰

  1. 系統中包括Java 1.1 或者更高版本。
  2. 在Java classpath中匯入你的SAX類別庫。
  3. 在Java classpath中匯入相容SAX的XML剖析器類別庫。

有很多實作SAX的剖析器,比如Apache的Xerces,Oracle的XML Parser等等。在本文中的例子使用的是Xerces剖析器,你可以從 http://xml.apache.org 取得。下載xerces.jar檔案然後加入到classpath中,這樣就建立好環境(在xerces.jar中已經包括了SAX介面,所以不必特意再去尋找SAX類別庫)。

在SAX API中有兩個套件,org.xml.sax和org.xml.sax.helper。其中org.xml.sax主要定義了SAX的一些基礎介面,如 XMLReader、ContentHandler、ErrorHandler、DTDHandler、EntityResolver等。而在 org.xml.sax.helper中則是一些方便工作人員使用的幫助性類別,如預設實作所有處理器介面的幫助性類別DefaultHandler、方 便工作人員建立XMLReader的XMLReaderFactory類別等等。在這兩個套件中還有一些應用於SAX1的介面,同時還有幾個類別它們只是 為了便於將在SAX1上開發的應用移植到SAX2上,在這篇文章中就不涉及了。下面是我們要關注的介面和類別︰

Package org.xml.sax介紹
Interfaces介面
Attributes定義了一個屬性清單介面,供存取元素的屬性清單而用。
ContentHandler處理剖析文件內容時產生的事件。
DTDHandler處理剖析DTD時的相對應事件。
EntityResolver處理外部實體。
ErrorHandler處理剖析過程中所遇到的文件錯誤事件。
Locator為了搜尋剖析中產生的內容事件在文件中的位置而準備的一個搜尋器介面。
XMLFilter提供了一個方便應用開發的過濾器介面。
XMLReader任何相容SAX2的剖析器都要實作這個介面,這個介面讓應用程式可以設定或尋找features和properties,註冊各種事件處理器,以及開始 剖析文件。
Classes類別
InputSource為XML實體準備的來源。
Exceptions例外
SAXException包裝了一般的SAX錯誤和警告。
SAXNotRecognizedException為識別不出某些識別資料而拋出的例外處理。
SAXNotSupportedException為不支援某個作業而拋出的例外處理。
SAXParseException包裝了一個關於XML剖析的錯誤或者警告。
Package org.xml.sax.helpers幫助性類別所在的套件
Classes類別
AttributesImpl對Attributes介面的預設實作
NamespaceSupport提供命名空間支援。
DefaultHandler預設實作了四個處理器介面,方便使用者開發,在開發過程中會經常用到。
LocatorImpl提供了一個對Locator介面的實作
XMLFilterImpl對過濾器介面的實作,使用過濾器進行應用程式開發時,繼承這個類別很方便。
XMLReaderFactory為方便建立不同的XMLReader而提供。也會經常用到。

理解並使用SAX

SAX的設計實作與DOM是完全不同的﹗DOM處理XML文件是將XML文件剖析成樹狀模型,放入記憶體進行處理。而SAX則是採用以事件為基礎的 處理模式,它將XML文件轉化成一系列的事件,由單獨的事件處理器來決定如何處理。為了瞭解如何使用SAX API處理XML文件,這裡先介紹一下SAX所使用的以事件為基礎的處理模式

這種以事件為基礎的處理模式是一種通用的程式設計模式,被廣泛應用於GUI設計。在JAVA的AWT,SWING以及JAVA BEANS中就有它的身影。而SAX的以事件為基礎的處理模式就與上面三者非常相像。

以事件為基礎的處理模式主要是圍繞著事件來源以及事件處理器來工作的。一個可以產生事件的物件被稱為事件來源,而可以針對事件產生回應的物件就被叫 做事件處理器。事件來源和事件處理器是透過在事件來源中的事件處理器註冊方法連繫的。這樣當事件來源產生事件後,呼叫事件處理器相對應的處理方法來處理一 個事件。當然在事件來源呼叫事件處理器中特定方法的時候,會傳遞給事件處理器相對應的狀態訊息,這樣事件處理器才能夠根據事件訊息來決定自己的行為。

在SAX介面中,事件來源是org.xml.sax套件中的XMLReader,它透過parse()方法來開始剖析XML文件並根據文件內容產生 事件。而事件處理器則是org.xml.sax套件中的ContentHandler,DTDHandler,ErrorHandler,以及 EntityResolver這四個介面。它們分別處理事件來源在剖析過程中產生的不同種類的事件(其中DTDHandler是為剖析文件DTD時而 用)。而事件來源XMLReader和這四個事件處理器的連繫是透過在XMLReader中所 對應的事件處理器註冊方法set***()來完成的。詳細介紹請見下表︰

處理器名稱所處理事件註冊方法
org.xml.sax.ContentHandler跟文件內容有關的所有事件︰
# 文件的開始和結束
# XML元素的開始和結束
# 可忽略的實體
# 命名空間字首對應的開始和結束
# 處理指令
# 字元資料和可忽略的空格
XMLReader中的setContentHandler(ContentHandler handler)方法
org.xml.sax.ErrorHandler處理XML文件剖析時產生的錯誤。如果一個應用程式沒有註冊一個錯誤處理器類別,會發生不可預料的剖析器行為。setErrorHandler(ErrorHandler handler)
org.xml.sax.DTDHandler處理對文件DTD進行剖析時產生的相對應事件setDTDHandler(DTDHandler handler)
org.xml.sax.EntityResolver處理外部實體setEntityResolver(EntityResolver resolver)

在這四個處理器介面中,對我們最重要的是ContentHandler介面。下面讓我們看一下對其中方法的說明︰

方法名稱方法說明
public void setDocumentLocator(Locator locator)設定一個可以搜尋事件發生位置的搜尋器物件
public void startDocument() throws SAXException用於處理剖析文件開始事件
public void endDocument() throws SAXException用於處理剖析文件結束事件
public void startPrefixMapping(java.lang.String prefix, java.lang.String uri) throws SAXException用於處理字首對應開始事件,從參數中可以得到字首名稱以及所指向的uri
public void endPrefixMapping(java.lang.String prefix) throws SAXException用於處理字首對應的結束事件,從參數中可以得到字首名稱
public void startElement(java.lang.String namespaceURI,java.lang.String localName,java.lang.String qName,Attributes atts) throws SAXException處理元素開始事件,從參數中可以獲得元素所在命名空間的uri,元素名稱,屬性清單等訊息
public void endElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName) throws SAXException處理元素結束事件,從參數中可以獲得元素所在命名空間的uri,元素名稱等訊息
public void characters(char[] ch, int start, int length) throws SAXException處理元素的字元內容,從參數中可以獲得內容
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException處理元素的可忽略空格
public void processingInstruction(java.lang.String target, java.lang.String data) throws SAXException處理剖析中產生的處理指令事件

這裡再介紹一下org.xml.sax.XMLReader中的方法,然後讓我們看一個具體的例子。XMLReader是所有相容SAX2的剖析器都要實作的介面,由它的方法開始 剖析文件,並且呼叫它的註冊方法來註冊各種事件處理器。請看下表︰

方法名稱方法介紹
public Boolean getFeature(java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException得到某個feature的值
public void setFeature(java.lang.String name,boolean value) throws SAXNotRecognizedException,SAXNotSupportedException設定某個feature的值,例如,如果需要剖析器支援對文件進行驗證那麼就這麼呼叫本方法。myReader.setFeature(http://xml.org/sax/features/validation,true);其中myReader是XMLReader的 範例。
public java.lang.Object getProperty(java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException傳回一個property的值
public void setProperty(java.lang.String name,java.lang.Object value)throws SAXNotRecognizedException,SAXNotSupportedException設定一個property的值
public void setEntityResolver(EntityResolver resolver)註冊處理外部實體的EntityResolver
public EntityResolver getEntityResolver()得到系統中註冊的EntityResolver
public void setDTDHandler(DTDHandler handler)註冊處理DTD剖析事件的DTDHandler
public DTDHandler getDTDHandler()得到系統中註冊的DTDHandler
public void setContentHandler(ContentHandler handler)註冊處理XML文件內容剖析事件的ContentHandler
public ContentHandler getContentHandler()得到系統中註冊的ContentHandler
public void setErrorHandler(ErrorHandler handler)註冊處理文件剖析錯誤事件的ErrorHandler
public ErrorHandler getErrorHandler()得到系統中註冊的ErrorHandler
public void parse(InputSource input)throws java.io.IOException,SAXException開始剖析一個XML文件。
public void parse(java.lang.String systemId)throws java.io.IOException,SAXException開始剖析一個使用系統識別資料符識別資料的XML文件。這個方法只是上面方法的一個捷徑它等同於︰parse(new InputSource(systemId));

一個範例

讓我們透過例子來看一下使用SAX剖析XML文件的應用程式是如何建立的。下面是在應用程式中被處理的XML文件。為了說明SAX對命名空間的支援,我在這裡特意加了一個有命名空間的元素,在這裡會產生相對應的字首對應開始和結束事件。

<?xml version="1.0" encoding="GB2312"?>
<我的書架 >
<技術書籍>
<圖書>
<書名>JAVA 2程式化詳解</書名>
<價格 貨幣單位="新台幣">150</價格>
<購買日期>2000,1,24</購買日期>
</圖書>
</技術書籍>
<book:文學書籍 xmlns:book="http://javausr.com"/>
<歷史書籍/>
</我的書架>

這裡的範例程式只是簡單地將遇到的事件訊息列印出來。我們首先實作ContentHandler介面來處理在XML文件剖析過程中產生的和文件內容相關的事件,程式碼如下所示MyContentHandler.java︰

package com.javausr.saxexample; 

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

public class MyContentHandler implements ContentHandler {

privateStringBuffer buf;

public void setDocumentLocator( Locator locator ) {
}

public void startDocument() throws SAXException {
buf=newStringBuffer();
System.out.println("*******開始剖析文件*******");
}

public void endDocument() throws SAXException {
System.out.println("*******剖析文件結束*******");
}

public void processingInstruction( String target, String instruction )
throws SAXException {
}

public void startPrefixMapping( String prefix, String uri ) {
System.out.println("\n字首對應: " + prefix +" 開始!"+ " 它的URI是:" + uri);
}

public void endPrefixMapping( String prefix ) {
System.out.println("\n字首對應: "+prefix+" 結束!");
}

public void startElement( String namespaceURI, String localName,
String fullName, Attributes attributes )
throws SAXException {
System.out.println("\n 元素: " + "["+fullName+"]" +" 開始剖析!");
// 列印出屬性訊息
for ( int i = 0; i < attributes.getLength(); i++ ) {
System.out.println("\t屬性名稱:" + attributes.getLocalName(i) + " 屬性值:" + attributes.getValue(i));
}
}

public void endElement( String namespaceURI, String localName,
String fullName )
throws SAXException {
//列印出非空的元素內容並將StringBuffer清空
String nullStr="";
if (!buf.toString().trim().equals(nullStr)){
System.out.println("\t內容是: " + buf.toString().trim());
}
buf.setLength(0);
// 列印元素剖析結束訊息
System.out.println("元素: "+"["+fullName+"]"+" 剖析結束!");
}

public void characters( char[] chars, int start, int length )
throws SAXException {
// 將元素內容累加到StringBuffer中
buf.append(chars,start,length);
}

public void ignorableWhitespace( char[] chars, int start, int length )
throws SAXException {
}

public void skippedEntity( String name ) throws SAXException {
}
}

下面讓我們建立一個引用了xerces剖析器來實作XMLReader介面、並使用剛才建立的MyContentHandler來處理對應剖析事件的MySAXApp.java類別︰

package com.javausr.saxexample; 
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import java.io.IOException;

public class MySAXApp {

publicstatic void main( String[] args ) {

if ( args.length != 1 ) {
System.out.println("輸入: java MySAXApp ");
System.exit(0);
}

try {
// 初始化reader
XMLReader reader = XMLReaderFactory.createXMLReader
("org.apache.xerces.parsers.SAXParser") ;

// 建立ContentHandler的範例
ContentHandler contentHandler = new MyContentHandler();

// 在reader中註冊實體化的ContentHandler
reader.setContentHandler( contentHandler );

// 開始剖析文件
reader.parse(args[0]);

} catch ( IOException e ) {
System.out.println("讀入文件時錯: " + e.getMessage());
} catch ( SAXException e ) {
System.out.println("剖析文件時錯: " + e.getMessage());
}
}
}

上面就是使用SAX剖析一個XML文件的基本過程,但是MyContentHandler只是處理了剖析過程中和文件內容相關的事件,如果在剖析過 程中出現了錯誤那我們需要實作ErrorHandler介面來處理。如果不註冊一個錯誤處理器來處理的話,那麼錯誤事件將不會被報告,而且剖析器會出現不 可預知的行為。在剖析過程中產生的錯誤被分成了3類,它們分別是warning,error,以及fatalerror,也就是說在 ErrorHandler中有這麼三個對應的方法來處理這些錯誤事件。下面是對這三個錯誤處理方法的介紹︰

方法名稱方法介紹
warning()SAX剖析器將用這個方法來報告在XML1.0規範中定義的非錯誤(error)或者致命錯誤(fatal error)的錯誤狀態。對這個錯誤預設的行為是什麼也不做。SAX剖析器必須在呼叫這個方法後繼續提供正常的剖析事件︰應用程式應該能繼續處理完文件。
error()這個方法對應在W3C XML 1.0規範的1.2部分中定義的"error"概念。例如,一個帶有有效性驗證的剖析器會使用這個方法來報告違反有效性驗證的情況。一個帶有有效性驗證的 剖析器會使用這個方法來報告違背有效性條件的情況。預設的行為是什麼也不做。SAX剖析器必須在呼叫這個方法後繼續提供正常的剖析事件︰應用程式應該能繼 續處理完文件。如果應用程式做不到這樣,則 剖析器即使在XML1.0規範沒有要求的情況下也要報告一個致命錯誤。
fatalError()這個方法對應在W3C XML1.0規範的1.2部分定義的"fatal error"概念。例如,一個剖析器會使用這個方法來報告格式錯誤。在剖析器呼叫這個方法後應用程式必須表明這個文件是不可使用的,而且應該 只是為了收集錯誤訊息而繼續進行處理(如果需要的話)︰實際上,一旦在這個方法被呼叫後SAX剖析器可以停止報告任何事件。

下面是實作了ErrorHandler介面的MyErrorHandler.java類別︰

package com.javausr.saxexample; 
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXException;

public class MyErrorHandler implements ErrorHandler {

public void warning( SAXParseException exception ) {
System.out.println("*******WARNING******");
System.out.println("\t行:\t" + exception.getLineNumber());
System.out.println("\t列:\t" + exception.getColumnNumber());
System.out.println("\t錯誤訊息:\t" + exception.getMessage());
System.out.println("********************");
}

public void error( SAXParseException exception ) throws SAXException{
System.out.println("******* ERROR ******");
System.out.println("\t行:\t" + exception.getLineNumber());
System.out.println("\t列:\t" + exception.getColumnNumber());
System.out.println("\t錯誤訊息:\t" + exception.getMessage());
System.out.println("********************");
}

public void fatalError( SAXParseException exception ) throws SAXException {
System.out.println("******** FATAL ERROR ********");
System.out.println("\t行:\t" + exception.getLineNumber());
System.out.println("\t列:\t" + exception.getColumnNumber());
System.out.println("\t錯誤訊息:\t" + exception.getMessage());
System.out.println("*****************************");
}
}

讓我們製造些錯誤來檢查一下我們的錯誤處理器工作情況。刪除元素<購買日期>的結束標籤,這樣會產生一個fatal error,下面是執行結果︰
D:\sax\classes>java com.javausr.saxexample.MySAXApp d:\book.xml

*******開始剖析文件*******

元素: [我的書架] 開始剖析!

元素: [技術書籍] 開始剖析!

元素: [圖書] 開始剖析!

元素: [書名] 開始剖析!

元素: [書名] 開始剖析!
內容是: JAVA 2程式化詳解
元素: [書名] 剖析結束!

元素: [價格] 開始剖析!
屬性名稱:貨幣單位 屬性值:新台幣
內容是: 150
元素: [價格] 剖析結束!

元素: [購買日期] 開始剖析!
******** FATAL ERROR ********
行: 8
列: 7
錯誤訊息: The element type "購買日期" must be terminated by the matching end-tag "</購買日期>".
*****************************
剖析文件時錯: Stopping after fatal error: The element type "購買日期" must be terminated by the matching end-tag "</購買日期>".

現在總結一下如何撰寫SAX的應用程式。一般步驟如下︰
實作一個或多個處理器介面(ContentHandler, ErrorHandler, DTDHandler ,or EntityResover)。
建立一個XMLReader類別的範例。
在新的XMLReader範例中透過大量的set*****() 方法註冊一個事件處理器的實體
呼叫XMLReader的parse()方法來處理文件。

使用DefaultHandler

現在的程式是比較完整了,但還有許多可以改進的地方。首先在我們實作的MyContentHandler.java中,你會發現有很多方法實際上什 麼也沒有做,但為了實作ContentHandler介面,不得不把它們寫出來,這樣很是麻煩。SAX API已經考慮到這個問題,在它的org.xml.sax.helper套件中為我們提供了一個方便實作各種處理器介面的幫助性類別 DefaultHandler。這個類別預設實作了上面提到的4個處理器介面。這樣我們只需繼承這個類別,然後覆寫(override)我們想要實作的事 件處理方法即可。下面我們來新增一個繼承了DefaultHandler的MyDefaultHandler.java類別,然後把在 MyContentHandler.java和MyErrorHandler.java中實作的事件處理方法照搬到 MyDefaultHandler.java類別中,那些沒有使用的方法就不必重複了。這裡是MyDefaultHandler.java︰

package com.javausr.saxexample; 
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;

public class MyDefaultHandler extends DefaultHandler {

privateStringBuffer buf;

public void startDocument() throws SAXException {
buf=newStringBuffer();
System.out.println("*******開始剖析文件*******");
}

public void endDocument() throws SAXException {
System.out.println("*******剖析文件結束*******");
}

public void startPrefixMapping( String prefix, String uri ) {
System.out.println("\n字首對應: " + prefix +" 開始!"+ " 它的URI是:"+uri);
}

public void endPrefixMapping( String prefix ) {
System.out.println("\n字首對應: "+prefix+" 結束!");
}

public void startElement( String namespaceURI, String localName,
String fullName, Attributes attributes )
throws SAXException {

System.out.println("\n元素: " + "["+fullName+"]" +" 開始剖析!");

// 列印出屬性訊息
for ( int i = 0; i < attributes.getLength(); i++ ) {
System.out.println("\t屬性名稱:" + attributes.getLocalName(i)
+ " 屬性值:" + attributes.getValue(i));
}
}

public void endElement( String namespaceURI, String localName,
String fullName )
throws SAXException {
//列印出非空的元素內容並將StringBuffer清空
String nullStr="";
if (!buf.toString().trim().equals(nullStr)){
System.out.println("\t內容是: " + buf.toString().trim());
}
buf.setLength(0);
//列印元素剖析結束訊息
System.out.println("元素: "+"["+fullName+"]"+" 剖析結束!");
}

public void characters( char[] chars, int start, int length )
throws SAXException {
//將元素內容累加到StringBuffer中
buf.append(chars,start,length);
}

public void warning( SAXParseException exception ) {
System.out.println("*******WARNING******");
System.out.println("\t行:\t" + exception.getLineNumber());
System.out.println("\t列:\t" + exception.getColumnNumber());
System.out.println("\t錯誤訊息:\t" + exception.getMessage());
System.out.println("********************");
}

public void error( SAXParseException exception ) throws SAXException{
System.out.println("******* ERROR ******");
System.out.println("\t行:\t" + exception.getLineNumber());
System.out.println("\t列:\t" + exception.getColumnNumber());
System.out.println("\t錯誤訊息:\t" + exception.getMessage());
System.out.println("********************");
}

public void fatalError( SAXParseException exception ) throws SAXException {
System.out.println("******** FATAL ERROR ********");
System.out.println("\t行:\t" + exception.getLineNumber());
System.out.println("\t列:\t" + exception.getColumnNumber());
System.out.println("\t錯誤訊息:\t" + exception.getMessage());
System.out.println("*****************************");
}
}

我們也要對MySAXApp.java做相對應的修改,修改已在原始碼中標出︰
package com.javausr.saxexample;

package com.javausr.saxexample; 
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
//引入DefaultHandler
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;
import java.io.IOException;

public class MySAXApp {

publicstatic void main( String[] args ) {

if ( args.length != 1 ) {
System.out.println("輸入: java MySAXApp ");
System.exit(0);
}

try {
// 初始化reader
XMLReader reader = XMLReaderFactory.createXMLReader
("org.apache.xerces.parsers.SAXParser") ;

// 建立DefaultHandler的範例
DefaultHandler defaultHandler=new MyDefaultHandler();

//在reader中將defaultHandler註冊為ContentHandler
reader.setContentHandler(defaultHandler);

//在reader中將defaultHandler註冊為ErrorHandler
reader.setErrorHandler(defaultHandler);

// 開始剖析文件
reader.parse(args[0]);

} catch ( IOException e ) {
System.out.println("讀入文件時錯: " + e.getMessage());
} catch ( SAXException e ) {
System.out.println("剖析文件時錯: " + e.getMessage());
}
}
}

使用過濾器

在SAX API中還提供了一個過濾器介面org.xml.sax.XMLFilter,以及對它的預設實作 org.xml.sax.helper.XMLFilterImpl。使用它們可以很容易的開發出複雜的SAX應用。這裡要先介紹一下過濾器設計模式。這 個設計模式很好理解,就像一個淨化水的過程。自然界中的水流過一個個的過濾器得到最後的飲用水。這些過濾器,有的是清除水中的泥沙,有的是消滅水中的細 菌,總之不同的過濾器完成不同的工作。在應用開發中,我們讓被改造的物件(這裡是事件串)透過這些過濾器物件從而得到改造後符合要求的物件。這樣,在過濾 器的幫助之下,我們可以非常方便的在每個過濾器中實作一個特定功能,從而建立結構複雜的應用程式。在應用程式中你可以構造任意多個過濾器,將它們串接起來 完成工作。

在SAX API中org.xml.sax.XMLFilter介面繼承了org.xml.sax.XMLReader介面。它與XMLReader不同的是它不像 XMLReader那樣透過剖析文件來獲取事件,而是從其它XMLReader中獲取事件,當然這也包括從其它的XMLFilter中獲取事件。在 org.xml.sax.XMLFilter中有兩個方法︰

方法名稱方法描述
Public void setParent(XMLReader parent)設定父XMLReader。這個方法讓應用程式將這個過濾器取得到它的父XMLReader (也可能是另一個過濾器)。
Public XMLReader getParent()獲取父XMLReader。這個方法讓應用程式可以查詢父XMLReader(也可能是另一個過濾器)。最好不要在父XMLReader中直接進行任何作業︰讓所有的事件透過這個過濾器來處理。

我們不需要自己實作org.xml.sax.XMLFilter介面,在SAX API 中提供了一個org.xml.sax.helper.XMLFilterImpl類別,它不只有實作了org.xml.sax.XMLFilter介面而 且還實作了其它四個核心處理器介面,我們只需要繼承它即可完成我們的過濾器。剛開始使用XMLFilterImpl比較容易讓人迷惑,你只需要記住︰

  1. 在你繼承的XMLFilterImpl類別中用set****()方法註冊的事件處理器是給過濾後的事件串用的。
  2. 在 你繼承的XMLFilterImpl類別中實作的那些事件處理方法,比如startDocument()、startElement()、 characters()等 才是這個過濾器實作它自身功能的地方。而透過繼承XMLFilterImpl而實作的這個類別會被轉型成各種處理器(它本身實作了四個處理器介面)用在它 的父XMLReader中。這個步驟會在你呼叫自己建立的過濾器的parse()方法開始 剖析文件時被自動執行(請參見SAX原始碼)。
  3. 如果不是使用帶參數的建構子建立XMLFilter物件,務必使用setParent(XMLReader parent)方法取得它的父XMLReader。
  4. 果使用多個過濾器的話,執行順序是從一開始到最後的過濾器。但是開始剖析卻要呼叫最後一個過濾器的parse()方法。

下面讓我們結合已有的例子來展示過濾器org.xml.sax.XMLFilter的作用。我們在這個過濾器中要過濾掉<技術書籍>這個元素,最後得到的事件串還是由上面實作的MyDefaultHandler來處理。原始碼如下MyFilter.java︰

package com.javausr.saxexample; 
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;

public class MyFilter extends XMLFilterImpl {

privateString currentElement;

public MyFilter( XMLReader parent ) {
super(parent);
}

/**
* 過濾掉元素<技術書籍>的開始事件
**/
public void startElement( String namespaceURI, String localName,
String fullName, Attributes attributes )
throws SAXException {

currentElement = localName;
if ( !localName.equals("技術書籍") ) {
super.startElement(namespaceURI, localName, fullName, attributes);
}
}

/**
* 過濾掉元素<技術書籍>的結束事件
**/
public void endElement(String namespaceURI, String localName, String
fullName)
throws SAXException {

if ( !localName.equals("技術書籍") ) {
super.endElement(namespaceURI, localName, fullName);
}
}

/**
* 過濾掉元素<技術書籍>中的內容
**/
public void characters(char[] buffer, int start, int length)
throws SAXException {
if ( !currentElement.equals("技術書籍") ) {
super.characters( buffer,start,length );
}
}
}

同樣我們還要修改MySAXApp.java,修改後的程式碼如下所示︰

package com.javausr.saxexample; 
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;
//引入XMLFilter
import org.xml.sax.XMLFilter;
import org.xml.sax.SAXException;
import java.io.IOException;

public class MySAXApp {

publicstatic void main( String[] args ) {


if ( args.length != 1 ) {
System.out.println("輸入: java MySAXApp ");
System.exit(0);
}

try {
// 初始化reader
XMLReader reader = XMLReaderFactory.createXMLReader
("org.apache.xerces.parsers.SAXParser") ;

//初始化過濾器
XMLFilter myFilter=new MyFilter(reader);

// 建立DefaultHandler的範例
DefaultHandler defaultHandler=new MyDefaultHandler();

//為過濾後的事件流設定ContentHandler
myFilter.setContentHandler(defaultHandler);

//為過濾後的事件流設定ErrorHandler
myFilter.setErrorHandler(defaultHandler);


// 開始剖析文件,注意是使用myFilter中的剖析方法
myFilter.parse(args[0]);

} catch ( IOException e ) {
System.out.println("讀入文件時錯: " + e.getMessage());
} catch ( SAXException e ) {
System.out.println("剖析文件時錯: " + e.getMessage());
}
}
}

這裡是最後的執行結果,我們可以發現有關<技術書籍>的全部事件已經被過濾掉了。認真看一下結果,你一定覺得奇怪,為什麼<技術 書籍>元素的子元素仍然存在。請記住SAX是把XML文件剖析成事件流,所有沒有被過濾的事件都會保留下來。這就是SAX和DOM的最大不同。在 DOM中文件被剖析成了樹狀模型,如果你刪除一個元素,那麼這個元素以及它的子元素就都會被刪除,這符合樹狀模型的特點。

D:\sax\classes>java com.javausr.saxexample.MySAXApp d:\book.xml

*******開始剖析文件*******

元素: [我的書架] 開始剖析!

元素: [圖書] 開始剖析!

元素: [書名] 開始剖析!
內容是: JAVA 2程式化詳解
元素: [書名] 剖析結束!

元素: [價格] 開始剖析!
屬性名稱:貨幣單位 屬性值:新台幣
內容是: 150
元素: [價格] 剖析結束!

元素: [購買日期] 開始剖析!
內容是: 2000,1,24
元素: [購買日期] 剖析結束!
元素: [圖書] 剖析結束!

字首對應: book 開始! 它的URI是:http://javausr.com

元素: [book:文學書籍] 開始剖析!
元素: [book:文學書籍] 剖析結束!

字首對應: book 結束!

元素: [歷史書籍] 開始剖析!
元素: [歷史書籍] 剖析結束!
元素: [我的書架] 剖析結束!
*******剖析文件結束*******

一些值得注意的問題

首先是有關元素內容的問題,在SAX API定義中元素內容可以在一次事件(由characters()方法處理)中傳回,也可以在多次事件中傳回,這樣我們就應該考慮不能一次得到所有內容資 料的情況。一般的解決辦法是定義一個StringBuffer由它來儲存內容資料,在元素結束或者新元素開始的時候清空這個StringBuffer從而 可以儲存新的內容資料。請參考上面的原始碼。

還有在SAX API中特意提到從 characters(char[] ch,int start,int length)方法中取資料時一定不要從傳回的字元數組範圍之外讀取,這一點我們也要切記。

另一個值得注意的問題是,在 startElement()方法中傳回的Attributes屬性清單中的屬性順序並沒有被特意規定,在不同的SAX實作中也各不相同。所以我們在編寫程式時不要把屬性順序想成一定的。

SAX與DOM的比較

透過上面的介紹我想大家對SAX已經有了一個基本的瞭解。每一個進行XML開發的程式化人員都知道DOM,那為什麼在有了DOM這個功能強大的文件 物件模型之後,我們還需要SAX?這就要從它們不同的實作方法上來分析。DOM剖析器是透過將XML文件剖析成樹狀模型並將其放入記憶體來完成剖析工作 的,而後對文件的作業都是在這個樹狀模型上完成的。這個在記憶體中的文件樹將是文件實際大小的幾倍。這樣做的好處是結構清楚、作業方便,而帶來的麻煩就是 極其耗費系統資源。而SAX正好克服了DOM的缺點。SAX剖析器的處理過程是通讀整個文件,根據文件內容產生事件,而把對這些事件的處理交由事件處理器 處理。SAX不需要在記憶體中儲存整個文件,它對系統資源的節省是顯而易見的。這樣在一些需要處理大型XML文件和效能要求比較高的場合就要用SAX了。

下面的表格列出了SAX和DOM在一些方面的對照︰

SAXDOM
依序讀入文件並產生相對應事件,可以處理任何大小的XML文件在記憶體中建立文件樹,不適於處理大型XML文件。
只能對文件按順序剖析一遍,不支援對文件的隨意存取。可以隨意存取文件樹的任何部分,沒有次數限制。
只能讀取XML文件內容,而不能修改可以隨意修改文件樹,從而修改XML文件。
開發上比較複雜,需要自己來實作事件處理器。易於理解,易於開發。
對工作人員而言更靈活,可以用SAX建立自己的XML物件模型。已經在DOM基礎之上建立好了文件樹。

透過對SAX和DOM的分析,它們各有自己的不同應用領域︰
SAX適於處理下面的問題︰

  1. 對大型文件進行處理。
  2. 只需要文件的部分內容,或者只需要從文件中得到特定訊息。
  3. 想建立自己的物件模型的時候。

DOM適於處理下面的問題︰

  1. 需要對文件進行修改
  2. 需要隨機對文件進行存取,例如XSLT剖析器。

對SAX的介紹到這裡就告一段落了,希望能對大家有所幫助︰),本文的絕大部分參考資料都來於http: //www.megginson.com/SAX/ 以及SAX API(雖然說SAX有了自己新的網站http://sax.sourceforge.net/ 但我從來沒有成功存取過﹗) ,感謝David Megginson和其它SAX工作人員給我們提供了這麼一個好東西。本文如有錯誤和不妥的地方還請大家指正。

附錄: SAX的版權聲明
SAX2 is Free!

I hereby abandon any property rights to SAX 2.0 (the Simple API for XML), and release all of the SAX 2.0 source code, compiled code, and documentation contained in this distribution into the Public Domain. SAX comes with NO WARRANTY or guarantee of fitness for any purpose.

David Megginson, david@megginson.com

2000-05-05

關於作者
王曉強,萬千程式開發者中的一員,並樂在其中。熱愛java和linux,一直利用java和xml相關技術進行應用開發,並在這些方面積累了豐富經驗。可透過 forff@sina.com 與他連絡。

posted on 2006-03-22 23:55 Vincent.Chen 阅读(578) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: