級(jí)別: 初級(jí)
王曉強(qiáng) (forff@sina.com),
2001 年 11 月 01 日
這里我將向大家介紹處理 XML 文檔的另一個(gè)重要接口 SAX(Simple API for XML)。其中包括它的基本情況,它的 API,一個(gè)開(kāi)發(fā)實(shí)例,實(shí)際開(kāi)發(fā)中一些需注意的問(wèn)題,以及它與 DOM 的對(duì)比。
SAX的基本情況
SAX同DOM一樣也是一個(gè)訪問(wèn)XML文檔的接口。SAX是Simple API for XML的縮寫(xiě)。它不像DOM那樣是W3C的推薦標(biāo)準(zhǔn)。它是由XML-DEV郵件列表的成員開(kāi)發(fā)維護(hù),由David Megginson領(lǐng)導(dǎo)(david@megginson.com)的一個(gè)Public Domain軟件。SAX是一個(gè)徹底的自由軟件,它的作者放棄了對(duì)它的所有權(quán)利,并且它也被許可用于任何目的(在文章最后附錄了它的版權(quán)聲明)。
到現(xiàn)在為止SAX的版本已經(jīng)發(fā)展到2.0。在這個(gè)最新版本中增加了對(duì)名稱空間(Namespaces)的支持,而且可以通過(guò)對(duì)features以及properties的設(shè)置來(lái)對(duì)解析器做全面的配置,這其中包括設(shè)置解析器是否對(duì)文檔進(jìn)行有效性驗(yàn)證,以及怎樣來(lái)處理帶有名稱空間的元素名稱等。SAX1中的接口已經(jīng)不再使用了,這里只會(huì)討論有關(guān)SAX2的開(kāi)發(fā)。在本文中提到SAX只是指SAX 2。另外,本文的所有例子都是用java編寫(xiě),SAX解析器也使用的是JAVA版本。
像DOM一樣,SAX并不是一個(gè)實(shí)際可以使用的XML文檔解析器,而是其他兼容SAX的解析器要實(shí)現(xiàn)的接口和幫助類的集合。如果你想使用SAX的話,你必須滿足下面的要求:
- 系統(tǒng)中包含Java 1.1 或者更高版本。
- 在Java classpath中包含進(jìn)你的SAX類庫(kù)。
- 在Java classpath中包含進(jìn)你要使用的兼容SAX的XML解析器類庫(kù)。
實(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類庫(kù))。
在SAX API中有兩個(gè)包,org.xml.sax和org.xml.sax.helper。其中org.xml.sax中主要定義了SAX的一些基礎(chǔ)接口,如XMLReader、ContentHandler、ErrorHandler、DTDHandler、EntityResolver等。而在org.xml.sax.helper中則是一些方便開(kāi)發(fā)人員使用的幫助類,如缺省實(shí)現(xiàn)所有處理器接口的幫助類DefaultHandler、方便開(kāi)發(fā)人員創(chuàng)建XMLReader的XMLReaderFactory類等等。在這兩個(gè)包中還有一些應(yīng)用于SAX1的接口,同時(shí)還有幾個(gè)類它們只是為了便于將在SAX1上開(kāi)發(fā)的應(yīng)用移植到SAX2上,在這篇文章中就不涉及了。下面是我們要關(guān)注的接口和類:
Package org.xml.sax |
介紹 |
Interfaces |
接口 |
Attributes |
定義了一個(gè)屬性列表接口,供訪問(wèn)元素的屬性列表而用。 |
ContentHandler |
處理解析文檔內(nèi)容時(shí)產(chǎn)生的事件。 |
DTDHandler |
處理解析DTD時(shí)的相應(yīng)事件。 |
EntityResolver |
處理外部實(shí)體。 |
ErrorHandler |
處理解析過(guò)程中所遇到的文檔錯(cuò)誤事件。 |
Locator |
為了定位解析中產(chǎn)生的內(nèi)容事件在文檔中的位置而準(zhǔn)備的一個(gè)定位器接口。 |
XMLFilter |
提供了一個(gè)方便應(yīng)用開(kāi)發(fā)的過(guò)濾器接口。 |
XMLReader |
任何兼容SAX2的解析器都要實(shí)現(xiàn)這個(gè)接口,這個(gè)接口讓?xiě)?yīng)用程序可以設(shè)置或查找features和properties,注冊(cè)各種事件處理器,以及開(kāi)始解析文檔。 |
Classes |
InputSource |
為XML實(shí)體準(zhǔn)備的輸入源。 |
Exceptions |
SAXException |
包裝了一般的SAX錯(cuò)誤和警告。 |
SAXNotRecognizedException |
為識(shí)別不出某些標(biāo)識(shí)而拋出的異常。 |
SAXNotSupportedException |
為不支持某個(gè)操作而拋出的異常。 |
SAXParseException |
包裝了一個(gè)關(guān)于XML解析的錯(cuò)誤或者警告。 |
Package org.xml.sax.helpers |
幫助類所在的包 |
Classes |
類 |
AttributesImpl |
對(duì)Attributes接口的缺省實(shí)現(xiàn) |
NamespaceSupport |
提供名稱空間支持。 |
DefaultHandler |
缺省實(shí)現(xiàn)了四個(gè)處理器接口,方便用戶開(kāi)發(fā),在開(kāi)發(fā)過(guò)程中會(huì)經(jīng)常用到。 |
LocatorImpl |
提供了一個(gè)對(duì)Locator接口的實(shí)現(xiàn) |
XMLFilterImpl |
對(duì)過(guò)濾器接口的實(shí)現(xiàn),使用過(guò)濾器進(jìn)行應(yīng)用程序開(kāi)發(fā)時(shí),繼承這個(gè)類很方便。 |
XMLReaderFactory |
為方便創(chuàng)建不同的XMLReader而提供。也會(huì)經(jīng)常用到。 |
理解并使用SAX
SAX的設(shè)計(jì)實(shí)現(xiàn)與DOM是完全不同的!DOM處理XML文檔是基于將XML文檔解析成樹(shù)狀模型,放入內(nèi)存進(jìn)行處理。而SAX則是采用基于事件驅(qū)動(dòng)的處理模式,它將XML文檔轉(zhuǎn)化成一系列的事件,由單獨(dú)的事件處理器來(lái)決定如何處理。為了了解如何使用SAX API處理XML文檔,這里先介紹一下SAX所使用的基于事件驅(qū)動(dòng)的處理模式。
這種基于事件的處理模式是一種通用的程序設(shè)計(jì)模式,被廣泛應(yīng)用于GUI設(shè)計(jì)。在JAVA的AWT,SWING以及JAVA BEANS中就有它的身影。而SAX的基于事件驅(qū)動(dòng)的處理模式就與上面三者中的非常相像。
基于事件的處理模式主要是圍繞著事件源以及事件處理器(或者叫監(jiān)聽(tīng)器)來(lái)工作的。一個(gè)可以產(chǎn)生事件的對(duì)象被稱為事件源,而可以針對(duì)事件產(chǎn)生響應(yīng)的對(duì)象就被叫做事件處理器。事件源和事件處理器是通過(guò)在事件源中的事件處理器注冊(cè)方法連接的。這樣當(dāng)事件源產(chǎn)生事件后,調(diào)用事件處理器相應(yīng)的處理方法,一個(gè)事件就獲得了處理。當(dāng)然在事件源調(diào)用事件處理器中特定方法的時(shí)候,會(huì)傳遞給事件處理器相應(yīng)事件的狀態(tài)信息,這樣事件處理器才能夠根據(jù)事件信息來(lái)決定自己的行為。
在SAX接口中,事件源是org.xml.sax包中的XMLReader,它通過(guò)parse()方法來(lái)開(kāi)始解析XML文檔并根據(jù)文檔內(nèi)容產(chǎn)生事件。而事件處理器則是org.xml.sax包中的ContentHandler,DTDHandler,ErrorHandler,以及EntityResolver這四個(gè)接口。它們分別處理事件源在解析過(guò)程中產(chǎn)生的不同種類的事件(其中DTDHandler是為解析文檔DTD時(shí)而用)。而事件源XMLReader和這四個(gè)事件處理器的連接是通過(guò)在XMLReader中的相應(yīng)的事件處理器注冊(cè)方法set***()來(lái)完成的。詳細(xì)介紹請(qǐng)見(jiàn)下表:
處理器名稱 |
所處理事件 |
注冊(cè)方法 |
org.xml.sax.ContentHandler |
跟文檔內(nèi)容有關(guān)的所有事件:
- 文檔的開(kāi)始和結(jié)束
- XML元素的開(kāi)始和結(jié)束
- 可忽略的實(shí)體
- 名稱空間前綴映射開(kāi)始和結(jié)束
- 處理指令
- 字符數(shù)據(jù)和可忽略的空格
|
XMLReader中的setContentHandler(ContentHandler handler)方法 |
org.xml.sax.ErrorHandler |
處理XML文檔解析時(shí)產(chǎn)生的錯(cuò)誤。如果一個(gè)應(yīng)用程序沒(méi)有注冊(cè)一個(gè)錯(cuò)誤處理器類,會(huì)發(fā)生不可預(yù)料的解析器行為。 |
setErrorHandler(ErrorHandler handler) |
org.xml.sax.DTDHandler |
處理對(duì)文檔DTD進(jìn)行解析時(shí)產(chǎn)生的相應(yīng)事件 |
setDTDHandler(DTDHandler handler) |
org.xml.sax.EntityResolver |
處理外部實(shí)體 |
setEntityResolver(EntityResolver resolver) |
在這四個(gè)處理器接口中,對(duì)我們最重要的是ContentHandler接口。下面讓我們看一下對(duì)其中方法的說(shuō)明:
方法名稱 |
方法說(shuō)明 |
public void setDocumentLocator(Locator locator) |
設(shè)置一個(gè)可以定位文檔內(nèi)容事件發(fā)生位置的定位器對(duì)象 |
public void startDocument() throws SAXException |
用于處理文檔解析開(kāi)始事件 |
public void endDocument() throws SAXException |
用于處理文檔解析結(jié)束事件 |
public void startPrefixMapping(java.lang.String prefix, java.lang.String uri) throws SAXException |
用于處理前綴映射開(kāi)始事件,從參數(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 |
處理元素開(kāi)始事件,從參數(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中的方法,然后讓我們看一個(gè)具體的例子。XMLReader是所有兼容SAX2的解析器都要實(shí)現(xiàn)的接口,由它的方法開(kāi)始解析文檔,并且調(diào)用它的注冊(cè)方法來(lái)注冊(cè)各種事件處理器。請(qǐ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 |
設(shè)置某個(gè)feature的值,例如,如果需要解析器支持對(duì)文檔進(jìn)行驗(yà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 |
返回一個(gè)property的值 |
public void setProperty(java.lang.String name,java.lang.Object value)throws SAXNotRecognizedException,SAXNotSupportedException |
設(shè)置一個(gè)property的值 |
public void setEntityResolver(EntityResolver resolver) |
注冊(cè)處理外部實(shí)體的EntityResolver |
public EntityResolver getEntityResolver() |
得到系統(tǒng)中注冊(cè)的EntityResolver |
public void setDTDHandler(DTDHandler handler) |
注冊(cè)處理DTD解析事件的DTDHandler |
public DTDHandler getDTDHandler() |
得到系統(tǒng)中注冊(cè)的DTDHandler |
public void setContentHandler(ContentHandler handler) |
注冊(cè)處理XML文檔內(nèi)容解析事件的ContentHandler |
public ContentHandler getContentHandler() |
得到系統(tǒng)中注冊(cè)的ContentHandler |
public void setErrorHandler(ErrorHandler handler) |
注冊(cè)處理文檔解析錯(cuò)誤事件的ErrorHandler |
public ErrorHandler getErrorHandler() |
得到系統(tǒng)中注冊(cè)的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è)使用系統(tǒng)標(biāo)識(shí)符標(biāo)識(shí)的XML文檔。這個(gè)方法只是上面方法的一個(gè)快捷方式它等同于:parse(new InputSource(systemId)); |

 |

|
一個(gè)實(shí)例
讓我們通過(guò)例子來(lái)看一下使用SAX解析XML文檔的應(yīng)用程序是如何建立的。下面是在應(yīng)用程序中被處理的XML文檔。為了說(shuō)明SAX對(duì)名稱空間的支持,我在這里特意加了一個(gè)有名稱空間的元素,在這里會(huì)產(chǎn)生相應(yīng)的前綴映射開(kāi)始和結(jié)束事件。
<?xml version="1.0" encoding="GB2312"?>
<我的書(shū)架 >
<技術(shù)書(shū)籍>
<圖書(shū)>
<書(shū)名>JAVA 2編程詳解</書(shū)名>
<價(jià)格 貨幣單位="人民幣">150</價(jià)格>
<購(gòu)買日期>2000,1,24</購(gòu)買日期>
</圖書(shū)>
</技術(shù)書(shū)籍>
<book:文學(xué)書(shū)籍 xmlns:book="http://javausr.com"/>
<歷史書(shū)籍/>
</我的書(shū)架>
|
這里的例子程序只是簡(jiǎn)單地將遇到的事件信息打印出來(lái)。我們首先實(shí)現(xiàn)ContentHandler接口來(lái)處理在XML文檔解析過(guò)程中產(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("*******開(kāi)始解析文檔*******");
}
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 +" 開(kāi)始!"+ " 它的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+"]" +" 開(kāi)始解析!");
// 打印出屬性信息
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)建一個(gè)調(diào)入了xerces解析器來(lái)實(shí)現(xiàn)XMLReader接口、并使用剛才創(chuàng)建的MyContentHandler來(lái)處理相應(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中注冊(cè)實(shí)例化的ContentHandler
reader.setContentHandler( contentHandler );
// 開(kāi)始解析文檔
reader.parse(args[0]);
} catch ( IOException e ) {
System.out.println("讀入文檔時(shí)錯(cuò): " + e.getMessage());
} catch ( SAXException e ) {
System.out.println("解析文檔時(shí)錯(cuò): " + e.getMessage());
}
}
}
|
下面讓我們來(lái)看一下執(zhí)行結(jié)果:
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)始解析!
內(nèi)容是: JAVA 2編程詳解
元素: [書(shū)名] 解析結(jié)束!
元素: [價(jià)格] 開(kāi)始解析!
屬性名稱:貨幣單位 屬性值:人民幣
內(nèi)容是: 150
元素: [價(jià)格] 解析結(jié)束!
元素: [購(gòu)買日期] 開(kāi)始解析!
內(nèi)容是: 2000,1,24
元素: [購(gòu)買日期] 解析結(jié)束!
元素: [圖書(shū)] 解析結(jié)束!
元素: [技術(shù)書(shū)籍] 解析結(jié)束!
前綴映射: book 開(kāi)始! 它的URI是:http://javausr.com
元素: [book:文學(xué)書(shū)籍] 開(kāi)始解析!
元素: [book:文學(xué)書(shū)籍] 解析結(jié)束!
前綴映射: book 結(jié)束!
元素: [歷史書(shū)籍] 開(kāi)始解析!
元素: [歷史書(shū)籍] 解析結(jié)束!
元素: [我的書(shū)架] 解析結(jié)束!
*******解析文檔結(jié)束*******
|
上面就是使用SAX解析一個(gè)XML文檔的基本過(guò)程,但是MyContentHandler只是處理了解析過(guò)程中和文檔內(nèi)容相關(guān)的事件,如果在解析過(guò)程中出現(xiàn)了錯(cuò)誤那我們需要實(shí)現(xiàn)ErrorHandler接口來(lái)處理。如果不注冊(cè)一個(gè)錯(cuò)誤處理器來(lái)處理的話,那么錯(cuò)誤事件將不會(huì)被報(bào)告,而且解析器會(huì)出現(xiàn)不可預(yù)知的行為。在解析過(guò)程中產(chǎn)生的錯(cuò)誤被分成了3類,它們分別是warning,error,以及fatalerror,也就是說(shuō)在ErrorHandler中有這么三個(gè)相應(yīng)的方法來(lái)處理這些錯(cuò)誤事件。下面是對(duì)這三個(gè)錯(cuò)誤處理方法的介紹:
方法名稱 |
方法介紹 |
warning() |
SAX解析器將用這個(gè)方法來(lái)報(bào)告在XML1.0規(guī)范中定義的非錯(cuò)誤(error)或者致命錯(cuò)誤(fatal error)的錯(cuò)誤狀態(tài)。對(duì)這個(gè)錯(cuò)誤缺省的行為是什么也不做。SAX解析器必須在調(diào)用這個(gè)方法后繼續(xù)提供正常的解析事件:應(yīng)用程序應(yīng)該能繼續(xù)處理完文檔。 |
error() |
這個(gè)方法對(duì)應(yīng)在W3C XML 1.0規(guī)范的1.2部分中定義的"error"概念。例如,一個(gè)帶有有效性驗(yàn)證的解析器會(huì)使用這個(gè)方法來(lái)報(bào)告違反有效性驗(yàn)證的情況。一個(gè)帶有有效性驗(yàn)證的解析器會(huì)使用這個(gè)方法來(lái)報(bào)告違背有些性約束的情況。缺省的行為是什么也不做。SAX解析器必須在調(diào)用這個(gè)方法后繼續(xù)提供正常的解析事件:應(yīng)用程序應(yīng)該能繼續(xù)處理完文檔。如果應(yīng)用程序做不到這樣,則解析器即使在XML1.0規(guī)范沒(méi)有要求的情況下也要報(bào)告一個(gè)致命錯(cuò)誤。 |
fatalError() |
這個(gè)方法對(duì)應(yīng)在W3C XML1.0規(guī)范的1.2部分定義的"fatal error"概念。例如,一個(gè)解析器會(huì)使用這個(gè)方法來(lái)報(bào)告違反格式良好約束的情況。在解析器調(diào)用這個(gè)方法后應(yīng)用程序必須表明這個(gè)文檔是不可使用的,而且應(yīng)該只是為了收集錯(cuò)誤信息而繼續(xù)進(jìn)行處理(如果需要的話):實(shí)際上,一旦在這個(gè)方法被調(diào)用后SAX解析器可以停止報(bào)告任何事件。 |
下面是實(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錯(cuò)誤信息:\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錯(cuò)誤信息:\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錯(cuò)誤信息:\t" + exception.getMessage());
System.out.println("*****************************");
}
}
|
我們也要對(duì)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;
//引入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") ;
// 創(chuàng)建ContentHandler的實(shí)例
ContentHandler contentHandler = new MyContentHandler();
// 在reader中注冊(cè)實(shí)例化的ContentHandler
reader.setContentHandler( contentHandler );
// 創(chuàng)建ErrorHandler的實(shí)例
ErrorHandler errorHandler = new MyErrorHandler();
// 在reader中注冊(cè)實(shí)例化的ErrorHandler
reader.setErrorHandler( errorHandler );
// 開(kāi)始解析文檔
reader.parse(args[0]);
} catch ( IOException e ) {
System.out.println("讀入文檔時(shí)錯(cuò): " + e.getMessage());
} catch ( SAXException e ) {
System.out.println("解析文檔時(shí)錯(cuò): " + e.getMessage());
}
}
|
讓我們?nèi)藶橹圃煨╁e(cuò)誤來(lái)檢查一下我們的錯(cuò)誤處理器工作情況。刪除元素<購(gòu)買日期>的閉合標(biāo)記,這樣會(huì)產(chǎn)生一個(gè)fatal error,下面是執(zhí)行結(jié)果: 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)始解析!
內(nèi)容是: JAVA 2編程詳解
元素: [書(shū)名] 解析結(jié)束!
元素: [價(jià)格] 開(kāi)始解析!
屬性名稱:貨幣單位 屬性值:人民幣
內(nèi)容是: 150
元素: [價(jià)格] 解析結(jié)束!
元素: [購(gòu)買日期] 開(kāi)始解析!
******** FATAL ERROR ********
行: 8
列: 7
錯(cuò)誤信息: The element type "購(gòu)買日期" must be terminated by the matching end-tag "</購(gòu)買日期>".
*****************************
解析文檔時(shí)錯(cuò): Stopping after fatal error: The element type "購(gòu)買日期"
must be terminated by the matching end-tag "</購(gòu)買日期>".
|
現(xiàn)在總結(jié)一下如何書(shū)寫(xiě)基于SAX的應(yīng)用程序。一般步驟如下:
- 實(shí)現(xiàn)一個(gè)或多個(gè)處理器接口(ContentHandler, ErrorHandler, DTDHandler ,or EntityResover)。
- 創(chuàng)建一個(gè)XMLReader類的實(shí)例。
- 在新的XMLReader實(shí)例中通過(guò)大量的set*****() 方法注冊(cè)一個(gè)事件處理器的實(shí)例
- 調(diào)用XMLReader的parse()方法來(lái)處理文檔。
使用DefaultHandler
現(xiàn)在的程序是比較完整了,但還有許多可以改進(jìn)的地方。首先在我們實(shí)現(xiàn)的MyContentHandler.java中,你會(huì)發(fā)現(xiàn)有很多方法實(shí)際上什么也沒(méi)有做,但為了實(shí)現(xiàn)ContentHandler接口,不得不把它們寫(xiě)出來(lái),這樣很是麻煩。SAX API已經(jīng)考慮到這個(gè)問(wèn)題,在它的org.xml.sax.helper包中為我們提供了一個(gè)方便實(shí)現(xiàn)各種處理器接口的幫助類DefaultHandler。這個(gè)類缺省實(shí)現(xiàn)了上面提到的4個(gè)處理器接口。這樣我們只需繼承這個(gè)類,然后覆蓋我們想要實(shí)現(xiàn)的事件處理方法即可。下面我們來(lái)新建一個(gè)繼承了DefaultHandler的MyDefaultHandler.java類,然后把在MyContentHandler.java和MyErrorHandler.java中實(shí)現(xiàn)的事件處理方法照搬到MyDefaultHandler.java類中,那些沒(méi)有使用的方法就不必重復(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("*******開(kāi)始解析文檔*******");
}
public void endDocument() throws SAXException {
System.out.println("*******解析文檔結(jié)束*******");
}
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+" 結(jié)束!");
}
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屬性名稱:" + 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錯(cuò)誤信息:\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錯(cuò)誤信息:\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錯(cuò)誤信息:\t" + exception.getMessage());
System.out.println("*****************************");
}
}
|
我們也要對(duì)MySAXApp.java做相應(yīng)的修改,修改已在源代碼中標(biāo)出: 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") ;
// 創(chuàng)建DefaultHandler的實(shí)例
DefaultHandler defaultHandler=new MyDefaultHandler();
//在reader中將defaultHandler注冊(cè)為ContentHandler
reader.setContentHandler(defaultHandler);
//在reader中將defaultHandler注冊(cè)為ErrorHandler
reader.setErrorHandler(defaultHandler);
// 開(kāi)始解析文檔
reader.parse(args[0]);
} catch ( IOException e ) {
System.out.println("讀入文檔時(shí)錯(cuò): " + e.getMessage());
} catch ( SAXException e ) {
System.out.println("解析文檔時(shí)錯(cuò): " + e.getMessage());
}
}
}
|
使用過(guò)濾器
在SAX API中還提供了一個(gè)過(guò)濾器接口org.xml.sax.XMLFilter,以及對(duì)它的缺省實(shí)現(xiàn)org.xml.sax.helper.XMLFilterImpl。使用它們可以很容易的開(kāi)發(fā)出復(fù)雜的SAX應(yīng)用。這里要先介紹一下過(guò)濾器設(shè)計(jì)模式。這個(gè)設(shè)計(jì)模式很好理解,就像一個(gè)凈化水的過(guò)程。自然界中的水流過(guò)一個(gè)個(gè)的過(guò)濾器得到最后的飲用水。這些過(guò)濾器,有的是清除水中的泥沙,有的是殺滅水中的細(xì)菌,總之不同的過(guò)濾器完成不同的任務(wù)。在應(yīng)用開(kāi)發(fā)中,我們讓被改造的對(duì)象(這里是事件流)通過(guò)這些過(guò)濾器對(duì)象從而得到改造后符合要求的對(duì)象。這樣,在過(guò)濾器的幫助之下,我們可以非常方便的在每個(gè)過(guò)濾器中實(shí)現(xiàn)一個(gè)特定功能,從而創(chuàng)建結(jié)構(gòu)復(fù)雜的應(yīng)用程序。在應(yīng)用程序中你可以構(gòu)造任意多個(gè)過(guò)濾器,將它們串接起來(lái)完成任務(wù)。
在SAX API中org.xml.sax.XMLFilter接口繼承了org.xml.sax.XMLReader接口。它與XMLReader不同的是它不像XMLReader那樣通過(guò)解析文檔來(lái)獲取事件,而是從其他XMLReader中獲取事件,當(dāng)然這也包括從其他的XMLFilter中獲取事件。在org.xml.sax.XMLFilter中有兩個(gè)方法:
方法名稱 |
方法描述 |
Public void setParent(XMLReader parent) |
設(shè)置父XMLReader。這個(gè)方法讓?xiě)?yīng)用程序?qū)⑦@個(gè)過(guò)濾器連接到它的父XMLReader (也可能是另一個(gè)過(guò)濾器)。 |
Public XMLReader getParent() |
獲取父XMLReader。這個(gè)方法讓?xiě)?yīng)用程序可以查詢父XMLReader(也可能是另一個(gè)過(guò)濾器)。最好不要在父XMLReader中直接進(jìn)行任何操作:讓所有的事件通過(guò)這個(gè)過(guò)濾器來(lái)處理。 |
我們不需要自己實(shí)現(xiàn)org.xml.sax.XMLFilter接口,在SAX API 中提供了一個(gè)org.xml.sax.helper.XMLFilterImpl類,它不僅實(shí)現(xiàn)了org.xml.sax.XMLFilter接口而且還實(shí)現(xiàn)了其他四個(gè)核心處理器接口,我們只需要繼承它即可完成我們的過(guò)濾器。剛開(kāi)始使用XMLFilterImpl比較容易讓人迷惑,你只需要記住:
- 在你繼承的XMLFilterImpl類中用set****()方法這冊(cè)的事件處理器是給過(guò)濾后的事件流而用的。
- 在你繼承的XMLFilterImpl類中實(shí)現(xiàn)的那些事件處理方法,比如startDocument()、startElement()、characters()等才是這個(gè)過(guò)濾器實(shí)現(xiàn)它自身功能的地方。而通過(guò)繼承XMLFilterImpl而實(shí)現(xiàn)的這個(gè)類會(huì)被造型成各種處理器(它本身實(shí)現(xiàn)了四個(gè)處理器接口)用在它的父XMLReader中。這個(gè)步驟會(huì)在你調(diào)用自己創(chuàng)建的過(guò)濾器的parse()方法開(kāi)始解析文檔時(shí)被自動(dòng)執(zhí)行(請(qǐng)參見(jiàn)SAX源代碼)。
- 如果不是使用帶參數(shù)的構(gòu)造器創(chuàng)建XMLFilter對(duì)象,務(wù)必使用setParent(XMLReader parent)方法連接它的父XMLReader。
- 如果使用多個(gè)過(guò)濾器的話,執(zhí)行順序是從父親到最后的過(guò)濾器。但是開(kāi)始解析卻要調(diào)用最后一個(gè)過(guò)濾器的parse()方法。
下面讓我們結(jié)合已有的例子來(lái)演示過(guò)濾器org.xml.sax.XMLFilter的作用。我們?cè)谶@個(gè)過(guò)濾器中要過(guò)濾掉<技術(shù)書(shū)籍>這個(gè)元素,最后得到的事件流還是由上邊實(shí)現(xiàn)的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ū)籍>的結(jié)束事件
**/
public void endElement(String namespaceURI, String localName, String
fullName)
throws SAXException {
if ( !localName.equals("技術(shù)書(shū)籍") ) {
super.endElement(namespaceURI, localName, fullName);
}
}
/**
* 過(guò)濾掉元素<技術(shù)書(shū)籍>中的內(nèi)容
**/
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;
//引入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") ;
//初始化過(guò)濾器
XMLFilter myFilter=new MyFilter(reader);
// 創(chuàng)建DefaultHandler的實(shí)例
DefaultHandler defaultHandler=new MyDefaultHandler();
//為過(guò)濾后的事件流設(shè)置ContentHandler
myFilter.setContentHandler(defaultHandler);
//為過(guò)濾后的事件流設(shè)置ErrorHandler
myFilter.setErrorHandler(defaultHandler);
// 開(kāi)始解析文檔,注意是使用myFilter中的解析方法
myFilter.parse(args[0]);
} catch ( IOException e ) {
System.out.println("讀入文檔時(shí)錯(cuò): " + e.getMessage());
} catch ( SAXException e ) {
System.out.println("解析文檔時(shí)錯(cuò): " + e.getMessage());
}
}
}
|
這里是最后的執(zhí)行結(jié)果,我們可以發(fā)現(xiàn)有關(guān)<技術(shù)書(shū)籍>的全部事件已經(jīng)被過(guò)濾掉了。認(rèn)真看一下結(jié)果,你一定覺(jué)得奇怪,為什么<技術(shù)書(shū)籍>元素的孩子元素仍然存在。請(qǐng)記住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)始解析!
內(nèi)容是: JAVA 2編程詳解
元素: [書(shū)名] 解析結(jié)束!
元素: [價(jià)格] 開(kāi)始解析!
屬性名稱:貨幣單位 屬性值:人民幣
內(nèi)容是: 150
元素: [價(jià)格] 解析結(jié)束!
元素: [購(gòu)買日期] 開(kāi)始解析!
內(nèi)容是: 2000,1,24
元素: [購(gòu)買日期] 解析結(jié)束!
元素: [圖書(shū)] 解析結(jié)束!
前綴映射: book 開(kāi)始! 它的URI是:http://javausr.com
元素: [book:文學(xué)書(shū)籍] 開(kāi)始解析!
元素: [book:文學(xué)書(shū)籍] 解析結(jié)束!
前綴映射: book 結(jié)束!
元素: [歷史書(shū)籍] 開(kāi)始解析!
元素: [歷史書(shū)籍] 解析結(jié)束!
元素: [我的書(shū)架] 解析結(jié)束!
*******解析文檔結(jié)束*******
|
一些值得注意的問(wèn)題
首先是有關(guān)元素內(nèi)容的問(wèn)題,在SAX API定義中元素內(nèi)容可以在一次事件(由characters()方法處理)中返回,也可以在多次事件中返回,這樣我們就應(yīng)該考慮不能一次得到所有內(nèi)容數(shù)據(jù)的情況。一般的解決辦法是定義一個(gè)StringBuffer由它來(lái)保存內(nèi)容數(shù)據(jù),在元素結(jié)束或者新元素開(kāi)始的時(shí)候清空這個(gè)StringBuffer從而可以保存新的內(nèi)容數(shù)據(jù)。請(qǐng)參考上面的相應(yīng)的源代碼。
還有在SAX API中特意提到從 characters(char[] ch,int start,int length)方法中提取數(shù)據(jù)時(shí)一定不要從返回的字符數(shù)組范圍之外讀取,這一點(diǎn)我們也要切記。
另一個(gè)值得注意的問(wèn)題是,在 startElement()方法中返回的Attributes屬性列表中的屬性順序并沒(méi)有被特意規(guī)定,在不同的SAX實(shí)現(xiàn)中也各不相同。所以我們?cè)诰帉?xiě)程序時(shí)不要把屬性順序想成一定的。
SAX與DOM的比較
通過(guò)上面的介紹我想大家對(duì)SAX已經(jīng)有了一個(gè)基本的了解。每一個(gè)進(jìn)行XML開(kāi)發(fā)的編程人員都知道DOM,那為什么在有了DOM這個(gè)功能強(qiáng)大的文檔對(duì)象模型之后,我們還需要SAX?這就要從它們根本不同的實(shí)現(xiàn)方法上來(lái)分析。DOM解析器是通過(guò)將XML文檔解析成樹(shù)狀模型并將其放入內(nèi)存來(lái)完成解析工作的,而后對(duì)文檔的操作都是在這個(gè)樹(shù)狀模型上完成的。這個(gè)在內(nèi)存中的文檔樹(shù)將是文檔實(shí)際大小的幾倍。這樣做的好處是結(jié)構(gòu)清除、操作方便,而帶來(lái)的麻煩就是極其耗費(fèi)系統(tǒng)資源。而SAX正好克服了DOM的缺點(diǎn)。SAX解析器的處理過(guò)程是通讀整個(gè)文檔,根據(jù)文檔內(nèi)容產(chǎn)生事件,而把對(duì)這些事件的處理交由事件處理器處理。SAX不需要在內(nèi)存中保存整個(gè)文檔,它對(duì)系統(tǒng)資源的節(jié)省是顯而易見(jiàn)的。這樣在一些需要處理大型XML文檔和性能要求比較高的場(chǎng)合就要用SAX了。
下面的表格列出了SAX和DOM在一些方面的對(duì)照:
SAX |
DOM |
順序讀入文檔并產(chǎn)生相應(yīng)事件,可以處理任何大小的XML文檔 |
在內(nèi)存中創(chuàng)建文檔樹(shù),不適于處理大型XML文檔。 |
只能對(duì)文檔按順序解析一遍,不支持對(duì)文檔的隨意訪問(wèn)。 |
可以隨意訪問(wèn)文檔樹(shù)的任何部分,沒(méi)有次數(shù)限制。 |
只能讀取XML文檔內(nèi)容,而不能修改 |
可以隨意修改文檔樹(shù),從而修改XML文檔。 |
開(kāi)發(fā)上比較復(fù)雜,需要自己來(lái)實(shí)現(xiàn)事件處理器。 |
易于理解,易于開(kāi)發(fā)。 |
對(duì)開(kāi)發(fā)人員而言更靈活,可以用SAX創(chuàng)建自己的XML對(duì)象模型。 |
已經(jīng)在DOM基礎(chǔ)之上創(chuàng)建好了文檔樹(shù)。 |
通過(guò)對(duì)SAX和DOM的分析,它們各有自己的不同應(yīng)用領(lǐng)域:
- SAX適于處理下面的問(wèn)題:
- 對(duì)大型文檔進(jìn)行處理。
- 只需要文檔的部分內(nèi)容,或者只需要從文檔中得到特定信息。
- 想創(chuàng)建自己的對(duì)象模型的時(shí)候。
DOM適于處理下面的問(wèn)題:
- 需要對(duì)文檔進(jìn)行修改
- 需要隨機(jī)對(duì)文檔進(jìn)行訪問(wèn),例如XSLT解析器。
對(duì)SAX的介紹到這里就告一段落了,希望能對(duì)大家有所幫助:),本文的絕大部分參考資料都來(lái)源于http://www.megginson.com/SAX/ 以及SAX API(雖然說(shuō)SAX有了自己新的網(wǎng)站http://sax.sourceforge.net/ 但我從來(lái)沒(méi)有成功訪問(wèn)過(guò)!) ,感謝David Megginson和其他SAX開(kāi)發(fā)人員給我們提供了這么一個(gè)好東東。本文如有錯(cuò)誤和不妥的地方還請(qǐng)大家指正。
附錄: SAX的版權(quán)聲明
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
參考資料
學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
- IBM 試用版軟件:使用 IBM 試用版軟件構(gòu)建您的下一個(gè)開(kāi)發(fā)項(xiàng)目,這些試用版軟件可直接從 developerWorks 下載獲得。
討論
關(guān)于作者
 |
|
 |
王曉強(qiáng),萬(wàn)千程序開(kāi)發(fā)者中的一員,并樂(lè)在其中。熱愛(ài)java和linux,一直利用java和xml相關(guān)技術(shù)進(jìn)行應(yīng)用開(kāi)發(fā),并在這些方面積累了豐富經(jīng)驗(yàn)。可通過(guò)grekiller@yeah.net與他聯(lián)系。
|