DOM 心得
绯雨闲丸
本文介绍了 DOM(文档对象模型)的结构和常规使用方法。通过本文,读者可以学会用DOM来对XML文档进行常见的处理。本文不讨论DOM的设计和实现技巧。
关键词:
XML DOM
概述
DOM (文档对象模型)是对 XML 数据的描述体系,它用树型结构的文档来保存 XML 数据。此外, DOM 也包括了解析、处理 XML 数据的 API 。
在开始使用 DOM 之前,首先来了解一下它的结构。 DOM 整体上的结构是一个 Composite 模式。所有的 XML 单元,无论是文档、元素还是属性、文本,在 DOM 中都是一个 Node (节点)。按照 Composite 模式的定义,每个 Node 都可以包容其他的 Node ,于是很轻松地就构成了一个树型结构。举一个简单的例子,下面的 XML 文档
< Book >
< Title > Effective C++ < Author >
** ** < Name > Scott Meyers ** ** < Gender > Male ** ** < Nationality > USA
** ** < Publisher > Addison-Wesley
在 DOM 中的存储形式就会是这样:
既然已经了解了 DOM 文档的结构,下面就该学习如何操作 DOM 文档了。对于这样一个树型结构,比较重要的操作有文档生成、文档遍历、节点内容的处理(读取、修改等等)、节点本身的操作(插入、删除、替换等等)以及文档的序列化。下面,我们将逐个学习这些操作。
DOM 文档的生成
用 DOM 处理 XML 数据,首先需要以下三个步骤:
1. 创建 DocumentBuilderFactory 。该对象将创建 DocumentBuilder 。
2. 创建 DocumentBuilder 。 DocumentBuilder 将对输入实际进行解析以创建 Document 对象。
3. 解析输入的 XML ,创建 Document 对象。
DocumentBuilderFactory 是一个 Singleton ,所以不能直接 new 出来,应该调用 DocumentBuilderFactory.newInstance() 来得到 DocumentBuilderFactory 的实例。此外, DocumentBuilderFactory 也是一个对象工厂(从名字就能看出),可以用它来创建 DocumentBuilder 。
通常会使用 DocumentBuilder 的 parse 方法返回一个 Document 对象(需要插一句: Document 只是一个接口,用 javax.xml.parsers.DocumentBuilder 的 parse 方法得到的实际上是 org.apache.crimson.tree.XmlDocument 对象)。 parse 方法接受很多输入参数,包括 File 、 InputStream 、 InputSource 、 String 型的 URI 等等。 parse 方法会把输入源进行解析,在内存中生成一个 DOM 的树型结构—— Document 对象。
上面这三个步骤的常用代码如下:
File docFile = new File( "orders.xml" );
Document doc = null ;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(docFile);
} catch (Exception e) { System.out.print( "Problem parsing the file." ); }
parse 方法可能会抛出 IOException 或者 SAXException ,分别表示输入异常和解析异常。
在创建 DocumentBuilder 之前,可以为 DocumentBuilder 设置一些参数,调节它在生成 Document 时的行为方式。可控的参数包括:
l setCoalescing :确定解析器是否将 CDATA 节点转成文本,并将 CDATA 节点与其周围的文本节点合并(如果合适)。缺省值是 false 。
l setExpandEntityReferences :确定是否扩展外部实体引用。如果为 true ,则将外部数据插入文档。缺省值是 true 。
l setIgnoringcomments :确定是否忽略文件中的注释。缺省值是 false 。
l setIgnoringElementContentWhitespace :确定是否忽略元素内容中的空白(类似于浏览器处理 HTML 的方式)。缺省值是 false 。
l setNameSpaceAware :确定解析器是否注意名称空间信息。缺省值是 false 。
l setValidating :缺省情况下,解析器将不验证文档。将该参数设置为 true 以打开验证。
设置参数的语句如下:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(true);
DocumentBuilder db = dbf.newDocumentBuilder();
DOM 文档的遍历
DOM 采用了 Composite 模式。 Node 类是所有 XML 单元的基类, Element 、 Attr 、 Document 等等都是 Node 的派生类。每个 Node 都可以包容其他的 Node ,也可以包容文本格式的内容。所以, DOM 文档的遍历相当简单。
首先要获取文档的根节点。用 Document.getDocumentElement() 方法可以得到一个 Element 类型的对象,它就是文档的根节点。对于一个 HTML 文档, getDocumentElement() 方法得到的就是
1<html> 节点。
2
3只要得到根节点,就可以用 Node.getChildNodes() 方法得到该节点的所有直接子节点,从而遍历整个树型结构。另外,可以用 Node.hasChildNodes() 方法判断一个节点是否叶节点,从而得到遍历算法的结束条件。 getChildNodes() 方法的返回值是 NodeList 对象, NodeList 有两个方法: int getLength() 和 Node item(int) ,可以使用这两个方法来安全地访问其中的每个元素。
4
5上面这种方法是深度优先的遍历(采用迭代算法),还有一种方法是广度优先的遍历算法,要使用的方法是 getFirstChild() (获取第一个孩子节点)和 getNextSibling() (获取下一个兄弟节点)。
6
7### 处理元素的内容
8
9首先必须搞清楚“节点( node )”和“元素( element )”的概念:在 DOM 中节点和元素不是等价的。“元素”是指一对标记( tag )及其内部包含的字符串值的总和,例如下面这就是一个元素:
10
11< Country >
12
13**China **
14
15
16
17但是它却不是一个节点,而是两个。第一个节点是 <country> 节点,它的值是 null ;第二个节点是一个文本节点(节点名是 #text ),它的值是 "China\n" 。文本节点是 <country> 节点的子节点。
18
19所以,要处理一个元素的内容时,需要两个步骤:
20
211\. 找到代表该元素的节点;
22
232\. 处理该节点的第一个子节点;
24
25只要知道某个元素的名称,就可以用 Element.getElementsByTagName(String name) 方法来找到所有代表该元素的节点。 getElementsByTagName 方法会自动遍历整个树型结构,将找到的节点全部保存在一个 NodeList 中返回。由于 DOM 的树型结构是建立在内存中的,所以这个操作不会太慢。找到节点之后,用 Node.getFirstChild() 方法就可以得到代表该元素值的文本节点,用 Node.setNodeValue(String) 方法就可以修改节点的值。
26
27### 处理其他类型节点的内容
28
29如果要访问的节点是属性节点( Node.getNodeType()==ATTRIBUTE_NODE ),则可以通过 getAttributes() 方法获得节点中所有的属性。 getAttributes 方法会返回一个 NamedNodeMap 型的对象,这是一个名 \- 值映射表,可以通过 String 型的名称来随机访问,也可以通过 int 型的顺序号来顺序访问。 Attr 类(属性节点)有 getValue() 和 setValue() 两个 accessor ,用于访问属性的值。
30
31节点共有 12 种不同的类型,这里只介绍了元素节点和属性节点这两种最常用的,其他的就要自己查帮助了。 Node 有一个 getNodeType() 方法,会返回 short 型值,从而判断一个对象的真实类型,起到 RTTI 的作用。下面是 getNodeType() 方法所有可能的返回值:
32
33public static final short ELEMENT_NODE = 1;
34
35public static final short ATTRIBUTE_NODE = 2;
36
37public static final short TEXT_NODE = 3;
38
39public static final short CDATA_SECTION_NODE = 4;
40
41public static final short ENTITY_REFERENCE_NODE = 5;
42
43public static final short ENTITY_NODE = 6;
44
45public static final short PROCESSING_INSTRUCTION_NODE = 7;
46
47public static final short COMMENT_NODE = 8;
48
49public static final short DOCUMENT_NODE = 9;
50
51public static final short DOCUMENT_TYPE_NODE = 10;
52
53public static final short DOCUMENT_FRAGMENT_NODE = 11;
54
55public static final short NOTATION_NODE = 12;
56
57### 节点的处理
58
59对于树型数据结构,常见的节点处理就是节点的插入、删除和替换。 DOM 为这些操作提供了非常简单易用的 API 。
60
61插入节点可以用 Node.appendChild(Node) ,也可以用 Node.insertBefore(Node newChild, Node refChild) ;删除节点可以用 Node.removeChild(Node oldChild) ;替换节点可以用 Node.replaceChild(Node newChild, Node oldChild) 。 DOM 会自动调整树型结构,删除、替换的操作还会返回 oldChild 这个节点,非常方便。
62
63文档( Document )也是一个节点( Node ),所以也可以把节点直接插入到文档中。不过要注意:只有该文档创建出的节点才能插入到该文档中,否则会引发 WRONG_DOCUMENT_ERR 异常。创建节点使用 Document.createXxxx 方法。可以用 cloneNode(boolean deep) 方法来克隆一个节点,用 boolean 型的参数决定是否深度拷贝,但是克隆出的节点也不能插入别的文档中。另外,可以用 Document.importNode(Node importedNode, boolean deep) 方法来引入别的文档中的节点。
64
65需要处理元素的属性时,可以用 Element.setAttributeNode(Attr newAttr) 来插入属性,用 Element.removeAttribute(String name) 来删除不需要的属性。如果属性有同名的,可以用 Element. removeAttributeNode(Attr oldAttr) 来指定删除某一个属性节点。
66
67### 文档的序列化
68
69每个 Element 都覆盖了 toString 方法,所以只要指定某一个 Element 作为根,再调用它的 toString 方法,它就会递归得到其下的整个树型结构,并转换成 String 型对象。只要把这个 String 型对象输出到指定的设备上,就可以得到 XML 文档,非常方便。下面这段代码会生成一个新的 HTML 文档( HTML 可以说是 XML 的子集),并在标准输出设备上输出。
70
71Document newdoc = null ;
72
73try {
74
75<SPAN style="mso-tab-count</country></country></html>