天天看點

Dom4j--解析XML的首選

dom4j

功能簡介

dom4j是一個Java的XML API,類似于jdom,用來讀寫XML檔案的。dom4j是一個非常非常優秀的Java XML API,具有性能優異、功能強大和極端易用使用的特點,同時它也是一個開放源代碼的軟體,可以在SourceForge上找到它。在IBM developerWorks上面可以找到一篇文章,對主流的Java XML API進行的性能、功能和易用性的評測,dom4j無論在那個方面都是非常出色的。如今你可以看到越來越多的Java軟體都在使用dom4j來讀寫XML,特别值得一提的是連Sun的JAXM也在用dom4j。這是必須使用的jar包, Hibernate用它來讀寫配置檔案。

概念

DOM4J是dom4j.org出品的一個開源XML解析包,它的網站中這樣定義:

Dom4j is an easy to use, open source library for working with XML, XPath and XSLT on the Java platform using the Java Collections Framework and with full support for DOM, SAX and JAXP.

Dom4j是一個易用的、開源的庫,用于XML,XPath和XSLT。它應用于Java平台,采用了Java集合架構并完全支援DOM,SAX和JAXP。

DOM4J使用起來非常簡單。隻要你了解基本的XML-DOM模型,就能使用。然而他自己帶的指南隻有短短一頁(html),不過說的到挺全。國内的中文資料很少。因而俺寫這個短小的教程友善大家使用,這篇文章僅談及基本的用法,如需深入的使用,請……自己摸索或查找别的資料。

之前看過IBM developer社群的文章(參見附錄),提到一些XML解析包的性能比較,其中DOM4J的性能非常出色,在多項測試中名列前茅。(事實上DOM4J的官方文檔中也引用了這個比較)是以這次的項目中我采用了DOM4J作為XML解析工具。

在國内比較流行的是使用JDOM作為解析器,兩者各擅其長,但DOM4J最大的特色是使用大量的接口,這也是它被認為比JDOM靈活的主要原因。大師不是說過麼,“面向接口程式設計”。目前使用DOM4J的已經越來越多。如果你善于使用JDOM,不妨繼續用下去,隻看看本篇文章作為了解與比較,如果你正要采用一種解析器,不如就用DOM4J吧。

它的主要接口都在org.dom4j這個包裡定義:

Attribute Attribute定義了XML的屬性

Branch Branch為能夠包含子節點的節點如XML元素(Element)和文檔(Docuemnts)定義了一個公共的行為,

CDATA CDATA 定義了XML CDATA 區域

CharacterData CharacterData是一個辨別接口,辨別基于字元的節點。如CDATA,Comment, Text.

Comment Comment 定義了XML注釋的行為

Document 定義了XML文檔

DocumentType DocumentType 定義XML DOCTYPE聲明

Element Element定義XML 元素

ElementHandler ElementHandler定義了 Element 對象的處理器

ElementPath 被 ElementHandler 使用,用于取得目前正在處理的路徑層次資訊

Entity Entity定義 XML entity

Node Node為所有的dom4j中XML節點定義了多态行為

NodeFilter NodeFilter 定義了在dom4j節點中産生的一個濾鏡或謂詞的行為(predicate)

ProcessingInstruction ProcessingInstruction 定義 XML 處理指令.

Text Text 定義XML 文本節點.

Visitor Visitor 用于實作Visitor模式.

XPath XPath 在分析一個字元串後會提供一個XPath 表達式

看名字大緻就知道它們的涵義如何了。

要想弄懂這套接口,關鍵的是要明白接口的繼承關系:

interface java.lang.Cloneable

interface org.dom4j.Node

interface org.dom4j.Attribute

interface org.dom4j.Branch

interface org.dom4j.Document

interface org.dom4j.Element

interface org.dom4j.CharacterData

interface org.dom4j.CDATA

interface org.dom4j.Comment

interface org.dom4j.Text

interface org.dom4j.DocumentType

interface org.dom4j.Entity

interface org.dom4j.ProcessingInstruction

一目了然,很多事情都清楚了。大部分都是由Node繼承來的。知道這些關系,将來寫程式就不會出現ClassCastException了。

使用簡介

下面給出一些例子(部分摘自DOM4J自帶的文檔),簡單說一下如何使用。

1. 讀取并解析XML文檔:

讀寫XML文檔主要依賴于org.dom4j.io包,其中提供DOMReader和SAXReader兩類不同方式,而調用方式是一樣的。這就是依靠接口的好處。

// 從檔案讀取XML,輸入檔案名,傳回XML文檔

public Document read(String fileName) throws MalformedURLException, DocumentException {

SAXReader reader = new SAXReader();

Document document = reader.read(new File(fileName));

return document;

}

其中,reader的read方法是重載的,可以從InputStream, File, Url等多種不同的源來讀取。得到的Document對象就帶表了整個XML。

根據本人自己的經驗,讀取的字元編碼是按照XML檔案頭定義的編碼來轉換。如果遇到亂碼問題,注意要把各處的編碼名稱保持一緻即可。

2. 取得Root節點

讀取後的第二步,就是得到Root節點。熟悉XML的人都知道,一切XML分析都是從Root元素開始的。

public Element getRootElement(Document doc){

return doc.getRootElement();

}

3. 周遊XML樹

DOM4J提供至少3種周遊節點的方法:

1) 枚舉(Iterator)

// 枚舉所有子節點

for ( Iterator i = root.elementIterator(); i.hasNext(); ) {

Element element = (Element) i.next();

// do something

}

// 枚舉名稱為foo的節點

for ( Iterator i = root.elementIterator(foo); i.hasNext();) {

Element foo = (Element) i.next();

// do something

}

// 枚舉屬性

for ( Iterator i = root.attributeIterator(); i.hasNext(); ) {

Attribute attribute = (Attribute) i.next();

// do something

}

2)遞歸

遞歸也可以采用Iterator作為枚舉手段,但文檔中提供了另外的做法

public void treeWalk() {

treeWalk(getRootElement());

}

public void treeWalk(Element element) {

for (int i = 0, size = element.nodeCount(); i < size; i++) {

Node node = element.node(i);

if (node instanceof Element) {

treeWalk((Element) node);

} else { // do something....

}

}

}

3) Visitor模式

最令人興奮的是DOM4J對Visitor的支援,這樣可以大大縮減代碼量,并且清楚易懂。了解設計模式的人都知道,Visitor是GOF設計模式之一。其主要原理就是兩種類互相保有對方的引用,并且一種作為Visitor去通路許多Visitable。我們來看DOM4J中的Visitor模式(快速文檔中沒有提供)

隻需要自定一個類實作Visitor接口即可。

public class MyVisitor extends VisitorSupport {

public void visit(Element element){

System.out.println(element.getName());

}

public void visit(Attribute attr){

System.out.println(attr.getName());

}

}

調用: root.accept(new MyVisitor())

Visitor接口提供多種Visit()的重載,根據XML不同的對象,将采用不同的方式來通路。上面是給出的Element和Attribute的簡單實作,一般比較常用的就是這兩個。VisitorSupport是DOM4J提供的預設擴充卡,Visitor接口的Default Adapter模式,這個模式給出了各種visit(*)的空實作,以便簡化代碼。

注意,這個Visitor是自動周遊所有子節點的。如果是root.accept(MyVisitor),将周遊子節點。我第一次用的時候,認為是需要自己周遊,便在遞歸中調用Visitor,結果可想而知。

4. XPath支援

DOM4J對XPath有良好的支援,如通路一個節點,可直接用XPath選擇。

public void bar(Document document) {

List list = document.selectNodes( //foo/bar );

Node node = document.selectSingleNode(//foo/bar/author);

String name = node.valueOf( @name );

}

例如,如果你想查找XHTML文檔中所有的超連結,下面的代碼可以實作:

public void findLinks(Document document) throws DocumentException {

List list = document.selectNodes( //a/@href );

for (Iterator iter = list.iterator(); iter.hasNext(); ) {

Attribute attribute = (Attribute) iter.next();

String url = attribute.getValue();

}

}

5. 字元串與XML的轉換

有時候經常要用到字元串轉換為XML或反之,

// XML轉字元串

Document document = ...;

String text = document.asXML();

// 字元串轉XML

String text = <name>James</name> </person>;

Document document = DocumentHelper.parseText(text);

6 用XSLT轉換XML

public Document styleDocument(

Document document,

String stylesheet

) throws Exception {

// load the transformer using JAXP

TransformerFactory factory = TransformerFactory.newInstance();

Transformer transformer = factory.newTransformer(

new StreamSource( stylesheet )

);

// now lets style the given document

DocumentSource source = new DocumentSource( document );

DocumentResult result = new DocumentResult();

transformer.transform( source, result );

// return the transformed document

Document transformedDoc = result.getDocument();

return transformedDoc;

}

7. 建立XML

一般建立XML是寫檔案前的工作,這就像StringBuffer一樣容易。

public Document createDocument() {

Document document = DocumentHelper.createDocument();

Element root = document.addElement(root);

Element author1 =

root

.addElement(author)

.addAttribute(name, James)

.addAttribute(location, UK)

.addText(James Strachan);

Element author2 =

root

.addElement(author)

.addAttribute(name, Bob)

.addAttribute(location, US)

.addText(Bob McWhirter);

return document;

}

8. 檔案輸出

一個簡單的輸出方法是将一個Document或任何的Node通過write方法輸出

FileWriter out = new FileWriter( foo.xml );

document.write(out);

如果你想改變輸出的格式,比如美化輸出或縮減格式,可以用XMLWriter類

public void write(Document document) throws IOException {

// 指定檔案

XMLWriter writer = new XMLWriter(

new FileWriter( output.xml )

);

writer.write( document );

writer.close();

// 美化格式

OutputFormat format = OutputFormat.createPrettyPrint();

writer = new XMLWriter( System.out, format );

writer.write( document );

// 縮減格式

format = OutputFormat.createCompactFormat();

writer = new XMLWriter( System.out, format );

writer.write( document );

}

如何,DOM4J夠簡單吧,當然,還有一些複雜的應用沒有提到,如ElementHandler等。如果你動心了,那就一起來用DOM4J.

使用介紹2

本文主要讨論了用dom4j解析XML的基礎問題,包括建立XML文檔,添加、修改、删除節點,以及格式化(美化)輸出和中文問題。可作為dom4j的入門資料。

1. 下載下傳與安裝

dom4j是sourceforge.net上的一個開源項目,主要用于對XML的解析。從2001年7月釋出第一版以來,已陸續推出多個版本,目前最高版本為1.5。

dom4j專門針對Java開發,使用起來非常簡單、直覺,在Java界,dom4j正迅速普及。

可以到http://sourceforge.net/projects/dom4j下載下傳其最新版。

dom4j1.5的完整版大約13M,是一個名為dom4j-1.5.zip的壓縮包,解壓後有一個dom4j-1.5.jar檔案,這就是應用時需要引入的類包,另外還有一個jaxen-1.1-beta-4.jar檔案,一般也需要引入,否則執行時可能抛java.lang.NoClassDefFoundError: org/jaxen/JaxenException異常,其他的包可以選擇用之。

2. 示例XML文檔(holen.xml)

為了述說友善,先看一個XML文檔,之後的操作均以此文檔為基礎。

holen.xml

<?xml version="1.0" encoding="UTF-8"?>

<books>

<!--This is a test for dom4j, holen, 2004.9.11-->

<book show="yes">

<title>Dom4j Tutorials</title>

</book>

<book show="yes">

<title>Lucene Studing</title>

</book>

<book show="no">

<title>Lucene in Action</title>

</book>

<owner>O'Reilly</owner>

</books>

這是一個很簡單的XML文檔,場景是一個網上書店,有很多書,每本書有兩個屬性,一個是書名,一個為是否展示[show],最後還有一項是這些書的擁有者[owner]資訊。

3. 建立一個XML文檔

public int createXMLFile(String filename){

int returnValue = 0;

Document document = DocumentHelper.createDocument();

Element booksElement = document.addElement("books");

booksElement.addComment("This is a test for dom4j, holen, 2004.9.11");

Element bookElement = booksElement.addElement("book");

bookElement.addAttribute("show","yes");

Element titleElement = bookElement.addElement("title");

titleElement.setText("Dom4j Tutorials");

bookElement = booksElement.addElement("book");

bookElement.addAttribute("show","yes");

titleElement = bookElement.addElement("title");

titleElement.setText("Lucene Studing");

bookElement = booksElement.addElement("book");

bookElement.addAttribute("show","no");

titleElement = bookElement.addElement("title");

titleElement.setText("Lucene in Action");

Element ownerElement = booksElement.addElement("owner");

ownerElement.setText("O'Reilly");

try{

XMLWriter writer = new XMLWriter(new FileWriter(new File(filename)));

writer.write(document);

writer.close();

returnValue = 1;

}catch(Exception ex){

ex.printStackTrace();

}

return returnValue;

}

說明:

Document document = DocumentHelper.createDocument();

通過這句定義一個XML文檔對象。

Element booksElement = document.addElement("books");

通過這句定義一個XML元素,這裡添加的是根節點。

Element有幾個重要的方法:

l addComment:添加注釋

l addAttribute:添加屬性

l addElement:添加子元素

最後通過XMLWriter生成實體檔案,預設生成的XML檔案排版格式比較亂,可以通過OutputFormat類的createCompactFormat()方法或createPrettyPrint()方法格式化輸出,預設采用createCompactFormat()方法,顯示比較緊湊,這點将在後面詳細談到。

生成後的holen.xml檔案内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<books><!--This is a test for dom4j, holen, 2004.9.11--><book show="yes"><title>Dom4j Tutorials</title></book><book show="yes"><title>Lucene Studing</title></book><book show="no"><title>Lucene in Action</title></book><owner>O'Reilly</owner></books>

4. 修改XML文檔

有三項修改任務,依次為:

l 如果book節點中show屬性的内容為yes,則修改成no

l 把owner項内容改為Tshinghua,并添加date節點

l 若title内容為Dom4j Tutorials,則删除該節點

public int ModiXMLFile(String filename,String newfilename){

int returnValue = 0;

try{

SAXReader saxReader = new SAXReader();

Document document = saxReader.read(new File(filename));

List list = document.selectNodes("/books/book/@show" );

Iterator iter = list.iterator();

while(iter.hasNext()){

Attribute attribute = (Attribute)iter.next();

if(attribute.getValue().equals("yes")){

attribute.setValue("no");

}

}

list = document.selectNodes("/books/owner" );

iter = list.iterator();

if(iter.hasNext()){

Element ownerElement = (Element)iter.next();

ownerElement.setText("Tshinghua");

Element dateElement = ownerElement.addElement("date");

dateElement.setText("2004-09-11");

dateElement.addAttribute("type","Gregorian calendar");

}

list = document.selectNodes("/books/book");

iter = list.iterator();

while(iter.hasNext()){

Element bookElement = (Element)iter.next();

Iterator iterator = bookElement.elementIterator("title");

while(iterator.hasNext()){

Element titleElement=(Element)iterator.next();

if(titleElement.getText().equals("Dom4j Tutorials")){

bookElement.remove(titleElement);

}

}

}

try{

XMLWriter writer = new XMLWriter(new FileWriter(new File(newfilename)));

writer.write(document);

writer.close();

returnValue = 1;

}catch(Exception ex){

ex.printStackTrace();

}

}catch(Exception ex){

ex.printStackTrace();

}

return returnValue;

}

說明:

List list = document.selectNodes("/books/book/@show" );

list = document.selectNodes("/books/book");

上述代碼通過xpath查找到相應内容。

通過setValue()、setText()修改節點内容。

通過remove()删除節點或屬性。

5. 格式化輸出和指定編碼

預設的輸出方式為緊湊方式,預設編碼為UTF-8,但對于我們的應用而言,一般都要用到中文,并且希望顯示時按自動縮進的方式的顯示,這就需用到OutputFormat類。

public int formatXMLFile(String filename){

int returnValue = 0;

try{

SAXReader saxReader = new SAXReader();

Document document = saxReader.read(new File(filename));

XMLWriter writer = null;

OutputFormat format = OutputFormat.createPrettyPrint();

format.setEncoding("GBK");

writer= new XMLWriter(new FileWriter(new File(filename)),format);

writer.write(document);

writer.close();

returnValue = 1;

}catch(Exception ex){

ex.printStackTrace();

}

return returnValue;

}

說明:

OutputFormat format = OutputFormat.createPrettyPrint();

這句指定了格式化的方式為縮進式,則非緊湊式。

format.setEncoding("GBK");

指定編碼為GBK。

XMLWriter writer = new XMLWriter(new FileWriter(new File(filename)),format);

這與前面兩個方法相比,多加了一個OutputFormat對象,用于指定顯示和編碼方式。