来源:互联网,原作者:蒋平
DOM的全称是Document Object Model,也即文档对象模型。在应用程序中,基于DOM的XML分析器将一个XML文档转换成一个对象模型的集合(通常称DOM树),应用程序正是通过对这个对象模型的操作,来实现对XML文档数据的操作。通过DOM接口,应用程序可以在任何时候访问XML文档中的任何一部分数据,因此,这种利用DOM接口的机制也被称作随机访问机制。
DOM接口提供了一种通过分层对象模型来访问XML文档信息的方式,这些分层对象模型依据XML的文档结构形成了一棵节点树。无论XML文档中所描述的是什么类型的信息,即便是制表数据、项目列表或一个文档,利用DOM所生成的模型都是节点树的形式。也就是说,DOM强制使用树模型来访问XML文档中的信息。由于XML本质上就是一种分层结构,所以这种描述方法是相当有效的。
对于XML应用开发来说,DOM就是一个对象化的XML数据接口,一个与语言无关、与平台无关的标准接口规范。它定义了HTML文档和XML文档的逻辑结构,给出了一种访问和处理HTML文档和XML文档的方法。利用DOM,程序开发人员可以动态地创建文档,遍历文档结构,添加、修改、删除文档内容,改变文档的显示方式等等。可以这样说,文档代表的是数据,而DOM则代表了如何去处理这些数据。无论是在浏览器里还是在浏览器外,无论是在服务器上还是在客户端,只要有用到XML的地方,就会碰到对DOM的应用。
作为W3C的标准接口规范,目前,DOM由三部分组成,包括:核心(core)、HTML和XML。核心部分是结构化文档比较底层对象的集合,这一部分所定义的对象已经完全可以表达出任何HTML和XML文档中的数据了。HTML接口和XML接口两部分则是专为操作具体的HTML文档和XML文档所提供的高级接口,使对这两类文件的操作更加方便。
目前,DOM有两个版本,一个是由W3C于1998年8月18日通过的DOM Level 1,另一个则是正在制定过程中的DOM Level 2,W3C已于2000年3月7日通过了DOM Level 2的候选推荐版本。
一、DOM树
前面我们讲过,DOM为我们提供的访问XML文档信息的媒介是一种分层对象模型,而这个层次的结构,则是一棵根据XML文档生成的节点树。一个XML分析器,在对XML文档进行分析之后,不管这个文档有多简单或者多复杂,其中的信息都会被转化成一棵对象节点树。在这棵节点树中,有一个根节点--Document节点,所有其他的节点都是根节点的后代节点。节点树生成之后,就可以通过DOM接口访问、修改、添加、删除、创建树中的节点和内容。
以客户联系信息的例子来说明,下面给出一个XML文档片段:
<?xml version="1.0" encoding="gb2312" ?>
<addressbook>
<person sex = "male">
<name>张三</name>
<email>zhs@xml.net.cn</email>
</person>
<person sex = "male">
<name>李四</name>
<email>ls@xml.net.cn</email>
</person>
</addressbook>
用DOM来表示这段文档,如图所示:
在这棵文档对象树中,文档中所有的内容都是用节点来表示的。一个节点又可以包含其他节点,节点本身还可能包含一些信息,例如节点的名字、节点值、节点类型等。文档中的根实际上也是一个元素,之所以要把它单独列出来,是因为在XML文档中,所有其他元素都是根元素的后代元素,而且根元素是唯一的,具有其他元素所不具有的某些特征。
这个例子比较简单,事实上,DOM中还包含注释、处理指令、文档类型、实体、实体引用、命名空间、事件、样式单等多种对象模型。
文档对象模型利用对象来把文档模型化,这些模型不仅描述了文档的结构,还定义了模型中对象的行为。换句话说,在上面给出的例子里,图中的节点不是数据结构,而是对象,对象中包含方法和属性。在DOM中,对象模型要实现:
· 用来表示、操作文档的接口
· 接口的行为和属性
· 接口之间的关系以及互操作
二、DOM的四个基本接口
在DOM接口规范中,有四个基本的接口:Document,Node,NodeList以及NamedNodeMap。在这四个基本接口中,Document接口是对文档进行操作的入口,它是从Node接口继承过来的。Node接口是其他大多数接口的父类,像Document,Element,Attribute,Text,Comment等接口都是从Node接口继承过来的。NodeList接口是一个节点的集合,它包含了某个节点中的所有子节点。NamedNodeMap接口也是一个节点的集合,通过该接口,可以建立节点名和节点之间的一一映射关系,从而利用节点名可以直接访问特定的节点。下面将对这四个接口分别做一些简单的介绍。
1. Document接口
Document接口代表了整个XML/HTML文档,因此,它是整棵文档树的根,提供了对文档中的数据进行访问和操作的入口。由于元素、文本节点、注释、处理指令等都不能脱离文档的上下文关系而独立存在,所以在Document接口提供了创建其他节点对象的方法,通过该方法创建的节点对象都有一个ownerDocument属性,用来表明当前节点是由谁所创建的以及节点同Document之间的联系。在DOM树中,Document接口同其他接口之间的关系如下图所示:
由图可以看出,Document节点是DOM树中的根节点,也即对XML文档进行操作的入口节点。通过Document节点,可以访问到文档中的其他节点,如处理指令、注释、文档类型以及XML文档的根元素节点等等。另外,从上图我们还可以看出,在一棵DOM树中,Document节点可以包含多个处理指令、多个注释作为其子节点,而文档类型节点和XML文档根元素节点都是唯一的。关于Document接口的IDL(Interface Definition Language接口定义语言)定义和其中一些比较常用的属性和方法的详细介绍在许多参考书都可以找到,我们将在后面结合实际例子给予介绍。
2.Node接口
Node接口在整个DOM树中具有举足轻重的地位,DOM接口中有很大一部分接口是从Node接口继承过来的,例如,Element、Attr、CDATASection等接口,都是从Node继承过来的。在DOM树中,Node接口代表了树中的一个节点。一个典型的Node接口如下图所示:
如图所示,Node接口提供了访问DOM树中元素内容与信息的途径,并给出了对DOM树中的元素进行遍历的支持。同样,我们将在后面结合实际例子详细说明Node接口的具体使用方法。
3.NodeList接口
NodeList接口提供了对节点集合的抽象定义,它并不包含如何实现这个节点集的定义。NodeList用于表示有顺序关系的一组节点,比如某个节点的子节点序列。另外,它还出现在一些方法的返回值中,例如GetNodeByName。在DOM中,NodeList的对象是"live"的,换句话说,对文档的改变,会直接反映到相关的NodeList对象中。例如,如果通过DOM获得一个NodeList对象,该对象中包含了某个Element节点的所有子节点的集合,那么,当再通过DOM对Element节点进行操作(添加、删除、改动节点中的子节点)时,这些改变将会自动地反映到NodeList对象中,而不需DOM应用程序再做其他额外的操作。NodeList中的每个item都可以通过一个索引来访问,该索引值从0开始。
4.NamedNodeMap接口
实现了NamedNodeMap接口的对象中包含了可以通过名字来访问的一组节点的集合。不过注意,NamedNodeMap并不是从NodeList继承过来的,它所包含的节点集中的节点是无序的。尽管这些节点也可以通过索引来进行访问,但这只是提供了枚举NamedNodeMap中所包含节点的一种简单方法,并不表明在DOM规范中为NamedNodeMap中的节点规定了一种排列顺序。NamedNodeMap表示的是一组节点和其唯一名字的一一对应关系,这个接口主要用在属性节点的表示上。与NodeList相同,在DOM中,NamedNodeMap对象也是"live"的。
三、DOM的简单应用
1、创建Document对象
通过上面的介绍,我们已经对DOM接口有了部分了解,那么,这些接口以及接口中的属性和方法又应该如何使用呢?这正是接下来所要讲述的内容。
如前所述,利用DOM,程序开发人员可以动态地创建文档,遍历文档结构,添加、修改、删除文档内容等等。在本节中,我们将通过微软的XML分析器msxml,对DOM接口的这些应用做一详细的介绍。
首先,我们来讲一下一切操作的基础--创建Document对象。通过创建Document对象,应用程序或者脚本就得到了对XML文档进行操作的入口。下面给出了使用不同的编程语言创建Document对象的范例。
JavaScript:
var xmlDoc = new ActiveXObject("Microsoft.XMLDOM")
VBScript:
set xmlDoc = CreateObject("Microsoft.XMLDOM")
VB:
Dim xmlDoc As ObjectSet
xmlDoc = CreateObject("Microsoft.XMLDOM")
ASP:
set xmlDoc = Server.CreateObject("Microsoft.XMLDOM")
VC:
HRESULT hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDocument(LPVOID*), &m_pXMLDocument)
2、加载XML文档
在Document对象创建之后,我们就得到了对文档进行操作的入口,那么,创建的这个文档对象是如何同实际的XML文档关联在一起的呢?说来可笑,在W3C的DOM接口规范中,没有任何一个地方定义了DOM中的接口对象同实际文档相关联的方法。因此,不同的XML分析器所提供的加载XML文档的方法也不尽相同。在微软的msxml中,提供了一个load方法来加载XML文档,建立DOM树同XML文档之间的关联。
依旧以联系人列表信息的XML文档client.xml为例,可通过下述方式来加载文档:
Dim myDocument
Set myDocument = CreatObject("Microsoft.XMLDOM")
myDocument.async = False
myDocument.load("client.xml")
XML文档被加载后,就在内存中形成了一棵DOM树,如图所示:
3、遍历XML文档
现在,我们已经可以通过DOM来创建XML文档对象,并加载XML文档了。对于已经加载的文档,我们要从文档中获取所需要的内容,这就要求能够通过DOM树来访问树中的任何一个节点,也就是对DOM树的遍历。下面我们依旧以client.xml为例,通过几个实例来说明如何遍历DOM树中的节点。
首先,我们要获取XML文档的根元素节点,用VBScript语言描述这个操作如下:
Root = myDocument.documentElement
该语句的实际含义如下图黄色箭头所示。
在得到了文档的根元素节点之后,我们又将如何访问其他元素呢?以文档中的第二个person元素为例,对该元素节点以及其子节点的访问可以通过下面的方式来实现:
personNode = root.childNodes.item(1)
nameNode = personNode.childNodes.item(0)
textNode = nameNode.childNodes.item(0)
theName = textNode.nodeValue
上述访问语句执行后,theName的值是"李四"。下图黄色箭头给出了这一访问过程的示意:
在上面的代码中,root是文档的根元素节点addressbook节点,personNode和nameNode都是元素类型的节点,textNode是TEXT类型的节点,theName是一个字符串。childNodes是NodeList类型的属性,item是NodeList接口中Node类型的属性,通过item可以访问NodeList节点集合中的任意节点(这儿有一点需要注意,当我们要访问根元素节点addressbook的第二个person子节点personNode时,我们用的索引参数是"1",这是因为item中的索引参数是从0开始的,如果我们要访问节点集合中的第一个节点,则应该用item(0)来表示)。
在DOM规范中,要访问元素节点的文本内容,需要先得到元素节点的TEXT子节点,再通过TEXT节点的属性获取文本内容。微软在实现DOM接口时对DOM进行了部分扩展,可以通过元素类型节点的text属性直接获得元素中的文本内容。具体实用说明可以参考微软msdn中的帮助。
上面的例子给出了如何访问DOM树中的元素节点,对于DOM树中的属性节点,访问方法略有不同,可以通过下面的语句来实现:
Attr = node.attributes.getNamedItem("sex")
attrContent = attr.nodeValue
上述访问语句执行后,attrContent的值是"male"。下图用黄色箭头标出了这一访问的过程。
在上面的代码中,attr是属性类型的节点,attributes是NamedNodeMap类型的属性,getNamedItem是NamedNodeMap接口中的方法。属性的内容可以通过属性节点的nodeValue来获得。
4、添加元素
目前,我们已经能够通过DOM获取XML文档中的信息了。如前所述,通过DOM还可以动态地更改XML文档中的内容。下面我们仍旧使用上面的client.xml文档,通过实例来说明如何更改XML文档中的内容。
假如我们希望在addressbook.xml中,给第一个person元素增添一个字符串为"北大方正"的company元素,实现这一添加元素操作的语句如下:
node = root.childNodes.item(0)
newNode = myDocument.createElement("company")
node.insertBefore(newNode,node.lastNode)
textNode = myDocument.creatTextNode("北大方正")
node.childNodes.item(1).appendChild(textNode)
下面给出了添加元素的操作示意图:
5、删除元素
现在,我们再把前一小节添加的元素删除,可通过下面的代码实现这一目的:
node = root.childNodes.item(0)
oldNode = node.removeChild(node.childNodes.item(1))
其中,oldNode中存放的是已被删除的节点。在删除某个节点时,以该节点为根的子树将整个被删除,因此得到的结果DOM树恢复原状。
6、修改元素
元素内容的改变包括元素名称、元素属性、元素所包含的文本内容等项目的改变。下面仅就元素所包含的文本内容的改变给出一个范例,其他内容的改变实现方法与此类似。
假如想把张三的电子邮件地址更改为zhs@pku.edu.cn,通过下列语句就可以实现:
node = root.childNodes.item(0)
emailNode = node.childNodes.item(1)
emailNode.childNodes.item(0).nodeValue = zhs@pku.edu.cn
下面给出了修改后的效果:
至此,我们已经把对DOM树的常用操作做了简单的介绍。一般说来,支持DOM的XML分析器通常会对DOM做一些扩展,这些扩展不属于DOM规范中的标准,但却给DOM树的操作带来了方便,不同的分析器所做的扩展也不尽相同,可以通过查询相关技术支持资料或者帮助来获取更多的信息。
附录A:DOM对象属性