隨筆 - 3, 文章 - 152, 評(píng)論 - 17, 引用 - 0
          數(shù)據(jù)加載中……

          用于XML的簡(jiǎn)單API

          Eric Armstrong

          本章主要討論Simple API for XML (SAX),它是一種事件驅(qū)動(dòng)、串行存取XML文檔的機(jī)制。這是多數(shù)servlet和面向網(wǎng)絡(luò)的程序用來(lái)傳送和接收XML文檔的協(xié)議,因?yàn)樵谀壳癤ML文檔處理的可用機(jī)制中它最快并且所需內(nèi)存最少。

          SAX協(xié)議比文檔對(duì)象模型(DOM)需要進(jìn)行更多的編程。它是事件驅(qū)動(dòng)模型(你提供回調(diào)方法,解析器在讀取XML數(shù)據(jù)的時(shí)候調(diào)用它們),因此很難實(shí)現(xiàn)可視化。最后,不能倒退回文檔的前面部分或重新組織它,只能回退到一個(gè)串行數(shù)據(jù)流或重新組織從流中讀取的字符。

          由于上述原因,如果開(kāi)發(fā)者編寫(xiě)面向用戶的應(yīng)用程序,且該應(yīng)用程序顯示XML文檔并可能要對(duì)它進(jìn)行修改,那么就應(yīng)該使用教程下一部分介紹的DOM機(jī)制,文檔對(duì)象模型。

          然而,即便是專門(mén)建立DOM應(yīng)用程序,仍然要熟悉SAX模型,主要原因如下:

          • 相同的錯(cuò)誤處理

          在解析DOM文檔時(shí),會(huì)生成相同類型的異常,所以JAXP SAX和DOM應(yīng)用程序的錯(cuò)誤處理程序是相同的。

          • 處理驗(yàn)證錯(cuò)誤

          默認(rèn)情況下, 規(guī)范要求忽略驗(yàn)證錯(cuò)誤(在該教程的這一部分中會(huì)詳細(xì)介紹) 。如果想要在驗(yàn)證錯(cuò)誤事件中拋出一個(gè)異常(并且能這樣做),那么就需要理解SAX錯(cuò)誤處理的工作原理。

          • 轉(zhuǎn)換現(xiàn)有數(shù)據(jù)

          可以看到在本教程的DOM節(jié),可以使用一種機(jī)制將現(xiàn)有數(shù)據(jù)集轉(zhuǎn)換成XML——然而,要充分利用該機(jī)制首先必須理解SAX模型。


          注意:可以在下面的地址中找到本章的例子: <JWSDP_HOME>/docs/tutorial/examples/jaxp/sax/samples.

          何時(shí)使用 SAX

          當(dāng)要快速、高效地讀取XML數(shù)據(jù)時(shí),SAX是無(wú)可挑剔的。它對(duì)內(nèi)存的要求很小,因?yàn)樗粯?gòu)建XML數(shù)據(jù)的內(nèi)部表示(樹(shù)結(jié)構(gòu))。它僅僅將讀取的數(shù)據(jù)發(fā)送給應(yīng)用程序——然后應(yīng)用程序就可以隨意處理它所見(jiàn)到的數(shù)據(jù)。

          實(shí)際上,SAX API類似于串行I/O流。在流進(jìn)入的時(shí)候,可以看到數(shù)據(jù),但是不能回到以前的位置或跳躍到不同的位置。總的說(shuō)來(lái),如果僅想讀取數(shù)據(jù)并在這基礎(chǔ)上運(yùn)行應(yīng)用程序它就非常有效。

          理解SAX事件模型對(duì)于將現(xiàn)有數(shù)據(jù)轉(zhuǎn)換成XML非常有用。在從任意數(shù)據(jù)結(jié)構(gòu)生成XML 可以看到,轉(zhuǎn)換過(guò)程的關(guān)鍵是修改現(xiàn)有應(yīng)用程序,以便在讀取數(shù)據(jù)時(shí)發(fā)送合適的SAX事件。

          但是,當(dāng)需要修改XML結(jié)構(gòu)時(shí)——特別是需要互相修改的時(shí)候,使用類似于文檔對(duì)象模型(DOM)的內(nèi)存結(jié)構(gòu)更為合理。

          然而,雖然DOM提供了許多處理大型文檔(如書(shū)和論文)的強(qiáng)大功能,但是還是需要進(jìn)行復(fù)雜的編程 (在何時(shí)使用DOM中詳細(xì)介紹了該處理的細(xì)節(jié)) 。

          在簡(jiǎn)單的應(yīng)用程序中,這么復(fù)雜是完全沒(méi)有必要的。在快速開(kāi)發(fā)和簡(jiǎn)單的應(yīng)用程序中,使用面向?qū)ο蟮腦ML編程標(biāo)準(zhǔn)更加合理,在JDOM 和dom4j中會(huì)具體介紹。

          編寫(xiě)簡(jiǎn)單的XML文件

          現(xiàn)在開(kāi)始編寫(xiě)一個(gè)用來(lái)進(jìn)行幻燈片播放的XML數(shù)據(jù)的簡(jiǎn)單版本。在該練習(xí)中,使用文本編輯器創(chuàng)建數(shù)據(jù),這樣就能比較適應(yīng)XML文件的基本格式。你將使用該文件并在以后的練習(xí)中對(duì)它進(jìn)行擴(kuò)展。

          創(chuàng)建文件

          使用標(biāo)準(zhǔn)的文本編輯器,創(chuàng)建一個(gè)叫做slideSample.xml的文件。


          注意: 這里已經(jīng)有一個(gè)slideSample01.xml的版本。(可以瀏覽的版本是 slideSample01-xml.html)可以將該版本和你所編寫(xiě)的相比較,或者僅在閱讀此指南時(shí)查看。


          編寫(xiě)聲明

          下一步,編寫(xiě)聲明,其中該聲明將文件標(biāo)識(shí)為一個(gè)XML文檔。聲明以字符"<?"開(kāi)始這是處理指令的標(biāo)準(zhǔn)XML標(biāo)識(shí)符。(在本教程的后面部分可以看到其他處理指令)

            <?xml version='1.0' encoding='utf-8'?>  

          該行表明此文檔是一個(gè)XML文檔,它遵守XML1.0版本規(guī)范,并且使用8-位Unicode字符編碼方案。(要想獲得編碼方案的信息,請(qǐng)查看Java編碼方案 .)

          由于沒(méi)有指定文檔是“獨(dú)立的”,解析器假設(shè)該文檔可能包含到其他文檔的引用。想知道如何將一個(gè)文檔指定為“獨(dú)立的” 。

          添加注釋

          注釋會(huì)被XML解析器忽略。實(shí)際上,你根本就看不到它們,除非你激活解析器里的特殊設(shè)置。教程后面部分的處理詞法事件中會(huì)詳細(xì)介紹如何使用它。現(xiàn)在,添加下面突出顯示的內(nèi)容,在文件中增加一條注釋。

          <?xml version='1.0' encoding='utf-8'?> 
           
          <!-- A SAMPLE set of slides -->  

          定義根(Root)元素

          在聲明之后,每個(gè)XML文件都會(huì)精確地定義一個(gè)元素,這就是根元素。文件中的任何其他元素都包含在該元素中。輸入下面突出顯示文本,定義該文件的根元素slideshow

          <?xml version='1.0' encoding='utf-8'?> 
           
          <!-- A SAMPLE set of slides --> 
           
          <slideshow> 
           
          </slideshow> 

          注意:XML元素命名是大小寫(xiě)敏感的。結(jié)束標(biāo)簽必須和起始標(biāo)簽匹配。


          給元素添加屬性

          幻燈片播放有大量相關(guān)數(shù)據(jù)項(xiàng),它們都不需要任何結(jié)構(gòu)。所以可以將它們定義成slideshow 元素的屬性。添加下面突出顯示的文本,建立一些屬性:

          ...
            <slideshow 
              title="Sample Slide Show"
              date="Date of publication"
              author="Yours Truly"
              >
            </slideshow> 

          在創(chuàng)建標(biāo)簽或?qū)傩缘拿謺r(shí),除了字符和數(shù)字之外還可以使用連字符 ("-"),下劃線("_"),冒號(hào) (":")和句點(diǎn) (".") 。和HTML不同, XML屬性的值通常在引號(hào)之內(nèi),并且不用逗號(hào)分隔多個(gè)屬性。


          注意: 要慎重使用冒號(hào)或避免一起使用,因?yàn)樵诙xXML文檔的命名空間的時(shí)候也要使用它。


          添加嵌套元素

          XML能夠表示層次結(jié)構(gòu)化數(shù)據(jù),這意味著一個(gè)元素可以包含其他元素。添加下面突出顯示的文本,定義一個(gè)幻燈片(slide)元素,并在它內(nèi)部包含一個(gè)標(biāo)題(title)元素:

          <slideshow 
            ...
            >
           
             <!-- TITLE SLIDE -->
            <slide type="all">
              <title>Wake up to WonderWidgets!</title>
            </slide>
           
          </slideshow> 

          這里,也給幻燈片(slide)添加了一個(gè)type 屬性。該屬性主要是用來(lái)標(biāo)識(shí)幻燈片,是type="tech" 還是 type="exec",以便區(qū)別觀眾是技術(shù)人員還是管理人員,如果兩種類型的觀眾都有那么就是使用type="all"

          然而,更重要的是,該例子顯示了適合定義成元素(title 元素)和適合作為屬性 (type 屬性)的事務(wù)之間的區(qū)別。這里主要使用可視化啟發(fā)法。標(biāo)題是觀眾能夠看到的內(nèi)容。所以它是一個(gè)元素。而類型是永遠(yuǎn)都不會(huì)顯示出來(lái)的,所以它是屬性。另一種區(qū)分方法是,元素是容器, 就像瓶子一樣。類型是容器的特征(高的還是矮的,寬的還是窄的)。標(biāo)題是內(nèi)容的特征 (水、牛奶還是茶)。當(dāng)然,這些都不是非常嚴(yán)格的規(guī)則,但是在設(shè)計(jì)自己的XML結(jié)構(gòu)的時(shí)候,它們很有用。

          添加 HTML-風(fēng)格文本

          由于XML允許你定義任何你想定義的標(biāo)簽,因此可以定義一組看上去類似于HTML的標(biāo)簽。實(shí)際上這是通過(guò)XHTML標(biāo)準(zhǔn)實(shí)現(xiàn)的。在SAX教程的結(jié)束部分,你會(huì)進(jìn)一步了解它。現(xiàn)在,輸入下面突出顯示的文本,定義一個(gè)具有兩個(gè)列表項(xiàng)目的幻燈片,這些項(xiàng)目使用HTML-風(fēng)格的<em>標(biāo)簽進(jìn)行強(qiáng)調(diào) (通常使用斜體字):

            ...
            <!-- TITLE SLIDE -->
            <slide type="all">
              <title>Wake up to WonderWidgets!</title>
            </slide>
           
            <!-- OVERVIEW -->
            <slide type="all">
              <title>Overview</title>
                <item>Why <em>WonderWidgets</em> are great</item>
                <item>Who <em>buys</em> WonderWidgets</item>
            </slide>
           
          </slideshow> 

          后面會(huì)看到,如果XHTML元素使用了跟定義的title 元素相同的名字,兩者就會(huì)發(fā)生沖突。在講解解析參數(shù)化的DTD時(shí),會(huì)具體討論沖突產(chǎn)生機(jī)制 (DTD)和一些可用的解決方案。

          添加空元素

          HTML和XML的一個(gè)主要區(qū)別在于,所有的XML必須是形式良好的(well-formed)——這意味著每個(gè)標(biāo)簽必須有結(jié)束標(biāo)簽或?yàn)榭諛?biāo)簽。現(xiàn)在,你會(huì)很滿意于使用結(jié)束標(biāo)簽。添加下面突出顯示的文本,定義一個(gè)沒(méi)有內(nèi)容的空列表項(xiàng)元素:

            ...
            <!-- OVERVIEW -->
            <slide type="all">
              <title>Overview</title>
              <item>Why <em>WonderWidgets</em> are great</item>
              <item/>
              <item>Who <em>buys</em> WonderWidgets</item>
            </slide>
           
          </slideshow> 

          注意,任何元素都可以是空元素。它所做的是用"/>" 而不是">"來(lái)結(jié)束標(biāo)簽。輸入<item></item>可以實(shí)現(xiàn)相同的功能,它們是等價(jià)的。


          注意:使得XML形式良好的另一個(gè)因素是合適的嵌套。所以 <b><i>some_text</i></b>結(jié)構(gòu)良好,因?yàn)?CODE><i>...</i> 序列完全在<b>..</b> 標(biāo)簽內(nèi)部。而下面序列的結(jié)構(gòu)就不好: <b><i>some_text</b></i>


          最終產(chǎn)品

          這是XML文件的一個(gè)完整的版本:

          <?xml version='1.0' encoding='utf-8'?>
           
          <!--  A SAMPLE set of slides  --> 
          <slideshow 
            title="Sample Slide Show"
            date="Date of publication"
            author="Yours Truly"
            >
           
            <!-- TITLE SLIDE -->
            <slide type="all">
              <title>Wake up to WonderWidgets!</title>
            </slide>
           
            <!-- OVERVIEW -->
            <slide type="all">
              <title>Overview</title>
              <item>Why <em>WonderWidgets</em> are great</item>
              <item/>
              <item>Who <em>buys</em> WonderWidgets</item>
            </slide
          </slideshow> 

          現(xiàn)在已經(jīng)創(chuàng)建了一個(gè)可以使用的文件,下面準(zhǔn)備編寫(xiě)程序以使用SAX解析器回送它。在下一節(jié)完成該工作。

          使用SAX解析器回送XML文件

          在實(shí)際應(yīng)用中,沒(méi)有什么必要使用SAX解析器回送XML文件。通常,希望通過(guò)某種方式處理數(shù)據(jù),以便能夠有效地利用它。(如果想要回送它,可以建立DOM樹(shù),并用它來(lái)輸出結(jié)果。)但是,回送XML結(jié)構(gòu)是查看運(yùn)行中的SAX解析器的很好的方法,而且它在調(diào)試中也特別有用。

          在本練習(xí)中,將把SAX解析器事件回送到System.out。 請(qǐng)仔細(xì)查看XML處理程序的“Hello World”版本。它告訴你如何使用SAX解析器得到數(shù)據(jù),然后回送它以向你展示你究竟得到了什么。


          注意:本節(jié)討論的代碼在Echo01.java中。它使用的文件是slideSample01.xml。(可瀏覽的版本是slideSample01-xml.html)。


          創(chuàng)建框架

          首先創(chuàng)建文件Echo.java,并輸入該應(yīng)用程序的框架:

          public class Echo
          {
            public static void main(String argv[])
            { 
               } 
          } 

          由于準(zhǔn)備單獨(dú)運(yùn)行它,所以需要一個(gè)main方法。并且需要命令行參數(shù),這樣就能告訴應(yīng)用程序回送哪個(gè)文件。

          導(dǎo)入類

          接著,為應(yīng)用程序使用的類的添加導(dǎo)入語(yǔ)句:

          import java.io.*;
          import org.xml.sax.*;
          import org.xml.sax.helpers.DefaultHandler;
          import javax.xml.parsers.SAXParserFactory; 
          import javax.xml.parsers.ParserConfigurationException;
          import javax.xml.parsers.SAXParser; 
          public class Echo
          {
            ... 

          當(dāng)然, java.io中的類主要用來(lái)輸出。org.xml.sax 包定義了SAX 解析器使用的所有接口。SAXParserFactory 類創(chuàng)建了我們使用的實(shí)例。如果不能產(chǎn)生滿足特定參數(shù)配置的解析器,就拋出ParserConfigurationException 。(后面會(huì)具體介紹參數(shù)配置)。 SAXParser 是返回的用于解析的東西,并且DefaultHandler 定義將要處理解析器產(chǎn)生的SAX事件的類。

          設(shè)置I/O

          業(yè)務(wù)的第一件事是處理命令行參數(shù),得到要回送的文件的名字,并建立輸出流。添加下面突出顯示的文本,以處理這些任務(wù)并且做一些內(nèi)務(wù)工作:

          public static void main(String argv[])
           
          {
            if (argv.length != 1) {
              System.err.println("Usage: cmd filename");
              System.exit(1);
            }
            try {
              // 
              out = new OutputStreamWriter(System.out, "UTF8");
            } 
            catch (Throwable t) {
                t.printStackTrace();
            }
            System.exit(0);
          }
           
          static private Writer out; 

          創(chuàng)建輸出流書(shū)寫(xiě)器時(shí),選擇的是UTF-8字符編碼。也可以選擇US-ASCII或UTF-16,Java平臺(tái)也支持它們。如果想獲得關(guān)于這些字符集的更多信息,請(qǐng)查看Java編碼方案。

          實(shí)現(xiàn)ContentHandler接口

          滿足我們當(dāng)前需要的最重要的接口是ContentHandler 接口。該接口需要大量方法,其中SAX解析器在響應(yīng)各種解析事件時(shí)調(diào)用這些方法。處理事件的主要方法是: startDocument endDocument,、startElementendElementcharacters

          實(shí)現(xiàn)該接口的最簡(jiǎn)單的方法是擴(kuò)展DefaultHandler 類,該類定義在org.xml.sax.helpers 包中。該類為所有的ContentHandler 事件提供了不完成任何實(shí)際工作的方法。輸入下面突出顯示的代碼,擴(kuò)展該類:

          public class Echo extends DefaultHandler
          {
            ...
          } 

          注意:DefaultHandler 也為其他定義在DTDHandler EntityResolverErrorHandler 接口中的主要事件定義不完成任何工作的方法。下面,你會(huì)進(jìn)一步了解這些方法。

           

          接口需要所有的這些方法來(lái)拋出SAXException。這里拋出的異常會(huì)送給解析器,然后解析器再將其發(fā)送給調(diào)用解析器的代碼。在目前的程序中,這意味著它在main方法底部的Throwable異常處理程序中結(jié)束。

          遇到起始標(biāo)簽或結(jié)束標(biāo)簽時(shí),將標(biāo)簽的名字作為String 傳遞給startElementendElement 方法。遇到起始標(biāo)簽時(shí),也通過(guò)Attributes列表傳遞它定義的所有屬性。將元素內(nèi)部找到的字符作為字符數(shù)組傳遞,同時(shí)傳遞字符數(shù)目(length) 和指向第一個(gè)字符的數(shù)組中的偏移量。

          設(shè)置解析器

          現(xiàn)在(終于)要設(shè)置解析器了。添加下面突出顯示的文本,設(shè)置并啟動(dòng)它:

          public static void main(String argv[])
          {
            if (argv.length != 1) {
              System.err.println("Usage: cmd filename");
              System.exit(1);
            } 
                  // 
            DefaultHandler handler = new Echo(); 
                  //  SAXParserFactory factory = SAXParserFactory.newInstance();
            try {
              //    out = new OutputStreamWriter(System.out, "UTF8"); 
               //    SAXParser saxParser = factory.newSAXParser();
              saxParser.parse( new File(argv[0]), handler ); 
                  } catch (Throwable t) {
              t.printStackTrace();
            }
            System.exit(0);
          } 

          通過(guò)上面幾行代碼,創(chuàng)建了一個(gè)SAXParserFactory 實(shí)例,其中該實(shí)例由javax.xml.parsers.SAXParserFactory 系統(tǒng)屬性的設(shè)置決定。然后從庫(kù)中取出一個(gè)解析器,給解析器一個(gè)該類的實(shí)例來(lái)處理解析事件,以告訴它處理哪個(gè)輸入文件。


          注意: javax.xml.parsers.SAXParser 類是一個(gè)定義了大量方便方法的包裝器。它包裝了(有些不太友好) org.xml.sax.Parser 對(duì)象。如果需要,可以使用SAXParser getParser() 方法得到解析器。


          這時(shí),你只是在簡(jiǎn)單地捕獲解析器可能拋出的任何異常。在教程的使用非驗(yàn)證解析器處理錯(cuò)誤一節(jié)中將會(huì)了解到錯(cuò)誤處理的更多信息。

          編寫(xiě)輸出

          ContentHandler 方法拋出SAXExceptions 而不是IOExceptions,它可能在編寫(xiě)的時(shí)候出現(xiàn)。SAXException 能夠包裝另一個(gè)異常,這樣就能用負(fù)責(zé)異常處理細(xì)節(jié)的方法進(jìn)行輸出。添加下面突出顯示的代碼,定義一個(gè)emit 方法:

          static private Writer out; 
          private void emit(String s)
          throws SAXException
          {
            try {
              out.write(s);
              out.flush();
            } catch (IOException e) {
              throw new SAXException("I/O error", e);
            }
          }
          ... 

          調(diào)用emit時(shí),I/O錯(cuò)誤和它的標(biāo)識(shí)信息一起包裝在SAXException 中。然后,將該異常拋回給SAX解析器。后面會(huì)具體介紹SAX異常。現(xiàn)在,記住emit 是處理字符串輸出的小方法。

          間隔輸出

          這是進(jìn)行具體處理之前要建立的另一個(gè)基礎(chǔ)結(jié)構(gòu)。添加下面突出顯示的代碼,定義一個(gè)nl() 方法,以寫(xiě)出當(dāng)前系統(tǒng)使用的行結(jié)束符:

          private void emit(String s) 
            ...
          } 
          private void nl()
          throws SAXException
          {
            String lineEnd = System.getProperty("line.separator");
            try {
              out.write(lineEnd);
            } catch (IOException e) {
              throw new SAXException("I/O error", e);
            }
          } 

          注意: 雖然這看起來(lái)有點(diǎn)多余,但還需要在前面的代碼中多次調(diào)用nl()。現(xiàn)在定義它可以簡(jiǎn)化以后的代碼。它也為該教程后面部分縮排輸出結(jié)果提供了一個(gè)地方。


          處理內(nèi)容事件

          最后,編寫(xiě)一些實(shí)際處理ContentHandler 事件的代碼。

          文檔事件

          將下面突出顯示的代碼添加到start-document 和end-document 事件中:

          static private Writer out;
           
          public void startDocument()
          throws SAXException
          {
            emit("<?xml version='1.0' encoding='UTF-8'?>");
            nl();
          }
           
          public void endDocument()
          throws SAXException
          {
            try {
              nl();
              out.flush();
            } catch (IOException e) {
              throw new SAXException("I/O error", e);
            }
          } 
          private void echoText()
          ... 

          這里,當(dāng)解析器遇到文檔開(kāi)始時(shí),回送XML聲明。由于使用UTF-8建立OutputStreamWriter ,所以將該規(guī)范作為聲明的一部分。


          注意:然而, IO類不理解帶有帶有連字符號(hào)的編碼名,所以使用“UTF8”而不用“UTF-8”。


          在文檔最后,只要放置最終新行并刷新輸出結(jié)果流。不需要進(jìn)行很多操作。

          元素事件

          現(xiàn)在做件有趣的事情。添加下面突出顯示的代碼處理啟始元素(start-element)和結(jié)束元素(end-element)事件:

          public void startElement(String namespaceURI,
                  String sName, // simple name
                  String qName, // qualified name
                  Attributes attrs)
          throws SAXException
          {
            String eName = sName; // element name
            if ("".equals(eName)) eName = qName; // not namespaceAware
            emit("<"+eName);
            if (attrs != null) {
              for (int i = 0; i < attrs.getLength(); i++) {
                String aName = attrs.getLocalName(i); // Attr name
                if ("".equals(aName)) aName = attrs.getQName(i);
                emit(" ");
                emit(aName+"=\""+attrs.getValue(i)+"\"");
              }
            }
            emit(">");
          } 
          public void endElement(String namespaceURI,
                  String sName, // simple name
                  String qName  // qualified name
                  )
          throws SAXException
          {
            String eName = sName; // element name
            if ("".equals(eName)) eName = qName; // not namespaceAware
            emit("<"+eName+">");
          } 
          private void emit(String s)
          ... 

          使用該代碼,回送元素標(biāo)簽,包括在起始標(biāo)簽中定義的任何屬性。注意調(diào)用startElement() 方法時(shí),如果不能進(jìn)行命名空間處理,元素和屬性的簡(jiǎn)稱 ("local name") 可能變成空字符串。當(dāng)簡(jiǎn)稱為空字符串時(shí),代碼就使用限定名。

          字符事件

          要完成內(nèi)容事件的處理,必須處理解析器傳遞給應(yīng)用程序的字符。

          不要求解析器同時(shí)返回所有特定字符。解析器一次可以返回單個(gè)字符,也可以返回幾千個(gè)字符,但實(shí)現(xiàn)中仍然要遵守標(biāo)準(zhǔn)。所以,如果應(yīng)用程序要處理遇到的字符,最好將這些字符積累在緩沖區(qū)中,并且僅在你認(rèn)為已經(jīng)找出了所有的字符時(shí)才處理它們。

          添加下面的代碼定義文本緩沖區(qū):

          public class Echo01 extends DefaultHandler
          {
            StringBuffer textBuffer;
           
            public static void main(String argv[])
            { 
          ... 

          然后,添加下面突出顯示的代碼,積累解析器傳遞到緩沖中的字符:

          public void endElement(...)
          throws SAXException
          {
            ...
          } 
          public void characters(char buf[], int offset, int len)
          throws SAXException
          {
            String s = new String(buf, offset, len);
            if (textBuffer == null) {
              textBuffer = new StringBuffer(s);
            } else {
              textBuffer.append(s);
            }
          } 
          private void emit(String s)
          ... 

          接著,添加下面突出顯示的方法,將緩沖中的內(nèi)容發(fā)送到輸出流中。

          public void characters(char buf[], int offset, int len)
          throws SAXException
          {
            ...
          } 
          private void echoText()
          throws SAXException
          {
            if (textBuffer == null) return;
            String s = ""+textBuffer
            emit(s);
            textBuffer = null;
          } 
          private void emit(String s)
          ... 

          當(dāng)同一行兩次調(diào)用該方法(下面可以看到,這種情況經(jīng)常發(fā)生),緩沖區(qū)為空。在這種情況下,方法僅僅返回。然而,如果緩沖區(qū)非空,將它的內(nèi)容發(fā)送到輸出流中。

          最后,添加下面的代碼,以便能在開(kāi)始或結(jié)束一個(gè)元素的時(shí)候,回送緩沖區(qū)的內(nèi)容:

          public void startElement(...)
          throws SAXException
          {
            echoText();
            String eName = sName; // element name
            ...
          } 
          public void endElement(...)
          throws SAXException
          {
            echoText();
            String eName = sName; // element name
            ...
          } 

          當(dāng)然,元素結(jié)束時(shí),也完成了文本積累。所以,這時(shí)就可以回送了,以便在開(kāi)始下一個(gè)元素之前清除緩沖區(qū)。

          但是,在開(kāi)始一個(gè)元素的時(shí)候,也希望回送積累的文本。這對(duì)于文檔風(fēng)格的數(shù)據(jù)來(lái)說(shuō)很有必要,這種類型的數(shù)據(jù)包含混雜了文本的XML元素。例如,在該文檔段中:

          <para>This paragraph contains <bold>important</bold> 
          ideas.</para> 

          初始文本“This paragraph contains”被元素<bold> 的開(kāi)始所中斷。文本“important”被結(jié)束標(biāo)簽</bold>中斷,并且最后的文本“ideas”被結(jié)束標(biāo)簽</para>中斷。


          注意:在大多數(shù)時(shí)候,出現(xiàn)endElement() 事件的時(shí)候才回送積累的文本。當(dāng)這之后出現(xiàn)startElement() 事件時(shí),緩沖將為空。echoText() 方法中的第一行進(jìn)行該檢查,然后返回。


          恭喜!這時(shí)你已經(jīng)編寫(xiě)了一個(gè)完整的SAX解析器應(yīng)用程序。下一步是編譯并運(yùn)行它。


          注意:為了確保精確度,字符處理程序必須掃描緩沖區(qū)看看有沒(méi)有字符('&');左尖括號(hào)('<'),并且使用字符串 "&amp;" or "&lt;"替換它們。在替換和插入文本中的實(shí)體引用討論中,會(huì)獲得該類處理的更多信息。

          編譯并運(yùn)行程序

          在Java WSDP中,JAXP庫(kù)發(fā)布在目錄<JWSDP_HOME>/common/lib下。要編譯創(chuàng)建的程序,首先需要在合適的位置安裝JAXP JAR 文件。(JAR的文件名取決于使用的JAXP版本,位置取決于使用的Java平臺(tái)的版本。查看Java XML 發(fā)行注意事項(xiàng)<JWSDP_HOME>/docs/jaxp/ReleaseNotes.html 獲取最新詳細(xì)信息)。


          注意:由于將JAXP 1.1 建立在Java 2平臺(tái)的1.4版本中,你也可以執(zhí)行大多數(shù)的JAXP 教程部分(SAX、 DOM和XSLT),不需要安裝特殊的JAR 文件。然而,要使用JAXP中的新性能—— XML模式和XSLTC編譯轉(zhuǎn)換器——需要按照發(fā)行注意事項(xiàng)中的說(shuō)明安裝JAXP 1.2。


          在Java 2平臺(tái)的版本1.2和1.3中,可以執(zhí)行下列命令編譯并運(yùn)行程序:

          javac -classpath jaxp-jar-files Echo.java
          java -cp jaxp-jar-files Echo slideSample.xml 

          或者,也可以將JAR文件放置在平臺(tái)擴(kuò)展目錄下,并使用更加簡(jiǎn)單的命令:

          javac Echo.java
          java Echo slideSample.xml 

          在Java 2平臺(tái)的1.4版本中,必須將JAR文件視作建立在Java 2平臺(tái)上的“支持標(biāo)準(zhǔn)”(endorsed standard)的新版本。這就需要將JAR文件放在支持標(biāo)準(zhǔn)目錄jre/lib/endorsed中。(復(fù)制除了jaxp-api.jar. 之外所有的ARJ文件。忽略該文件是因?yàn)镴AXP API早就建立在1.4平臺(tái)中。)

          然后可以使用下面的命令編譯并運(yùn)行程序:

          javac Echo.java
          java Echo slideSample.xml 

          注意: 也可以精心在命令行上設(shè)置java.endorsed.dirs 系統(tǒng),讓它指向一個(gè)包含必要JAR文件的目錄,使用類似于下面的命令行參數(shù): -D"java.endorsed.dirs=somePath".

          檢查輸出

          下面是程序輸出的一部分,顯示了一些奇怪的空白:

          ...
          <slideshow title="Sample Slide Show" date="Date of publication" 
          author="Yours Truly">
           
           
            <slide type="all">
              <title>Wake up to WonderWidgets!</title>
            </slide>
            ... 

          注意:程序的輸出保存在Echo01-01.txt中。(可瀏覽的版本是Echo01-01.html.)


          查看該輸出,會(huì)發(fā)現(xiàn)大量問(wèn)題。就是多余的垂直空白是哪里來(lái)的?并且為什么代碼沒(méi)有進(jìn)行處理,元素就能夠很好地縮進(jìn)?稍后就來(lái)回答這些問(wèn)題。首先,需要注意結(jié)果的一些方面:

          ·   在文件頂部定義的注釋<!-- A SAMPLE set of slides -->沒(méi)有出現(xiàn)在列表中。忽略注釋,除非實(shí)現(xiàn)LexicalHandler。在本教程的后面部分會(huì)具體介紹。

          ·   所有的元素屬性都列在一行中。如果窗口不夠?qū)挘涂床坏剿袃?nèi)容。

          ·   定義的單標(biāo)簽空元素 (<item/>) 和雙標(biāo)簽空元素(<item></item>)的處理方法相同。它們的目標(biāo)和意圖是一樣的。(僅僅是更加容易輸入并需要較少的空間)

          識(shí)別事件

          該版本的回送(Echo)程序?qū)τ陲@示XML非常有用,但是它沒(méi)有告訴你解析器內(nèi)部究竟在做些什么。下一步是修改該程序,這樣你就能看到空白和垂直行是怎么來(lái)的。

          注意:本節(jié)討論的代碼在Echo02.java中。它的輸出結(jié)果在Echo02-01.txt中。(可瀏覽的版本是Echo02-01.html)

          執(zhí)行如下代碼,以便當(dāng)事件發(fā)生的時(shí)候識(shí)別它:

          public void startDocument()
          throws SAXException
          {
            nl();
            nl(); 
            emit("START DOCUMENT");
            nl(); 
            emit("<?xml version='1.0' encoding='UTF-8'?>");
            nl();
          }
           
          public void endDocument()
          throws SAXException
          {
            nl(); 
            emit("END DOCUMENT");
            try {
            ...
          }
           
          public void startElement(...)
          throws SAXException
          {
            echoText();
            nl(); 
            emit("ELEMENT: ");
            String eName = sName; // element name
            if ("".equals(eName)) eName = qName; // not namespaceAware
            emit("<"+eName);
            if (attrs != null) {
              for (int i = 0; i < attrs.getLength(); i++) {
                String aName = attrs.getLocalName(i); // Attr name
                if ("".equals(aName)) aName = attrs.getQName(i);
                emit(" ");
                emit(aName+"=\""+attrs.getValue(i)+"\"");
                nl(); 
                emit("   ATTR: ");
                emit(aName);
                emit("\t\"");
                emit(attrs.getValue(i));
                emit("\"");
              }
            }
            if (attrs.getLength() > 0) nl();
            emit(">");
          } 
          public void endElement(...)
          throws SAXException
          {
            echoText();
            nl(); 
            emit("END_ELM: ");
            String eName = sName; // element name
            if ("".equals(eName)) eName = qName; // not namespaceAware
            emit("<"+eName+">");
          }
           
          ... 
          private void echoText()
          throws SAXException
          { 
            if (textBuffer == null) return;
            nl(); 
            emit("CHARS: |"); 
            String s = ""+textBuffer
            emit(s);
            emit("|");
            textBuffer = null;
          } 

          編譯并運(yùn)行該版本的程序,以輸出更多信息。現(xiàn)在每行顯示一個(gè)屬性。但是,更加重要的是,它輸出類似于下面的行:

            CHARS: |
           
          | 

          表明縮進(jìn)空間和分隔屬性的新行來(lái)自于解析器傳遞給characters() 方法的數(shù)據(jù)。


          注意:XML規(guī)范要求所有輸入行分隔符都放在單個(gè)新行中。Java、C和UNIX系統(tǒng)都有自己的新行字符,但是在Windows系統(tǒng)中使用別名“l(fā)inefeed”。


          壓縮輸出結(jié)果

          為了提高輸出可讀性,修改程序,這樣它就不會(huì)輸出空白了。

          注意:本節(jié)討論的代碼在Echo03.java中。

          做出下面的修改,壓縮所有空白輸出字符:

          public void echoText()
          throws SAXException
          {
            nl(); 
            emit("CHARS: |");
            emit("CHARS:   ");
            String s = ""+textBuffer;
            if (!s.trim().equals("")) emit(s);
            emit("|");
          } 

          然后,添加如下代碼,顯示解析器發(fā)送的每個(gè)字符集。

          public void characters(char buf[], int offset, int len)
          throws SAXException
          {
            if (textBuffer != null) {
              echoText();
              textBuffer = null;
            }
            String s = new String(buf, offset, len);
            ...
          } 

          如果現(xiàn)在運(yùn)行程序,你可以發(fā)現(xiàn)你已經(jīng)消除了縮進(jìn),這是因?yàn)榭s進(jìn)的空間是元素前面的空白的一部分。添加下面突出顯示的代碼管理縮進(jìn):

          static private Writer out; 
          private String indentString = "    "; // Amount to indent 
          private int indentLevel = 0;
           
          ... 
          public void startElement(...)
          throws SAXException
          {
            indentLevel++;
            nl(); 
            emit("ELEMENT: ");
            ...
          } 
          public void endElement(...)
          throws SAXException
          {
            nl(); 
            emit("END_ELM: ");
            emit("</"+sName+">");
            indentLevel--;
          }
          ...
          private void nl()
          throws SAXException
          {
            ...
            try {
              out.write(lineEnd);
              for (int i=0; i < indentLevel; i++)
                out.write(indentString);
            } catch (IOException e) {
            ... 
          } 

          該代碼建立縮進(jìn)字符串,跟蹤當(dāng)前的縮進(jìn)層,并且在調(diào)用n1方法的時(shí)候,輸出縮進(jìn)字符串。如果將縮進(jìn)字符串設(shè)置為"",輸出結(jié)果就不縮進(jìn) (試驗(yàn)一下,你會(huì)明白為什么需要添加縮進(jìn))

          當(dāng)你知道你已經(jīng)看到了添加到回送(Echo)程序的“機(jī)械”代碼的最后部分,你會(huì)覺(jué)得很開(kāi)心。從這里開(kāi)始,你所作的事情都會(huì)進(jìn)一步幫你理解解析器的工作原理。目前所進(jìn)行的步驟,告訴了你解析器是如何查看它處理的XML數(shù)據(jù)的。它也給你提供了一個(gè)有效的調(diào)試工具,幫助你理解解析器看到的內(nèi)容。

          檢查輸出

          下面是該版本程序的部分輸出:

          ELEMENT: <slideshow
          ...
          >
          CHARS: 
          CHARS: 
            ELEMENT: <slide
            ... 
            END_ELM: </slide>
          CHARS: 
          CHARS:    

          注意:完整的輸出結(jié)果在Echo03-01.txt(可瀏覽的版本是Echo03-01.html)

          注意, 同一行兩次調(diào)用characters方法。檢查源代碼文件slideSample01.xml ,它表明在第一個(gè)幻燈片之前有注釋。第一個(gè)characters 調(diào)用出現(xiàn)在注釋之前。第二個(gè)調(diào)用出現(xiàn)在注釋之后。 (以后,你會(huì)明白當(dāng)解析器遇到注釋的時(shí)候,它是如何通知你的,雖然,在多數(shù)時(shí)候你不需要這樣的通知)

          同樣也要注意, characters 方法是在第一個(gè)幻燈片之后調(diào)用的,也可以在這之前調(diào)用。在考慮分層結(jié)構(gòu)化數(shù)據(jù)的時(shí)候,這看起來(lái)很多余。說(shuō)到底,你希望slideshow 元素包含slide 元素,而不是文本。以后,你會(huì)知道如何使用DTD限制slideshow 元素。在這樣做的時(shí)候,就不會(huì)再調(diào)用characters 方法。

          然而,沒(méi)有DTD時(shí),解析器必須假設(shè)它能看到的所有元素包含文本,類似于概述幻燈片的第一項(xiàng)元素:

          <item>Why <em>WonderWidgets</em> are great</item> 

          層次結(jié)構(gòu)如下所示:

          ELEMENT:        <item>
          CHARS:        Why 
            ELEMENT:         <em>
            CHARS:         WonderWidgets
            END_ELM:        </em>
          CHARS:         are great
          END_ELM:        </item> 

          文檔和數(shù)據(jù)

          在本例中,很明顯有些字符結(jié)合了元素的層次結(jié)構(gòu)。文本可以包圍元素這個(gè)事實(shí)(或避免在DTD或模式中這樣做)能夠解釋為什么有的時(shí)候聽(tīng)到別人討論"XML數(shù)據(jù)"而在其他時(shí)候則聽(tīng)到別人討論"XML文檔"。XML能夠處理結(jié)構(gòu)化數(shù)據(jù)和包含標(biāo)簽的文本文檔。兩者之間的區(qū)別在于元素之間是不是允許有文本。


          注意:在本教程的下面部分,要使用ContentHandler 接口中的ignorableWhitespace 方法。只有有DTD的時(shí)候才能調(diào)用該方法。如果DTD規(guī)定slideshow 不能包含文本,那么忽略定義中slide 元素旁邊的所有空白。另外,如果slideshow 可以包含文本(在缺少DTD的時(shí)候必須為true),那么解析器必須假設(shè)看到的slide 元素之間的空白和行是文檔的重要部分。

          添加其他事件處理程序

          ignorableWhitespace之外,可以發(fā)現(xiàn)即便是在非常簡(jiǎn)單的程序中也有其他兩個(gè)ContentHandler方法:setDocumentLocatorprocessingInstruction 本節(jié)主要介紹如何實(shí)現(xiàn)這兩個(gè)事件處理程序。

          識(shí)別文檔的位置

          locator 是一個(gè)對(duì)象,它包含了查找文檔所必需的信息。Locator 類包含了一個(gè)系統(tǒng)ID (URL)或公共標(biāo)識(shí)符(URN)或兩者都有。如果想要查找跟當(dāng)前文檔相關(guān)的一些東西就必需該信息——例如, HTML瀏覽器處理定位標(biāo)簽href="anotherFile" 的屬性——瀏覽器使用當(dāng)前文檔的路徑來(lái)查找anotherFile

          也可以使用定位器打印診斷信息。除了文檔的位置和公共標(biāo)識(shí)符外,定位器包含給出最近使用的列和行的數(shù)目的方法。但是,僅在解析器的開(kāi)始部分調(diào)用一次setDocumentLocator 方法。要獲得當(dāng)前行或列的編碼,必須要在調(diào)用setDocumentLocator 時(shí)保存定位器,然后在其他事件處理方法中使用它。

          注意:本節(jié)討論的代碼在Echo04.java中,它的輸出結(jié)果在Echo04-01.txt中。(可瀏覽的版本是Echo04-01.html.)

          首先刪除最后一個(gè)例子中添加的額外字符回送代碼:

          public void characters(char buf[], int offset, int len)
          throws SAXException
          {
            if (textBuffer != null) {
              echoText();
              textBuffer = null;
            }
            String s = new String(buf, offset, len);
            ...
          } 

          然后,將下面的方法添加到Echo程序中,獲得文檔定位器,并使用它回送文檔的系統(tǒng)ID。

          ...
          private String indentString = "    "; // Amount to indent
          private int indentLevel = 0; 
          public void setDocumentLocator(Locator l)
          {
            try {
              out.write("LOCATOR");
              out.write("SYS ID: " + l.getSystemId() );
              out.flush();
            } catch (IOException e) {
              // Ignore errors
            }
          } 
          public void startDocument()
          ... 

          注意:

          ·   同其他ContentHandler 方法比較,該方法并不返回SAXException。所以,不使用emit 輸出結(jié)果,該代碼將輸出直接寫(xiě)到System.out。(該方法只是保存Locator 以作將來(lái)使用,在這里不產(chǎn)生異常.)

          ·   這些方法的拼寫(xiě)是"Id"不是 "ID"。所以有getSystemIdgetPublicId

          slideSample01.xml編譯和運(yùn)行程序時(shí)下面的內(nèi)容就是輸出結(jié)果的重要部分:

          LOCATOR
          SYS ID: file:<path>/../samples/slideSample01.xml 
          START DOCUMENT
          <?xml version='1.0' encoding='UTF-8'?>
          ... 

          這里,很明顯setDocumentLocator在開(kāi)始文檔之前調(diào)用的。如果在事件處理代碼中進(jìn)行了初始化,情況就可能有所不同。

          處理處理指令

          有時(shí)在XML數(shù)據(jù)中編寫(xiě)特定于應(yīng)用程序的處理指令很有意義。在該練習(xí)中,在slideSample.xml 文件中添加處理指令,然后修改Echo 程序來(lái)顯示它。

          注意:本節(jié)討論的代碼在Echo05.java中。 它操作的文件是slideSample02.xml 輸出結(jié)果在Echo05-02.txt中。(可瀏覽的版本是slideSample02-xml.html Echo05-02.html.)

          理解XML中可以得知,處理指令的格式是<?target data?>,其中"target"是要進(jìn)行處理的目標(biāo)應(yīng)用程序,并且"data"是要處理的指令或信息。添加下面的文本,為虛構(gòu)幻燈片播放程序添加處理指令,該程序要求用戶找出顯示哪個(gè)幻燈片(技術(shù)層、執(zhí)行層或兩者都有):

          <slideshow 
            ...
            > 
            <!-- PROCESSING INSTRUCTION -->
            <?my.presentation.Program QUERY="exec, tech, all"?> 
            <!-- TITLE SLIDE --> 

          注意:

          ·   處理指令的"data"部分可以包含空格也能為空。但是在初始<? 和目標(biāo)標(biāo)識(shí)符中不能有任何空格。

          ·   數(shù)據(jù)位于第一個(gè)空格之后。

          ·   利用完整的Web-unique包前綴來(lái)完全限定目標(biāo)很有意義,所以避免了它跟其他可能處理相同數(shù)據(jù)的程序沖突。

          ·    為了提高可讀性,最好在應(yīng)用程序名后加一個(gè)冒號(hào)(:),如下所示:

          <?my.presentation.Program: QUERY="..."?> 

          該冒號(hào)使得目標(biāo)名成為"label",它識(shí)別指令的目標(biāo)接收者。然而,w3c規(guī)范允許目標(biāo)文件中有":" ,但是IE5的一些版本卻不支持,會(huì)出錯(cuò)。本教程避免在目標(biāo)名中使用冒號(hào)。

          現(xiàn)在可以使用一個(gè)處理指令,將下面的代碼添加到Echo應(yīng)用程序中:

          public void characters(char buf[], int offset, int len)
          ...
          } 
          public void processingInstruction(String target, String data)
          throws SAXException
          {
            nl(); 
            emit("PROCESS: ");
            emit("<?"+target+" "+data+"?>");
          } 
          private void echoText()
          ... 

          完成編輯后,編寫(xiě)并運(yùn)行程序。輸出結(jié)果的相關(guān)部分看起來(lái)如下所示:

          ELEMENT: <slideshow
            ...
          >
          PROCESS: <?my.presentation.Program QUERY="exec, tech, all"?>
          CHARS: 
          ... 

          小結(jié)

          在小異常ignorableWhitespace中,你已經(jīng)使用了大部分的ContentHandler 方法來(lái)處理最通用的SAX 事件。后面會(huì)介紹ignorableWhitespace 。然后,將深入了解如何在SAX解析處理中處理錯(cuò)誤。

          使用非驗(yàn)證解析器處理錯(cuò)誤

          該版本的Echo程序使用非驗(yàn)證解析器。所以它不能確定XML文檔是否包含了正確的標(biāo)簽,或這些標(biāo)簽是否在正確的序列中。換句話說(shuō),它不能告訴你文檔是否有效。然而,它能判斷出文檔結(jié)構(gòu)是否良好。

          本節(jié)中,要修改幻燈片顯示(slideshow)文件,以生成各類錯(cuò)誤,并觀察解析器是如何處理它們的。你能夠看出默認(rèn)情況下哪些錯(cuò)誤情形是可以忽略的,也可以看到如何處理它們的。

          生成錯(cuò)誤

          解析器可以產(chǎn)生三類錯(cuò)誤:致命錯(cuò)誤、錯(cuò)誤和警告。在本練習(xí)中,我們將修改XML文件以產(chǎn)生一個(gè)致命錯(cuò)誤。然后查看Echo應(yīng)用程序是如何處理它的。

          注意:本練習(xí)中要?jiǎng)?chuàng)建的XML結(jié)構(gòu)在slideSampleBad1.xml中。輸出結(jié)果在Echo05-Bad1.txt中。 (可瀏覽版本是slideSampleBad1-xml.html Echo05-Bad1.html.)

          產(chǎn)生致命錯(cuò)誤的一個(gè)最簡(jiǎn)單的方法就是刪除空item 元素最后的"/" ,創(chuàng)建一個(gè)沒(méi)有相應(yīng)結(jié)束標(biāo)簽的標(biāo)簽。這構(gòu)成了一個(gè)致命錯(cuò)誤,因?yàn)楦鶕?jù)定義,所有的XML 文檔必須具有良好的結(jié)構(gòu)。步驟如下:

          1. 將slideSample.xml 復(fù)制到 badSample.xml

          2. 編輯badSample.xml 并且刪除下面的字符:

          ...
          <!-- OVERVIEW -->
          <slide type="all">
            <title>Overview</title>
            <item>Why <em>WonderWidgets</em> are great</item>
            <item/>
            <item>Who <em>buys</em> WonderWidgets</item>
          </slide>
          ... 

          產(chǎn)生:

          ...
          <item>Why <em>WonderWidgets</em> are great</item>
          <item>
          <item>Who <em>buys</em> WonderWidgets</item> 
          ... 

          1. 在新文件上運(yùn)行Echo程序。

          輸出結(jié)果給出如下錯(cuò)誤消息:

          org.xml.sax.SAXParseException: 
            The element type "item" must be terminated by the
            matching end-tag "</item>".
          ...
          at org.apache.xerces.parsers.AbstractSAXParser...
          ...
          at Echo.main(...) 

          注意:上面的代碼是JAXP 1.2 庫(kù)產(chǎn)生的。如果使用不同的解析器,錯(cuò)誤消息可能會(huì)有所不同。

          當(dāng)出現(xiàn)致命錯(cuò)誤時(shí),解析器就不能繼續(xù)下去。所以,如果應(yīng)用程序沒(méi)有產(chǎn)生異常(一會(huì)兒你就會(huì)看到怎么做了),那么默認(rèn)的錯(cuò)誤-事件處理程序會(huì)產(chǎn)生一個(gè)致命錯(cuò)誤。棧跟蹤是由main方法中的Throwable 異常處理程序產(chǎn)生的:

              ...
          } catch (Throwable t) {
            t.printStackTrace();
          } 

          棧跟蹤不是非常有用。然后,你會(huì)看到出現(xiàn)錯(cuò)誤時(shí)如何產(chǎn)生更好的診斷信息。

          處理SAXParseException

          遇到錯(cuò)誤時(shí),解析器產(chǎn)生SAXParseException--SAXException 的一個(gè)子類,以識(shí)別錯(cuò)誤出現(xiàn)的文件和位置。

          注意:本練習(xí)中要?jiǎng)?chuàng)建的代碼在Echo06.java中。輸出結(jié)果在Echo06-Bad1.txt中。(可瀏覽版本是 Echo06-Bad1.html.)

          添加下面的代碼,在出現(xiàn)異常時(shí)產(chǎn)生更好的診斷信息:

          ...
          } catch (SAXParseException spe) {
            // Error generated by the parser
            System.out.println("\n** Parsing error" 
              + ", line " + spe.getLineNumber()
              + ", uri " + spe.getSystemId());
            System.out.println("   " + spe.getMessage() );
           
          } catch (Throwable t) {
            t.printStackTrace();
          } 

          這時(shí)運(yùn)行程序?qū)a(chǎn)生一個(gè)錯(cuò)誤消息,該錯(cuò)誤消息非常有用,格式如下:

          ** Parsing error, line 22, uri file:<path>/slideSampleBad1.xml
            The element type "item" must be ... 

          注意:錯(cuò)誤消息的文本取決于使用的解析器。該消息是用JAXP 1.2產(chǎn)生的。

          注意:在產(chǎn)品應(yīng)用程序中不適合這樣捕獲異常。現(xiàn)在開(kāi)始逐步建立完整的錯(cuò)誤處理程序。另外,它能捕獲所有的空指針異常,當(dāng)傳遞給解析器空值時(shí),會(huì)拋出該異常。

          處理SAXException

          有時(shí)解析器能夠產(chǎn)生一個(gè)更加通用的SAXException 實(shí)例,但是這個(gè)實(shí)例通常在應(yīng)用程序的事件處理方法產(chǎn)生錯(cuò)誤的時(shí)候出現(xiàn)。例如, ContentHandler 接口中的startDocument 方法的會(huì)返回一個(gè)SAXException

          public void startDocument() throws SAXException 

          所有的ContentHandler 方法 (除了 setDocumentLocator) 具有該特征定義。

          SAXException 可以使用消息、另一個(gè)異常或兩個(gè)都用。例如,當(dāng)Echo.startDocument 使用emit 方法輸出一個(gè)字符串時(shí),所有出現(xiàn)的I/O異常都包裝在SAXException 中并且被發(fā)送回解析器。

          private void emit(String s)
          throws SAXException
          {
            try {
              out.write(s);
              out.flush();
            } catch (IOException e) {
              throw new SAXException("I/O error", e);
            }
          } 

          注意: 如果在調(diào)用setDocumentLocator 的時(shí)候保存Locator 對(duì)象,你可以使用它來(lái)產(chǎn)生SAXParseException,識(shí)別文檔和位置,而不產(chǎn)生SAXException

          當(dāng)解析器將異常發(fā)送回調(diào)用解析器的代碼,可以使用原始異常產(chǎn)生棧跟蹤。添加下面的代碼實(shí)現(xiàn)該功能:

             ...
          } catch (SAXParseException err) {
            System.out.println("\n** Parsing error" 
              + ", line " + err.getLineNumber()
              + ", uri " + err.getSystemId());
            System.out.println("   " + err.getMessage());
           
          } catch (SAXException sxe) {
            // Error generated by this application
            // (or a parser-initialization error)
            Exception  x = sxe;
            if (sxe.getException() != null)
              x = sxe.getException();
            x.printStackTrace();
           
          } catch (Throwable t) {
            t.printStackTrace();
          } 

          該代碼測(cè)試SAXException 有沒(méi)有包裝另外一個(gè)異常。如果是,它產(chǎn)生一個(gè)棧跟蹤,以精細(xì)化錯(cuò)誤處理代碼,其中異常就是從棧跟蹤開(kāi)始的。如果異常僅包含一條消息,代碼從產(chǎn)生異常的地方開(kāi)始打印棧跟蹤。

          改進(jìn)SAXParseException處理程序

          由于SAXParseException 也能包裝另一個(gè)異常,添加下面的代碼,在棧跟蹤中使用包含的異常:

              ...
          } catch (SAXParseException err) {
            System.out.println("\n** Parsing error" 
              + ", line " + err.getLineNumber()
              + ", uri " + err.getSystemId());
            System.out.println("   " + err.getMessage());
           
            // Use the contained exception, if any
            Exception  x = spe;
            if (spe.getException() != null)
              x = spe.getException();
            x.printStackTrace(); 
          } catch (SAXException sxe) {
            // Error generated by this application
            // (or a parser-initialization error)
            Exception      x = sxe;
            if (sxe.getException() != null)
              x = sxe.getException();
            x.printStackTrace(); 
          } catch (Throwable t) {
            t.printStackTrace();
          }       

          現(xiàn)在程序要處理它看到的任何SAX解析異常。可以看到解析器產(chǎn)生致命錯(cuò)誤的異常。但是對(duì)于非致命性錯(cuò)誤和警告,默認(rèn)的錯(cuò)誤處理程序不會(huì)產(chǎn)生異常,也不會(huì)顯示任何消息。下面,學(xué)進(jìn)一步學(xué)習(xí)錯(cuò)誤和警告,并查看如何提供錯(cuò)誤處理程序處理它們。

          處理 ParserConfigurationException

          最后,回想一下,如果類SAXParserFactory不能創(chuàng)建一個(gè)解析器就會(huì)拋出異常。如果factory不能找到創(chuàng)建解析器(類不能找到錯(cuò)誤)的類就會(huì)產(chǎn)生這樣的錯(cuò)誤,即不允許訪問(wèn)它 (非法訪問(wèn)異常)或不能將它實(shí)例化(實(shí)例化錯(cuò)誤)。

          添加下面的代碼處理這類錯(cuò)誤:

          } catch (SAXException sxe) {
            Exception      x = sxe;
            if (sxe.getException() != null)
              x = sxe.getException();
            x.printStackTrace();
           
          } catch (ParserConfigurationException pce) {
            // Parser with specified options can't be built
            pce.printStackTrace();
           
          } catch (Throwable t) {
            t.printStackTrace(); 

          不可否認(rèn),這里有相當(dāng)多的錯(cuò)誤處理程序。但是現(xiàn)在你至少知道了可能出現(xiàn)的異常類型。

          注意:如果不能找到系統(tǒng)屬性指定的factory類或不能將它實(shí)例化,也可拋出javax.xml.parsers.FactoryConfigurationError 。這是一個(gè)非陷阱錯(cuò)誤(non-trappable error),程序不能恢復(fù)它。

          處理 IOException

          最后,添加IOExceptions的處理程序:

          } catch (ParserConfigurationException pce) {
            // Parser with specified options can't be built
            pce.printStackTrace();
           
          } catch (IOException ioe) {
            // I/O error
            ioe.printStackTrace();
          }
           
          } catch (Throwable t) {
            ... 

          Throwables 的處理程序捕捉空指針錯(cuò)誤,但是注意,在這點(diǎn)上它跟IOException 處理程序相同。這里,僅僅顯示可能出現(xiàn)的例外,以免其中的一些你的應(yīng)用程序能夠恢復(fù)。

          處理非致命錯(cuò)誤

          當(dāng)XML文檔不能滿足有效性約束的時(shí)候,會(huì)出現(xiàn)非致命 錯(cuò)誤。如果解析器發(fā)現(xiàn)文檔無(wú)效,就會(huì)產(chǎn)生一個(gè)錯(cuò)誤事件。給定一個(gè)DTD 或模式,當(dāng)文檔具有無(wú)效標(biāo)簽,或標(biāo)簽出現(xiàn)在不應(yīng)該出現(xiàn)的地方,或元素包含無(wú)效數(shù)據(jù)(模式的情況中),驗(yàn)證解析器就會(huì)產(chǎn)生該錯(cuò)誤。

          實(shí)際上到現(xiàn)在為止還沒(méi)有涉及到有效性問(wèn)題。但是,由于現(xiàn)在討論的是錯(cuò)誤處理這一主題,所以現(xiàn)在可以編寫(xiě)錯(cuò)誤處理代碼。

          理解非致命性錯(cuò)誤的最重要的原則是在默認(rèn)情況下,它們是可以忽略的。

          但是如果在文檔中出現(xiàn)有效性錯(cuò)誤,你可能不樂(lè)意繼續(xù)處理它。你可能希望將它們作為致命錯(cuò)誤處理。在下面編寫(xiě)的代碼中,建立錯(cuò)誤處理程序?qū)崿F(xiàn)該功能。

          注意:本練習(xí)中創(chuàng)建的程序的代碼在Echo07.java 中。

          為了接管錯(cuò)誤處理,覆蓋方法DefaultHandler ,該方法將致命錯(cuò)誤、非致命錯(cuò)誤和警告作為ErrorHandler 接口的一部分處理。SAX 解析器將SAXParseException 發(fā)送給這些方法,所以當(dāng)出現(xiàn)錯(cuò)誤的時(shí)候,只要拋回它就能產(chǎn)生錯(cuò)誤。

          添加下面的代碼,覆蓋錯(cuò)誤處理程序:

          public void processingInstruction(String target, String data)
          throws SAXException
          {
            ...
          } 
          // treat validation errors as fatal
          public void error(SAXParseException e)
          throws SAXParseException
          {
            throw e;
          } 

          注意:查看org.xml.sax.helpers.DefaultHandler中定義的錯(cuò)誤處理方法很有意義。可以看到error()warning() 方法沒(méi)有做什么事情,而fatalError() 拋出一個(gè)異常。當(dāng)然,你可以覆蓋fatalError() 方法拋出不同的異常。但是當(dāng)出現(xiàn)致命錯(cuò)誤的時(shí)候,你的程序不能拋出一個(gè)異常,然后SAX解析器將會(huì)拋出一個(gè)異常——XML規(guī)范需要該異常。

          處理警告

          默認(rèn)情況下,警告也被忽略。警告能提供很多信息,并且需要一個(gè)DTD。例如,如果在DTD中兩次定義了一個(gè)元素,產(chǎn)生一個(gè)警告——它并不是不合法的,并且不會(huì)引起任何問(wèn)題,但是你可能希望知道這些,因?yàn)樗赡懿皇枪室獾摹?

          添加下面的代碼,以便在出現(xiàn)警告時(shí)生成消息:

          // treat validation errors as fatal
          public void error(SAXParseException e)
          throws SAXParseException
          {
            throw e;
          } 
          // dump warnings too
          public void warning(SAXParseException err)
          throws SAXParseException
          {
            System.out.println("** Warning"
              + ", line " + err.getLineNumber()
              + ", uri " + err.getSystemId());
            System.out.println("   " + err.getMessage());
          } 

          由于沒(méi)有DTD或模式的情況下很難產(chǎn)生警告, 所以你現(xiàn)在還看不到任何警告。但是如果出現(xiàn)了警告,你也能處理。

          posted on 2005-03-23 15:19 閱讀(309) 評(píng)論(0)  編輯  收藏 所屬分類: Java_Xml

          主站蜘蛛池模板: 武冈市| 松桃| 嵊泗县| 大足县| 福安市| 崇左市| 阳高县| 定日县| 宁国市| 石河子市| 荔波县| 象山县| 肇东市| 丹凤县| 武安市| 永昌县| 佛坪县| 宁强县| 万源市| 拉孜县| 安多县| 元阳县| 荔波县| 泉州市| 莒南县| 敖汉旗| 东乡| 石屏县| 南昌县| 运城市| 凌云县| 蓝山县| 格尔木市| 开封市| 九寨沟县| 温州市| 神农架林区| 台东县| 上高县| 东兰县| 志丹县|