Java與XML讀寫之DOM篇
DOM
初步
DOM是Document Object Model的縮寫,即文檔對象模型。前面說過,XML將數據組織為一顆樹,
所以DOM就是對這顆樹的一個對象描敘。通俗的說,就是通過解析XML文檔,為XML文檔在
邏輯上建立一個樹模型,樹的節點是一個個對象。我們通過存取這些對象就能夠存取XML文檔
的內容。
下面我們來看一個簡單的例子,看看在DOM中,我們是如何來操作一個XML文檔的。
這是一個XML文檔,也是我們要操作的對象:
下面,我們需要把這個文檔的內容解析到一個個的Java對象中去供程序使用,利用JAXP,我們
只需幾行代碼就能做到這一點。首先,我們需要建立一個解析器工廠,以利用這個工廠來獲得
一個具體的解析器對象:
我們在這里使用DocumentBuilderFacotry的目的是為了創建與具體解析器無關的程序,當
DocumentBuilderFactory類的靜態方法newInstance()被調用時,它根據一個系統變量來決定具體
使用哪一個解析器。又因為所有的解析器都服從于JAXP所定義的接口,所以無論具體使用哪一
個解析器,代碼都是一樣的。所以當在不同的解析器之間進行切換時,只需要更改系統變量的
值,而不用更改任何代碼。這就是工廠所帶來的好處。這個工廠模式的具體實現,可以參看下
面的類圖。
當獲得一個工廠對象后, 使用它的靜態方法newDocumentBuilder() 方法可以獲得一個
DocumentBuilder對象,這個對象代表了具體的DOM解析器。但具體是哪一種解析器,微軟的或
者IBM的,對于程序而言并不重要。
然后,我們就可以利用這個解析器來對XML文檔進行解析了:
DocumentBuilder的parse()方法接受一個XML文檔名作為輸入參數,返回一個Document對象,這
<?xml version="1.0" encoding="UTF-8"?>
<messages>
<message>Good-bye serialization, hello Java!</message>
</messages>
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("c:/xml/message.xml");
個Document對象就代表了一個XML文檔的樹模型。以后所有的對XML文檔的操作,都與解析器
無關,直接在這個Document對象上進行操作就可以了。而具體對Document操作的方法,就是由
DOM所定義的了。
Jaxp支持W3C所推薦的DOM 2。如果你對DOM很熟悉,那么下面的內容就很簡單了:只需要按
照DOM的規范來進行方法調用就可以。當然,如果你對DOM不清楚,也不用著急,后面我們
會有詳細的介紹。在這兒,你所要知道并牢記的是:DOM是用來描敘XML文檔中的數據的模型,
引入DOM的全部原因就是為了用這個模型來操作XML文檔的中的數據。DOM規范中定義有節
點(即對象)、屬性和方法,我們通過這些節點的存取來存取XML的數據。
從上面得到的Document對象開始,我們就可以開始我們的DOM之旅了。使用Document對象的
getElementsByTagName()方法,我們可以得到一個NodeList對象,一個Node對象代表了一個XML
文檔中的一個標簽元素,而NodeList對象,觀其名而知其意,所代表的是一個Node對象的列表:
我們通過這樣一條語句所得到的是XML文檔中所有<message>標簽對應的Node對象的一個列
表。然后,我們可以使用NodeList對象的item()方法來得到列表中的每一個Node對象:
當一個Node對象被建立之后,保存在XML文檔中的數據就被提取出來并封裝在這個Node中了。
NodeList nl = doc.getElementsByTagName("message");
Node my_node = nl.item(0);
在這個例子中,要提取Message標簽內的內容,我們通常會使用Node對象的getNodeValue()方法:
請注意,這里還使用了一個getFirstChild()方法來獲得message下面的第一個子Node對象。雖然在
message 標簽下面除了文本外并沒有其它子標簽或者屬性, 但是我們堅持在這里使用
getFirseChild()方法,這主要和W3C對DOM的定義有關。W3C把標簽內的文本部分也定義成一
個Node,所以先要得到代表文本的那個Node,我們才能夠使用getNodeValue()來獲取文本的內
容。
現在,既然我們已經能夠從XML文件中提取出數據了,我們就可以把這些數據用在合適的地方,
來構筑應用程序。
下面的內容,我們將更多的關注DOM,為DOM作一個較為詳細的解析,使我們使用起來更為
得心應手。
DOM
詳解
DOM的基本對象有5個:Document,Node,NodeList,Element和Attr。下面就這些對象的功
能和實現的方法作一個大致的介紹。
Document對象代表了整個XML的文檔,所有其它的Node,都以一定的順序包含在Document
String message = my_node.getFirstChild().getNodeValue();
1.基本的DOM對象
對象之內,排列成一個樹形的結構,程序員可以通過遍歷這顆樹來得到XML文檔的所有的內容,
這也是對XML文檔操作的起點。我們總是先通過解析XML源文件而得到一個Document對象,
然后再來執行后續的操作。此外,Document還包含了創建其它節點的方法,比如createAttribut()
用來創建一個Attr對象。它所包含的主要的方法有:
createAttribute(String):用給定的屬性名創建一個Attr對象,并可在其后使用setAttributeNode
方法來放置在某一個Element對象上面。
createElement(String):用給定的標簽名創建一個Element對象,代表XML文檔中的一個標簽,
然后就可以在這個Element對象上添加屬性或進行其它的操作。
createTextNode(String):用給定的字符串創建一個Text對象,Text對象代表了標簽或者屬性中
所包含的純文本字符串。如果在一個標簽內沒有其它的標簽,那么標簽內的文本所代表的Text
對象是這個Element對象的唯一子對象。
getElementsByTagName(String):返回一個NodeList對象,它包含了所有給定標簽名字的標簽。
getDocumentElement():返回一個代表這個DOM樹的根節點的Element對象,也就是代表XML
文檔根元素的那個對象。
Node對象是DOM結構中最為基本的對象,代表了文檔樹中的一個抽象的節點。在實際使用的
時候,很少會真正的用到Node這個對象,而是用到諸如Element、Attr、Text等Node對象的子
對象來操作文檔。Node對象為這些對象提供了一個抽象的、公共的根。雖然在Node對象中定
義了對其子節點進行存取的方法,但是有一些Node子對象,比如Text對象,它并不存在子節點,
這一點是要注意的。Node對象所包含的主要的方法有:
appendChild(org.w3c.dom.Node):為這個節點添加一個子節點,并放在所有子節點的最后,
如果這個子節點已經存在,則先把它刪掉再添加進去。
getFirstChild():如果節點存在子節點,則返回第一個子節點,對等的,還有getLastChild()方法
返回最后一個子節點。
getNextSibling() : 返回在DOM 樹中這個節點的下一個兄弟節點, 對等的, 還有
getPreviousSibling()方法返回其前一個兄弟節點。
getNodeName():根據節點的類型返回節點的名稱。
getNodeType():返回節點的類型。
getNodeValue():返回節點的值。
hasChildNodes():判斷是不是存在有子節點。
hasAttributes():判斷這個節點是否存在有屬性。
getOwnerDocument():返回節點所處的Document對象。
insertBefore(org.w3c.dom.Node new,org.w3c.dom.Node ref):在給定的一個子對象前再插入
一個子對象。
removeChild(org.w3c.dom.Node):刪除給定的子節點對象。
replaceChild(org.w3c.dom.Node new,org.w3c.dom.Node old):用一個新的Node對象代替給
定的子節點對象。
NodeList對象,顧名思義,就是代表了一個包含了一個或者多個Node的列表。可以簡單的把它
看成一個Node的數組,我們可以通過方法來獲得列表中的元素:
GetLength():返回列表的長度。
Item(int):返回指定位置的Node對象。
Element對象代表的是XML文檔中的標簽元素,繼承于Node,亦是Node的最主要的子對象。在
標簽中可以包含有屬性,因而Element對象中有存取其屬性的方法,而任何Node中定義的方法,
也可以用在Element對象上面。
getElementsByTagName(String):返回一個NodeList對象,它包含了在這個標簽中其下的子孫
節點中具有給定標簽名字的標簽。
getTagName():返回一個代表這個標簽名字的字符串。
getAttribute(String):返回標簽中給定屬性名稱的屬性的值。在這兒需要主要的是,應為XML
文檔中允許有實體屬性出現,而這個方法對這些實體屬性并不適用。這時候需要用到
getAttributeNodes()方法來得到一個Attr對象來進行進一步的操作。
getAttributeNode(String):返回一個代表給定屬性名稱的Attr對象。
Attr對象代表了某個標簽中的屬性。Attr繼承于Node,但是因為Attr實際上是包含在Element中
的,它并不能被看作是Element的子對象,因而在DOM中Attr并不是DOM樹的一部分,所以Node
中的getparentNode(),getpreviousSibling()和getnextSibling()返回的都將是null。也就是說,
Attr其實是被看作包含它的Element對象的一部分,它并不作為DOM樹中單獨的一個節點出現。
這一點在使用的時候要同其它的Node子對象相區別。
需要說明的是,上面所說的DOM對象在DOM中都是用接口定義的,在定義的時候使用的是與
具體語言無關的IDL語言來定義的。因而,DOM其實可以在任何面向對象的語言中實現,只要
它實現了DOM所定義的接口和功能就可以了。同時,有些方法在DOM中并沒有定義,是用IDL
的屬性來表達的,當被映射到具體的語言時,這些屬性被映射為相應的方法。
有了上面的介紹,相信你對DOM理解的更多了吧。下面的例子將讓你對DOM更加熟悉起來。
先說說這個例子到底要做的是什么吧,我們希望在一個名為link.xml文件中保存了一些URL地
址,通過一個簡單的程序,我們可以通過DOM把這些URL讀出并顯示出來,也可以反過來向這
個XML文件中寫入加入的URL地址。很簡單,卻很實用,也足夠來例示DOM的絕大部分用法了。
XML文件本身不復雜,就不給出它的DTD了。link.xml:
2.DOM實例
<?xml version="1.0" standalone="yes"?>
<links>
<link>
<text>JSP Insider</text>
<url newWindow="no">http://www.jspinsider.com</url>
<author>JSP Insider</author>
<date>
<day>2</day>
<month>1</month>
<year>2001</year>
</date>
<description>A JSP information site.</description>
</link>
<link>
<text>The makers of Java</text>
<url newWindow="no">http://java.sun.com</url>
<author>Sun Microsystems</author>
<date>
<day>3</day>
<month>1</month>
<year>2001</year>
</date>
<description>Sun Microsystem's website.</description>
</link>
<link>
第一個程序我們稱為xmldisplay.java,具體的程序清單可以在附件中找到。主要的功能就是讀
取這個XML文件中各個節點的內容,然后在格式化輸出在System.out上,我們來看看這個程序:
這是引入必要的類, 因為在這里使用的是Sun 所提供的XML 解析器, 因而需要引入
java.xml.parsers包,其中包含了有DOM解析器和SAX解析器的具體實現。org.w3c.dom包中定
義了w3c所制定的DOM接口。
除了上面講到的,還有一個小技巧,對Document對象調用normalize(),可以去掉XML文檔中作
為格式化內容的空白而映射在DOM樹中的不必要的Text Node對象。否則你得到的DOM樹可能
并不如你所想象的那樣。特別是在輸出的時候,這個normalize()更為有用。
剛才說過,XML文檔中的空白符也會被作為對象映射在DOM樹中。因而,直接調用Node方法
的getChildNodes方法有時候會有些問題,有時不能夠返回所期望的NodeList對象。解決的辦法
是使用Element的getElementByTagName(String),返回的NodeLise就是所期待的對象了。然
后,可以用item()方法提取想要的元素。
<text>The standard JSP container</text>
<url newWindow="no">http://jakarta.apache.org</url>
<author>Apache Group</author>
<date>
<day>4</day>
<month>1</month>
<year>2001</year>
</date>
<description>Some great software.</description>
</link>
</links>
import javax.xml.parsers.*;
import org.w3c.dom.*;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc=builder.parse("links.xml");
doc.normalize();
NodeList links =doc.getElementsByTagName("link");
for (int i=0;i<links.getLength();i++){
Element link=(Element) links.item(i);
System.out.print("Content: ");
System.out.println(link.getElementsByTagName("text").item(0).getFirstChild().getNodeValue()
上面的代碼片斷就完成了對XML文檔內容的格式化輸出。只要注意到一些細節的問題,比如
getFirstChile()方法和getElementsByTagName()方法的使用,這些還是比較容易的。
下面的內容,就是在修改了DOM樹后重新寫入到XML文檔中去的問題了。這個程序名為
xmlwrite.java。在JAXP1.0版本中,并沒有直接的類和方法能夠處理XML文檔的寫入問題,需
要借助其它包中的一些輔助類。而在JAXP1.1版本中,引入了對XSLT的支持,所謂XSLT,就
是對XML文檔進行變換(Translation)后,得到一個新的文檔結構。利用這個新加入的功能,
我們就能夠很方便的把新生成或者修改后的DOM樹從新寫回到XML文件中去了,下面我們來看
看代碼的實現,這段代碼的主要功能是向links.xml文件中加入一個新的link節點:
新引入的java.xml.transform包中的幾個類,就是用來處理XSLT變換的。
我們希望在上面的XML文件中加入一個新的link節點,因而首先還是要讀入links.xml文件,構建
一個DOM樹,然后再對這個DOM樹進行修改(添加節點),最后把修改后的DOM寫回到links.xml
文件中:
);
System.out.print("URL: ");
System.out.println(link.getElementsByTagName("url").item(0).getFirstChild().getNodeValue());
System.out.print("Author: ");
System.out.println(link.getElementsByTagName("author").item(0).getFirstChild().getNodeVal
ue());
System.out.print("Date: ");
Element linkdate=(Element) link.getElementsByTagName("date").item(0);
String day=linkdate.getElementsByTagName("day").item(0).getFirstChild().getNodeValue();
String
month=linkdate.getElementsByTagName("month").item(0).getFirstChild().getNodeValue();
String year=linkdate.getElementsByTagName("year").item(0).getFirstChild().getNodeValue();
System.out.println(day+"-"+month+"-"+year);
System.out.print("Description: ");
System.out.println(link.getElementsByTagName("description").item(0).getFirstChild().getNod
eValue());
System.out.println();
}
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
為了看清重點,簡化程序,我們把要加入的內容硬編碼到記憶String對象中,而實際操作中,往
往利用一個界面來提取用戶輸入,或者通過JDBC從數據庫中提取想要的內容。
首先應該明了的是,無論什么類型的Node,Text型的也好,Attr型的也好,Element型的也好,
它們的創建都是通過Document對象中的createXXX()方法來創建的(XXX代表具體要創建的類
型),因此,我們要向XML文檔中添加一個link項目,首先要創建一個link對象:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc=builder.parse("links.xml");
doc.normalize();
//---取得變量----
String text="Hanzhong's Homepage";
String url="
String author="Hzliu Liu";
String discription="A site from Hanzhong Liu, give u lots of suprise!!!";
Text textseg;
Element link=doc.createElement("link");
Element linktext=doc.createElement("text");
textseg=doc.createTextNode(text);
linktext.appendChild(textseg);
link.appendChild(linktext);
Element linkurl=doc.createElement("url");
textseg=doc.createTextNode(url);
linkurl.appendChild(textseg);
link.appendChild(linkurl);
Element linkauthor=doc.createElement("author");
textseg=doc.createTextNode(author);
linkauthor.appendChild(textseg);
link.appendChild(linkauthor);
java.util.Calendar rightNow = java.util.Calendar.getInstance();
String day=Integer.toString(rightNow.get(java.util.Calendar.DAY_OF_MONTH));
String month=Integer.toString(rightNow.get(java.util.Calendar.MONTH));
String year=Integer.toString(rightNow.get(java.util.Calendar.YEAR));
Element linkdate=doc.createElement("date");
Element linkdateday=doc.createElement("day");
textseg=doc.createTextNode(day);
linkdateday.appendChild(textseg);
Element linkdatemonth=doc.createElement("month");
創建節點的過程可能有些千篇一律,但需要注意的地方是,對Element中所包含的text(在DOM
中,這些text也是代表了一個Node的,因此也必須為它們創建相應的node),不能直接用Element
對象的setNodeValue()方法來設置這些text的內容,而需要用創建的Text對象的setNodeValue()
方法來設置文本,這樣才能夠把創建的Element和其文本內容添加到DOM樹中。看看前面的代
碼,你會更好的理解這一點:
最后,不要忘記把創建好的節點添加到DOM樹中。Document類的getDocumentElement()方法,
返回代表文檔根節點的Element對象。在XML文檔中,根節點一定是唯一的。
然后就是用XSLT把DOM樹輸出了。這里的TransformerFactory也同樣應用了工廠模式,使得具
體的代碼同具體的變換器無關。實現的方法和DocumentBuilderFactory相同,這兒就不贅述了。
Transformer類的transfrom方法接受兩個參數、一個數據源Source和一個輸出目標Result。這里
分別使用的是DOMSource和StreamResult,這樣就能夠把DOM的內容輸出到一個輸出流中,
當這個輸出流是一個文件的時候,DOM的內容就被寫入到文件中去了。
好了,關于DOM的話題就先到這里,下篇文章將介紹的內容是SAX。請看Java與XML讀寫之
SAX篇
textseg=doc.createTextNode(month);
linkdatemonth.appendChild(textseg);
Element linkdateyear=doc.createElement("year");
textseg=doc.createTextNode(year);
linkdateyear.appendChild(textseg);
linkdate.appendChild(linkdateday);
linkdate.appendChild(linkdatemonth);
linkdate.appendChild(linkdateyear);
link.appendChild(linkdate);
Element linkdiscription=doc.createElement("description");
textseg=doc.createTextNode(discription);
linkdiscription.appendChild(textseg);
link.appendChild(linkdiscription);
doc.getDocumentElement().appendChild(link);
TransformerFactory tFactory =TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new java.io.File("links.xml"));
transformer.transform(source, result);
posted on 2007-12-23 03:25
豪 閱讀(1556)
評論(1) 編輯 收藏 引用 所屬分類:
Java