天天看點

SAX解析XML檔案

就目前來說,有三種方式可以解析xml檔案:dom、sax、stax。dom将整個xml檔案加載到記憶體中,并建構出節點樹;應用程式可以通過周遊節點樹的方式來解析xml檔案中的各個節點、屬性等資訊;這種方式便于對xml節點的添加修改等,而且解析也很友善,然後它比較耗費記憶體,解析速度也不快。sax則是基于事件的解析,解析器在一次讀取xml檔案中根據讀取的資料産生相應的事件,由應用程式實作相應的事件處理邏輯,即它是一種“推”的解析方式;這種解析方法速度快、占用記憶體少,但是它需要應用程式自己處了解析器的狀态,實作起來會比較麻煩,而且它隻支援對xml檔案的讀取,不支援寫入。不同于sax的“推”的解析方式,stax是基于“拉”的解析方式,即應用程式根據自己的需要控制解析器的讀取;這種方式繼承了sax解析速度快、占用記憶體少等優點,同時它好保持了接口簡單、程式設計容易等特點;不過它也應該不支援寫xml檔案的,木有仔細看過這個架構,先猜測一下~~。貌似dom底層采用了sax的實作,因而本文首先介紹基于sax方式的xml檔案解析。

sax的解析架構相對比較簡單,以下是它核心類關系圖:

SAX解析XML檔案

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"

    <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以目前來看感覺可以忽略。有興趣對這個做深入研究的童鞋可以參考一下文檔:

<a href="http://xerces.apache.org/xml-commons/components/resolver/resolver-article.html">http://xerces.apache.org/xml-commons/components/resolver/resolver-article.html</a>

<a href="http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/docbooksys/segmentedhtml/ch08s02.html">http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/docbooksys/segmentedhtml/ch08s02.html</a>

在使用inputsource時,還需要注意資源檔案的查找順序問題,即inputsource中的characterstream、bytestream、systemid都可以表示一個資源檔案,因而需要定義它們的查找順序:即先檢視characterstream字段,如果有值則使用(此時encoding字段無效);然後檢視bytestream字段,如果有值,則使用;最後嘗試使用uri解析systemid字段(可以是相對路徑),如果能找到對應的資源檔案,則使用;否則,抛出異常。對後兩種情況,可以指定encoding字段表示資源檔案的編碼方式。

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是實作真正解析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的全稱,用于定義目前解析器支援的特性,比如按注釋,所有解析器都要識别的特性有:

<a href="http://xml.org/sax/features/namespaces">http://xml.org/sax/features/namespaces</a>

<a href="http://xml.org/sax/features/namespace-prefixes">http://xml.org/sax/features/namespace-prefixes</a>

還有其他一些常見的feature有(預設abstractsaxparser支援的feature):

<a href="http://xml.org/sax/features/string-interning">http://xml.org/sax/features/string-interning</a>

<a href="http://xml.org/sax/features/is-standalone">http://xml.org/sax/features/is-standalone</a>

<a href="http://xml.org/sax/features/xml-1.1">http://xml.org/sax/features/xml-1.1</a>

<a href="http://xml.org/sax/features/lexical-handler/parameter-entities">http://xml.org/sax/features/lexical-handler/parameter-entities</a>

<a href="http://xml.org/sax/features/resolve-dtd-uris">http://xml.org/sax/features/resolve-dtd-uris</a>

<a href="http://xml.org/sax/features/xmlns-uris">http://xml.org/sax/features/xmlns-uris</a>

<a href="http://xml.org/sax/features/unicode-normalization-checking">http://xml.org/sax/features/unicode-normalization-checking</a>

<a href="http://xml.org/sax/features/use-entity-resolver2">http://xml.org/sax/features/use-entity-resolver2</a>

<a href="http://xml.org/sax/features/use-attributes2">http://xml.org/sax/features/use-attributes2</a>

<a href="http://xml.org/sax/features/use-locator2">http://xml.org/sax/features/use-locator2</a>

<a href="http://xml.org/sax/features/internal/parser-settings">http://xml.org/sax/features/internal/parser-settings</a>

<a href="http://xml.org/sax/features/internal/xinclude">http://xml.org/sax/features/internal/xinclude</a>

一些使用者自定義的解析器可以指定自己支援的feature。xmlreader提供接口查詢、設定指定目前xmlreader是否支援某種feature。

2.       property

xmlreader還支援通過uri方式擷取解析器相關的一些屬性值,一些擴充的事件handler也可以通過該方式定義。如常見的屬性定義有:

<a href="http://xml.org/sax/properties/document-xml-version">http://xml.org/sax/properties/document-xml-version</a>

<a href="http://xml.org/sax/properties/lexical-handler">http://xml.org/sax/properties/lexical-handler</a>

<a href="http://xml.org/sax/properties/declaration-handler">http://xml.org/sax/properties/declaration-handler</a>

<a href="http://xml.org/sax/properties/dom-node">http://xml.org/sax/properties/dom-node</a>

具體可以參考xerces中的介紹:

<a href="http://xerces.apache.org/xerces2-j/properties.html">http://xerces.apache.org/xerces2-j/properties.html</a>

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接口提供應用程式自定義實體解析的擴充點,即應用程式可以注冊自己的實體解析類以實作自己特定的邏輯,如在本地、資料庫、網絡中查找外部實體。比如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檔案:

&lt;!element website (name,copyright)&gt;

&lt;!element name (#pcdata)&gt;

&lt;!-- parameter entity--&gt;

&lt;!entity % copyrightelement "&lt;!element copyright (#pcdata)&gt;"&gt;

%copyrightelement;

&lt;!--normal entity--&gt;

&lt;!entity name "cnblog"&gt;

&lt;!--external entity--&gt;

&lt;!entity copyright system "copyright.desc"&gt;

該dtd可以在以下xml檔案中使用:

&lt;?xml version="1.1" encoding="utf-8"?&gt;

&lt;!doctype website system "../dtds/entitiesdtd.dtd"&gt;

&lt;website&gt;

    &lt;name&gt;&amp;name;&lt;/name&gt;

    &lt;copyright&gt;&amp;copyright;&lt;/copyright&gt;

&lt;/website&gt;

此時,在解析xml檔案時,&amp;name;值為cnblog,而&amp;copyright;的值為copyright.desc檔案中的内容。entityresolver接口也隻有在需要解析外部實體是才會被調用,比如在解析&amp;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 &amp;&amp; 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 -&gt; download from website or wherever.

    return null;

如果使用xsd作為xml的定義模闆,我們可以定義schemalocation将xsd檔案引入,比如spring配置檔案中的用法:

&lt;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"&gt;

    &lt;bean id="mycustomer" class="levin.spring.test.customer"&gt;

        &lt;property name="name" value="customer"&gt;&lt;/property&gt;

        &lt;property name="address" value="shanghai"&gt;&lt;/property&gt;

        &lt;property name="user" ref="user"&gt;&lt;/property&gt;

    &lt;/bean&gt;

    &lt;context:property-holder id="propertyholder" class="org.springframework.context.propertyholder" /&gt;

&lt;/beans&gt;

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 -&gt; download from website or wherever.

其實以上配置檔案還可以寫成:

&lt;?xml version="1.0" encoding="utf-8"?&gt;

       xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"&gt;

    &lt;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" /&gt;

最後,預設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);

提供使用者在notation定義以及存在沒有解析的entity定義時注冊使用者自定義的處理邏輯。這裡需要解釋兩個定義:什麼是notation以及什麼是沒有解析的entity定義。

首先來解釋一下什麼是notation:在xml檔案中可以包含一些非xml的資料,notation即定義了這種非xml資料的格式,進而應用程式可以識别這些資料。然而什麼是非xml資料呢?舉個例子,以下dtd定義了一個image标簽,它包含source屬性,而這個source屬性指向某個gif圖檔:

&lt;!notation gif system "image/gif"&gt;

&lt;!entity jenn system "http://images.about.com/sites/guidepics/html.gif" ndata gif&gt;

&lt;!element image empty&gt;

&lt;!attlist image source entity #required&gt;

對應的xml資料段:

&lt;image source="jenn" /&gt;

其次,什麼是沒有解析的entity定義呢?沒有解析并不是在dtd檔案中所有沒有被使用到的entity,而是指所有那些ndata類型的實體定義,因為這些實體需要使用者自己去解析,因而xml解析引擎不會預設對他們做解析。比如以上的&amp;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接口處理所有對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)

    public void ignorablewhitespace (char ch[], int start, int length)

    public void processinginstruction (string target, string data)

    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方法

d.      startelement、endelement方法

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資料:

SAX解析XML檔案

如果我們需要讀取該部分的字元串内容,可以使用以下方式:

new string(ch, start, length)

f.        ignorablewhitespace方法

g.       processinginstruction方法

在xml檔案中還可以定義一下指令,以供一些特定的處理工具讀取并處理。saxpaser在遇到這些指令時,就會調用這個方法。這些指令如:

&lt;?xml-stylesheet href="show.css" type="text/css" ?&gt;

此時target為:xml-stylesheet,data為:href=”show.css” type=”text/css”

h.      skippedentity方法

當saxparser跳過一個實體時,該方法會被調用。那麼什麼情況下它會跳過一個實體的解析呢?這個我貌似也一直木有讀懂….幸好這個方法用的也不多。

errorhandler在解析器在解析過程中遇到錯誤時被調用。errorhandler分成三個級别:warn、error、fatalerror,解析器會根據目前錯誤的級别調用相應的方法。

public interface errorhandler {

    public abstract void warning (saxparseexception exception)

    public abstract void error (saxparseexception exception)

    public abstract void fatalerror (saxparseexception exception)

defaulthandler類實作了所有以上接口entityresolver、dtdhandler、contenthandler、errorhandler,并提供了所有方法的空實作。我們在使用sax時,一般都是繼承自defaulthandler,然後實作需要的方法,而保持其他方法預設實作。

該接口是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)

//在一個dtd定義結束時調用

    public abstract void enddtd ()

    //在一個實體解析開始時調用

    public abstract void startentity (string name)

    //在一個實體解析結束時調用

    public abstract void endentity (string name)

    //cdata資料區開始時調用

    public abstract void startcdata ()

    //在一個cdata資料區結束後調用

    public abstract void endcdata ()

    //所有的注釋資訊,包括外部實體(如dtd)中的注釋資訊

    public abstract void comment (char ch[], int start, int length)

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)

//定義一個屬性标簽時的回調。

public abstract void attributedecl (string ename, string aname,

        string type, string mode, string value)

    //定義一個内部實體時的回調

    public abstract void internalentitydecl (string name, string value)

    //定義一個外部實體時的回調

    public abstract void externalentitydecl (string name, string publicid,

                         string systemid)

寫了那麼多,花了我好幾天的時間,不過還是收獲不少,至少對sax解析xml的方式有了一個比較深入的了解了。最後使用一個簡單的例子結束這篇文章吧。

首先簡單的定義xml檔案:

&lt;?xml version="1.0" encoding="utf-8"?&gt; 

&lt;books&gt; 

    &lt;book id="12"&gt; 

        &lt;name&gt;thinking in java&lt;/name&gt; 

        &lt;price&gt;85.5&lt;/price&gt; 

    &lt;/book&gt; 

    &lt;book id="15"&gt; 

        &lt;name&gt;spring in action&lt;/name&gt; 

        &lt;price&gt;39.0&lt;/price&gt; 

&lt;/books&gt;

對應book類:

class book {

    private string id;

    private string name;

private double price;

一個簡單的解析器:

public class bookxmlparser extends defaulthandler {

    private locator locator;

    private list&lt;book&gt; books;

    private book currentbook;

    private string pretag;

    @override

    public void setdocumentlocator(locator locator) {

        this.locator = locator;

    }

    public void startdocument() throws saxexception {

        books = new arraylist&lt;book&gt;();

        currentbook = null;

        pretag = null;

    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 {

            books.add(currentbook);

            currentbook = null;

    public void characters(char ch[], int start, int length)

        if(pretag != null &amp;&amp; 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");

    public void fatalerror(saxparseexception e) throws saxexception {

        system.out.println("fatal error occurred: \n");

        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);