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的話,你必須滿足下面的要求:?
系統中包含Java?1.1?或者更高版本。?
在Java?classpath中包含進你的SAX類庫。?
在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?{
????private?StringBuffer?buf;
????public?void?setDocumentLocator(?Locator?locator?)?{
????}
????public?void?startDocument()?throws?SAXException?{
????????buf=new?StringBuffer();
????????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?{
??public?static?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());
????}
??}
}
?
下面讓我們來看一下執行結果:?
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解析一個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("*****************************");
????}
}
?
我們也要對MySAXApp.java類做一些修改(在源代碼中藍色標出的部分)使它使用MyErrorHandler.java:
package?com.javausr.saxexample;?
import?org.xml.sax.XMLReader;
import?org.xml.sax.helpers.XMLReaderFactory;
import?org.xml.sax.ContentHandler;
//引入ErrorHandler
import?org.xml.sax.ErrorHandler;
import?org.xml.sax.SAXException;
import?java.io.IOException;
public?class?MySAXApp?{
????
????????public?static?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?);
????????????//?創建ErrorHandler的實例
????????????ErrorHandler?errorHandler?=?new?MyErrorHandler();
????????????//?在reader中注冊實例化的ErrorHandler
????????????reader.setErrorHandler(?errorHandler?);
????????????//?開始解析文檔
????????????reader.parse(args[0]);
????}?catch?(?IOException?e?)?{
????????System.out.println("讀入文檔時錯:?"?+?e.getMessage());
????}?catch?(?SAXException?e?)?{
????????System.out.println("解析文檔時錯:?"?+?e.getMessage());
????}
??}
?
讓我們人為制造些錯誤來檢查一下我們的錯誤處理器工作情況。刪除元素<購買日期>的閉合標記,這樣會產生一個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個處理器接口。這樣我們只需繼承這個類,然后覆蓋我們想要實現的事件處理方法即可。下面我們來新建一個繼承了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?{
????private?StringBuffer?buf;
????public?void?startDocument()?throws?SAXException?{
????????buf=new?StringBuffer();
????????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;?
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?{
??public?static?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比較容易讓人迷惑,你只需要記住:?
在你繼承的XMLFilterImpl類中用set****()方法這冊的事件處理器是給過濾后的事件流而用的。?
在你繼承的XMLFilterImpl類中實現的那些事件處理方法,比如startDocument()、startElement()、characters()等才是這個過濾器實現它自身功能的地方。而通過繼承XMLFilterImpl而實現的這個類會被造型成各種處理器(它本身實現了四個處理器接口)用在它的父XMLReader中。這個步驟會在你調用自己創建的過濾器的parse()方法開始解析文檔時被自動執行(請參見SAX源代碼)。?
如果不是使用帶參數的構造器創建XMLFilter對象,務必使用setParent(XMLReader?parent)方法連接它的父XMLReader。?
如果使用多個過濾器的話,執行順序是從父親到最后的過濾器。但是開始解析卻要調用最后一個過濾器的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?{
???private?String?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?{
??public?static?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在一些方面的對照:?SAX?DOM?
順序讀入文檔并產生相應事件,可以處理任何大小的XML文檔?在內存中創建文檔樹,不適于處理大型XML文檔。?
只能對文檔按順序解析一遍,不支持對文檔的隨意訪問。?可以隨意訪問文檔樹的任何部分,沒有次數限制。?
只能讀取XML文檔內容,而不能修改?可以隨意修改文檔樹,從而修改XML文檔。?
開發上比較復雜,需要自己來實現事件處理器。?易于理解,易于開發。?
對開發人員而言更靈活,可以用SAX創建自己的XML對象模型。?已經在DOM基礎之上創建好了文檔樹。?
通過對SAX和DOM的分析,它們各有自己的不同應用領域:?
SAX適于處理下面的問題:?
對大型文檔進行處理。?
只需要文檔的部分內容,或者只需要從文檔中得到特定信息。?
想創建自己的對象模型的時候。
DOM適于處理下面的問題:?
需要對文檔進行修改?
需要隨機對文檔進行訪問,例如XSLT解析器。
對SAX的介紹到這里就告一段落了,希望能對大家有所幫助:),本文的絕大部分參考資料都來源于http://www.megginson.com/SAX/?以及SAX?API(雖然說SAX有了自己新的網站http://sax.sourceforge.net/?但我從來沒有成功訪問過!)?,感謝David?Megginson和其他SAX開發人員給我們提供了這么一個好東東。本文如有錯誤和不妥的地方還請大家指正。