文档对象模型是使用最广泛的 API 之一。它为 XML 文档提供了一种结构化的表示方法,使用户可以访问和修改 XML 文档的内容。DOM Level 3 Core 规范现在处于 Last Call(最后请求)状态,它是 W3C 制定的一系列 DOM 规范中最新的一个。DOM Level 3 Core 规范作了多处增强,使一些常用的操作执行起来更加容易,并且使得一些以前无法完成的任务成为可能。它还支持一些最新的标准,如 XML 中的命名空间、XML 信息集(Information Set)和 XML Schema,从而提供了对内存中 XML 数据的更完整的视图。
本文的第一部分讨论对节点的操作, 第 2 部分侧重于对文档和类型信息的操作,并解释如何在 Xerces 中使用 DOM。
在 DOM Level 2 中,重命名一个节点的操作代价较高:必须在树中创建一个新节点,并删除老的节点。
DOM Level 3 的
Document
接口现在有一个新的方法,它可以完成下面的所有任务:
renameNode
使您可以在一次调用中重命名一个属性或者树中的一个元素。一定要注意,虽然这种操作只是试图改变现有节点的名称,但是在某些情况下,其实现可能做不到真正地重命名节点。相反,该操作可能不得不用新的名称创建一个新节点,并用这个新节点替换旧节点。原因是
DOM 被设计成对多种不同的实现类型进行工作,对于其中某些类型,改变一个元素或者属性的名称不像改变一个对象中的字段那么简单。例如,在 Web
浏览器中,将一个元素的名称从“P”改为“INPUT”会被解释为将一段文字转换为表单文本域,这是无法真正做到的,也是不希望出现的。所以,浏览器会创建一个新的节点,并用这个新节点替换旧的节点。不过,所有这些对于您来说都是透明的,最终得到的是一个具有您所希望的名称的节点。
通常,您会希望合并内存中的两个文档或者将一个文档中的部分内容加入到另一个文档中。在 DOM Level 2 中,可以用
Document
接口的
importNode
方法来做类似这样的工作。不过,这个方法不会改变原来的树。相反,它先创建源节点及其下属节点的一个克隆版本,再将这个克隆版本插入到目标文档中。如果所要做的就只有这些,那么没有问题,但是如果需要将节点从一个文档移到另一个文档,就会有些麻烦。这不仅要求清除留在后面的源节点,而且当要移动的子树很大时需要付出很高的代价。
有了 DOM Level 3,就可以利用
adoptNode 更有效地完成这项任务。在
Document
接口中也可以找到这个方法,
adoptNode
方法可以高效地将子树从一个文档移到另一个文档中。它实际上改变了子树中节点的
ownerDocument 。清单1展示了如何在文档之间移动元素以及重命名节点。
清单1. 移动元素和重命名节点
// Renaming nodes
Element element = document.createElementNS("http://example.com", "street");
// if implementation can rename the node, element returned
// is the same object as was originally created
element = document.renameNode(element, "http://example.com", "address");
// adopting previously created node to a different document
Node adoptedNode = document2.adoptNode(element);
|
同样,因为 DOM 被设计成在多种不同类型的实现上工作,而源文档和目标文档可能属于两种不同的实现类型,所以有可能无法将节点从一个文档移到另一个文档。在这种情况下,
adoptNode
会抛出一个可以被捕获的
NOT_SUPPORTED_ERR DOMException。 不过,这只有在应用程序真正同时处理多种
DOM 实现时才有必要。
DOM Level 3
引入了一组方法,通过这些方法可以用多种方式对节点进行比较。其中包括用于测试两个节点是否相等、是否相同以及它们在文档资料树中的相对位置的方法。您可能熟悉相同性(identity)和相等性(equality)的概念。在
Java 语言中,相同性是用操作符
== 来测试的,而相等性则是用像
equals
这样的方法进行测试的。两个对象要相同,它们必须在内存中是
同一个对象。另一方面,两个对象要相等,它们只需具有相同的特性即可。因此,两个相同的对象必定是相等的,但是两个相等的对象不一定是相同的。
DOM Level 3 定义在什么情况下两个节点相等,并为
Node
提供了方法
isEqualNode ,用以进行这种测试。例如,如果创建了两个没有任何属性的、名为“foo”的空元素节点,那么它们是相等的,但是它们不是相同的。
可以用类似
==
的操作来测试相同性,然而,一些具有复杂内部结构的
DOM 实现并不是将它们的对象直接公开为节点,而是创建返回给应用程序的代理。它们可能对同一个节点创建多个代理。这意味着在每次调用这个方法时,由像
getFirstChild
这样的 DOM
操作返回的对象可能有所不同
-- 即使其他方面没有任何改变。在这种情况下,如果比较返回对象的相同性,那么您会发现它们是不相同的。不过,它们其实引用的是实现中的同一个节点。可以用
isSameNode
证明这一点。它会告诉您所查看的是同一对象的不同代理还是实际上不同的对象。
除了我们上面说过的关于相同对象的相等性外,如果
isSameNode
返回“true”,则
isEqualNode
总是返回“true”。
但是两个相等的节点不一定是相同的。
最后一个可以帮助您比较节点内容的是
compareDocumentPosition
方法。这个方法让您可以找出两个节点在文档树中的相对位置。不需要在旧书里面查找最佳的算法以确定一个节点是否在树中另一个节点的前面。这个方法告诉您所有想知道的内容:一个节点是另一个节点的后代还是祖先、在前面还是后面等等。
此外,一个看上去很方便的函数实际上可能不仅仅只是具有方便的好处。确实,通过实现完成像
compareDocumentPosition
这样的操作比自己做要更有效,这是因为它知道什么最适合于其内部结构。例如,一个遍历树的操作会迫使您选择是从第一个子节点开始,然后是其下一个兄弟节点,还是通过得到子节点列表并枚举该列表来遍历树。取决于其内部结构的实际情况,一种方法可能比另一种方法更快一些。但是您无法确定这一点,就算是使用的方法对一种实现来说是最好的,也不能肯定它对另一种实现同样是最好的。另一方面,如果使用像
compareDocumentPosition
这样的方法并根据实现情况来遍历树,那么就可以保证总是使用最好的方法完成这项工作。DOM
Level 3 Core 中有几个这样的函数,其中一个是下一节将描述的
textContent 。
直到现在,要替换一个元素节点的文本内容,必须删除它的子节点,用新的内容创建一个
Text
节点,并将它作为
Element
节点的子节点插入。获取内容同样需要几个步骤,如清单2所示。
清单2. 用 DOM Level 2 获取元素的文本内容
// Assuming element has two children comment and
// a text node
NodeList list = elem.getChildNodes();
int len = list.getLength();
for (int i=0;i<len;i++){
elem.removeChild(list.item(i));
}
elem.appendChild(document.createTextNode("content"));
|
有了 DOM Level 3,检索和设置一个
Element
节点的文本内容就容易得多了。对
textContent 属性的新的读/写操作可以方便地操作文本内容:
Setting
这个属性会删除所有子节点,并且在没有将它的值设置为空值时,用一个文本节点替换这些子节点,
getting
这个属性返回这个节点及其后代节点的文本内容的串接。
清单3. 用 DOM Level 3 获取元素的文本内容并修改它
String oldContent = elem.getTextContent();
elem.setTextContent("content");
|
这也使得创建只包含一段文本的元素变得很直观 —— 需要做的就是创建这个元素并设置其
textContent 。这基本上就完成了这个
Text
节点,并让您更直接地处理文档中的文本。
增加的另一个有用的功能是
Text
接口的新属性
wholeText 。它返回在逻辑相邻的文本节点中所包含的所有文本。实际上,这意味着在查看一个元素的子节点,而该字节点又是一个
Text 节点时,可以在一次调用中得到文档中这个位置上的所有文本。不再需要担心文本包含在几个相邻的
Text
节点中,因而需要串接这些节点的情况。
wholeText
属性直接就提供答案了。
在许多情况下,DOM 并不真正包含应用程序中具有的所有数据,它只是其中的一部分。事实上,一个 DOM 节点通常与应用程序中的一些其他对象相关。管理这两个结构之间的关系是一种挑战。在过去,为了完成这项工作,必须在结构中储存对 DOM 节点的引用,或者在做不到这一点的话,则必须使用另一种结构,比如一个哈希表,来储存关于如何从一种结构转换到另一种结构的信息。结果,当 DOM 变化时,维护这些信息会成为非常令人头痛的任务。特别是节点有可能在您不知道的情况下被修改或者删除,使您没有机会相应地更新自己的结构。
DOM Level 3 可以为您完成大量这方面的工作。首先,它让您可以将对应用程序的引用储存在一个
Node 上。这个对象与一个键相关联,您可以在以后用这个键来检索这个对象。在
Node
上可以有任意多个对象,只要使用不同的键就行了。其次,可以注册一个处理程序,当发生了任何可能影响您自己的结构的事件时
—— 如克隆了一个节点、导入到另一个文档、删除或者重命名 ——就调用这个处理程序。这样,管理与 DOM 相关联的数据就容易得多了。您不再需要为同时维护两个结构而担心了。而只需实现相应的处理程序,并在修改
DOM 树的时候调用它。可以根据需要灵活选择使用全局处理程序或者对每一个节点使用不同的处理程序完成这项工作。不管是哪种情况,当附加了一些数据的节点上有事件发生时,已注册的处理程序将被调用,并提供所有必需的信息来相应地更新结构。
我们向您展示了 DOM Level 3 Core
是如何使得对节点的处理更加容易的,不管这种处理是重命名节点,将节点从一个文档移到另一个文档,还是比较节点。我们还展示了
DOM
Level 3 Core 如何让您以更自然的方式访问和修改文档中的文本内容,而不必处理总爱制造麻烦的
Text
节点。最后,我们解释了如何用 DOM
Level 3 Core 更轻松地维护您自己的、与 DOM 相关联的结构。
在第二部分中,我们将向您展示 DOM Level 3 Core 的其他有意思的功能,例如,如何进行
bootstrap,如何操作
DOMImplementation
对象而不必在应用程序中添加任何依赖于实现的代码,DOM
如何映射到 XMLInfoset,如何重新验证内存中的文档,以及如何在 Xerces 中使用 DOM Level 3 Core。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 阅读
DOM
Level 2 CoreW3C 推荐标准。
- 熟悉最新的
DOM
Level 3 CoreLast Call 草案。
- 了解关于
Xerces2
DOM 实现的内容。
- 下载最新的
Xerces-J
解析器。
- 本系列文章的
第
2 部分(
developerWorks,2003年8月)介绍了
DOM Level 3 Core 的其他一些特性,例如“bootstrap”、重新验证内存中的 DOM 以及 Apache Xerces2
项目中这个 API 的早期实现。
- 在
developerWorks
XML专区
中可以找到更多 XML 资源,包括入门教程
理解DOM(
developerWorks,2003年7月)。
- 研究
IBM
WebSphere Studio Site Developer,这是一个健壮的、易于使用的开发环境,可用于创建、编译和维护动态
Web 站点、应用程序和 Web 服务。
- 看看如何成为
IBM
认证的 XML 和相关技术的开发人员。
Arnaud Le Hors 是 IMB 的高级软件工程师,也是 XML Standards Strategy Group 的成员。他在各种 W3C 工作组中担任 IBM 的代表,例如 XML Core 和 DOM。他是 DOM Level 1、2 和 3 以及核心规范(Core Specification)的编辑之一。 Arnaud 还参与了 Xerces 的开发,同时也是 Xerces2 的设计者之一。可以通过 lehors@us.ibm.com 与他联系。