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

InputSource類
InputSource是SAX中對要被解析的XML資源文件的抽象,它封裝了以下信息:
private Reader characterStream;
private InputStream byteStream;
private String systemId;
private String publicId;
private String encoding;
應用程序可以顯示的設置characterStream、byteStream來指定實際的XML資源文件,或者使用systemId和publicId的方式來定位XML資源文件。這里systemId、publicId借用了導入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”
很多框架都實現了自己的EntityResolver,以實現自定義的Entity查找邏輯,就像Spring,它首先在當前ClassPath下查找對應的DTD文件(Spring.jar文件中)。
在實現中,很少去使用publicId的,以Spring源碼中沒有使用publicId,甚至我在xerces源碼中都沒有看到對publicId的處理,不過看它的文檔,貌似是提供了publicId的實現,不知道是Java的實現版本沒有提供還是我沒有找對地方,而且按測試的結果,它不能只存在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字段(可以是相對路徑),如果能找到對應的資源文件,則使用;否則,拋出異常。對后兩種情況,可以指定encoding字段表示資源文件的編碼方式。
XMLReaderFactory類
XMLReaderFactory從這個類的名字中已經能知道它是用于創建XMLReader實例的工場類,它提供兩個靜態方法以創建XMLReader實例:
public static XMLReader createXMLReader();
public static XMLReader createXMLReader(String className);
對不帶參數的createXMLReader()方法,它實現了一種動態查找XMLReader具體實現類的方式:
1. 首先查看系統屬性中是否存在“org.xml.sax.driver”屬性的定義,如果存在,則使用該屬性定義的XMLReader實現類。
2. 其次查看ClassPath下是否存在“META-INF/services/org.xml.sax.driver”文件的定義,如果存在,則使用該文件中定義XMLReader的實現類。
3. 否則,默認使用“com.sun.org.apache.xerces.internal.parsers.SAXParser”類作為XMLReader的實現類。
而對帶參數的createXMLReader()工場方法來說,它只是實例化傳入的XMLReader的實現類。
XMLReader接口和實現
XMLReader是實現真正解析XML資源文件的接口,之所以不使用Parser是因為這個名稱已經在SAX1中被使用,而在SAX2中將實現解析的接口名稱重命名成XMLReader。在使用SAX解析XML資源文件時,默認使用SAXParser實現類,它繼承自AbstractSAXParser(參考以上類關系圖)。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的全稱,用于定義當前解析器支持的特性,比如按注釋,所有解析器都要識別的特性有:
http://xml.org/sax/features/namespaces
http://xml.org/sax/features/namespace-prefixes
還有其他一些常見的Feature有(默認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提供接口查詢、設置指定當前XMLReader是否支持某種Feature。
2. Property
XMLReader還支持通過URI方式獲取解析器相關的一些屬性值,一些擴展的事件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)注冊方法
應用程序通過注冊相應的事件處理器來和XMLReader解析器教務,解析器在解析過程中產生的事件都會通過調用相應事件處理器中的相應的方法來將信息傳給應用程序。默認支持的時間處理器有:
EntityResolver:實現用戶自定義外部實體解析處理邏輯。
DTDHandler:實現在NOTATION定義以及存在沒有解析的Entity定義時,用戶可以加入自定義的處理邏輯。
ContentHandler:處理所有對XML內容解析時產生的所有事件。
ErrorHandler:解析器在解析過程中遇到錯誤時被調用。
通過設置屬性值的方式,AbstractSAXParser還支持以下兩種Handler:
LexicalHandler:SAX2擴展接口,提供更多的XML資源文件相關的信息,如注釋、CDATA等。
DeclHandler:SAX2擴展接口,在DTD定義事件中提供回調方法。
4. 解析方法(parse)
解析器實現解析XML資源文件的方法。應用程序可以傳入InputSource實例,也可以傳入systemId的值,即一個URI字符串或相對路徑指定的文件名。解析方法(parse)是線程同步的,對一個XMLReader實例在解析時,另一個線程會等待該線程結束后才開始解析新的文件,因而一般情況下,在多個線程中都會創建各自的XMLReader實例。然而當一個XMLReader實例解析完成后,我們可以重用該XMLReader實例解析新的XML文件,此時之前的Feature、Property以及Handler的注冊等信息都保持不變,我們可以手動調用相應的方法改變之。
EntityResolver接口
EntityResolver接口提供應用程序自定義實體解析的擴展點,即應用程序可以注冊自己的實體解析類以實現自己特定的邏輯,如在本地、數據庫、網絡中查找外部實體。比如Spring就實現了自己的BeansDtdResolver,對InputSource小節中定義的spring-beans.dtd,它會先查找BeanDtdResolver類所在的目錄(一般為jar包內)中存在的spring-beans.dtd,如果存在該文件,則會加載該文件作為DTD文件,否則,使用默認的EntityResolver(即返回null,則XML引擎自己提供的解析器)。
然而什么是實體(Entity)呢?如果使用DTD作為XML文件的定義模板,那么在引入DTD文件時,即使引入一個外部實體,其PUBLIC、SYSTEM定義分別對應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文件中的內容。EntityResolver接口也只有在需要解析外部實體是才會被調用,比如在解析&name;實體時就不會被調用。
EntityResolver的接口定義如下:
public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
}
resolveEntity()方法返回用戶自定義的InputSource實例,如果該方法返回null,則XML解析引擎默認為外部實體為URL,并使用該URL創建InputSource。
一般情況下使用XML解析引擎內部默認的實現即可,但是像Spring這種從本地讀取DTD文件時,則需要實現自己的EntityResolver,其實現核心代碼如下:
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標簽中,并在標簽中使用xsi:schemaLocation定義了所有xsd文件的位置,在XML解析中,每當遇到一個新的命名空間,解析器就會先調用resolveEntity方法,并將xsi:schemaLocation中定義的位置傳入resolveEntity()方法(publicId為null、systemId為schema的位置,如http://www.springframework.org/schema/context/spring-context.xsd, 默認解析器會從這個位置上查找xsd文件,然后開始解析相應的標簽,比如對默認命名空間所使用的xsd文件,SAXParser會先解析該文件,然后真正解析beans標簽。如DTD作為外部實體的導入,應用程序可以定義自己的EntityResolver以實現自定義的外部實體查找邏輯,如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>
最后,默認SAXParser默認實現不支持XSD文件的解析,我們需要手動的設置以下屬性以打開這一屬性:
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的數據,NOTATION即定義了這種非XML數據的格式,從而應用程序可以識別這些數據。然而什么是非XML數據呢?舉個例子,以下DTD定義了一個image標簽,它包含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>
對應的XML數據段:
<image source="JENN" />
我們知道在XML文件文本數據,中一般只能包含文本數據,那么如何將圖片文件嵌入到XML文件中呢?XML提供了三種方法實現在XML文件中包含二進制數據:一種是使用以上NOTATION的方式;一種是使用mine擴展;還有一種是將二進制數據保存到<![CDATA[ …]]>中(具體可以參考:http://www.ibm.com/developerworks/cn/xml/x-binary/index.html)。
其次,什么是沒有解析的Entity定義呢?沒有解析并不是在DTD文件中所有沒有被使用到的Entity,而是指所有那些NDATA類型的實體定義,因為這些實體需要用戶自己去解析,因而XML解析引擎不會默認對他們做解析。比如以上的&name;實體是被解析過的,因而它會轉換成“cnblog”值顯示出來;而以上JENN的這個實體則沒有被XML解析引擎解析,因而它會觸發DTDHandler接口的調用。
那么我們再來看一下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兩種特性的回調方法:
1. 當XML解析引擎在解析DTD文件中NOTATION的定義時,它會調用notationDecl()方法,此時用戶注冊的DTDHandler則可以根據傳入的publicId和systemId解析當前NOTATION相應的數據。
2. 當XML解析引擎在解析DTD文件中NDATA類型的Entity定義時,它會調用unparsedEntityDecl()方法。用戶可以根據傳入的publicId、systemId、notationName等信息解析當前Entity,比如以上則是從指定的URL中讀取html.gif文件,讀到的數據可以name作為key保存起來,之后,當用戶在解析source屬性是,可以根據source屬性中的值得到相應的數據保存起來。
一般來說DTD文件的解析要在所有XML文件中的Element解析之前,因而DTDHandler中的回調函數一般在startDocument()事件之后,而在第一個startElement()事件之前。
最后,感覺應該很少有用戶會去使用NOTATION的概念吧,除非用戶想在XML文件中包含二進制數據,其實我個人感覺這種二進制數據的實現方式也不好,所以個人感覺很少有人需要去實現DTDHandler的接口。
ContentHandler接口
ContentHandler接口處理所有對XML內容解析時產生的所有事件。它的接口定義如下:
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文件內容時,它會首先調用setDocumentLocator設置一個Locator實例,應用程序可以從Locator實例中獲取解析器當前在解析的位置:
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則用于定位當前事件發生時所在解析文件的行號和列號,行號和列號只是用于近似的診斷信息,不保證完全正確。
b. startDocument、endDocument方法
這兩個方法提供在XML文件解析開始和結束的位置插入應用程序自定義的邏輯,只是兩個常見的擴展點。
c. startPrefixMapping、endPrefixMapping方法
這兩個方法是在引入一個命名空間和結束該命名空間的作用域時調用的,比如在EntityResolver接口中定義了兩種方式的Spring XML配置文件,對第一個定義文件中,context命名空間在beans標簽中定義,因而在解析beans標簽時,它會首先調用startPrefixMapping方法,其參數中prefix為context,uri為http://www.springframework.org/schema/context,然后在調用beans標簽的startElement方法;此時context命名空間的作用域范圍在beans標簽內,因而當beans結束后(在調用beans標簽相關的endElement方法),會調用endPrefixMapping方法。而在第二個定義文件中,context命名空間在context:property-holder標簽中定義,此時startPrefixMapping方法會在解析該標簽時調用,但是在調用該標簽相關的startElement之前;并且此時context的命名空間的作用域也只是在context:property-holder中。
d. startElement、endElement方法
在解析一個標簽開始和結束時分別會調用這兩個方法。在調用startElement之前,xsd外部實體的引入、解析、驗證都已經完成了(resolveEntity方法),命名空間解析也已經完成(startPrefixMapping方法)。在startElement方法的參數中,uri為該標簽命名空間所的uri的值,如http://www.springframework.org/schema/context,如果沒有定義則為空;localName指本地名字,如property-holder;qName為包含命名空間的名字,如context:property-holder;atts為該標簽中定義的屬性值,它封裝在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方法獲取屬性個數,然后使用索引方式獲取相應的值,而在其他情況下,個人不能推薦使用索引方式。對命名屬性,可以使用兩種方式:一種是使用包含命名空間的全名(qName),另一種是使用uri加localName的方式定義。
關于getType,這里的type指的是DTD、XSD中定義的屬性的類型:"CDATA", "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", or "NOTATION",默認為CDATA。而在Java讀取到的所有屬性的值都是字符串。
e. characters方法
SAXParser在沒讀到一些非標簽內中的字符串時,都會調用該方法,這些字符串可以是某個標簽的值,也可以是標簽之前的空格和換行符。如一下XML數據:

會在標記的7個位置分別調用characters方法,其中ch數組是文件讀取的字符串數組,其大小默認為2k,可以使用http://apache.org/xml/properties/input-buffer-size屬性值設置其大小,但是如果新設置的大小不能一次讀取不可分割的數據內容時,該大小或增加;start指定當前字符串在ch數組中的起始位置;length則是指當前字符串的長度。
如果我們需要讀取該部分的字符串內容,可以使用以下方式:
new String(ch, start, length)
f. ignorableWhitespace方法
當解析器遇到可忽略的空格時被調用。什么是可忽略的空格呢?可以參考:http://www.w3.org/TR/2006/REC-xml-20060816/#sec-white-space,我一直沒有讀懂文檔中的解釋.....而且據xerces中解釋,ignorableWhitespace只用于DTD中,可參考:http://xerces.apache.org/xerces2-j/faq-sax.html中相關內容。不過貌似我們很少使用這個方式,我就不深究了。
g. processingInstruction方法
在XML文件中還可以定義一下指令,以供一些特定的處理工具讀取并處理。SAXPaser在遇到這些指令時,就會調用這個方法。這些指令如:
<?xml-stylesheet href="show.css" type="text/css" ?>
此時target為:xml-stylesheet,data為:href=”show.css” type=”text/css”
h. skippedEntity方法
當SAXParser跳過一個實體時,該方法會被調用。那么什么情況下它會跳過一個實體的解析呢?這個我貌似也一直木有讀懂….幸好這個方法用的也不多。
ErrorHandler接口
ErrorHandler在解析器在解析過程中遇到錯誤時被調用。ErrorHandler分成三個級別:warn、error、fatalerror,解析器會根據當前錯誤的級別調用相應的方法。
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類實現了所有以上接口EntityResolver、DTDHandler、ContentHandler、ErrorHandler,并提供了所有方法的空實現。我們在使用SAX時,一般都是繼承自DefaultHandler,然后實現需要的方法,而保持其他方法默認實現。
LexicalHandler接口
該接口是SAX2提供的擴展接口,它可以處理XML資源文件中更多的詞匯(Lexical)信息,如注釋、CDATA等。可以使用以下方法注冊該接口:
xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
LexicalHandler接口定義:
public interface LexicalHandler {
//在一個DTD定義開始時調用
public abstract void startDTD (String name, String publicId, String systemId)
throws SAXException;
//在一個DTD定義結束時調用
public abstract void endDTD ()
throws SAXException;
//在一個實體解析開始時調用
public abstract void startEntity (String name)
throws SAXException;
//在一個實體解析結束時調用
public abstract void endEntity (String name)
throws SAXException;
//CDATA數據區開始時調用
public abstract void startCDATA ()
throws SAXException;
//在一個CDATA數據區結束后調用
public abstract void endCDATA ()
throws SAXException;
//所有的注釋信息,包括外部實體(如DTD)中的注釋信息
public abstract void comment (char ch[], int start, int length)
throws SAXException;
}
DeclHandler接口
SAX2擴展接口,它提供了DTD定義事件的回調方法。可以使用一下方法注冊該接口:
xmlReader.setProperty("http://xml.org/sax/properties/declaration-handler", handler);
DeclHandler接口定義:
public interface DeclHandler {
//定義一個Element標簽時的回調,name為Element Name,model為”EMPTY”, “ANY”,
//或者所有其他復雜Element定義的值
public abstract void elementDecl (String name, String model)
throws SAXException;
//定義一個屬性標簽時的回調。
public abstract void attributeDecl (String eName, String aName,
String type, String mode, String value)
throws SAXException;
//定義一個內部實體時的回調
public abstract void internalEntityDecl (String name, String value)
throws SAXException;
//定義一個外部實體時的回調
public abstract void externalEntityDecl (String name, String publicId,
String systemId)
throws SAXException;
}
一個具體SAX解析實例
寫了那么多,花了我好幾天的時間,不過還是收獲不少,至少對SAX解析XML的方式有了一個比較深入的了解了。最后使用一個簡單的例子結束這篇文章吧。
首先簡單的定義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>
對應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);
}
}