用于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模型,主要原因如下: 在解析DOM文檔時(shí),會(huì)生成相同類型的異常,所以JAXP SAX和DOM應(yīng)用程序的錯(cuò)誤處理程序是相同的。 默認(rèn)情況下, 規(guī)范要求忽略驗(yàn)證錯(cuò)誤(在該教程的這一部分中會(huì)詳細(xì)介紹) 。如果想要在驗(yàn)證錯(cuò)誤事件中拋出一個(gè)異常(并且能這樣做),那么就需要理解SAX錯(cuò)誤處理的工作原理。 可以看到在本教程的DOM節(jié),可以使用一種機(jī)制將現(xiàn)有數(shù)據(jù)集轉(zhuǎn)換成XML——然而,要充分利用該機(jī)制首先必須理解SAX模型。 注意:可以在下面的地址中找到本章的例子: 何時(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è)叫做 注意: 這里已經(jīng)有一個(gè) 編寫(xiě)聲明
下一步,編寫(xiě)聲明,其中該聲明將文件標(biāo)識(shí)為一個(gè)XML文檔。聲明以字符"<?"開(kāi)始 <?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'?>
定義根(Root)元素
在聲明之后,每個(gè)XML文件都會(huì)精確地定義一個(gè)元素,這就是根元素。文件中的任何其他元素都包含在該元素中。輸入下面突出顯示文本,定義該文件的根元素 <?xml version='1.0' encoding='utf-8'?> <!-- A SAMPLE set of slides -->
注意:XML元素命名是大小寫(xiě)敏感的。結(jié)束標(biāo)簽必須和起始標(biāo)簽匹配。 給元素添加屬性
幻燈片播放有大量相關(guān)數(shù)據(jù)項(xiàng),它們都不需要任何結(jié)構(gòu)。所以可以將它們定義成 ... <slideshow
> </slideshow> 在創(chuàng)建標(biāo)簽或?qū)傩缘拿謺r(shí),除了字符和數(shù)字之外還可以使用連字符 ( 注意: 要慎重使用冒號(hào)或避免一起使用,因?yàn)樵诙xXML文檔的命名空間的時(shí)候也要使用它。 添加嵌套元素
XML能夠表示層次結(jié)構(gòu)化數(shù)據(jù),這意味著一個(gè)元素可以包含其他元素。添加下面突出顯示的文本,定義一個(gè)幻燈片(slide)元素,并在它內(nèi)部包含一個(gè)標(biāo)題(title)元素: <slideshow ... >
</slideshow> 這里,也給幻燈片(slide)添加了一個(gè) 然而,更重要的是,該例子顯示了適合定義成元素( 添加 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>
</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>Who <em>buys</em> WonderWidgets</item> </slide> </slideshow> 注意,任何元素都可以是空元素。它所做的是用"/>" 而不是">"來(lái)結(jié)束標(biāo)簽。輸入 注意:使得XML形式良好的另一個(gè)因素是合適的嵌套。所以 最終產(chǎn)品
<?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解析器事件回送到 注意:本節(jié)討論的代碼在 創(chuàng)建框架
首先創(chuàng)建文件
{ 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)然, 設(shè)置I/O
業(yè)務(wù)的第一件事是處理命令行參數(shù),得到要回送的文件的名字,并建立輸出流。添加下面突出顯示的文本,以處理這些任務(wù)并且做一些內(nèi)務(wù)工作: public static void main(String argv[]) {
}
創(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)前需要的最重要的接口是 實(shí)現(xiàn)該接口的最簡(jiǎn)單的方法是擴(kuò)展 public class Echo { ... } 注意:
接口需要所有的這些方法來(lái)拋出 遇到起始標(biāo)簽或結(jié)束標(biāo)簽時(shí),將標(biāo)簽的名字作為 設(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); }
try { // out = new OutputStreamWriter(System.out, "UTF8");
} catch (Throwable t) { t.printStackTrace(); } System.exit(0); } 通過(guò)上面幾行代碼,創(chuàng)建了一個(gè) 注意: 這時(shí),你只是在簡(jiǎn)單地捕獲解析器可能拋出的任何異常。在教程的使用非驗(yàn)證解析器處理錯(cuò)誤一節(jié)中將會(huì)了解到錯(cuò)誤處理的更多信息。 編寫(xiě)輸出
... 調(diào)用emit時(shí),I/O錯(cuò)誤和它的標(biāo)識(shí)信息一起包裝在 間隔輸出
這是進(jìn)行具體處理之前要建立的另一個(gè)基礎(chǔ)結(jié)構(gòu)。添加下面突出顯示的代碼,定義一個(gè) private void emit(String s) ... }
} 注意: 雖然這看起來(lái)有點(diǎn)多余,但還需要在前面的代碼中多次調(diào)用 處理內(nèi)容事件
最后,編寫(xiě)一些實(shí)際處理 文檔事件
將下面突出顯示的代碼添加到start-document 和end-document 事件中: static private Writer out;
private void echoText() ... 這里,當(dāng)解析器遇到文檔開(kāi)始時(shí),回送XML聲明。由于使用UTF-8建立 注意:然而, IO類不理解帶有帶有連字符號(hào)的編碼名,所以使用“UTF8”而不用“UTF-8”。 在文檔最后,只要放置最終新行并刷新輸出結(jié)果流。不需要進(jìn)行很多操作。 元素事件
現(xiàn)在做件有趣的事情。添加下面突出顯示的代碼處理啟始元素(start-element)和結(jié)束元素(end-element)事件:
private void emit(String s) ... 使用該代碼,回送元素標(biāo)簽,包括在起始標(biāo)簽中定義的任何屬性。注意調(diào)用 字符事件
要完成內(nèi)容事件的處理,必須處理解析器傳遞給應(yīng)用程序的字符。 不要求解析器同時(shí)返回所有特定字符。解析器一次可以返回單個(gè)字符,也可以返回幾千個(gè)字符,但實(shí)現(xiàn)中仍然要遵守標(biāo)準(zhǔn)。所以,如果應(yīng)用程序要處理遇到的字符,最好將這些字符積累在緩沖區(qū)中,并且僅在你認(rèn)為已經(jīng)找出了所有的字符時(shí)才處理它們。 public class Echo01 extends DefaultHandler { public static void main(String argv[]) { ... 然后,添加下面突出顯示的代碼,積累解析器傳遞到緩沖中的字符: public void endElement(...) throws SAXException { ... }
private void emit(String s) ... 接著,添加下面突出顯示的方法,將緩沖中的內(nèi)容發(fā)送到輸出流中。 public void characters(char buf[], int offset, int len) throws SAXException { ... }
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 { String eName = sName; // element name ... } public void endElement(...) throws SAXException { 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元素。例如,在該文檔段中:
ideas. 初始文本“This paragraph contains”被元素 注意:在大多數(shù)時(shí)候,出現(xiàn) 恭喜!這時(shí)你已經(jīng)編寫(xiě)了一個(gè)完整的SAX解析器應(yīng)用程序。下一步是編譯并運(yùn)行它。 注意:為了確保精確度,字符處理程序必須掃描緩沖區(qū)看看有沒(méi)有字符(' 編譯并運(yùn)行程序
在Java WSDP中,JAXP庫(kù)發(fā)布在目錄 注意:由于將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 java -cp 或者,也可以將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)目錄 javac Echo.java java Echo slideSample.xml 注意: 也可以精心在命令行上設(shè)置 檢查輸出
<slideshow title="Sample Slide Show" date="Date of publication" author="Yours Truly"> <slide type="all"> <title>Wake up to WonderWidgets!</title> </slide> ... 注意:程序的輸出保存在 查看該輸出,會(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) · 所有的元素屬性都列在一行中。如果窗口不夠?qū)挘涂床坏剿袃?nèi)容。 · 定義的單標(biāo)簽空元素 ( 識(shí)別事件
該版本的回送(Echo)程序?qū)τ陲@示XML非常有用,但是它沒(méi)有告訴你解析器內(nèi)部究竟在做些什么。下一步是修改該程序,這樣你就能看到空白和垂直行是怎么來(lái)的。 注意:本節(jié)討論的代碼在 執(zhí)行如下代碼,以便當(dāng)事件發(fā)生的時(shí)候識(shí)別它: public void startDocument() throws SAXException {
emit("<?xml version='1.0' encoding='UTF-8'?>"); nl(); } public void endDocument() throws SAXException {
try { ... } public void startElement(...) throws SAXException { echoText();
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(...) throws SAXException { echoText();
String eName = sName; // element name if ("".equals(eName)) eName = qName; // not namespaceAware emit("<"+eName+">"); } ... private void echoText() throws SAXException { if (textBuffer == null) return;
String s = ""+textBuffer emit(s); textBuffer = null; } 編譯并運(yùn)行該版本的程序,以輸出更多信息。現(xiàn)在每行顯示一個(gè)屬性。但是,更加重要的是,它輸出類似于下面的行: | 表明縮進(jìn)空間和分隔屬性的新行來(lái)自于解析器傳遞給 注意:XML規(guī)范要求所有輸入行分隔符都放在單個(gè)新行中。Java、C和UNIX系統(tǒng)都有自己的新行字符,但是在Windows系統(tǒng)中使用別名“l(fā)inefeed”。 壓縮輸出結(jié)果
為了提高輸出可讀性,修改程序,這樣它就不會(huì)輸出空白了。 注意:本節(jié)討論的代碼在 public void echoText() throws SAXException { nl(); emit("CHARS: |");
String s = ""+textBuffer;
emit("|"); } 然后,添加如下代碼,顯示解析器發(fā)送的每個(gè)字符集。 public void characters(char buf[], int offset, int len) throws SAXException {
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;
... public void startElement(...) throws SAXException { nl(); emit("ELEMENT: "); ... } public void endElement(...) throws SAXException { nl(); emit("END_ELM: "); emit("</"+sName+">"); } ... private void nl() throws SAXException { ... try { out.write(lineEnd);
} 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é)果在 注意, 同一行兩次調(diào)用 同樣也要注意, 然而,沒(méi)有DTD時(shí),解析器必須假設(shè)它能看到的所有元素包含文本,類似于概述幻燈片的第一項(xiàng)元素: <item>Why <em>WonderWidgets</em> are great</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ū)別在于元素之間是不是允許有文本。 注意:在本教程的下面部分,要使用 添加其他事件處理程序
除 識(shí)別文檔的位置
locator 是一個(gè)對(duì)象,它包含了查找文檔所必需的信息。 也可以使用定位器打印診斷信息。除了文檔的位置和公共標(biāo)識(shí)符外,定位器包含給出最近使用的列和行的數(shù)目的方法。但是,僅在解析器的開(kāi)始部分調(diào)用一次 注意:本節(jié)討論的代碼在 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 startDocument() ... · 同其他 · 這些方法的拼寫(xiě)是" 在 LOCATOR SYS ID: file: START DOCUMENT <?xml version='1.0' encoding='UTF-8'?> ... 這里,很明顯 處理處理指令
有時(shí)在XML數(shù)據(jù)中編寫(xiě)特定于應(yīng)用程序的處理指令很有意義。在該練習(xí)中,在 注意:本節(jié)討論的代碼在 從理解XML中可以得知,處理指令的格式是 <slideshow ... >
<!-- TITLE SLIDE --> · 處理指令的"data"部分可以包含空格也能為空。但是在初始 · 利用完整的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) ... }
private void echoText() ... 完成編輯后,編寫(xiě)并運(yùn)行程序。輸出結(jié)果的相關(guān)部分看起來(lái)如下所示: ELEMENT: <slideshow ... > PROCESS: <?my.presentation.Program QUERY="exec, tech, all"?> CHARS: ... 小結(jié)
在小異常 使用非驗(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)在 產(chǎn)生致命錯(cuò)誤的一個(gè)最簡(jiǎn)單的方法就是刪除空 1. 將 ... <!-- OVERVIEW --> <slide type="all"> <title>Overview</title> <item>Why <em>WonderWidgets</em> are great</item> <item/> <item>Who <em>buys</em> WonderWidgets</item> </slide> ... ... <item>Why <em>WonderWidgets</em> are great</item> <item> <item>Who <em>buys</em> WonderWidgets</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方法中的 ... } catch (Throwable t) {
} 棧跟蹤不是非常有用。然后,你會(huì)看到出現(xiàn)錯(cuò)誤時(shí)如何產(chǎn)生更好的診斷信息。 處理SAXParseException
遇到錯(cuò)誤時(shí),解析器產(chǎn)生 注意:本練習(xí)中要?jiǎng)?chuàng)建的代碼在 添加下面的代碼,在出現(xiàn)異常時(shí)產(chǎn)生更好的診斷信息:
} 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è)更加通用的 public void startDocument() throws SAXException 所有的
private void emit(String s) throws SAXException { try { out.write(s); out.flush(); } } } 注意: 如果在調(diào)用 當(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 (Throwable t) { t.printStackTrace(); } 該代碼測(cè)試 改進(jìn)SAXParseException處理程序
由于 ... } 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(); } 現(xiàn)在程序要處理它看到的任何SAX解析異常。可以看到解析器產(chǎn)生致命錯(cuò)誤的異常。但是對(duì)于非致命性錯(cuò)誤和警告,默認(rèn)的錯(cuò)誤處理程序不會(huì)產(chǎn)生異常,也不會(huì)顯示任何消息。下面,學(xué)進(jìn)一步學(xué)習(xí)錯(cuò)誤和警告,并查看如何提供錯(cuò)誤處理程序處理它們。 處理 ParserConfigurationException
最后,回想一下,如果類 } catch (SAXException sxe) { Exception x = sxe; if (sxe.getException() != null) x = sxe.getException(); x.printStackTrace();
} catch (Throwable t) { t.printStackTrace(); 不可否認(rèn),這里有相當(dāng)多的錯(cuò)誤處理程序。但是現(xiàn)在你至少知道了可能出現(xiàn)的異常類型。 注意:如果不能找到系統(tǒng)屬性指定的factory類或不能將它實(shí)例化,也可拋出 處理 IOException
} catch (ParserConfigurationException pce) { // Parser with specified options can't be built pce.printStackTrace();
} catch (Throwable t) { ... 讓 處理非致命錯(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)建的程序的代碼在 為了接管錯(cuò)誤處理,覆蓋方法 添加下面的代碼,覆蓋錯(cuò)誤處理程序: public void processingInstruction(String target, String data) throws SAXException { ... }
注意:查看 處理警告
默認(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; }
由于沒(méi)有DTD或模式的情況下很難產(chǎn)生警告, 所以你現(xiàn)在還看不到任何警告。但是如果出現(xiàn)了警告,你也能處理。 |
posted on 2005-03-23 15:19 辰 閱讀(309) 評(píng)論(0) 編輯 收藏 所屬分類: Java_Xml