就目前來說,有三種方式可以解析XML文件:DOM、SAX、StAX。DOM將整個XML文件加載到內(nèi)存中,并構(gòu)建出節(jié)點樹;應(yīng)用程序可以通過遍歷節(jié)點樹的方式來解析XML文件中的各個節(jié)點、屬性等信息;這種方式便于對XML節(jié)點的添加修改等,而且解析也很方便,然后它比較耗費內(nèi)存,解析速度也不快。SAX則是基于事件的解析,解析器在一次讀取XML文件中根據(jù)讀取的數(shù)據(jù)產(chǎn)生相應(yīng)的事件,由應(yīng)用程序?qū)崿F(xiàn)相應(yīng)的事件處理邏輯,即它是一種“推”的解析方式;這種解析方法速度快、占用內(nèi)存少,但是它需要應(yīng)用程序自己處理解析器的狀態(tài),實現(xiàn)起來會比較麻煩,而且它只支持對XML文件的讀取,不支持寫入。不同于SAX的“推”的解析方式,StAX是基于“拉”的解析方式,即應(yīng)用程序根據(jù)自己的需要控制解析器的讀取;這種方式繼承了SAX解析速度快、占用內(nèi)存少等優(yōu)點,同時它好保持了接口簡單、編程容易等特點;不過它也應(yīng)該不支持寫XML文件的,木有仔細(xì)看過這個框架,先猜測一下~~。貌似DOM底層采用了SAX的實現(xiàn),因而本文首先介紹基于SAX方式的XML文件解析。
SAX的解析框架相對比較簡單,以下是它核心類關(guān)系圖:

InputSource類
InputSource是SAX中對要被解析的XML資源文件的抽象,它封裝了以下信息:
private Reader characterStream;
private InputStream byteStream;
private String systemId;
private String publicId;
private String encoding;
應(yīng)用程序可以顯示的設(shè)置characterStream、byteStream來指定實際的XML資源文件,或者使用systemId和publicId的方式來定位XML資源文件。這里systemId、publicId借用了導(dǎo)入DTD文件是定義的SYSTEM、PUBLIC概念。在DTD中,SYSTEM和PUBLIC都表示外部資源文件,所不同的是SYSTEM指定的是具體的資源文件,它可以是相對路徑也可以是絕對路徑,而PUBLIC則是使用定義的名稱查找資源文件。如以下是Spring使用DTD時的寫法:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" " http://www.springframework.org/dtd/spring-beans.dtd ">
這里的publicId是:“-//SPRING//DTD BEAN//EN”
systemId是:“http://www.springframework.org/dtd/spring-beans.dtd”
很多框架都實現(xiàn)了自己的EntityResolver,以實現(xiàn)自定義的Entity查找邏輯,就像Spring,它首先在當(dāng)前ClassPath下查找對應(yīng)的DTD文件(Spring.jar文件中)。
在實現(xiàn)中,很少去使用publicId的,以Spring源碼中沒有使用publicId,甚至我在xerces源碼中都沒有看到對publicId的處理,不過看它的文檔,貌似是提供了publicId的實現(xiàn),不知道是Java的實現(xiàn)版本沒有提供還是我沒有找對地方,而且按測試的結(jié)果,它不能只存在publicId也就是說如果存在publicId的話,systemId也必須存在。按著文檔對publicId做一個簡單的解釋,在以上的定義中“-//SPRINT/DTD BEAN//EN”只是一個名字,在XML解析引擎中使用這個名字去查找資源文件真正的位置,這個名字和資源文件實際路徑的映射文件一般定義在一個catalog文件中(有些引擎支持配置),其定義格式一般為:
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<public publicId="-//OASIS//DTD XML DocBook V4.1.2//EN"
uri="docbook/xml/docbookx.dtd"/>
<system systemId="urn:x-oasis:docbook-xml-v4.1.2"
uri="docbook/xml/docbookx.dtd"/>
<delegatePublic publicIdStartString="-//Example//"
catalog="http://www.example.com/catalog"/>
<public publidId="-//SPRING//DTD BEAN//EN"
uri="http://www.springframework.org/dtd/spring-beans.dtd" />
</catalog>
publicId以目前來看感覺可以忽略。有興趣對這個做深入研究的童鞋可以參考一下文檔:
http://xerces.apache.org/xml-commons/components/resolver/resolver-article.html
http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/docbooksys/segmentedhtml/ch08s02.html
在使用InputSource時,還需要注意資源文件的查找順序問題,即InputSource中的characterStream、byteStream、systemId都可以表示一個資源文件,因而需要定義它們的查找順序:即先查看characterStream字段,如果有值則使用(此時encoding字段無效);然后查看byteStream字段,如果有值,則使用;最后嘗試使用URI解析systemId字段(可以是相對路徑),如果能找到對應(yīng)的資源文件,則使用;否則,拋出異常。對后兩種情況,可以指定encoding字段表示資源文件的編碼方式。
XMLReaderFactory類
XMLReaderFactory從這個類的名字中已經(jīng)能知道它是用于創(chuàng)建XMLReader實例的工場類,它提供兩個靜態(tài)方法以創(chuàng)建XMLReader實例:
public static XMLReader createXMLReader();
public static XMLReader createXMLReader(String className);
對不帶參數(shù)的createXMLReader()方法,它實現(xiàn)了一種動態(tài)查找XMLReader具體實現(xiàn)類的方式:
1. 首先查看系統(tǒng)屬性中是否存在“org.xml.sax.driver”屬性的定義,如果存在,則使用該屬性定義的XMLReader實現(xiàn)類。
2. 其次查看ClassPath下是否存在“META-INF/services/org.xml.sax.driver”文件的定義,如果存在,則使用該文件中定義XMLReader的實現(xiàn)類。
3. 否則,默認(rèn)使用“com.sun.org.apache.xerces.internal.parsers.SAXParser”類作為XMLReader的實現(xiàn)類。
而對帶參數(shù)的createXMLReader()工場方法來說,它只是實例化傳入的XMLReader的實現(xiàn)類。
XMLReader接口和實現(xiàn)
XMLReader是實現(xiàn)真正解析XML資源文件的接口,之所以不使用Parser是因為這個名稱已經(jīng)在SAX1中被使用,而在SAX2中將實現(xiàn)解析的接口名稱重命名成XMLReader。在使用SAX解析XML資源文件時,默認(rèn)使用SAXParser實現(xiàn)類,它繼承自AbstractSAXParser(參考以上類關(guān)系圖)。XMLReader接口提供以下方法:
public interface XMLReader {
public boolean getFeature (String name) throws SAXNotRecognizedException, SAXNotSupportedException;
public void setFeature (String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException;
public Object getProperty (String name) throws SAXNotRecognizedException, SAXNotSupportedException;
public void setProperty (String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException;
public void setEntityResolver (EntityResolver resolver);
public EntityResolver getEntityResolver ();
public void setDTDHandler (DTDHandler handler);
public DTDHandler getDTDHandler ();
public void setContentHandler (ContentHandler handler);
public ContentHandler getContentHandler ();
public void setErrorHandler (ErrorHandler handler);
public ErrorHandler getErrorHandler ();
public void parse (InputSource input) throws IOException, SAXException;
public void parse (String systemId) throws IOException, SAXException;
}
這個接口定義了一些操作XML解析器的屬性和方法:
1. Feature
Feature值一般是一個URI的全稱,用于定義當(dāng)前解析器支持的特性,比如按注釋,所有解析器都要識別的特性有:
http://xml.org/sax/features/namespaces
http://xml.org/sax/features/namespace-prefixes
還有其他一些常見的Feature有(默認(rèn)AbstractSAXParser支持的Feature):
http://xml.org/sax/features/string-interning
http://xml.org/sax/features/is-standalone
http://xml.org/sax/features/xml-1.1
http://xml.org/sax/features/lexical-handler/parameter-entities
http://xml.org/sax/features/resolve-dtd-uris
http://xml.org/sax/features/xmlns-uris
http://xml.org/sax/features/unicode-normalization-checking
http://xml.org/sax/features/use-entity-resolver2
http://xml.org/sax/features/use-attributes2
http://xml.org/sax/features/use-locator2
http://xml.org/sax/features/internal/parser-settings
http://xml.org/sax/features/internal/xinclude
一些用戶自定義的解析器可以指定自己支持的Feature。XMLReader提供接口查詢、設(shè)置指定當(dāng)前XMLReader是否支持某種Feature。
2. Property
XMLReader還支持通過URI方式獲取解析器相關(guān)的一些屬性值,一些擴(kuò)展的事件Handler也可以通過該方式定義。如常見的屬性定義有:
http://xml.org/sax/properties/document-xml-version
http://xml.org/sax/properties/lexical-handler
http://xml.org/sax/properties/declaration-handler
http://xml.org/sax/properties/dom-node
具體可以參考xerces中的介紹:
http://xerces.apache.org/xerces2-j/properties.html
3. 事件處理器(Event Handlers)注冊方法
應(yīng)用程序通過注冊相應(yīng)的事件處理器來和XMLReader解析器教務(wù),解析器在解析過程中產(chǎn)生的事件都會通過調(diào)用相應(yīng)事件處理器中的相應(yīng)的方法來將信息傳給應(yīng)用程序。默認(rèn)支持的時間處理器有:
EntityResolver:實現(xiàn)用戶自定義外部實體解析處理邏輯。
DTDHandler:實現(xiàn)在NOTATION定義以及存在沒有解析的Entity定義時,用戶可以加入自定義的處理邏輯。
ContentHandler:處理所有對XML內(nèi)容解析時產(chǎn)生的所有事件。
ErrorHandler:解析器在解析過程中遇到錯誤時被調(diào)用。
通過設(shè)置屬性值的方式,AbstractSAXParser還支持以下兩種Handler:
LexicalHandler:SAX2擴(kuò)展接口,提供更多的XML資源文件相關(guān)的信息,如注釋、CDATA等。
DeclHandler:SAX2擴(kuò)展接口,在DTD定義事件中提供回調(diào)方法。
4. 解析方法(parse)
解析器實現(xiàn)解析XML資源文件的方法。應(yīng)用程序可以傳入InputSource實例,也可以傳入systemId的值,即一個URI字符串或相對路徑指定的文件名。解析方法(parse)是線程同步的,對一個XMLReader實例在解析時,另一個線程會等待該線程結(jié)束后才開始解析新的文件,因而一般情況下,在多個線程中都會創(chuàng)建各自的XMLReader實例。然而當(dāng)一個XMLReader實例解析完成后,我們可以重用該XMLReader實例解析新的XML文件,此時之前的Feature、Property以及Handler的注冊等信息都保持不變,我們可以手動調(diào)用相應(yīng)的方法改變之。
EntityResolver接口
EntityResolver接口提供應(yīng)用程序自定義實體解析的擴(kuò)展點,即應(yīng)用程序可以注冊自己的實體解析類以實現(xiàn)自己特定的邏輯,如在本地、數(shù)據(jù)庫、網(wǎng)絡(luò)中查找外部實體。比如Spring就實現(xiàn)了自己的BeansDtdResolver,對InputSource小節(jié)中定義的spring-beans.dtd,它會先查找BeanDtdResolver類所在的目錄(一般為jar包內(nèi))中存在的spring-beans.dtd,如果存在該文件,則會加載該文件作為DTD文件,否則,使用默認(rèn)的EntityResolver(即返回null,則XML引擎自己提供的解析器)。
然而什么是實體(Entity)呢?如果使用DTD作為XML文件的定義模板,那么在引入DTD文件時,即使引入一個外部實體,其PUBLIC、SYSTEM定義分別對應(yīng)publicId和systemId。實體的另一個使用地方是在DTD文件定義時可以定義命名實體,而該命名實體可以在XML文件中使用(貌似這個用途不怎么多~~)。比如我們可以定義如下DTD文件:
<!ELEMENT website (name,copyright)>
<!ELEMENT name (#PCDATA)>
<!-- Parameter Entity-->
<!ENTITY % copyrightElement "<!ELEMENT copyright (#PCDATA)>">
%copyrightElement;
<!--Normal Entity-->
<!ENTITY name "cnblog">
<!--External Entity-->
<!ENTITY copyright SYSTEM "copyright.desc">
該DTD可以在以下XML文件中使用:
<?xml version="1.1" encoding="UTF-8"?>
<!DOCTYPE website SYSTEM "../dtds/entitiesDtD.dtd">
<website>
<name>&name;</name>
<copyright>©right;</copyright>
</website>
此時,在解析XML文件時,&name;值為cnblog,而©right;的值為copyright.desc文件中的內(nèi)容。EntityResolver接口也只有在需要解析外部實體是才會被調(diào)用,比如在解析&name;實體時就不會被調(diào)用。
EntityResolver的接口定義如下:
public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
}
resolveEntity()方法返回用戶自定義的InputSource實例,如果該方法返回null,則XML解析引擎默認(rèn)為外部實體為URL,并使用該URL創(chuàng)建InputSource。
一般情況下使用XML解析引擎內(nèi)部默認(rèn)的實現(xiàn)即可,但是像Spring這種從本地讀取DTD文件時,則需要實現(xiàn)自己的EntityResolver,其實現(xiàn)核心代碼如下:
public InputSource resolveEntity(String publicId, String systemId) throws IOException {
if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
......
Resource resource = new ClassPathResource(dtdFile, getClass());
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
return source;
......
}
// Use the default behavior -> download from website or wherever.
return null;
}
如果使用XSD作為XML的定義模板,我們可以定義schemaLocation將XSD文件引入,比如Spring配置文件中的用法:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myCustomer" class="levin.spring.test.Customer">
<property name="name" value="customer"></property>
<property name="address" value="shanghai"></property>
<property name="user" ref="user"></property>
</bean>
<context:property-holder id="propertyHolder" class="org.springframework.context.PropertyHolder" />
</beans>
以上例子,我們將所有XML的命名空間定義在beans標(biāo)簽中,并在標(biāo)簽中使用xsi:schemaLocation定義了所有xsd文件的位置,在XML解析中,每當(dāng)遇到一個新的命名空間,解析器就會先調(diào)用resolveEntity方法,并將xsi:schemaLocation中定義的位置傳入resolveEntity()方法(publicId為null、systemId為schema的位置,如http://www.springframework.org/schema/context/spring-context.xsd, 默認(rèn)解析器會從這個位置上查找xsd文件,然后開始解析相應(yīng)的標(biāo)簽,比如對默認(rèn)命名空間所使用的xsd文件,SAXParser會先解析該文件,然后真正解析beans標(biāo)簽。如DTD作為外部實體的導(dǎo)入,應(yīng)用程序可以定義自己的EntityResolver以實現(xiàn)自定義的外部實體查找邏輯,如Spring定義自己的EntityResolver(PluggableSchemaResolver),以從本地查找xsd文件:
public InputSource resolveEntity(String publicId, String systemId) throws IOException {
String resourceLocation = getSchemaMapping(systemId);
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
return source;
}
// Use the default behavior -> download from website or wherever.
return null;
}
其實以上配置文件還可以寫成:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myCustomer" class="levin.spring.test.Customer">
<property name="name" value="customer"></property>
<property name="address" value="shanghai"></property>
<property name="user" ref="user"></property>
</bean>
<context:property-holder xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
id="propertyHolder" class="org.springframework.context.PropertyHolder" />
</beans>
最后,默認(rèn)SAXParser默認(rèn)實現(xiàn)不支持XSD文件的解析,我們需要手動的設(shè)置以下屬性以打開這一屬性:
XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); reader.setFeature("http://apache.org/xml/features/validation/schema", true);
DTDHandler接口
提供用戶在NOTATION定義以及存在沒有解析的Entity定義時注冊用戶自定義的處理邏輯。這里需要解釋兩個定義:什么是NOTATION以及什么是沒有解析的Entity定義。
首先來解釋一下什么是NOTATION:在XML文件中可以包含一些非XML的數(shù)據(jù),NOTATION即定義了這種非XML數(shù)據(jù)的格式,從而應(yīng)用程序可以識別這些數(shù)據(jù)。然而什么是非XML數(shù)據(jù)呢?舉個例子,以下DTD定義了一個image標(biāo)簽,它包含source屬性,而這個source屬性指向某個gif圖片:
<!NOTATION gif SYSTEM "image/gif">
<!ENTITY JENN SYSTEM "http://images.about.com/sites/guidepics/html.gif" NDATA gif>
<!ELEMENT image EMPTY>
<!ATTLIST image source ENTITY #REQUIRED>
對應(yīng)的XML數(shù)據(jù)段:
<image source="JENN" />
我們知道在XML文件文本數(shù)據(jù),中一般只能包含文本數(shù)據(jù),那么如何將圖片文件嵌入到XML文件中呢?XML提供了三種方法實現(xiàn)在XML文件中包含二進(jìn)制數(shù)據(jù):一種是使用以上NOTATION的方式;一種是使用mine擴(kuò)展;還有一種是將二進(jìn)制數(shù)據(jù)保存到<![CDATA[ …]]>中(具體可以參考:http://www.ibm.com/developerworks/cn/xml/x-binary/index.html)。
其次,什么是沒有解析的Entity定義呢?沒有解析并不是在DTD文件中所有沒有被使用到的Entity,而是指所有那些NDATA類型的實體定義,因為這些實體需要用戶自己去解析,因而XML解析引擎不會默認(rèn)對他們做解析。比如以上的&name;實體是被解析過的,因而它會轉(zhuǎn)換成“cnblog”值顯示出來;而以上JENN的這個實體則沒有被XML解析引擎解析,因而它會觸發(fā)DTDHandler接口的調(diào)用。
那么我們再來看一下DTDHandler接口提供的方法吧:
public interface DTDHandler {
public abstract void notationDecl (String name, String publicId,
String systemId) throws SAXException;
public abstract void unparsedEntityDecl (String name, String publicId,
String systemId, String notationName) throws SAXException;
}
這兩個方法正好提供了DTDHandler兩種特性的回調(diào)方法:
1. 當(dāng)XML解析引擎在解析DTD文件中NOTATION的定義時,它會調(diào)用notationDecl()方法,此時用戶注冊的DTDHandler則可以根據(jù)傳入的publicId和systemId解析當(dāng)前NOTATION相應(yīng)的數(shù)據(jù)。
2. 當(dāng)XML解析引擎在解析DTD文件中NDATA類型的Entity定義時,它會調(diào)用unparsedEntityDecl()方法。用戶可以根據(jù)傳入的publicId、systemId、notationName等信息解析當(dāng)前Entity,比如以上則是從指定的URL中讀取html.gif文件,讀到的數(shù)據(jù)可以name作為key保存起來,之后,當(dāng)用戶在解析source屬性是,可以根據(jù)source屬性中的值得到相應(yīng)的數(shù)據(jù)保存起來。
一般來說DTD文件的解析要在所有XML文件中的Element解析之前,因而DTDHandler中的回調(diào)函數(shù)一般在startDocument()事件之后,而在第一個startElement()事件之前。
最后,感覺應(yīng)該很少有用戶會去使用NOTATION的概念吧,除非用戶想在XML文件中包含二進(jìn)制數(shù)據(jù),其實我個人感覺這種二進(jìn)制數(shù)據(jù)的實現(xiàn)方式也不好,所以個人感覺很少有人需要去實現(xiàn)DTDHandler的接口。
ContentHandler接口
ContentHandler接口處理所有對XML內(nèi)容解析時產(chǎn)生的所有事件。它的接口定義如下:
public interface ContentHandler {
public void setDocumentLocator(Locator locator);
public void startDocument() throws SAXException;
public void endDocument() throws SAXException;
public void startPrefixMapping (String prefix, String uri)
throws SAXException;
public void endPrefixMapping (String prefix) throws SAXException;
public void startElement (String uri, String localName, String qName,
Attributes atts) throws SAXException;
public void endElement (String uri, String localName, String qName)
throws SAXException;
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 (String target, String data)
throws SAXException;
public void skippedEntity (String name) throws SAXException;
}
a. setDocumentLocator方法
解析器解析XML文件內(nèi)容時,它會首先調(diào)用setDocumentLocator設(shè)置一個Locator實例,應(yīng)用程序可以從Locator實例中獲取解析器當(dāng)前在解析的位置:
public interface Locator {
public abstract String getPublicId();
public abstract String getSystemId();
public abstract int getLineNumber();
public abstract int getColumnNumber();
}
其中publicId、systemId用于定位正在解析的文件,它可以是正在解析的XML文件,也可以是在XML中引入的外部實體文件。lineNumber、columnNumber則用于定位當(dāng)前事件發(fā)生時所在解析文件的行號和列號,行號和列號只是用于近似的診斷信息,不保證完全正確。
b. startDocument、endDocument方法
這兩個方法提供在XML文件解析開始和結(jié)束的位置插入應(yīng)用程序自定義的邏輯,只是兩個常見的擴(kuò)展點。
c. startPrefixMapping、endPrefixMapping方法
這兩個方法是在引入一個命名空間和結(jié)束該命名空間的作用域時調(diào)用的,比如在EntityResolver接口中定義了兩種方式的Spring XML配置文件,對第一個定義文件中,context命名空間在beans標(biāo)簽中定義,因而在解析beans標(biāo)簽時,它會首先調(diào)用startPrefixMapping方法,其參數(shù)中prefix為context,uri為http://www.springframework.org/schema/context,然后在調(diào)用beans標(biāo)簽的startElement方法;此時context命名空間的作用域范圍在beans標(biāo)簽內(nèi),因而當(dāng)beans結(jié)束后(在調(diào)用beans標(biāo)簽相關(guān)的endElement方法),會調(diào)用endPrefixMapping方法。而在第二個定義文件中,context命名空間在context:property-holder標(biāo)簽中定義,此時startPrefixMapping方法會在解析該標(biāo)簽時調(diào)用,但是在調(diào)用該標(biāo)簽相關(guān)的startElement之前;并且此時context的命名空間的作用域也只是在context:property-holder中。
d. startElement、endElement方法
在解析一個標(biāo)簽開始和結(jié)束時分別會調(diào)用這兩個方法。在調(diào)用startElement之前,xsd外部實體的引入、解析、驗證都已經(jīng)完成了(resolveEntity方法),命名空間解析也已經(jīng)完成(startPrefixMapping方法)。在startElement方法的參數(shù)中,uri為該標(biāo)簽命名空間所的uri的值,如http://www.springframework.org/schema/context,如果沒有定義則為空;localName指本地名字,如property-holder;qName為包含命名空間的名字,如context:property-holder;atts為該標(biāo)簽中定義的屬性值,它封裝在Attributes接口中:
public interface Attributes {
public abstract int getLength ();
public abstract String getURI (int index);
public abstract String getLocalName (int index);
public abstract String getQName (int index);
public abstract String getType (int index);
public abstract String getValue (int index);
public int getIndex (String uri, String localName);
public int getIndex (String qName);
public abstract String getType (String uri, String localName);
public abstract String getType (String qName);
public abstract String getValue (String uri, String localName);
public abstract String getValue (String qName);
}
Attributes類提供了兩種類型的查找方式:索引和命名屬性。其中索引的順序并沒有定義,因而在不同版本中的順序可能是不同的,這里沒用定義遍歷所有屬性的方法,因而我們可以使用getLength方法獲取屬性個數(shù),然后使用索引方式獲取相應(yīng)的值,而在其他情況下,個人不能推薦使用索引方式。對命名屬性,可以使用兩種方式:一種是使用包含命名空間的全名(qName),另一種是使用uri加localName的方式定義。
關(guān)于getType,這里的type指的是DTD、XSD中定義的屬性的類型:"CDATA", "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", or "NOTATION",默認(rèn)為CDATA。而在Java讀取到的所有屬性的值都是字符串。
e. characters方法
SAXParser在沒讀到一些非標(biāo)簽內(nèi)中的字符串時,都會調(diào)用該方法,這些字符串可以是某個標(biāo)簽的值,也可以是標(biāo)簽之前的空格和換行符。如一下XML數(shù)據(jù):

會在標(biāo)記的7個位置分別調(diào)用characters方法,其中ch數(shù)組是文件讀取的字符串?dāng)?shù)組,其大小默認(rèn)為2k,可以使用http://apache.org/xml/properties/input-buffer-size屬性值設(shè)置其大小,但是如果新設(shè)置的大小不能一次讀取不可分割的數(shù)據(jù)內(nèi)容時,該大小或增加;start指定當(dāng)前字符串在ch數(shù)組中的起始位置;length則是指當(dāng)前字符串的長度。
如果我們需要讀取該部分的字符串內(nèi)容,可以使用以下方式:
new String(ch, start, length)
f. ignorableWhitespace方法
當(dāng)解析器遇到可忽略的空格時被調(diào)用。什么是可忽略的空格呢?可以參考:http://www.w3.org/TR/2006/REC-xml-20060816/#sec-white-space,我一直沒有讀懂文檔中的解釋.....而且據(jù)xerces中解釋,ignorableWhitespace只用于DTD中,可參考:http://xerces.apache.org/xerces2-j/faq-sax.html中相關(guān)內(nèi)容。不過貌似我們很少使用這個方式,我就不深究了。
g. processingInstruction方法
在XML文件中還可以定義一下指令,以供一些特定的處理工具讀取并處理。SAXPaser在遇到這些指令時,就會調(diào)用這個方法。這些指令如:
<?xml-stylesheet href="show.css" type="text/css" ?>
此時target為:xml-stylesheet,data為:href=”show.css” type=”text/css”
h. skippedEntity方法
當(dāng)SAXParser跳過一個實體時,該方法會被調(diào)用。那么什么情況下它會跳過一個實體的解析呢?這個我貌似也一直木有讀懂….幸好這個方法用的也不多。
ErrorHandler接口
ErrorHandler在解析器在解析過程中遇到錯誤時被調(diào)用。ErrorHandler分成三個級別:warn、error、fatalerror,解析器會根據(jù)當(dāng)前錯誤的級別調(diào)用相應(yīng)的方法。
public interface ErrorHandler {
public abstract void warning (SAXParseException exception)
throws SAXException;
public abstract void error (SAXParseException exception)
throws SAXException;
public abstract void fatalError (SAXParseException exception)
throws SAXException;
}
DefaultHandler類
DefaultHandler類實現(xiàn)了所有以上接口EntityResolver、DTDHandler、ContentHandler、ErrorHandler,并提供了所有方法的空實現(xiàn)。我們在使用SAX時,一般都是繼承自DefaultHandler,然后實現(xiàn)需要的方法,而保持其他方法默認(rèn)實現(xiàn)。
LexicalHandler接口
該接口是SAX2提供的擴(kuò)展接口,它可以處理XML資源文件中更多的詞匯(Lexical)信息,如注釋、CDATA等。可以使用以下方法注冊該接口:
xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
LexicalHandler接口定義:
public interface LexicalHandler {
//在一個DTD定義開始時調(diào)用
public abstract void startDTD (String name, String publicId, String systemId)
throws SAXException;
//在一個DTD定義結(jié)束時調(diào)用
public abstract void endDTD ()
throws SAXException;
//在一個實體解析開始時調(diào)用
public abstract void startEntity (String name)
throws SAXException;
//在一個實體解析結(jié)束時調(diào)用
public abstract void endEntity (String name)
throws SAXException;
//CDATA數(shù)據(jù)區(qū)開始時調(diào)用
public abstract void startCDATA ()
throws SAXException;
//在一個CDATA數(shù)據(jù)區(qū)結(jié)束后調(diào)用
public abstract void endCDATA ()
throws SAXException;
//所有的注釋信息,包括外部實體(如DTD)中的注釋信息
public abstract void comment (char ch[], int start, int length)
throws SAXException;
}
DeclHandler接口
SAX2擴(kuò)展接口,它提供了DTD定義事件的回調(diào)方法。可以使用一下方法注冊該接口:
xmlReader.setProperty("http://xml.org/sax/properties/declaration-handler", handler);
DeclHandler接口定義:
public interface DeclHandler {
//定義一個Element標(biāo)簽時的回調(diào),name為Element Name,model為”EMPTY”, “ANY”,
//或者所有其他復(fù)雜Element定義的值
public abstract void elementDecl (String name, String model)
throws SAXException;
//定義一個屬性標(biāo)簽時的回調(diào)。
public abstract void attributeDecl (String eName, String aName,
String type, String mode, String value)
throws SAXException;
//定義一個內(nèi)部實體時的回調(diào)
public abstract void internalEntityDecl (String name, String value)
throws SAXException;
//定義一個外部實體時的回調(diào)
public abstract void externalEntityDecl (String name, String publicId,
String systemId)
throws SAXException;
}
一個具體SAX解析實例
寫了那么多,花了我好幾天的時間,不過還是收獲不少,至少對SAX解析XML的方式有了一個比較深入的了解了。最后使用一個簡單的例子結(jié)束這篇文章吧。
首先簡單的定義XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="12">
<name>thinking in java</name>
<price>85.5</price>
</book>
<book id="15">
<name>Spring in Action</name>
<price>39.0</price>
</book>
</books>
對應(yīng)Book類:
class Book {
private String id;
private String name;
private double price;
}
一個簡單的解析器:
public class BookXmlParser extends DefaultHandler {
private Locator locator;
private List<Book> books;
private Book currentBook;
private String preTag;
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
@Override
public void startDocument() throws SAXException {
books = new ArrayList<Book>();
currentBook = null;
preTag = null;
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if("book".equals(qName)) {
currentBook = new Book();
currentBook.setId(attributes.getValue("id"));
}
preTag = qName;
}
public void endElement(String uri, String localName, String qName)
throws SAXException {
if("book".equals(qName)) {
books.add(currentBook);
currentBook = null;
}
preTag = null;
}
public void characters(char ch[], int start, int length)
throws SAXException {
if(preTag != null && currentBook != null) {
String value = new String(ch, start, length);
if("name".equals(preTag)) {
currentBook.setName(value);
} else if("price".equals(preTag)) {
currentBook.setPrice(Double.parseDouble(value));
}
}
}
public void warning(SAXParseException e) throws SAXException {
System.out.println("Warning occurred: \n");
System.out.println("Location Info: " + locatorInfo());
e.printStackTrace(System.out);
}
public void error(SAXParseException e) throws SAXException {
System.out.println("Error occurred: \n");
System.out.println("Location Info: " + locatorInfo());
e.printStackTrace(System.out);
}
public void fatalError(SAXParseException e) throws SAXException {
System.out.println("Fatal Error occurred: \n");
System.out.println("Location Info: " + locatorInfo());
e.printStackTrace(System.out);
throw e;
}
private String locatorInfo() {
return "resource: " + locator.getSystemId() + ", Locator Info: [" +
locator.getLineNumber() + ", " + locator.getColumnNumber() + "]";
}
public static void main(String[] args) throws Exception {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
BookXmlParser handler = new BookXmlParser();
xmlReader.setContentHandler(handler);
xmlReader.setEntityResolver(handler);
xmlReader.setErrorHandler(handler);
xmlReader.setDTDHandler(handler);
xmlReader.parse("resources/xmlfiles/book.xml");
System.out.println("Book List:");
System.out.println(handler.books);
}
}