DOM 是Document Object Model 的缩写,即文档对象模型。XML 将数据组织为一颗树,所以DOM 就是对这颗树的一个对象描叙。通俗的说,就是通过解析XML 文档,为 XML 文档在逻辑上建立一个树模型,树的节点是一个个对象。我们通过存取这些对象就能够存取XML 文档的内容。
下面我们来看一个简单的例子,看看在DOM 中,我们是如何来操作一个XML 文档的。
这是一个XML 文档,也是我们要操作的对象:
<?xml version="1.0" encoding="UTF-8"?>
<messages>
<message>Good-bye serialization, hello Java!</message>
</messages>
下面,我们需要把这个文档的内容解析到一个Java 对象中去供程序使用,利用JAXP ,我们只需几行代码就能做到这一点。首先,我们需要建立一个解析器工厂,以利用这个工厂来获得一个具体的解析器对象:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
我们在这里使用DocumentBuilderFacotry 的目的是为了创建与具体解析器无关的程序,当DocumentBuilderFactory 类的静态方法newInstance() 被调用时,它根据一个系统变量来决定具体使用哪一个解析器。又因为所有的解析器都服从于JAXP 所定义的接口,所以无论具体使用哪一个解析器,代码都是一样的。所以当在不同的解析器之间进行切换时,只需要更改系统变量的值,而不用更改任何代码。这就是工厂所带来的好处。这个工厂模式的具体实现,可以参看下面的类图。
DocumentBuilder builder = factory.newDocumentBuilder();
当获得一个工厂对象后,使用它的静态方法newDocumentBuilder() 方法可以获得一个DocumentBuilder 对象,这个对象代表了具体的DOM 解析器。但具体是哪一种解析器,微软的或者IBM 的,对于程序而言并不重要。
API javax.xml.parsers.DocumentBuilderFactory 1.4
返回DocumentBuilderFactory 类的一个实例
static DocumentBuilderFactory newInstance()
返回DocumentBuilder 类的一个实例
DocumentBuilder newDocumentBuilder()
然后,我们就可以利用这个解析器来对XML 文档进行解析了:
Document doc = builder.parse("src/message.xml");
DocumentBuilder 的parse() 方法接受一个XML 文档名作为输入参数,返回一个Document 对象,这个Document 对象就代表了一个XML 文档的树模型。以后所有的对XML 文档的操作,都与解析器无关,直接在这个Document 对象上进行操作就可以了。而具体对 Document 操作的方法,就是由DOM 所定义的了。
API javax.xml.parsers.DocumentBuilder 1.4
解析来自给定文件、URL 或输入流的XML 文档,返回解析后的文档
-
Document parse(File f)
-
Document parse(String url)
-
Document parse(InputStream in)
JAXP 支持W3C 所推荐的DOM 2 。如果你对DOM 很熟悉,只需要按照DOM 的规范来进行方法调用就可以。DOM 是用来描叙XML 文档中的数据的模型,引入DOM 的全部原因就是为了用这个模型来操作XML 文档的中的数据。DOM 规范中定义有节点(即对象)、属性和方法,我们通过这些节点的存取来存取XML 的数据。
上面得到了Document 对象,使用它的 getElementsByTagName() 方法,我们可以得到一个NodeList 对象,一个Node 对象代表了一个XML 文档中的一个标签元素,而 NodeList 对象,观其名而知其意,所代表的是一个Node 对象的列表:
NodeList children = doc.getElementsByTagName("message");
我们通过这样一条语句所得到的是XML 文档中所有<message> 标签对应的Node 对象的一个列表。然后,我们可以使用NodeList 对象的item() 方法来得到列表中的每一个Node 对象:
Node child = children.item(0);
当一个Node 对象被建立之后,保存在XML 文档中的数据就被提取出来并封装在这个Node 中了。在这个例子中,要提取Message 标签内的内容,我们通常会使用Node 对象的getNodeValue() 方法: String message = child.getFirstChild().getNodeValue();
注意: 这里还使用了一个getFirstChild() 方法来获得message 下面的第一个子Node 对象。虽然在message 标签下面除了文本外并没有其它子标签或者属性,但是我们坚持在这里使用getFirseChild() 方法,这主要和W3C 对DOM 的定义有关。W3C 把标签内的文本部分也定义成一个Node ,所以先要得到代表文本的那个Node ,我们才能够使用getNodeValue() 来获取文本的内容。
现在,既然已经能够从XML 文件中提取出数据了,就可以把这些数据用在合适的地方,来构筑应用程序。
DOM 的基本对象有5 个:Document ,Node ,NodeList ,Element 和Attr 。如下结构图。
Document 对象代表了整个XML 的文档,所有其它的Node ,都以一定的顺序包含在Document 对象之内,排列成一个树形的结构,程序员可以通过遍历这颗树来得到XML 文档的所有的内容,这也是对XML 文档操作的起点。我们总是先通过解析XML 源文件而得到一个Document 对象,然后再来执行后续的操作。此外,Document 还包含了创建其它节点的方法,比如createAttribut() 用来创建一个Attr 对象。它所包含的主要的方法有:
API org.w3c.dom.Document
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 对象所包含的主要的方法有:
API org.w3c.dom.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 的数组,我们可以通过方法来获得列表中的元素:
API org.w3c.dom. NodeList
GetLength() : 返回列表的长度。
Item(int) : 返回指定位置的Node 对象。
Element 对象代表的是XML 文档中的标签元素,继承于Node ,亦是Node 的最主要的子对象。在标签中可以包含有属性,因而Element 对象中有存取其属性的方法,而任何Node 中定义的方法,也可以用在Element 对象上面。
API org.w3c.dom. 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 的属性来表达的,当被映射到具体的语言时,这些属性被映射为相应的方法。
<?xml version="1.0" encoding="UTF-8"?> <links> <link> <text>The makers of Java</text> <url newWindow="no">http://java.sun.com</url> <author>Sun Microsystems</author> <date> <day>5</day> <month>4</month> <year>2008</year> </date> <description>Sun Microsystem's website.</description> </link> <link> <text>Janwer's Homepage</text> <url>www.janwer.com</url> <author>张峻伟</author> <date> <day>5</day> <month>3</month> <year>2008</year> </date> <description> A site from Janwer Zhang, give u lots of suprise!!! </description> </link> <link> <text>张峻伟的个人主页</text> <url>janwer.iteye.com</url> <author>张峻伟</author> <date> <day>5</day> <month>3</month> <year>2008</year> </date> <description>有关J2EE</description> </link> <link> <text>zhang janwer's Homepage</text> <url>www.junwei.com</url> <author>janwer zhang</author> <date> <day>6</day> <month>3</month> <year>2008</year> </date> <description>A site from J2EE,C#,C++,C and so on!</description> </link> </links>
我们希望在一个名为server.xml 文件中保存了一些URL 地址,通过一个简单的程序,我们可以通过DOM 把这些URL 读出并显示出来,也可以反过来向这个XML 文件中写入加入的URL 地址。
package cn.janwer.xml;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class XmlRead {
public static void main(String args[]) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("src/server.xml");
doc.normalize(); //以去掉XML文档中作为格式化内容的空白
/**
* XML文档中的空白符也会被作为对象映射在DOM树中。
* 因而,直接调用Node方法的 getChildNodes方法有时候会有些问题,
* 有时不能够返回所期望的NodeList对象。
* 解决的办法是使用Element的 getElementByTagName(String),
* 返回的NodeLise就是所期待的对象了。然后,可以用item()方法提取想要的元素。
*/
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());
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().getNodeValue());
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().getNodeValue().trim());
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
下面的内容,就是在修改了DOM 树后重新写入到XML 文档中去的问题了。这个程序名为 Xmlwrite.java 。在JAXP1.0 版本中,并没有直接的类和方法能够处理XML 文档的写入问题,需要借助其它包中的一些辅助类。而在 JAXP1.1 版本中,引入了对XSLT 的支持,所谓XSLT ,就是对XML 文档进行变换(Translation )后,得到一个新的文档结构。利用这个新加入的功能,我们就能够很方便的把新生成或者修改后的DOM 树从新写回到XML 文件中去了,下面我们来看看代码的实现,这段代码的主要功能是向 links.xml 文件中加入一个新的link 节点。
我们希望在上面的XML 文件中加入一个新的link 节点,因而首先还是要读入links.xml 文件,构建一个DOM 树,然后再对这个DOM 树进行修改(添加节点),最后把修改后的DOM 写回到links.xml 文件中:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance ();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("src/server.xml");
doc.normalize();
// --- 取得变量 ----
String text = "zhang janwer's Homepage";
String url = "www.junwei.com";
String author = "janwer zhang";
String discription = "A site from J2EE,C#,C++,C and so on!";
为了看清重点,简化程序,我们把要加入的内容硬编码到记忆String 对象中,而实际操作中,往往利用一个界面来提取用户输入,或者通过JDBC 从数据库中提取想要的内容。
Text textseg;
Element link=doc.createElement("link");
首先应该明了的是,无论什么类型的Node ,Text 型的也好,Attr 型的也好,Element 型的也好,它们的创建都是通过Document 对象中的 createXXX() 方法来创建的(XXX 代表具体要创建的类型)。
创建节点的过程可能有些千篇一律,但需要注意的地方是,对Element 中所包含的text (在DOM 中,这些text 也是代表了一个Node 的,因此也必须为它们创建相应的node ),不能直接用Element 对象的setNodeValue() 方法来设置这些text 的内容,而需要用创建的Text 对象的setNodeValue() 方法来设置文本,这样才能够把创建的Element 和其文本内容添加到DOM 树中。看看前面的代码,你会更好的理解这一点:
doc.getDocumentElement().appendChild(link);
最后,不要忘记把创建好的节点添加到DOM 树中。Document 类的getDocumentElement() 方法,返回代表文档根节点的Element 对象。在XML 文档中,根节点一定是唯一的。
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);
然后就是用XSLT 把DOM 树输出了。这里的TransformerFactory 也同样应用了工厂模式,使得具体的代码同具体的变换器无关。实现的方法和DocumentBuilderFactory 相同,这儿就不赘述了。Transformer 类的transfrom 方法接受两个参数、一个数据源Source 和一个输出目标Result 。这里分别使用的是DOMSource 和StreamResult ,这样就能够把DOM 的内容输出到一个输出流中,当这个输出流是一个文件的时候,DOM 的内容就被写入到文件中去了。
package cn.janwer.xml;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
public class Xmlwriter {
public static void main(String args[]) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("src/server.xml");
doc.normalize();
// ---取得变量----
String text = "zhang janwer's Homepage";
String url = "www.junwei.com";
String author = "janwer zhang";
String discription = "A site from J2EE,C#,C++,C and so on!";
// -------------
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");
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("src/server.xml"));
transformer.transform(source, result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
编辑XML 文档总结:
1. 更改节点数据
Node.setNodeValue(elemValue);
2. 添加节点
String totalString = new Double(total).toString();
Node totalNode = doc.createTextNode(totalString);
//Document 对象创建新的文本节点,该节点带有作为值的 totalString
Element totalElement = doc.createElement("total");
// 创建新元素 total
totalElement.appendChild(totalNode);
// 将节点添加到新的 total 元素。
thisOrder.insertBefore(totalElement, thisOrder.getFirstChild());
// 将新元素添加到 Document ,指定新的 Node ,然后指定新 Node 在 Node 之前
3. 除去节点
Node deadNode = thisOrderItem.getParentNode().removeChild(thisOrderItem);
4. 替换节点
Element backElement = doc.createElement("backordered");
// 创建新元素 backordered
Node deadNode = thisOrderItem.getParentNode().replaceChild(backElement,thisOrderItem);
5. 创建和设置属性
Element backElement = doc.createElement("backordered");
// 创建新元素 backordered
backElement.setAttributeNode(doc.createAttribute("itemid"));
// 创建新属性 itemid
String itemIdString = thisOrderItem.getAttributeNode("itemid").getNodeValue();
// 取得 thisOrderItem 的属性 itemid 的值
backElement.setAttribute("itemid", itemIdString);
// 设置 backElement 的属性 item 的值 , 可以省略 createAttribute
Node deadNode = thisOrderItem.getParentNode().replaceChild(backElement,thisOrderItem);
6. 除去属性
Element thisOrder = (Element)orders.item(orderNum);
Element customer = (Element)thisOrder.getElementsByTagName("cusomertid").item(0);
customer.removeAttribute("limit");
// 去除属性 limit