碰巧,你在過去可能已經用過眾多Java庫中的某一個以操作XML數據結構。那么JDOM(Java文檔對象模型,Java Document Object Model)的用途是什么?開發者又為什么需要它呢?
JDOM是用于Java優化的XML數據操作的源代碼開放的Java庫。盡管它與萬維網協會(W3C,World Wide Web Consortium)的DOM類似,但它是一種可供選擇的文檔對象模型,它不是在DOM基礎上構建的,也不以DOM為模型。它們的主要區別是:DOM是與語言無關的,最初是用于HTML頁的JavaScript操作,而JDOM專用于Java,因此它利用Java的特性,其中包括方法過載(method overloading)、集合(collection)、映像(reflection)和人們熟悉的編程風格等。對Java編程人員來說,JDOM顯得更自然、更"適用"。就像Java優化的RMI(remote method invocation,遠程方法調用)庫要比與語言無關的CORBA(Common Object Request Broker Architecture,通用對象申請中介體系結構)更自然一樣。
你可以根據源代碼開放的Apache類型(商業友好)的許可,在jdom.org找到JDOM。它是合作設計開發的,并擁有超過3000個訂閱者的郵件列表。此庫已經被Sun公司的Java社區進程(JCP,Java Community Process)接受作為Java規范申請(JSR-102),并很快成為正式Java規范。
本系列文章將對JDOM進行技術介紹。本文提供一些關于重要類的信息。下一篇文章將介紹如何在自己的Java程序中應用JDOM。
ORACLE XML工具
XML Developer Kit(XDK)是Oracle為開發人員提供的免費XML工具庫。它包括可以與JDOM一起使用的XML分析器和XSLT轉換引擎。您可以在Oracle XML主頁oracle.com/xml上找到關于這些工具的很多信息。
要下載這個分析器,請查找名字為"XDK for Java"的XML Developer Kit。點擊下載鏈接左欄中的"Software"。將該軟件解壓縮后,文件xalparserv2.jar中即包括該分析器。
為了配置JDOM和其他軟件,以便以缺省方式使用Oracle分析器的軟件,需要將JAXP的javax.xml.parsers.SAXParserFactory系統屬性設置為oracle.xml.jax. JXSAXParserFactory。它告訴JAXP你選擇使用Oracle分析器。最簡單的設置方法是采用命令行:
java -Djavax.xml.parsers.SAXParserFactory=
oracle.xml.jaxp.JXSAXParserFactory
也可以通過編程來設置:
System.setProperty("jaxax.xml.parsers
.SAXParserFactory",
"oracle.xml.jaxp.JXSAXParserFactory");
除了XDK之外,Oracle還在Oracle9i數據庫版本2中提供了一個本地XML知識庫。Oracle9i XML數據庫(XDB)是一個高性能、本地XML存儲和檢索的技術。它將W3C數據模型全面應用到Oracle9i數據庫中,并為定位和查詢XML提供了新的標準訪問方法。采用XDB可以充分利用關系型數據庫技術和XML技術的優點。 |
JDOM軟件包結構
JDOM庫包括六個軟件包。第一個是org.jdom包,它包括表示XML文檔以及其組件的類,這些組件有:Attribute、CDATA、Comment、DocType、Document、Element、EntityRef、Namespace、Processing Instruction和Text等。如果你對XML很熟悉,通過類的名字就可以知道它的用途了。
下一個是org.jdom.input包,它包括用于構建XML文檔的類。其中最主要和最重要的類是SAXBuilder。SAXBuilder通過監聽輸入的XML簡單API(SAX,Simple API for XML)事件建立相應的文檔。如果需要由一個文件或其他流(stream)建立文檔,那就可以采用SAXBuilder。它采用SAX分析器讀取流,并根據SAX分析器的"回調"建立文檔。這一設計的一個好處就是SAX分析器越快,SAXBuilder也會越快。另一個主要輸入類是DOMBuilder。DOMBuilder由DOM樹構建。如果預先已經有了一個DOM樹,并希望用JDOM版本的樹來代替,那么采用DOMBuilder是非常方便的。
對于這些可能的生成器來說,不存在什么限制。例如,由于Xerces有Xerces本地接口(XNI,Xerces Native Interface),可用于在比SAX較低的層次上進行操作,所以編寫一個XNIBuilder以支持某些分析器知識,而不采用SAX,就可能很有意義。一個構成JDOM項目的很流行的生成器是ResultSetBuilder。它利用JDBC結果集合,生成一個XML文檔,用以表示SQL結果,根據是哪種元素和哪種屬性,它有多種不同的編排。
org.jdom.output包中包括輸出XML文檔的一些類。其中最重要的類是XMLOutputter。它將文檔轉換為一個字節流,用于輸出到文件、流和接口程序(sockets)中。XMLOutputter具有很多支持原始輸出(raw output)、完美輸出(pretty output)、壓縮輸出或其他輸出的特殊配置選項。它是一個相當復雜的類。這可能是為什么DOM level2中仍然沒有這種能力的原因。
其他輸出器(outoutter)包括SAXOutputter,它可以產生基于文檔內容的SAX事件。盡管它看起來有些神秘,但事實證明這個類在XSLT變換中極其有用,這是因為相對于將文檔數據傳遞到一個引擎的字節來說,SAX事件是一種更為有效的方法。還有一個DOMOutputter輸出器,它用于生成表示文檔的DOM樹。一個有趣的輸出器是JTreeOutputter,它只有幾十行代碼,它可以建立表示文檔的JTree。將它與ResultSetBuilder聯合起來后,僅用幾行代碼就可以通過SQL查詢得到結果的樹視圖。
注意,與在DOM中不同,文檔并不與它們的生成器捆綁在一起。這就形成了一個出色的模型,在這種模型中,你可以用一些類保持數據,另一些類構造數據,其他一些類來定制數據。可以隨意進行混合和匹配。
org.jdom.transform和org.jdom.xpath包中還有支持內置的XSLT轉換和XPath查找的類。
最后,org.jdom.adapters包中還包括在DOM交互中對庫提供幫助的類。該庫的使用者永遠不需要調用這一軟件包中的類。這些類是隨時可用的,因為每個DOM實現都為特定的引導任務生成不同的方法名稱,所以適配器類將標準調用轉換成分析器專用的調用。Java API for XML Processing(JAXP)為解決此問題提供了另一種方法,事實上降低了對這些類的需要,但是這些類仍然保留了下來,這是因為由于許可證的原因,并不是所有的分析器都支持JAXP,也不是任何地方都安裝有JAXP。
生成文檔
文檔由org.jdom.Documentclass表示,你可以從頭建立一個文檔:
// This builds: <root/>
Document doc = new Document(new Element("root"));
也可以由文件、流、系統ID或URL建立一個文檔:
// This builds a document of whatever's in the given resource
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(url);
將幾個調用結合在一起,可以很容易地在JDOM中建立一個簡單的文檔:
// This builds: <root>This is the root</root>
Document doc = new Document();
Element e = new Element("root");
e.setText("This is the root");
doc.addContent(e);
如果你是一位非常有才能的用戶,你可能更愿意使用"方法鏈",在這種方式中,多種方法是依次調用的。這種方式之所以有效是因為集合方法返回它們所作用的對象,下面是一個例子:
Document doc = new Document(
new Element("root").setText("This is the root"));
為了進行對比,下面給出如何用JAXP/DOM創建相同的文檔:
// JAXP/DOM
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element root = doc.createElement("root");
Text text = doc.createText("This is the root");
root.appendChild(text);
doc.appendChild(root);
用SAXBuilder生成文檔
如前所示,SAXBuilder提供了一種由任意面向字節的數據源來創建文檔的簡單機制。缺省的無參數SAXBuilder()構造器在后臺利用JAXP來選擇一個SAX分析器。如果你希望改變分析器,則可以設置javax.xml.parsers.SAXParserFactory系統屬性,以指向你的分析器提供的SAXParser Factory實現。對于Oracle9i版本2 XML分析器,應當這樣做:
java -Djavax.xml.parsers.SAXParserFactory=
oracle.xml.jaxp.JXSAXParserFactory YourApp
對于Xerces分析器,應當這樣做:
java -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp
.SAXParserFactoryImpl YourApp
如果沒有安裝JAXP,SAXBuilder缺省地指向Apache Xerces。一旦你創建了SAXBuilder實例,你就可以在生成器中設置幾個屬性,其中包括:
setValidation(boolean validate)
這一方法告訴分析器在生成過程中是否根據Document Type Definition(DTD,文檔類型定義)進行驗證。缺省設置為"否"(off)。所用的DTD是文檔中DocType引用的一個。根據任何其他DTD進行驗證是不可能的,因為還沒有分析器支持這一功能。
setIgnoringElementContentWhitespace(boolean ignoring)
這一方法告訴分析器是否忽略元素內容中所謂的"可忽略空格(whitespace)"。按照XML1.0規范,元素內容中的空格必須由分析器保留,但當根據DTD進行驗證時,分析器可能知道文檔的特定部分不會支持空格,所以這一區域的任何空格都是"可忽略的"。其缺省設置為"否"(off)。如果你不希望使一個文檔"往返旅行",將輸入內容以原樣輸出的話,那么將這一開關打開通常會使性能略微有些提高。注意,這一標志只有在驗證已完成時才有效。而進行驗證會導致性能下降,所以這一技巧僅在已應用驗證時才有用。
setFeature(String name, String value)
這一方法設置基礎SAX分析器的一個特性。這是一個原始的"傳遞(pass-through)"調用,所以在應用這一方法時應非常小心,因為對特性的錯誤設置(如弄錯名稱空間)可能會中斷JDOM行為。而且,依靠任何特定分析器特性都會限制可移植性。這一調用對于啟用模式驗證最為有用。
setProperty(String name, Object value)
這一方法設置基礎SAX分析器的一個屬性。它也是一個原始的"傳遞"調用,它具有同樣的風險,而且對于有才能的用戶同樣有用,特別是對于模式驗證。下面的代碼結合這些方法,利用JAXP選擇的分析器讀取本地文件,驗證功能有效,可忽略的空格都被忽略。
SAXBuilder builder = new SAXBuilder();
builder.setValidation(true);
builder.setIgnoringElementContentWhitespace(true);
Document doc = builder.build(new File("/tmp/foo.xml"));
用XMLOutputter輸出
文檔可以以多種不同的格式輸出,但最常見的輸出格式還是字節流。在JDOM中,XMLOutputter類提供這一能力。它的缺省無參數生成器試圖忠實地輸出一個與內存中貯存的完全一樣的文檔。下面的代碼向一個文件生成一個文檔的原始表示。
// Raw output
XMLOutputter outp = new XMLOutputter();
outp.output(doc, fileStream);
如果你不關心空格,那么你可以對文本塊進行整理,以節省一點帶寬:
// Compressed output
outp.setTextTrim(true);
outp.output(doc, socketStream);
如果希望文件打印得很漂亮,使其展示給人們看,則可以增加一些縮進空格,并增加一些新行:
outp.setTextTrim(true);
outp.setIndent(" ");
outp.setNewlines(true);
outp.output(doc, System.out);
對于已有格式化空格的文檔,要打印得很漂亮,就一定要進行整理。否則,就會在已經格式的文檔上添加新的格式,使打印的文檔顯得很難看。
定位元素樹
JDOM使得元素樹的定位非常容易。為了取得根元素,可調用:
Element root = doc.getRootElement();
要得到它所有子元素的列表:
List allChildren = root.getChildren();
僅想得到具有給定名稱的元素:
List namedChildren = root.getChildren("name");
僅想得到具有給定名稱的第一個元素:
Element child = root.getChild("name");
getChildren()調用返回的列表是java.util.List,它是所有Java編程人員都知道的列表(List)接口的一個實現。這一列表令人感興趣的地方在于它是活的。該列表的任何變化都會在支持的文檔中立即反應出來。
// Remove the fourth child
allChildren.remove(3);
// Remove children named "jack"
allChildren.removeAll(root.getChildren("jack"));
// Add a new child, at the tail or at the head
allChildren.add(new Element("jane"));
allChildren.add(0, new Element("jill"));
利用該List隱喻(List metaphor)可以不必增加過多的方法而進行許多元素操作。但是為了方便起見,在結尾增加元素或者刪去給定名字的元素之類的常用任務都有涉及到元素(Element)自身的方法,而不需要首先得到該List:
root.removeChildren("jill");
root.addContent(new Element("jenny"));
采用JDOM的一個好處就是它可以很容易地在一個文檔內或在文檔之間移動元素。兩種情況的代碼相同,如下所示:
Element movable = new Element("movable");
parent1.addContent(movable); // place
parent1.removeContent(movable); // remove
parent2.addContent(movable); // add
采用DOM時,移動元素就沒有這么容易,因為在DOM中,元素是與它的生成工具緊緊聯系在一起的。所以在文檔之間移動DOM元素時,必須將其"導入"。
而采用JDOM時,你需要記住的唯一一件事件就是在將一個元素增加到其他位置之前,需要先先將它刪除,這樣你就不會在樹中形成循環。detach()方法可以用一行代碼完成分離/增加:
parent3.addContent(movable.detach());
如果在將一個元素增加到另一個父元素之前忘了將它分離,則該庫會產生一個異常(帶有一個真正準確而有用的錯誤信息)。該庫還會檢查元素的名字和內容,以確保它們不包括空格之類的不恰當字符。它還會驗證其他一些規則,如只有一個根元素、名稱空間的聲明是一致的、在注釋和CDATA部分沒有禁止使用的字符串等等。這一特性能夠盡可能早地在該進程中進行"格式正確性"(well-formedness)錯誤檢查。
處理元素的屬性
元素屬性的格式如下所示:
<table width="100%" border="0"> ... </table>
利用對元素的引用,可以要求元素的任意給定屬性值:
String val = table.getAttributeValue("width");
也可以將屬性看作一個對象,用于進行一些特殊的操作,如類型變換等:
Attribute border = table.getAttribute("border");
int size = border.getIntValue();
要設置或改變一個屬性,可以采用setAttribute():
table.setAttribute("vspace", "0");
要刪除一個屬性,可以采用removeAttribute():
table.removeAttribute("vspace");
處理元素文本內容
一個文本內容類似于:
<description>
A cool demo
</description>
在JDOM中,這一文本可以通過調用直接獲得:
String desc = description.getText();
要記住,因為XML 1.0規范要求保留空格,所以它會返回"\n A cool demo\n"。當然,有經驗的編程人員常常不希望得到格式化的空格。有一個很方便的方法可以檢索文本而同時忽略其中的空格:
String betterDesc = description.getTextTrim();
如果你真地希望去掉空格,那么有一個getTextNormalize()方法可以將內部空白成為一個標準空格。這一方法對于類似于下面這樣的文本是非常方便的:
<description>
Sometimes you have text content with formatting
space within the string.
</description>
要改變文本內容,可以應用setText()方法:
description.setText("A new description");
這一文本內的任何特殊字符都可以正確地被解釋為一個字符,并根據需要在輸出時刪除,使以保持文本的語法正確。比如說你進行了這樣一個調用:
element.setText("<xml/> content");
在內部存儲中,仍然將這些字符串看作字符。不會對其內容進行隱式分析。在輸出中,你將看到:
<xml/> content<elt>
這一操作保留了前面setText()調用的語義。如果你希望在一個元素中保留XML內容,則必須增加相應的JDOM子元素對象。
處理CDATA節也可能在JDOM內進行。一個CDATA節指出不應被分析的文本塊。它實質上是一個"語法糖塊(syntactic sugar)",它允許很容易地包含HTML或XML內容,而不需要很多<和>換碼字符。要建立一個CDATA節,只需要在CDATA對象內封裝字符串即可:
element.addContent(new CDATA("<xml/> content"));
JDOM最了不起的地方是getText()調用返回字符串時,不會麻煩調用程序去判斷它是否由CDATA節表示。
處理混合內容
一些元素包括有很多元素,如空格、注釋、文本、子元素,以及其他元素:
<table>
<!-- Some comment -->
Some text
<tr>Some child element</tr>
</table>
如果一個元素中同時包含文本和子元素,就說它包含有"混合內容" 。處理混合內容可能是非常困難的,但JDOM使它變得非常容易。標準使用情況--返回文本內容和定位子元素--都非常簡單:
String text = table.getTextTrim(); // "Some text"
Element tr = table.getChild("tr"); // A straight reference
對于需要注釋、空格塊、處理指令和實體引用這樣一些更復雜的應用來說,原始混合內容可以作為一個List(列表)來提供:
List mixedCo = table.getContent();
Iterator itr = mixedCo.iterator();
while (itr.hasNext()) {
Object o = i.next();
if (o instanceof Comment) {
...
}
// Types include Comment, Element, CDATA, DocType,
// ProcessingInstruction, EntityRef, and Text
}
就像子元素列表一樣,對原始內容列表的改變會影響到支持的文檔:
// Remove the Comment. It's "1" because "0" is a whitespace block.
mixedCo.remove(1);
如果你的觀察力很強,則你會注意到這里有一個Text類。JDOM在內部采用Text類存儲串內容,從而允許串具有"父輩(parentage)"關系,并更容易支持XPath訪問。作為一個編程人員,在檢索或設置文本時,不需要擔心這種類,而只需要在訪問原始內容列表時關心它就行了。.
對于DocType、處理指令和EntityRef類的詳細內容,請參見jdom.org上的API文檔。
關于第2部分
在本文中,開始研究了如何在應用程序中應用JDOM。在下一篇文章中,我將研究XML Namespace、ResultSetBuilder、XSLT和XPath。你現在可以在otn.oracle.com/oraclemagazine中找到這一系列文章的第二部分。
Jason Hunter (jasonhunter@servlets.com) 是一位顧問、Servlets.com的發行人,Apache軟件基金會的副總裁。他還是JCP執行委員會的委員