SAX一章中已經(jīng)編寫了包含用于放映的幻燈片的XML文件。然后又使用SAX API將XML回送到顯示器。
本章中,將使用文檔對象模型(DOM)建立一個小的SlideShow應(yīng)用程序。首先構(gòu)建DOM并查看它,然后看看如何編寫XML結(jié)構(gòu)的DOM ,將它顯示在GUI中,并且操作樹結(jié)構(gòu)。
文檔對象模型是普通的樹結(jié)構(gòu),每個節(jié)點(diǎn)包含一個來自于XML結(jié)構(gòu)的組件。兩種最常見的節(jié)點(diǎn)類型是元素節(jié)點(diǎn)(element node)和文本節(jié)點(diǎn)(text node)。使用DOM函數(shù)能夠創(chuàng)建節(jié)點(diǎn)、刪除節(jié)點(diǎn)、改變節(jié)點(diǎn)內(nèi)容并且遍歷節(jié)點(diǎn)層次。
本章將解析現(xiàn)有的XML文件以構(gòu)建DOM,顯示并查看DOM的層次結(jié)構(gòu),將DOM轉(zhuǎn)換成能夠友好顯示的Jtree,并且展現(xiàn)命名空間的語法。你將從頭開始創(chuàng)建一個DOM,并且了解如何使用Sun的JAXP實(shí)現(xiàn)的一些特定實(shí)現(xiàn)性能將現(xiàn)有的數(shù)據(jù)集轉(zhuǎn)換成XML。
首先要確信,在你的應(yīng)用程序中DOM是最好的選擇。下一節(jié)何時使用DOM將具體介紹它。
注意:可以在下面的頁面中找到本章的例子<JWSDP_HOME>/docs/tutorial/examples/jaxp/dom/samples.
何時使用DOM
文檔對象模型 (DOM) 是一個文檔標(biāo)準(zhǔn)(例如,文章和書)。另外,JAXP 1.2實(shí)現(xiàn)支持XML Schema,在任何給定的應(yīng)用程序中必須慎重考慮這一點(diǎn)。
另外,如果你正在處理簡單數(shù)據(jù)結(jié)構(gòu),并且XML Schema不是你計劃的一部分,那么你可能會發(fā)現(xiàn)一個面向?qū)ο蟮臉?biāo)準(zhǔn)如JDOM 和dom4j 更加適合于實(shí)現(xiàn)你的目標(biāo)。
DOM從一開始就是一種與語言無關(guān)的模型。這是因?yàn)樗鼘iT用于像C或Perl這類語言,沒有利用Java的面向?qū)ο蟮男阅堋_@個事實(shí)再加上文檔/數(shù)據(jù)定義,也能幫助解釋處理DOM和處理JDOM或dom4j結(jié)構(gòu)之間的區(qū)別。
本節(jié)中,將討論遵守這些標(biāo)準(zhǔn)的模型之間的區(qū)別,以便能夠幫助你為應(yīng)用程序選擇最合適的模型。
文檔vs.數(shù)據(jù)
DOM中使用的文檔模型和JDOM或dom4j中使用的數(shù)據(jù)模型的主要區(qū)別在于:
· 層次結(jié)構(gòu)中的節(jié)點(diǎn)類型
· “混合內(nèi)容”的性能
正是構(gòu)成數(shù)據(jù)層次結(jié)構(gòu)的 “節(jié)點(diǎn)” 的不同造成了這兩個模型之間編程的區(qū)別。然而,只有混合內(nèi)容的性能能解釋不同標(biāo)準(zhǔn)定義“節(jié)點(diǎn)”有何區(qū)別。所以,首先來看看DOM的“混合內(nèi)容模型”。
混合內(nèi)容模型
回想在對文檔-驅(qū)動編程 (DDP) 的討論中,可以將文本和元素自由結(jié)合到DOM層次結(jié)構(gòu)中。這種結(jié)構(gòu)被稱為DOM模型中的“混合內(nèi)容”。
文檔中經(jīng)常出現(xiàn)混合內(nèi)容。例如,為了表示該結(jié)構(gòu):
<sentence>This is an <bold>important</bold> idea.</sentence>
DOM節(jié)點(diǎn)的層次結(jié)構(gòu)看起來跟下面的很像,其中每行代表一個節(jié)點(diǎn):
ELEMENT: sentence
+ TEXT: This is an
+ ELEMENT: bold
+ TEXT: important
+ TEXT: idea.
注意,句元素(sentence element)包含文本,后面跟著子元素,再后面是其他文本。 “混合內(nèi)容模型”是通過將文本和元素混合起來而定義的。
節(jié)點(diǎn)種類
為了提供混合內(nèi)容的性能,DOM節(jié)點(diǎn)生來就非常簡單。例如,在前面的例子中,第一個元素的內(nèi)容 (它的value)簡單地標(biāo)識了節(jié)點(diǎn)的類型。
DOM用戶第一次通常都會被這個事實(shí)迷惑。到訪過<sentence> 節(jié)點(diǎn)后,他們就想要節(jié)點(diǎn)的“內(nèi)容”,并且想得到其他有用信息。但是,它們得到的是元素的名字“sentence”。
注意:DOM Node API定義了nodeValue()、node.nodeType()和nodeName() 方法。對于第一個元素節(jié)點(diǎn),nodeName() 返回 "sentence",而nodeValue() 返回空。對于第一個文本節(jié)點(diǎn), nodeName() 返回"#text",并且nodeValue() 返回"This is an "。很重要的一點(diǎn)是元素的value 和它的content不一樣。
處理DOM時,獲得關(guān)心的內(nèi)容意味著要檢查節(jié)點(diǎn)包含的子元素列表,忽略那些不感興趣的,只處理那些感興趣的。
例如,在上面的例子中,如果要查詢句子的“text(文本)”意味著什么?根據(jù)你的應(yīng)用程序,下面的每一條都是合理的:
· This is an
· This is an idea.
· This is an important idea.
· This is an <bold>important</bold> idea.
簡單模型
使用DOM可以方便地創(chuàng)建所需的語義。然而,也需要經(jīng)過必要的處理,以實(shí)現(xiàn)這些語義。而像JDOM和dom4j這樣的標(biāo)準(zhǔn),使得事情更加簡單,因?yàn)閷哟谓Y(jié)構(gòu)中的每個節(jié)點(diǎn)都是一個對象。
雖然JDOM 和dom4j 允許使用混合內(nèi)容元素,但是它們主要不是用于這類情況的。相反,它們主要用于XML結(jié)構(gòu)包含數(shù)據(jù)的應(yīng)用程序中。
如傳統(tǒng)數(shù)據(jù)處理中所介紹的,數(shù)據(jù)結(jié)構(gòu)中的元素包含文本或其他元素,但是不能兩個都包含。例如,下面的XML表示簡單的通訊簿:
<addressbook>
<entry>
<name>Fred</name>
<email>fred@home</email>
</entry>
...
</addressbook>
注意: 對于這類非常簡單的XML數(shù)據(jù)結(jié)構(gòu),也可以使用Java平臺1.4版本中的正則表達(dá)式包(java.util.regex)。
在JDOM 和dom4j中,一旦到達(dá)包含文本的元素,就會調(diào)用一個方法如text() 來獲得它的內(nèi)容。處理DOM時,必須查看子元素列表,將節(jié)點(diǎn)的文本“放在一起”,就如前面所見到的——即便該列表只包含一項(xiàng)(TEXT 節(jié)點(diǎn))。
所以,對于簡單的數(shù)據(jù)結(jié)構(gòu)(如前面的通訊簿),使用JDOM或dom4j可以節(jié)省很多工作。即便是數(shù)據(jù)在技術(shù)上是“混合”的,也可以使用這些模型中的某個模型,除非給定節(jié)點(diǎn)有一個(并且僅有一個)文本段。
下面是這類結(jié)構(gòu)的一個例子,它在JDOM或dom4j中也很容易處理:
<addressbook>
<entry>Fred
<email>fred@home</email>
</entry>
...
</addressbook>
這里,每項(xiàng)都有標(biāo)識文本,并且后面還有其他元素。使用該結(jié)構(gòu),程序能夠?yàn)g覽一個項(xiàng),調(diào)用text() 來查看它屬于誰,并且如果<email> 子元素在當(dāng)前節(jié)點(diǎn)下,就處理它。
增加復(fù)雜度
但是要完全理解查找或操作DOM時需要進(jìn)行的處理,就必需知道DOM包含的節(jié)點(diǎn)。
下面的例子主要就是針對這點(diǎn)的。它是該數(shù)據(jù)的一個表示法:
<sentence>
The &projectName; <![CDATA[<i>project</i>]]> is
<?editor: red><bold>important</bold><?editor: normal>.
</sentence>
該句子包含一個實(shí)體引用——一個在其他地方定義的指向“實(shí)體”的指針。這里,實(shí)體包含項(xiàng)目名。這個例子也包含CDATA段(未解釋的數(shù)據(jù),如HTML中的<pre> 數(shù)據(jù)),和處理指令 (<?...?>)。在本例中該處理指令告訴編輯器在繪制文本時使用哪種顏色。
下面是該數(shù)據(jù)的DOM結(jié)構(gòu)。它代表了強(qiáng)壯的應(yīng)用程序要處理的結(jié)構(gòu):
+ ELEMENT: sentence
+ TEXT: The
+ ENTITY REF: projectName
+ COMMENT: The latest name we're using
+ TEXT: Eagle
+ CDATA: <i>project</i>
+ TEXT: is
+ PI: editor: red
+ ELEMENT: bold
+ TEXT: important
+ PI: editor: normal
本例描繪了可能在DOM中出現(xiàn)的節(jié)點(diǎn)。雖然在絕大多數(shù)時候你的應(yīng)用程序能夠忽略它們中的大部分,但是真正強(qiáng)壯的實(shí)現(xiàn)需要識別并處理它們中的每一個。
同樣,到訪節(jié)點(diǎn)的處理涉及到子元素的處理,它忽略你不關(guān)心的元素僅僅查看關(guān)心的元素,直到找到感興趣的節(jié)點(diǎn)。
通常,在這類情況下,僅對找到包含指定文本的節(jié)點(diǎn)感興趣。例如,在DOM API 中,有一個例子,那個例子中希望找到一個<coffee> 節(jié)點(diǎn),該節(jié)點(diǎn)的<name> 元素包含文本“Mocha Java”。要執(zhí)行該查找,程序需要處理<coffee> 元素列表,并且對于每一個列表:a)獲取它下面的<name> 元素。b)檢查該元素下的TEXT 節(jié)點(diǎn)。
然而,該例是建立在一些簡單的假設(shè)之上的。它假設(shè)處理指令、注釋、CDATA節(jié)點(diǎn)和實(shí)體引用不能存在于數(shù)據(jù)結(jié)構(gòu)中。許多簡單的應(yīng)用程序能夠去掉這些假設(shè)。另外,真正強(qiáng)壯的應(yīng)用程序需要處理各類有效的XML數(shù)據(jù)。
(僅當(dāng)輸入數(shù)據(jù)包含所需的簡化的XML結(jié)構(gòu)時,“簡單”的應(yīng)用程序才能運(yùn)行。但是沒有任何驗(yàn)證機(jī)制能夠確保不存在更加復(fù)雜的結(jié)構(gòu)。畢竟,XML是專門為它們設(shè)計的。)
要變得更加強(qiáng)壯,DOM API中的例子代碼必須完成以下事情:
1. 搜索<name> 元素時:
a. 忽略注釋、屬性和處理指令。
b. 允許出現(xiàn)<coffee> 子元素沒有按指定的順序出現(xiàn)的情況。
c. 如果沒有驗(yàn)證,跳過包含可忽略空白的TEXT 節(jié)點(diǎn)。
2. 提取節(jié)點(diǎn)的文本時:
a. 從CDATA 節(jié)點(diǎn)和文本節(jié)點(diǎn)提取文本。
b. 收集文本時忽略注釋、屬性和處理指令。
c. 如果遇到了實(shí)體引用節(jié)點(diǎn)或另一個元素節(jié)點(diǎn),遞歸。(也就是說,將文本提取過程應(yīng)用于所有的子節(jié)點(diǎn))。
注意:JAXP 1.2解析器不將實(shí)體引用節(jié)點(diǎn)插入DOM。相反,它插入包含引用內(nèi)容的TEXT 節(jié)點(diǎn)。另外,建立在1.4平臺中的JAXP 1.1解析器并不插入實(shí)體引用。所以,所以獨(dú)立于解析器的強(qiáng)壯的實(shí)現(xiàn)需要能處理實(shí)體引用節(jié)點(diǎn)。
當(dāng)然,許多應(yīng)用程序不用擔(dān)心這類事情,因?yàn)樗鼈兛吹降臄?shù)據(jù)受到了嚴(yán)格的控制。如果數(shù)據(jù)能夠來自于各類外部源,應(yīng)用程序就必須考慮這些可能性。
在DOM教程搜索節(jié)點(diǎn) 和獲得節(jié)點(diǎn)內(nèi)容的結(jié)尾處給出了執(zhí)行這些函數(shù)的代碼。現(xiàn)在,目標(biāo)就是確定DOM是不是適合于你的應(yīng)用程序。
選擇模型
可以看到,使用DOM時,即便是像得到節(jié)點(diǎn)中的文本這樣簡單的操作也要進(jìn)行大量的編碼。所以,如果程序?qū)⑻幚砗唵螖?shù)據(jù)結(jié)構(gòu),JDOM、dom4j甚至1.4正則表達(dá)式包(java.util.regex)將更能滿足你的需求。
另外,對于完備的文檔和復(fù)雜的應(yīng)用程序,DOM 提供了大量靈活性。并且如果需要使用XML Schema,那么至少DOM是可以使用的方法。
如果你要在開發(fā)的應(yīng)用程序中處理文檔和數(shù)據(jù),那么DOM可能仍然是最好的選擇。畢竟,一旦編寫了檢查和處理DOM結(jié)構(gòu)的代碼,自定義它以實(shí)現(xiàn)特定的目的就很容易了。所以,選擇在DOM中完成所有的事情,只需要處理一個API集合而不是兩個。
還有,DOM標(biāo)準(zhǔn)是標(biāo)準(zhǔn)的。它很強(qiáng)壯且完整,并且有許多實(shí)現(xiàn)。這是許多大型安裝的決定因素——特別是對產(chǎn)品應(yīng)用程序,以避免在API發(fā)生改變時進(jìn)行大量的改寫。
最后,雖然目前不允許對通訊簿中的文本加粗、傾斜、設(shè)置顏色和改變字體大小,某一天可能還是需要處理這些。由于實(shí)際上DOM將處理你拋給它的所有內(nèi)容,選擇DOM能夠更加容易生成“能適應(yīng)未來(future-proof)”的應(yīng)用程序。
將XML數(shù)據(jù)讀入到DOM中
本節(jié)將通過讀取現(xiàn)有XML文件來構(gòu)建一個文檔對象模型 (DOM)。在下面幾節(jié),你將會學(xué)習(xí)如何在Swing樹組件中顯示XML并且練習(xí)操作DOM。
注意:在教程的下一節(jié)轉(zhuǎn)換XML樣式表語言中,你將看到如何寫出作為XML文件的DOM。(你也將看到如何相對簡單地將現(xiàn)有的數(shù)據(jù)文件轉(zhuǎn)換成XML)
創(chuàng)建程序
文檔對象模型(DOM)提供了創(chuàng)建節(jié)點(diǎn)、修改節(jié)點(diǎn)、刪除及重新組織它們的API。在本教程的第五節(jié)創(chuàng)建和操縱DOM中可以看到,創(chuàng)建DOM相對比較容易。
然而,在創(chuàng)建DOM之前,要先了解DOM的結(jié)構(gòu)。這系列練習(xí)通過在Swing Jtree中顯示DOM,以便能看到DOM的內(nèi)部。
創(chuàng)建框架
既然已經(jīng)對如何創(chuàng)建DOM有了大致了解,現(xiàn)在來建立一個簡單程序來將XML文檔讀入DOM,然后在回寫。
注意:本節(jié)討論的代碼在DomEcho01.java中。它操作在文件 slideSample01.xml 上。(可瀏覽的版本是 slideSample01-xml.html)
先來看看應(yīng)用程序的基本邏輯,確保命令行上帶有一個參數(shù):
public class DomEcho {
public static void main(String argv[])
{
if (argv.length != 1) {
System.err.println(
"Usage: java DomEcho filename");
System.exit(1);
}
}// main
}// DomEcho
導(dǎo)入所需的類
本節(jié)中將看到所有的類都是單獨(dú)命名的。這樣,在引用API文檔時就能知道每個類的出處。在你自己的應(yīng)用程序中,你可能希望使用短格式javax.xml.parsers.*來替換如下所示的導(dǎo)入語句。
添加如下代碼以導(dǎo)入要使用的JAXP API:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
想在解析XML文檔時拋出異常,添加下面幾行:
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
添加下面幾行來讀取示例XML文件并識別錯誤:
import java.io.File;
import java.io.IOException;
最后,為DOM和DOM異常導(dǎo)入W3C定義:
import org.w3c.dom.Document;
import org.w3c.dom.DOMException;
注意:僅在遍歷或操作DOM時才會拋出DOMException 。使用下面介紹的不同機(jī)制來報告解析過程中出現(xiàn)的錯誤。
聲明 DOM
org.w3c.dom.Document 類是文檔對象模型(DOM)的W3C名。不管解析XML文檔還是創(chuàng)建一個XML文檔,都會產(chǎn)生一個文檔實(shí)例。在教程的后面將在另一個方法中引用該對象,所以,在這里將它定義成全局對象:
public class DomEcho
{
static Document document;
public static void main(String argv[])
{
它必須是static的,因?yàn)槟阈枰诤芏虝r間內(nèi)從main方法生成它的內(nèi)容。
處理錯誤
下一步,插入錯誤處理邏輯。該邏輯基本上跟SAX教程的使用非驗(yàn)證解析器處理錯誤中的代碼相同,所以在這里就不詳細(xì)介紹了。在這里要特別提醒的是,當(dāng)解析XML文檔遇到困難時,需要JAXP-conformant 文檔構(gòu)造器來報告SAX異常。實(shí)際上DOM解析器不需要在內(nèi)部使用SAX解析器,但是由于早就有了SAX標(biāo)準(zhǔn),看來可以用它來報告錯誤。結(jié)果,DOM和SAX應(yīng)用程序的錯誤處理代碼就非常簡單:
public static void main(String argv[])
{
if (argv.length != 1) {
...
}
try {
} 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() );
// Use the contained exception, if any
Exception x = spe;
if (spe.getException() != null)
x = spe.getException();
x.printStackTrace();
} catch (SAXException sxe) {
//
Error generated during parsing
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 (IOException ioe) {
// I/O error
ioe.printStackTrace();
}
}// main
實(shí)例化工廠
接著,添加下面的代碼,得到工廠的一個實(shí)例,該實(shí)例能夠給出一個文檔構(gòu)造器:
public static void main(String argv[])
{
if (argv.length != 1) {
...
}
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
try {
獲得解析器并解析文件
現(xiàn)在,添加下面的代碼以獲得構(gòu)造器的一個實(shí)例,并用它來解析指定的文件:
try {
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse( new File(argv[0]) );
} catch (SAXParseException spe) {
保存該文件!
到現(xiàn)在,你可能覺得啟動每個JAXP應(yīng)用程序的方法幾乎相同。的確如此!保存該版本的文件,將它作為模板。后面將要使用它,它是XSLT轉(zhuǎn)換應(yīng)用程序的基礎(chǔ)。
運(yùn)行程序
在整個DOM教程中,將使用SAX段中看到的示例幻燈片顯示。尤其是,你將使用slideSample01.xml(它是一個簡單的XML文件,內(nèi)部沒有什么內(nèi)容)和slideSample10.xml(是一個更加復(fù)雜的例子,引入了DTD、處理指令、實(shí)體引用和CDATA段)。
關(guān)于如何編譯并運(yùn)行程序的說明,請查看SAX教程的編譯和運(yùn)行程序 。以"DomEcho"替代"Echo"作為程序的名字。
在slideSample01.xml上運(yùn)行該程序。如果運(yùn)行沒有出錯,你就成功地解析了一個XML文檔并且構(gòu)建了一個DOM。恭喜!
注意:目前你只能聽從我的建議,因?yàn)檫@時你沒有任何辦法來顯示結(jié)果。但是在不久的將來...
其他信息
既然你已經(jīng)成功地讀取了一個DOM,要高效使用DocumentBuilder 必須知道一到兩點(diǎn)。即:
· 配置Factory
· 處理驗(yàn)證錯誤
配置工廠
默認(rèn)情況下,工廠返回非驗(yàn)證解析器,對命名空間一無所知。要獲得一個驗(yàn)證解析器,并且/或能理解命名空間,請使用下面的命令來設(shè)置一個或多個參數(shù),以配置該工廠:
public static void main(String argv[])
{
if (argv.length != 1) {
...
}
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(true);
try {
...
注意:不要求JAXP-conformant解析器支持這些參數(shù)的所有的結(jié)合,雖然引用解析器支持。如果你指定了一個無效的參數(shù)結(jié)合,在獲得解析器實(shí)例時factory產(chǎn)生ParserConfigurationException 。
在DOM教程的最后一節(jié)使用命名空間中將詳細(xì)介紹如何使用命名空間。本節(jié)要結(jié)束了,雖然你仍然希望知道…
處理驗(yàn)證錯誤
記住,在你閱讀SAX教程時,你所要做的事情就是構(gòu)建一個DOM?這里,那些信息起作用了。
記住對驗(yàn)證錯誤的默認(rèn)反應(yīng)(由SAX標(biāo)準(zhǔn)提出)就是什么不也做。JAXP標(biāo)準(zhǔn)要求拋出SAX異常,所以使用跟SAX應(yīng)用程序中幾乎相同的錯誤處理機(jī)制。尤其需要使用DocumentBuilder的setErrorHandler方法提供實(shí)現(xiàn)SAX ErrorHandler 接口的對象。
注意:也可以使用DocumentBuilder 中的setEntityResolver 方法。
下面的代碼使用匿名內(nèi)部類來定義ErrorHandler。下面的代碼確保驗(yàn)證錯誤產(chǎn)生異常。
builder.setErrorHandler(
new org.xml.sax.ErrorHandler() {
// ignore fatal errors (an exception is guaranteed)
public void fatalError(SAXParseException exception)
throws SAXException {
}
// 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());
}
);
本段代碼使用匿名內(nèi)部類來產(chǎn)生實(shí)現(xiàn)ErrorHandler 接口的對象實(shí)例。由于它沒有類名字,所以是“匿名”的。你可以將它看作"ErrorHandler"實(shí)例,雖然技術(shù)上它是實(shí)現(xiàn)指定接口的沒有名字的實(shí)例。這些代碼本質(zhì)上跟使用非驗(yàn)證解析器處理錯誤中的相同。要了解驗(yàn)證問題的更多知識,請參照使用驗(yàn)證解析器。
展望未來
下節(jié)中將在Jtree中顯示DOM結(jié)構(gòu),并且開始展示它的結(jié)構(gòu)。例如,你將看到實(shí)體引用和CDATA段是如何出現(xiàn)在DOM中的。可能更加重要的是,你將看到如何將文本節(jié)點(diǎn)(它包含實(shí)際的數(shù)據(jù))放在DOM中的元素節(jié)點(diǎn)下。
顯示DOM層次結(jié)構(gòu)
要創(chuàng)建或操縱一個文檔對象層次結(jié)構(gòu) (DOM),必須要清楚地了解DOM中節(jié)點(diǎn)的結(jié)構(gòu)。本節(jié)中將展示DOM的內(nèi)部結(jié)構(gòu)。
回送樹節(jié)點(diǎn)
現(xiàn)在你所需要的是展示DOM中節(jié)點(diǎn)的方法,這樣你就能看到節(jié)點(diǎn)內(nèi)究竟包含什么。要實(shí)現(xiàn)這一點(diǎn),將DOM轉(zhuǎn)換成JTreeModel 并且在Jtree中顯示完整的DOM。這些需要大量的工作,但是它不僅將產(chǎn)生你將來要使用的診斷工具,而且可以幫助你了解DOM結(jié)構(gòu)。
將DomEcho 轉(zhuǎn)換為GUI 應(yīng)用程序
由于DOM是樹,并且Swing JTree 組件都跟顯示樹有關(guān),所以有必要將DOM放入Jtree,以便查看。該過程的第一步是處理DomEcho 程序,讓它成為一個GUI 應(yīng)用程序。
注意:本節(jié)討論的代碼在DomEcho02.java中。
添加Import語句
首先導(dǎo)入建立應(yīng)用程序和顯示Jtree需要的GUI組件:
// GUI components and layouts
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
DOM教程的后面部分將設(shè)計DOM顯示,以產(chǎn)生Jtree顯示的用戶友好版本。用戶選擇樹中的元素時,子元素將顯示在鄰近的編輯器面板中。所以,進(jìn)行安裝工作時,需要導(dǎo)入建立分割視圖(JSplitPane)和顯示子元素文本(JEditorPane)所需的組件:
import javax.swing.JSplitPane;
import javax.swing.JEditorPane;
添加一些你需要的支持類:
// GUI support classes
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
最后,導(dǎo)入一些類來建立別致的邊框:
// For creating borders
import javax.swing.border.EmptyBorder;
import javax.swing.border.BevelBorder;
import javax.swing.border.CompoundBorder;
(這些是可選的。如果想要簡化代碼,可以跳過它們以及相關(guān)代碼。)
創(chuàng)建GUI框架
下一步是將應(yīng)用程序轉(zhuǎn)換為GUI應(yīng)用程序。為此,靜態(tài)main方法將創(chuàng)建main類的一個實(shí)例,它將成為GUI面板。
首先擴(kuò)展Swing JPanel 類,將該類變成一個GUI面板:
public class DomEcho02 extends JPanel
{
// Global value so it can be ref'd by the tree-adapter
static Document document;
...
定義一些用來控制窗口大小的常量:
public class DomEcho02 extends JPanel
{
// Global value so it can be ref'd by the tree-adapter
static Document document;
static final int windowHeight = 460;
static final int leftWidth = 300;
static final int rightWidth = 340;
static final int windowWidth = leftWidth + rightWidth;
現(xiàn)在,在main方法中,調(diào)用創(chuàng)建GUI面板所在的外部框架的方法:
public static void main(String argv[])
{
...
DocumentBuilderFactory factory ...
try {
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse( new File(argv[0]) );
makeFrame();
} catch (SAXParseException spe) {
...
然后,需要定義makeFrame方法本身。它包含創(chuàng)建框架的標(biāo)準(zhǔn)代碼,并能很好地處理退出條件,給出main面板的實(shí)例,定制它的大小,最后將其定位于屏幕上并使之可視化:
...
} // main
public static void makeFrame()
{
// Set up a GUI framework
JFrame frame = new JFrame("DOM Echo");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e)
{System.exit(0);}
});
//
Set up the tree, the views, and display it all
final DomEcho02 echoPanel = new DomEcho02();
frame.getContentPane().add("Center", echoPanel );
frame.pack();
Dimension screenSize =
Toolkit.getDefaultToolkit().getScreenSize();
int w = windowWidth + 10;
int h = windowHeight + 10;
frame.setLocation(screenSize.width/3 - w/2,
screenSize.height/2 - h/2);
frame.setSize(w, h);
frame.setVisible(true)
} // makeFrame
添加顯示組件
將程序轉(zhuǎn)換成GUI應(yīng)用程序的工作只剩下創(chuàng)建類構(gòu)造函數(shù)并讓它創(chuàng)建面板的內(nèi)容。下面是構(gòu)造函數(shù):
public class DomEcho02 extends JPanel
{
...
static final int windowWidth = leftWidth + rightWidth;
public DomEcho02()
{
} // Constructor
這里,使用前面導(dǎo)入的邊框類建立常用邊框(可選):
public DomEcho02()
{
// Make a nice border
EmptyBorder eb = new EmptyBorder(5,5,5,5);
BevelBorder bb = new BevelBorder(BevelBorder.LOWERED);
CompoundBorder cb = new CompoundBorder(eb,bb);
this.setBorder(new CompoundBorder(cb,eb));
} // Constructor
然后,創(chuàng)建一棵空樹并且將它放入JScrollPane ,這樣當(dāng)它變大時,用戶就能看到它的內(nèi)容:
public DomEcho02(
{
...
//
Set up the tree
JTree tree = new JTree();
// Build left-side view
JScrollPane treeView = new JScrollPane(tree);
treeView.setPreferredSize(
new Dimension( leftWidth, windowHeight ));
} // Constructor
現(xiàn)在,創(chuàng)建不可編輯的JEditPane ,它將最終保存選中的JTree 節(jié)點(diǎn)指向的內(nèi)容:
public DomEcho02(
{
....
// Build right-side view
JEditorPane htmlPane = new JEditorPane("text/html","");
htmlPane.setEditable(false);
JScrollPane htmlView = new JScrollPane(htmlPane);
htmlView.setPreferredSize(
new Dimension( rightWidth, windowHeight ));
} // Constructor
構(gòu)建了左邊的JTree 和右邊的JEditorPane 后,創(chuàng)建一個JSplitPane 來存放它們:
public DomEcho02()
{
....
// Build split-pane view
JSplitPane splitPane =
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
treeView, htmlView );
splitPane.setContinuousLayout( true );
splitPane.setDividerLocation( leftWidth );
splitPane.setPreferredSize(
new Dimension( windowWidth + 10, windowHeight+10 ));
} // Constructor
該代碼建立了帶有垂直分割線的JSplitPane 。它將在樹和編輯器面板之間產(chǎn)生“水平分割線”。(實(shí)際上,更是水平布局)你也要設(shè)置分割線的位置,這樣樹能夠得到它要的寬度,將剩下的窗口寬度分配給編輯器面板。
最后,指定面板的布局并且添加split pane:
public DomEcho02()
{
...
// Add GUI components
this.setLayout(new BorderLayout());
this.add("Center", splitPane );
} // Constructor
恭喜!該程序現(xiàn)在是一個GUI應(yīng)用程序。現(xiàn)在可以運(yùn)行它,看看屏幕上的總體布局是怎樣的。為了引用,這里有完整的構(gòu)造函數(shù):
public DomEcho02()
{
// Make a nice border
EmptyBorder eb = new EmptyBorder(5,5,5,5);
BevelBorder bb = new BevelBorder(BevelBorder.LOWERED);
CompoundBorder CB = new CompoundBorder(eb,bb);
this.setBorder(new CompoundBorder(CB,eb));
// Set up the tree
JTree tree = new JTree();
// Build left-side view
JScrollPane treeView = new JScrollPane(tree);
treeView.setPreferredSize(
new Dimension( leftWidth, windowHeight ));
// Build right-side view
JEditorPane htmlPane = new JEditorPane("text/html","");
htmlPane.setEditable(false);
JScrollPane htmlView = new JScrollPane(htmlPane);
htmlView.setPreferredSize(
new Dimension( rightWidth, windowHeight ));
// Build split-pane view
JSplitPane splitPane =
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
treeView, htmlView )
splitPane.setContinuousLayout( true );
splitPane.setDividerLocation( leftWidth );
splitPane.setPreferredSize(
new Dimension( windowWidth + 10, windowHeight+10 ));
// Add GUI components
this.setLayout(new BorderLayout());
this.add("Center", splitPane );
} // Constructor
創(chuàng)建適配器以便在Jtree中顯示DOM
現(xiàn)在已經(jīng)有一個GUI框架來顯示JTree ,下一步是獲得Jtree以顯示DOM。但是JTree 希望顯示TreeModel。 DOM 是樹,但它不是TreeModel。所以需要創(chuàng)建一個適配器使得DOM