SAX同DOM一樣也是一個訪問XML文檔的接口。SAX是Simple API for XML的縮寫。它不像DOM那樣是W3C的推薦標(biāo)準(zhǔn)。它是由XML-DEV郵件列表的成員開發(fā)維護(hù),由David Megginson領(lǐng)導(dǎo)(david@megginson.com)的一個Public Domain軟件。SAX是一個徹底的自由軟件,它的作者放棄了對它的所有權(quán)利,并且它也被許可用于任何目的(在文章最后附錄了它的版權(quán)聲明)。
到現(xiàn)在為止SAX的版本已經(jīng)發(fā)展到2.0。在這個最新版本中增加了對名稱空間(Namespaces)的支持,而且可以通過對features以及properties的設(shè)置來對解析器做全面的配置,這其中包括設(shè)置解析器是否對文檔進(jìn)行有效性驗證,以及怎樣來處理帶有名稱空間的元素名稱等。SAX1中的接口已經(jīng)不再使用了,這里只會討論有關(guān)SAX2的開發(fā)。在本文中提到SAX只是指SAX 2。另外,本文的所有例子都是用java編寫,SAX解析器也使用的是JAVA版本。
實(shí)現(xiàn)了SAX的解析器有很多,比如Apache的Xerces,Oracle的XML Parser等等。在本文中的例子程序使用的都是Xerces解析器,你可以從 http://xml.apache.org 得到它。讓我們下載得到xerces.jar文件然后將其加入到classpath中去,這樣我們就已經(jīng)建立好環(huán)境(在xerces.jar中已經(jīng)包含了SAX接口,所以不必特意再去尋找SAX類庫)。
在SAX API中有兩個包,org.xml.sax和org.xml.sax.helper。其中org.xml.sax中主要定義了SAX的一些基礎(chǔ)接口,如XMLReader、ContentHandler、ErrorHandler、DTDHandler、EntityResolver等。而在org.xml.sax.helper中則是一些方便開發(fā)人員使用的幫助類,如缺省實(shí)現(xiàn)所有處理器接口的幫助類DefaultHandler、方便開發(fā)人員創(chuàng)建XMLReader的XMLReaderFactory類等等。在這兩個包中還有一些應(yīng)用于SAX1的接口,同時還有幾個類它們只是為了便于將在SAX1上開發(fā)的應(yīng)用移植到SAX2上,在這篇文章中就不涉及了。下面是我們要關(guān)注的接口和類:
Package org.xml.sax | 介紹 |
Interfaces | 接口 |
Attributes | 定義了一個屬性列表接口,供訪問元素的屬性列表而用。 |
ContentHandler | 處理解析文檔內(nèi)容時產(chǎn)生的事件。 |
DTDHandler | 處理解析DTD時的相應(yīng)事件。 |
EntityResolver | 處理外部實(shí)體。 |
ErrorHandler | 處理解析過程中所遇到的文檔錯誤事件。 |
Locator | 為了定位解析中產(chǎn)生的內(nèi)容事件在文檔中的位置而準(zhǔn)備的一個定位器接口。 |
XMLFilter | 提供了一個方便應(yīng)用開發(fā)的過濾器接口。 |
XMLReader | 任何兼容SAX2的解析器都要實(shí)現(xiàn)這個接口,這個接口讓應(yīng)用程序可以設(shè)置或查找features和properties,注冊各種事件處理器,以及開始解析文檔。 |
Classes | |
InputSource | 為XML實(shí)體準(zhǔn)備的輸入源。 |
Exceptions | |
SAXException | 包裝了一般的SAX錯誤和警告。 |
SAXNotRecognizedException | 為識別不出某些標(biāo)識而拋出的異常。 |
SAXNotSupportedException | 為不支持某個操作而拋出的異常。 |
SAXParseException | 包裝了一個關(guān)于XML解析的錯誤或者警告。 |
Package org.xml.sax.helpers | 幫助類所在的包 |
Classes | 類 |
AttributesImpl | 對Attributes接口的缺省實(shí)現(xiàn) |
NamespaceSupport | 提供名稱空間支持。 |
DefaultHandler | 缺省實(shí)現(xiàn)了四個處理器接口,方便用戶開發(fā),在開發(fā)過程中會經(jīng)常用到。 |
LocatorImpl | 提供了一個對Locator接口的實(shí)現(xiàn) |
XMLFilterImpl | 對過濾器接口的實(shí)現(xiàn),使用過濾器進(jìn)行應(yīng)用程序開發(fā)時,繼承這個類很方便。 |
XMLReaderFactory | 為方便創(chuàng)建不同的XMLReader而提供。也會經(jīng)常用到。 |
這種基于事件的處理模式是一種通用的程序設(shè)計模式,被廣泛應(yīng)用于GUI設(shè)計。在JAVA的AWT,SWING以及JAVA BEANS中就有它的身影。而SAX的基于事件驅(qū)動的處理模式就與上面三者中的非常相像。
基于事件的處理模式主要是圍繞著事件源以及事件處理器(或者叫監(jiān)聽器)來工作的。一個可以產(chǎn)生事件的對象被稱為事件源,而可以針對事件產(chǎn)生響應(yīng)的對象就被叫做事件處理器。事件源和事件處理器是通過在事件源中的事件處理器注冊方法連接的。這樣當(dāng)事件源產(chǎn)生事件后,調(diào)用事件處理器相應(yīng)的處理方法,一個事件就獲得了處理。當(dāng)然在事件源調(diào)用事件處理器中特定方法的時候,會傳遞給事件處理器相應(yīng)事件的狀態(tài)信息,這樣事件處理器才能夠根據(jù)事件信息來決定自己的行為。
在SAX接口中,事件源是org.xml.sax包中的XMLReader,它通過parse()方法來開始解析XML文檔并根據(jù)文檔內(nèi)容產(chǎn)生事件。而事件處理器則是org.xml.sax包中的ContentHandler,DTDHandler,ErrorHandler,以及EntityResolver這四個接口。它們分別處理事件源在解析過程中產(chǎn)生的不同種類的事件(其中DTDHandler是為解析文檔DTD時而用)。而事件源XMLReader和這四個事件處理器的連接是通過在XMLReader中的相應(yīng)的事件處理器注冊方法set***()來完成的。詳細(xì)介紹請見下表:
處理器名稱 | 所處理事件 | 注冊方法 |
org.xml.sax.ContentHandler | 跟文檔內(nèi)容有關(guān)的所有事件:
| XMLReader中的setContentHandler(ContentHandler handler)方法 |
org.xml.sax.ErrorHandler | 處理XML文檔解析時產(chǎn)生的錯誤。如果一個應(yīng)用程序沒有注冊一個錯誤處理器類,會發(fā)生不可預(yù)料的解析器行為。 | setErrorHandler(ErrorHandler handler) |
org.xml.sax.DTDHandler | 處理對文檔DTD進(jìn)行解析時產(chǎn)生的相應(yīng)事件 | setDTDHandler(DTDHandler handler) |
org.xml.sax.EntityResolver | 處理外部實(shí)體 | setEntityResolver(EntityResolver resolver) |
在這四個處理器接口中,對我們最重要的是ContentHandler接口。下面讓我們看一下對其中方法的說明:
方法名稱 | 方法說明 |
public void setDocumentLocator(Locator locator) | 設(shè)置一個可以定位文檔內(nèi)容事件發(fā)生位置的定位器對象 |
public void startDocument() throws SAXException | 用于處理文檔解析開始事件 |
public void endDocument() throws SAXException | 用于處理文檔解析結(jié)束事件 |
public void startPrefixMapping(java.lang.String prefix, java.lang.String uri) throws SAXException | 用于處理前綴映射開始事件,從參數(shù)中可以得到前綴名稱以及所指向的uri |
public void endPrefixMapping(java.lang.String prefix) throws SAXException | 用于處理前綴映射結(jié)束事件,從參數(shù)中可以得到前綴名稱 |
public void startElement(java.lang.String namespaceURI,java.lang.String localName,java.lang.String qName,Attributes atts) throws SAXException | 處理元素開始事件,從參數(shù)中可以獲得元素所在名稱空間的uri,元素名稱,屬性列表等信息 |
public void endElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName) throws SAXException | 處理元素結(jié)束事件,從參數(shù)中可以獲得元素所在名稱空間的uri,元素名稱等信息 |
public void characters(char[] ch, int start, int length) throws SAXException | 處理元素的字符內(nèi)容,從參數(shù)中可以獲得內(nèi)容 |
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException | 處理元素的可忽略空格 |
public void processingInstruction(java.lang.String target, java.lang.String data) throws SAXException | 處理解析中產(chǎn)生的處理指令事件 |
這里再介紹一下org.xml.sax.XMLReader中的方法,然后讓我們看一個具體的例子。XMLReader是所有兼容SAX2的解析器都要實(shí)現(xiàn)的接口,由它的方法開始解析文檔,并且調(diào)用它的注冊方法來注冊各種事件處理器。請看下表:
方法名稱 | 方法介紹 |
public Boolean getFeature(java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException | 得到某個feature的值 |
public void setFeature(java.lang.String name,boolean value) throws SAXNotRecognizedException,SAXNotSupportedException | 設(shè)置某個feature的值,例如,如果需要解析器支持對文檔進(jìn)行驗證那么就這么調(diào)用本方法。myReader.setFeature(http://xml.org/sax/features/validation,true);其中myReader是XMLReader的實(shí)例。 |
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 | 設(shè)置一個property的值 |
public void setEntityResolver(EntityResolver resolver) | 注冊處理外部實(shí)體的EntityResolver |
public EntityResolver getEntityResolver() | 得到系統(tǒng)中注冊的EntityResolver |
public void setDTDHandler(DTDHandler handler) | 注冊處理DTD解析事件的DTDHandler |
public DTDHandler getDTDHandler() | 得到系統(tǒng)中注冊的DTDHandler |
public void setContentHandler(ContentHandler handler) | 注冊處理XML文檔內(nèi)容解析事件的ContentHandler |
public ContentHandler getContentHandler() | 得到系統(tǒng)中注冊的ContentHandler |
public void setErrorHandler(ErrorHandler handler) | 注冊處理文檔解析錯誤事件的ErrorHandler |
public ErrorHandler getErrorHandler() | 得到系統(tǒng)中注冊的ErrorHandler |
public void parse(InputSource input)throws java.io.IOException,SAXException | 開始解析一個XML文檔。 |
public void parse(java.lang.String systemId)throws java.io.IOException,SAXException | 開始解析一個使用系統(tǒng)標(biāo)識符標(biāo)識的XML文檔。這個方法只是上面方法的一個快捷方式它等同于:parse(new InputSource(systemId)); |
<?xml version="1.0" encoding="GB2312"?><我的書架 > <技術(shù)書籍> <圖書> <書名>JAVA 2編程詳解</書名> <價格 貨幣單位="人民幣">150</價格> <購買日期>2000,1,24</購買日期> </圖書> </技術(shù)書籍> <book:文學(xué)書籍 xmlns:book="http://javausr.com"/> <歷史書籍/></我的書架> |
這里的例子程序只是簡單地將遇到的事件信息打印出來。我們首先實(shí)現(xiàn)ContentHandler接口來處理在XML文檔解析過程中產(chǎn)生的和文檔內(nèi)容相關(guān)的事件,代碼如下所示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("*******解析文檔結(jié)束*******"); } 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+" 結(jié)束!"); } 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 { //打印出非空的元素內(nèi)容并將StringBuffer清空 String nullStr=""; if (!buf.toString().trim().equals(nullStr)){ System.out.println("\t內(nèi)容是: " + buf.toString().trim()); } buf.setLength(0); //打印元素解析結(jié)束信息 System.out.println("元素: "+"["+fullName+"]"+" 解析結(jié)束!"); } public void characters( char[] chars, int start, int length ) throws SAXException { //將元素內(nèi)容累加到StringBuffer中 buf.append(chars,start,length); } public void ignorableWhitespace( char[] chars, int start, int length ) throws SAXException { } public void skippedEntity( String name ) throws SAXException { }} |
下面讓我們創(chuàng)建一個調(diào)入了xerces解析器來實(shí)現(xiàn)XMLReader接口、并使用剛才創(chuàng)建的MyContentHandler來處理相應(yīng)解析事件的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") ; // 創(chuàng)建ContentHandler的實(shí)例 ContentHandler contentHandler = new MyContentHandler(); // 在reader中注冊實(shí)例化的ContentHandler reader.setContentHandler( contentHandler ); // 開始解析文檔 reader.parse(args[0]); } catch ( IOException e ) { System.out.println("讀入文檔時錯: " + e.getMessage()); } catch ( SAXException e ) { System.out.println("解析文檔時錯: " + e.getMessage()); } }} |
下面讓我們來看一下執(zhí)行結(jié)果:
D:\sax\classes>java com.javausr.saxexample.MySAXApp d:\book.xml*******開始解析文檔*******元素: [我的書架] 開始解析!元素: [技術(shù)書籍] 開始解析!元素: [圖書] 開始解析!元素: [書名] 開始解析! 內(nèi)容是: JAVA 2編程詳解元素: [書名] 解析結(jié)束!元素: [價格] 開始解析! 屬性名稱:貨幣單位 屬性值:人民幣 內(nèi)容是: 150元素: [價格] 解析結(jié)束!元素: [購買日期] 開始解析! 內(nèi)容是: 2000,1,24元素: [購買日期] 解析結(jié)束!元素: [圖書] 解析結(jié)束!元素: [技術(shù)書籍] 解析結(jié)束!前綴映射: book 開始! 它的URI是:http://javausr.com元素: [book:文學(xué)書籍] 開始解析!元素: [book:文學(xué)書籍] 解析結(jié)束!前綴映射: book 結(jié)束!元素: [歷史書籍] 開始解析!元素: [歷史書籍] 解析結(jié)束!元素: [我的書架] 解析結(jié)束!*******解析文檔結(jié)束******* |
上面就是使用SAX解析一個XML文檔的基本過程,但是MyContentHandler只是處理了解析過程中和文檔內(nèi)容相關(guān)的事件,如果在解析過程中出現(xiàn)了錯誤那我們需要實(shí)現(xiàn)ErrorHandler接口來處理。如果不注冊一個錯誤處理器來處理的話,那么錯誤事件將不會被報告,而且解析器會出現(xiàn)不可預(yù)知的行為。在解析過程中產(chǎn)生的錯誤被分成了3類,它們分別是warning,error,以及fatalerror,也就是說在ErrorHandler中有這么三個相應(yīng)的方法來處理這些錯誤事件。下面是對這三個錯誤處理方法的介紹:
方法名稱 | 方法介紹 |
warning() | SAX解析器將用這個方法來報告在XML1.0規(guī)范中定義的非錯誤(error)或者致命錯誤(fatal error)的錯誤狀態(tài)。對這個錯誤缺省的行為是什么也不做。SAX解析器必須在調(diào)用這個方法后繼續(xù)提供正常的解析事件:應(yīng)用程序應(yīng)該能繼續(xù)處理完文檔。 |
error() | 這個方法對應(yīng)在W3C XML 1.0規(guī)范的1.2部分中定義的"error"概念。例如,一個帶有有效性驗證的解析器會使用這個方法來報告違反有效性驗證的情況。一個帶有有效性驗證的解析器會使用這個方法來報告違背有些性約束的情況。缺省的行為是什么也不做。SAX解析器必須在調(diào)用這個方法后繼續(xù)提供正常的解析事件:應(yīng)用程序應(yīng)該能繼續(xù)處理完文檔。如果應(yīng)用程序做不到這樣,則解析器即使在XML1.0規(guī)范沒有要求的情況下也要報告一個致命錯誤。 |
fatalError() | 這個方法對應(yīng)在W3C XML1.0規(guī)范的1.2部分定義的"fatal error"概念。例如,一個解析器會使用這個方法來報告違反格式良好約束的情況。在解析器調(diào)用這個方法后應(yīng)用程序必須表明這個文檔是不可使用的,而且應(yīng)該只是為了收集錯誤信息而繼續(xù)進(jìn)行處理(如果需要的話):實(shí)際上,一旦在這個方法被調(diào)用后SAX解析器可以停止報告任何事件。 |
下面是實(shí)現(xiàn)了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類做一些修改(在源代碼中藍(lán)色標(biāo)出的部分)使它使用MyErrorHandler.java:
package com.javausr.saxexample;
import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import org.xml.sax.ContentHandler;//引入ErrorHandlerimport 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") ; // 創(chuàng)建ContentHandler的實(shí)例 ContentHandler contentHandler = new MyContentHandler(); // 在reader中注冊實(shí)例化的ContentHandler reader.setContentHandler( contentHandler ); // 創(chuàng)建ErrorHandler的實(shí)例 ErrorHandler errorHandler = new MyErrorHandler(); // 在reader中注冊實(shí)例化的ErrorHandler reader.setErrorHandler( errorHandler ); // 開始解析文檔 reader.parse(args[0]); } catch ( IOException e ) { System.out.println("讀入文檔時錯: " + e.getMessage()); } catch ( SAXException e ) { System.out.println("解析文檔時錯: " + e.getMessage()); } } |
讓我們?nèi)藶橹圃煨╁e誤來檢查一下我們的錯誤處理器工作情況。刪除元素<購買日期>的閉合標(biāo)記,這樣會產(chǎn)生一個fatal error,下面是執(zhí)行結(jié)果:
D:\sax\classes>java com.javausr.saxexample.MySAXApp d:\book.xml
*******開始解析文檔*******元素: [我的書架] 開始解析!元素: [技術(shù)書籍] 開始解析!元素: [圖書] 開始解析!元素: [書名] 開始解析!元素: [書名] 開始解析! 內(nèi)容是: JAVA 2編程詳解元素: [書名] 解析結(jié)束!元素: [價格] 開始解析! 屬性名稱:貨幣單位 屬性值:人民幣 內(nèi)容是: 150元素: [價格] 解析結(jié)束!元素: [購買日期] 開始解析!******** 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 "</購買日期>". |
現(xiàn)在總結(jié)一下如何書寫基于SAX的應(yīng)用程序。一般步驟如下:
現(xiàn)在的程序是比較完整了,但還有許多可以改進(jìn)的地方。首先在我們實(shí)現(xiàn)的MyContentHandler.java中,你會發(fā)現(xiàn)有很多方法實(shí)際上什么也沒有做,但為了實(shí)現(xiàn)ContentHandler接口,不得不把它們寫出來,這樣很是麻煩。SAX API已經(jīng)考慮到這個問題,在它的org.xml.sax.helper包中為我們提供了一個方便實(shí)現(xiàn)各種處理器接口的幫助類DefaultHandler。這個類缺省實(shí)現(xiàn)了上面提到的4個處理器接口。這樣我們只需繼承這個類,然后覆蓋我們想要實(shí)現(xiàn)的事件處理方法即可。下面我們來新建一個繼承了DefaultHandler的MyDefaultHandler.java類,然后把在MyContentHandler.java和MyErrorHandler.java中實(shí)現(xiàn)的事件處理方法照搬到MyDefaultHandler.java類中,那些沒有使用的方法就不必重復(fù)了。這里是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("*******解析文檔結(jié)束*******"); } public void startPrefixMapping( String prefix, String uri ) {System.out.println("\n前綴映射: " + prefix +" 開始!"+ " 它的URI是:"+uri); } public void endPrefixMapping( String prefix ) { System.out.println("\n前綴映射: "+prefix+" 結(jié)束!"); } 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 { //打印出非空的元素內(nèi)容并將StringBuffer清空 String nullStr=""; if (!buf.toString().trim().equals(nullStr)){ System.out.println("\t內(nèi)容是: " + buf.toString().trim()); } buf.setLength(0); //打印元素解析結(jié)束信息 System.out.println("元素: "+"["+fullName+"]"+" 解析結(jié)束!"); } public void characters( char[] chars, int start, int length ) throws SAXException { //將元素內(nèi)容累加到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做相應(yīng)的修改,修改已在源代碼中標(biāo)出:
package com.javausr.saxexample;
import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;//引入DefaultHandlerimport 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") ; // 創(chuàng)建DefaultHandler的實(shí)例 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,以及對它的缺省實(shí)現(xiàn)org.xml.sax.helper.XMLFilterImpl。使用它們可以很容易的開發(fā)出復(fù)雜的SAX應(yīng)用。這里要先介紹一下過濾器設(shè)計模式。這個設(shè)計模式很好理解,就像一個凈化水的過程。自然界中的水流過一個個的過濾器得到最后的飲用水。這些過濾器,有的是清除水中的泥沙,有的是殺滅水中的細(xì)菌,總之不同的過濾器完成不同的任務(wù)。在應(yīng)用開發(fā)中,我們讓被改造的對象(這里是事件流)通過這些過濾器對象從而得到改造后符合要求的對象。這樣,在過濾器的幫助之下,我們可以非常方便的在每個過濾器中實(shí)現(xiàn)一個特定功能,從而創(chuàng)建結(jié)構(gòu)復(fù)雜的應(yīng)用程序。在應(yīng)用程序中你可以構(gòu)造任意多個過濾器,將它們串接起來完成任務(wù)。
在SAX API中org.xml.sax.XMLFilter接口繼承了org.xml.sax.XMLReader接口。它與XMLReader不同的是它不像XMLReader那樣通過解析文檔來獲取事件,而是從其他XMLReader中獲取事件,當(dāng)然這也包括從其他的XMLFilter中獲取事件。在org.xml.sax.XMLFilter中有兩個方法:
方法名稱 | 方法描述 |
Public void setParent(XMLReader parent) | 設(shè)置父XMLReader。這個方法讓應(yīng)用程序?qū)⑦@個過濾器連接到它的父XMLReader (也可能是另一個過濾器)。 |
Public XMLReader getParent() | 獲取父XMLReader。這個方法讓應(yīng)用程序可以查詢父XMLReader(也可能是另一個過濾器)。最好不要在父XMLReader中直接進(jìn)行任何操作:讓所有的事件通過這個過濾器來處理。 |
我們不需要自己實(shí)現(xiàn)org.xml.sax.XMLFilter接口,在SAX API 中提供了一個org.xml.sax.helper.XMLFilterImpl類,它不僅實(shí)現(xiàn)了org.xml.sax.XMLFilter接口而且還實(shí)現(xiàn)了其他四個核心處理器接口,我們只需要繼承它即可完成我們的過濾器。剛開始使用XMLFilterImpl比較容易讓人迷惑,你只需要記?。?
下面讓我們結(jié)合已有的例子來演示過濾器org.xml.sax.XMLFilter的作用。我們在這個過濾器中要過濾掉<技術(shù)書籍>這個元素,最后得到的事件流還是由上邊實(shí)現(xiàn)的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); } /** * 過濾掉元素<技術(shù)書籍>的開始事件 **/ public void startElement( String namespaceURI, String localName, String fullName, Attributes attributes ) throws SAXException { currentElement = localName; if ( !localName.equals("技術(shù)書籍") ) { super.startElement(namespaceURI, localName, fullName, attributes); } } /** * 過濾掉元素<技術(shù)書籍>的結(jié)束事件 **/ public void endElement(String namespaceURI, String localName, String fullName) throws SAXException { if ( !localName.equals("技術(shù)書籍") ) { super.endElement(namespaceURI, localName, fullName); } } /** * 過濾掉元素<技術(shù)書籍>中的內(nèi)容 **/ public void characters(char[] buffer, int start, int length) throws SAXException { if ( !currentElement.equals("技術(shù)書籍") ) { 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;//引入XMLFilterimport 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); // 創(chuàng)建DefaultHandler的實(shí)例 DefaultHandler defaultHandler=new MyDefaultHandler(); //為過濾后的事件流設(shè)置ContentHandler myFilter.setContentHandler(defaultHandler); //為過濾后的事件流設(shè)置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()); } }} |
這里是最后的執(zhí)行結(jié)果,我們可以發(fā)現(xiàn)有關(guān)<技術(shù)書籍>的全部事件已經(jīng)被過濾掉了。認(rèn)真看一下結(jié)果,你一定覺得奇怪,為什么<技術(shù)書籍>元素的孩子元素仍然存在。請記住SAX是把XML文檔解析成事件流,所有沒有被過濾的事件都會保留下來。這就是SAX和DOM的最大不同。在DOM中文檔被解析成了樹狀模型,如果你刪除一個元素,那么這個元素以及它的孩子元素就都會被刪除,這符合樹狀模型的特點(diǎn)。
D:\sax\classes>java com.javausr.saxexample.MySAXApp d:\book.xml
*******開始解析文檔*******元素: [我的書架] 開始解析!元素: [圖書] 開始解析!元素: [書名] 開始解析! 內(nèi)容是: JAVA 2編程詳解元素: [書名] 解析結(jié)束!元素: [價格] 開始解析! 屬性名稱:貨幣單位 屬性值:人民幣 內(nèi)容是: 150元素: [價格] 解析結(jié)束!元素: [購買日期] 開始解析! 內(nèi)容是: 2000,1,24元素: [購買日期] 解析結(jié)束!元素: [圖書] 解析結(jié)束!前綴映射: book 開始! 它的URI是:http://javausr.com元素: [book:文學(xué)書籍] 開始解析!元素: [book:文學(xué)書籍] 解析結(jié)束!前綴映射: book 結(jié)束!元素: [歷史書籍] 開始解析!元素: [歷史書籍] 解析結(jié)束!元素: [我的書架] 解析結(jié)束!*******解析文檔結(jié)束******* |
首先是有關(guān)元素內(nèi)容的問題,在SAX API定義中元素內(nèi)容可以在一次事件(由characters()方法處理)中返回,也可以在多次事件中返回,這樣我們就應(yīng)該考慮不能一次得到所有內(nèi)容數(shù)據(jù)的情況。一般的解決辦法是定義一個StringBuffer由它來保存內(nèi)容數(shù)據(jù),在元素結(jié)束或者新元素開始的時候清空這個StringBuffer從而可以保存新的內(nèi)容數(shù)據(jù)。請參考上面的相應(yīng)的源代碼。
還有在SAX API中特意提到從 characters(char[] ch,int start,int length)方法中提取數(shù)據(jù)時一定不要從返回的字符數(shù)組范圍之外讀取,這一點(diǎn)我們也要切記。
另一個值得注意的問題是,在 startElement()方法中返回的Attributes屬性列表中的屬性順序并沒有被特意規(guī)定,在不同的SAX實(shí)現(xiàn)中也各不相同。所以我們在編寫程序時不要把屬性順序想成一定的。
通過上面的介紹我想大家對SAX已經(jīng)有了一個基本的了解。每一個進(jìn)行XML開發(fā)的編程人員都知道DOM,那為什么在有了DOM這個功能強(qiáng)大的文檔對象模型之后,我們還需要SAX?這就要從它們根本不同的實(shí)現(xiàn)方法上來分析。DOM解析器是通過將XML文檔解析成樹狀模型并將其放入內(nèi)存來完成解析工作的,而后對文檔的操作都是在這個樹狀模型上完成的。這個在內(nèi)存中的文檔樹將是文檔實(shí)際大小的幾倍。這樣做的好處是結(jié)構(gòu)清除、操作方便,而帶來的麻煩就是極其耗費(fèi)系統(tǒng)資源。而SAX正好克服了DOM的缺點(diǎn)。SAX解析器的處理過程是通讀整個文檔,根據(jù)文檔內(nèi)容產(chǎn)生事件,而把對這些事件的處理交由事件處理器處理。SAX不需要在內(nèi)存中保存整個文檔,它對系統(tǒng)資源的節(jié)省是顯而易見的。這樣在一些需要處理大型XML文檔和性能要求比較高的場合就要用SAX了。
下面的表格列出了SAX和DOM在一些方面的對照:
SAX | DOM |
順序讀入文檔并產(chǎn)生相應(yīng)事件,可以處理任何大小的XML文檔 | 在內(nèi)存中創(chuàng)建文檔樹,不適于處理大型XML文檔。 |
只能對文檔按順序解析一遍,不支持對文檔的隨意訪問。 | 可以隨意訪問文檔樹的任何部分,沒有次數(shù)限制。 |
只能讀取XML文檔內(nèi)容,而不能修改 | 可以隨意修改文檔樹,從而修改XML文檔。 |
開發(fā)上比較復(fù)雜,需要自己來實(shí)現(xiàn)事件處理器。 | 易于理解,易于開發(fā)。 |
對開發(fā)人員而言更靈活,可以用SAX創(chuàng)建自己的XML對象模型。 | 已經(jīng)在DOM基礎(chǔ)之上創(chuàng)建好了文檔樹。 |
通過對SAX和DOM的分析,它們各有自己的不同應(yīng)用領(lǐng)域:
SAX適于處理下面的問題:
DOM適于處理下面的問題:
三種構(gòu)造解析器方法:
方法1:
方法2:
方法3: