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