欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DOM

程序员文章站 2022-05-30 12:14:20
...


本文介绍了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++</Title> 

<Author>

  <Name>Scott Meyers</Name> 

  <Gender>Male</Gender> 

  <Nationality>USA</Nationality> 

  </Author>

  <Publisher>Addison-Wesley</Publisher> 

  </Book>


在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()方法得到的就是<html>节点。

只要得到根节点,就可以用Node.getChildNodes()方法得到该节点的所有直接子节点,从而遍历整个树型结构。另外,可以用Node.hasChildNodes()方法判断一个节点是否叶节点,从而得到遍历算法的结束条件。getChildNodes()方法的返回值是NodeList对象,NodeList有两个方法:int getLength()和Node item(int),可以使用这两个方法来安全地访问其中的每个元素。

上面这种方法是深度优先的遍历(采用迭代算法),还有一种方法是广度优先的遍历算法,要使用的方法是getFirstChild()(获取第一个孩子节点)和getNextSibling()(获取下一个兄弟节点)。

处理元素的内容
首先必须搞清楚“节点(node)”和“元素(element)”的概念:在DOM中节点和元素不是等价的。“元素”是指一对标记(tag)及其内部包含的字符串值的总和,例如下面这就是一个元素:

<Country>
China
</Country>

但是它却不是一个节点,而是两个。第一个节点是<Country>节点,它的值是null;第二个节点是一个文本节点(节点名是#text),它的值是"China\n"。文本节点是<Country>节点的子节点。

所以,要处理一个元素的内容时,需要两个步骤:

1.       找到代表该元素的节点;

2.       处理该节点的第一个子节点;

只要知道某个元素的名称,就可以用Element.getElementsByTagName(String name)方法来找到所有代表该元素的节点。getElementsByTagName方法会自动遍历整个树型结构,将找到的节点全部保存在一个NodeList中返回。由于DOM的树型结构是建立在内存中的,所以这个操作不会太慢。找到节点之后,用Node.getFirstChild()方法就可以得到代表该元素值的文本节点,用Node.setNodeValue(String)方法就可以修改节点的值。

处理其他类型节点的内容
如果要访问的节点是属性节点(Node.getNodeType()==ATTRIBUTE_NODE),则可以通过getAttributes()方法获得节点中所有的属性。getAttributes方法会返回一个NamedNodeMap型的对象,这是一个名-值映射表,可以通过String型的名称来随机访问,也可以通过int型的顺序号来顺序访问。Attr类(属性节点)有getValue()和setValue()两个accessor,用于访问属性的值。

节点共有12种不同的类型,这里只介绍了元素节点和属性节点这两种最常用的,其他的就要自己查帮助了。Node有一个getNodeType()方法,会返回short型值,从而判断一个对象的真实类型,起到RTTI的作用。下面是getNodeType()方法所有可能的返回值:
    public static final short ELEMENT_NODE                                 = 1;

    public static final short ATTRIBUTE_NODE              = 2;

    public static final short TEXT_NODE                              = 3;

    public static final short CDATA_SECTION_NODE                = 4;

    public static final short ENTITY_REFERENCE_NODE           = 5;

    public static final short ENTITY_NODE                           = 6;

    public static final short PROCESSING_INSTRUCTION_NODE = 7;

    public static final short COMMENT_NODE                      = 8;

    public static final short DOCUMENT_NODE                     = 9;

    public static final short DOCUMENT_TYPE_NODE             = 10;

    public static final short DOCUMENT_FRAGMENT_NODE      = 11;

    public static final short NOTATION_NODE               = 12;


节点的处理
对于树型数据结构,常见的节点处理就是节点的插入、删除和替换。DOM为这些操作提供了非常简单易用的API。

插入节点可以用Node.appendChild(Node),也可以用Node.insertBefore(Node newChild, Node refChild);删除节点可以用Node.removeChild(Node oldChild);替换节点可以用Node.replaceChild(Node newChild, Node oldChild)。DOM会自动调整树型结构,删除、替换的操作还会返回oldChild这个节点,非常方便。

文档(Document)也是一个节点(Node),所以也可以把节点直接插入到文档中。不过要注意:只有该文档创建出的节点才能插入到该文档中,否则会引发WRONG_DOCUMENT_ERR异常。创建节点使用Document.createXxxx方法。可以用cloneNode(boolean deep)方法来克隆一个节点,用boolean型的参数决定是否深度拷贝,但是克隆出的节点也不能插入别的文档中。另外,可以用Document.importNode(Node importedNode, boolean deep)方法来引入别的文档中的节点。

需要处理元素的属性时,可以用Element.setAttributeNode(Attr newAttr)来插入属性,用Element.removeAttribute(String name)来删除不需要的属性。如果属性有同名的,可以用Element. removeAttributeNode(Attr oldAttr)来指定删除某一个属性节点。

文档的序列化
每个Element都覆盖了toString方法,所以只要指定某一个Element作为根,再调用它的toString方法,它就会递归得到其下的整个树型结构,并转换成String型对象。只要把这个String型对象输出到指定的设备上,就可以得到XML文档,非常方便。下面这段代码会生成一个新的HTML文档(HTML可以说是XML的子集),并在标准输出设备上输出。

Document newdoc = null;

try {

   DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

    DocumentBuilder db = dbf.newDocumentBuilder();

    newdoc = db.newDocument();       

} catch(Exception e){};

Element head = newdoc.createElement("Head");

Element title = newdoc.createElement("Title");

title.appendChild(newdoc.createTextNode("Document created by DOM"));

head.appendChild(title);

Element body = newdoc.createElement("Body");

body.appendChild(newdoc.createTextNode("This is a test document"));

 

Element newroot = newdoc.createElement("Html");

newroot.appendChild(body);

newroot.insertBefore(head, body);

 

newdoc.appendChild(newroot);

System.out.println(newroot);


总结
DOM在内存中生成树型结构来处理XML数据,在处理速度和方便性上有优势,但是比较耗存储空间。如果XML文档比较大的话,解析过程可能会需要比较长的时间