让DOM遍历--DOM Traversal 模块一瞥

2002-8-19 14:18:16【作者】畅享网

让DOM遍历
 
--DOM Traversal 模块一瞥


Brett McLaughlin(
brett@newInstance.com

Enhydra 策略顾问,Lutris Technologies

2001 年 8 月

“文档对象模型(DOM)”提供了有用的模块来以高级方式扩展其核心功能。本文深入研究了 DOM Traversal 模块,演示了如何查明您的语法分析器是否支持该模块以及如何使用它来遍历选中的节点集或整个 DOM 树。读完本文之后,您将彻底理解 DOM Traversal,并会在您的 Java 和 XML 编程工具箱中拥有一个强大的新工具。八个样本代码清单演示了这些技术。

如果您在过去三年中作过很多 XML 处理,那么您几乎一定遇到过“文档对象模型”(简称 DOM)。这种对象模型表示应用程序中的 XML 文档,并提供一种简单的方式来读取 XML 并写入或更改现有文档中的数据(如果您是 DOM 新手,请参阅参考资料来获得更多背景知识。)如果您正在努力成为一名 XML 高手,那您可能已经彻底地学过 DOM,并且知道如何使用它所提供的几乎每一种方法。然而,还有许多 DOM 功能没有被大多数开发人员认识到。

大多数开发人员实际都已接触过 DOM 核心。该核心指的是 DOM 规范,它概括 DOM 的含义、它应该如何操作以及提供哪些方法等等。甚至有经验的开发人员都不太知道或了解许多不太常用的 DOM 模块。这些模块允许开发人员更高效而轻松地使用树、同时处理不同的范围的节点、对 HTML 或 CSS 页面进行操作以及其它任务,所有这些都不是仅使用核心 DOM 规范可以做到的。在以后几个月中,我计划写几篇文章,详细介绍几个模块,包括 HTML 模块 — Range 模块 — 在本文中,将介绍 Traversal 模块。

通过学习如何使用 DOM Traversal,您将看到遍历整个 DOM 树、构建定制对象过滤器来轻易查找所需数据以及以前所未有的轻松方式遍历 DOM 树是多么快捷。我还将向您介绍一个实用程序,该程序允许您检查您选择的语法分析器是否支持特定的 DOM 模块,同时,我还将为您演示许多其它样本代码。那么,请启动您喜爱的源码编辑器,然后让我们开始。

获得信息

首先,确保您有所需工具来遍历一些示例代码。对于本文,您手头要有一个 XML 语法分析器。该语法分析器需要提供 DOM 实现。事实上,那很简单;几乎每一种您可以得到的 XML 语法分析器都支持 SAX(Simple API for XML)和 DOM。您要确保您所用的语法分析器具有 DOM 级别 2 支持,这很简单,只需阅读该语法分析器的发行说明或简单地从供应商处获得最新版本即可。

获得语法分析器之后,您需要确保它支持我们正在讨论的 DOM Traversal 模块。虽然这应该也可以在语法分析器文档中找到关于这方面的说明,但我想为您演示一个简单的编程方法来检查这一点。事实上,“清单 1”中演示的程序可以让您询问任何语法分析器:看它是否有任何模块。我在其中包括了大多数常见 DOM 模块的特定检查,当然包括 DOM Traversal。这个程序使用 DOM 类 org.w3c.dom.DOMImplementation 及其 hasFeature() 方法:通过传入每个模块的名称来检查是否支持这些模块,找出实际支持哪些模块很容易。代码相当简单,我把阅读程序流程的任务留给您。

清单 1. DOMModuleChecker 类

import org.w3c.dom.DOMImplementation;

public class DOMModuleChecker {

/** Vendor DOMImplementation impl class */

private String vendorImplementationClass =

 "org.apache.xerces.dom.DOMImplementationImpl";

 /** Modules to check */

private String[] moduleNames =

 {"XML", "Views", "Events", "CSS", "Traversal", "Range", "HTML"};

 public DOMModuleChecker() {

  }

public DOMModuleChecker(String vendorImplementationClass) {

 this.vendorImplementationClass = vendorImplementationClass;

   }

 public void check() throws Exception {

DOMImplementation impl =

  (DOMImplementation)Class.forName(vendorImplementationClass)

.newInstance();

 for (int i=0; i

请确保您的 CLASSPATH 环境变量和工作目录中有您的语法分析器(应该包括 DOM 实现)。您可以编译这个源文件,然后运行它。另外,请注意“清单 1”中的粗体行;如果您正在使用 Apache Xerces 以外的语法分析器,需要为 DOMImplementation 接口提供那个语法分析器的 DOM 实现类。如果您正在使用 Xerces,则可以保持“清单 1”中的程序不变,然后编译它即可。

我就是用最新版本 Apache Xerces 1.4.1(在我的类路径中)这样做的。得到以下输出:

清单 2. 看看 Xerces 支持哪些模块

Brett McLaughlin@GALADRIEL ~

$ java DOMModuleChecker

Support for XML is included in this DOM implementation.

Support for Views is not included in this DOM implementation.

Support for Events is included in this DOM implementation.

Support for CSS is not included in this DOM implementation.

Support for Traversal is included in this DOM implementation.

Support for Range is not included in this DOM implementation.

Support for HTML is not included in this DOM implementation.

这让我知道:对 Traversal 模块的支持(也是本文的主题)确实存在,那么我就准备继续前进了。如果您的语法分析器不同,并且不提供 DOM Traversal 支持,那么我建议您使用 Apache Xerces,至少对本文中的示例使用它(有关链接,请查看参考资料一节)。一旦得到了支持 DOM Traversal 的语法分析器,请继续阅读下一节“入门”。

入门

构成 DOM Traversal 的类都在 org.w3c.dom.traversal 包中,我将在本节中探讨它们。首先,您可能只想看一看包中的类;只有四个类。第一个类是 DocumentTraversal,它是所有处理遍历的工作开始的地方。它用来创建模块所提供的两种类型的遍历类: NodeIterator 和 TreeWalker。我稍后就讲这两个类。很容易想出如何创建这些类:使用 createNodeIterator() 创建 NodeIterator,使用 createTreeWalker() 创建 TreeWalker。很简单,哈?还有一个类是 NodeFilter,它用来定制在迭代和树遍历中返回的节点。

下一步,需要找出语法分析器中的哪个(或哪些)类实现 org.w3c.dom.DocumentTraversal 接口,以便可以创建一个树遍历器或节点迭代器。通过参考语法分析器的 Java 文档来这样做是最简单的。然而,一般来说,实现 DOM org.w3c.dom.Document 接口的类也实现 DocumentTraversal。因此,需要完成如“清单 3”中所示的工作:

清单 3. 获得一个 DocumentTraversal 实例

// Get access to your parser's org.w3c.dom.Document implementation

Document doc = new org.apache.xerces.dom.DocumentImpl();

// Get a traversal instance by type-casting

DocumentTraversal traversal = (DocumentTraversal)doc;

// Create node iterators or tree walkers

注:我在“清单 3”中留下了一个注释,在那里放置您自己的 DOM 代码。我将在后面详述这点。
首先,对象的类型被强制转换成 DocumentTraversal。然后,准备创建 NodeIterator 和 TreeWalker 实例。

节点知道

既然知道如何获得 DocumentTraversal 实例,您就可以让 DOM Traversal 开始工作。但是,在开始迭代节点之前,需要为您演示一个具体的示例。清单 4 演示了一个 XML 文档,该文档存储了有关一个在线书店中书籍(显然是一部分书籍)的信息。

可以看到每个 book 项都有一个标题、作者和简短描述。这些描述使用 keyword 标记包围了一些关键字。如果将属性 search 设置成“true”,则将在关键字搜索中使用这个字;如果设置成“false”,则只在内部索引中使用这些关键字(请原谅,这只是个示例!)。在对在线书籍目录的处理中,一个常见任务是允许用户通过这些关键字搜索书籍。例如,客户可能想要有关“middle earth”或“galaxy”或其它任何关键字的书籍。这些字出现在这些书籍的描述中,但使用标准 DOM 方式找到它们却不容易。而这正是 DOM Traversal 的用武之地。通常,必须找到根元素,再找到根元素下面的 book 元素,找到每本书籍的 description 元素,然后在其中搜索 search 元素为“true”的 keyword 元素。即使对于这项相当普通的任务,也需要许多代码来实现。然而,DOM Traversal 使这变得容易。

首先,需要通过实现 org.w3c.dom.traversal.NodeFilter 接口中的唯一方法 acceptNode() 来创建该接口的一个实现。此方法接受一个 DOM org.w3c.dom.Node 作为唯一自变量,然后,它将被传递给正在处理的 DOM 结构中的每一个节点。然后,它开始处理该节点并返回一个常数(以 Java short 形式),指出应该将该节点返回给当前 NodeIterator 还是应该忽略它。 这意味着,开发人员无需编写许多非常恼人的节点迭代代码(这个名称开始有意义了,不是吗?)。此过滤器只需检查所提供 节点的特殊类型、属性及其值以及其它标准,从而判断是接受还是拒绝该节点。既然一副图画顶一千个字的说明,一段代码顶一百万字的描述,那么我将为您演示 NodeFilter 的一个实现,该实现只接受位于 keyword 元素内且 search 属性为“true”的节点。请查看“清单 5”。

清单 5. 获得可搜索的关键字

import org.w3c.dom.Element;

import org.w3c.dom.Node;

import org.w3c.dom.traversal.NodeFilter;

public class KeywordsNodeFilter implements NodeFilter {

public short acceptNode(Node n) {

if (n.getNodeType() == Node.TEXT_NODE) {

Node parent = n.getParentNode();

 if (parent.getNodeName().equalsIgnoreCase("keyword")) {

 if (((Element)parent).getAttributeNode("search")

 .getNodeValue()

 .equalsIgnoreCase("true")) {

return FILTER_ACCEPT;

 }

 }

 }

 // If we got here, not interested

 return FILTER_SKIP;

 }

}

如果您非常熟悉 DOM 核心,则“清单 5”对您来说有许多意义。首先,您只需要实际关键字本身(而不是 keyword 元素),因此这个过滤器检查节点,看它是否是一个 org.w3c.dom.Text 节点。然后,如果其父节点名为 keyword,则它检查该元素的属性值,如果该属性值是“true”,那么它接受这个节点(使用在 NodeFilter 接口中定义的 FILTER_ACCEPT 常数)。否则,通过返回 FILTER_SKIP 来跳过该节点。很简单,是吗?

编写完这个过滤器之后,现在只需用这个过滤器创建一个新的 NodeIterator。有几段信息要提供给我在前一节讨论过的 createNodeIterator() 方法。首先,提供一个元素,从该元素这个地方开始搜索;除非只想搜索 DOM 树的特定部分,否则,我通常从根元素开始搜索。第二步,可以通过指定只搜索元素或属性或其它结构来限定搜索范围。因为有 NodeFilter,我实际上要迭代所有节点(让过滤器做这件事),因此我提供了常数 NodeFilter.SHOW_ALL。 下一个自变量是 NodeFilter 的示例,这个示例当然是清单 5 中 KeywordsNodeFilter 类的一个示例。最后的自变量是一个 Boolean 值,它指出是否应该扩展实体引用(如果不知道什么是实体引用,请查看 XML 教程,该教程可在参考资料一节中找到)。我几乎总是希望扩展它们,因此我通常提供一个 true 值。然后,迭代器象普通 Java 迭代器那样工作。要看到所有这些是如何工作的,请查看清单 6,该清单将所有这些细节组织在一起,创建了一个搜索 XML 文档并打印出所有可搜索关键字的程序。

到目前为止,根据对其它清单的解释,您应该理解“清单 6”了。请编译此程序和清单 5 中的代码。然后,将清单 4 的内容保存为一个 XML 文档。我将我的这个文档命名为 keywords.xml。将 xerces.jar 添加到类路径和工作目录中,然后运行 KeywordSearcher 类。我知道我省略了最后一步的描述,但是如果您也是一名 Java 用户,就不应该有任何编译和设置方面的问题。在对您自己的 XML 目录副本运行这个类之后,应该得到类似于“清单 7”的结果。

清单 7. 运行关键字搜索程序

C:\javaxml2\ibm>java KeywordSearcher keywords.xml

Processing file: keywords.xml

Search phrase found: 'galaxy'

Search phrase found: 'Hyperion'

Search phrase found: 'dwarves'

Search phrase found: 'hobbit'

Search phrase found: 'Foundation'

Search phrase found: 'Wheel of Time'

Search phrase found: 'The Path of Daggers'

显然,随着文档越来越复杂,NodeFilter 实现也会相应地更加复杂。此处的要点在于,DOM Traversal 与这个小过滤器一样有用,它在更复杂的情况下会变得极其强大。例如,可以根据元素/属性名,在文档中查找表示成属性 或元素的数据。对于核心 DOM 代码来说,这确实是个棘手的任务,它同样需要进行许多树的遍历,而 NodeIterator 则可以为您处理这些。因此,让您的想象力自由驰骋,并构筑那些过滤器吧!

在森林中查找树

在结束关于 DOM Traversal 的讲座之前,我要简短介绍一下 TreeWalker。由于篇幅所限(我希望这是一篇文章,而不只是一个章节),不想过于深入,但是因为您已经了解了 NodeIterator,这应该很简单。通过清单 8 中的方法创建 TreeWalker:

如果认识到 TreeWalker 方法采用的参数与 createNodeFilter() 方法相同,那并不会引起什么问题。事实上,剩下的唯一问题是“迭代节点与遍历树有什么区别?”答案是:使用 TreeWalker 时,可以维护一个树结构。在使用 NodeIterator 时,返回的节点实际上已从其树中的初始位置分离。迭代节点使操作很快捷,因为一旦返回节点就废弃其树位置。但是,使用 TreeWalker 时,当节点从定制节点过滤器返回时,节点仍保留在它们的树上下文中。这就允许您可以实际通过过滤器来查看整个 XML 文档。

做一个练习,尝试编写一个程序来显示清单 4 中不带处理说明、注释或属性的 XML 文档。 在开始前,有一个提示:首先,要使用 TreeWalker 来确保保留树格式。其次,编写一个定制的 NodeFilter 实现,以便只接受元素或文本类型的节点。最后,使用清单 6 中的程序作为模板,并更改几行代码。然后,就像那样,您自己就得到了一个定制的 DOM 树视图。如果您理解了节点部分并且可以编写出这个样本程序,那么您正在成为 DOM Traversal 高手的路上顺利前进。

希望您一直都在看 Traversal 模块所展示的所有可能性。以过滤方式遍历 DOM 树使得寻找元素、属性、文本和其它 DOM 结构变得容易。您还应该能够使用 DOM Traversal 模块编写更有效、结构更好的代码。因此,采用一个现有的搜索 DOM 树的程序,然后转换它,使其使用 traversal 方法;我知道您将对该结果感到满意。同以往一样,请让我知道本文是否对您有所帮助(使用本文所附的论坛),咱们网上见。

参考资料

论坛中向作者提问或者对本文中的建议提出意见。您还可以单击本文顶部或底部的讨论图标进入本文的论坛。

如果需要背景知识,请尝试 developerWorks XML 教程介绍Understanding DOM 教程。

可以在 W3C 的 DOM Activity 页面开始背景知识的学习。

直接访问当前的 DOM 规范 DOM 级别 2

研读 DOM Traversal 规范

Apache Xerces 中获得完整的开放源码 DOM 实现。

在相关文章中查看 JAXP(Java API for XML)处理

IBM WebSphere Application Server 通过基于 Apache Xerces 语法分析器的内置 XML4J 语法分析器支持 DOM 处理。在“WAS 高级版”的详细在线文档中探讨 DOM 处理支持的实质。查找 What Is 选项卡中的 DOM 部分,它位于 XML 和 Developing Applications 的下面。

关于作者

Brett McLaughlin (brett@newInstance.com) 是 Lutris Technologies 的 Enhydra 策略顾问和分布式系统体系结构方面的专家。 他是《Java 和 XML》(O'Reilly) 的作者。他参与了如 Java Servlet、Enterprise JavaBean 技术、XML 和商家对商家应用程序等技术的研究。他与 Jason Hunter 一起发起了 JDOM 项目,该项目为从 Java 应用程序中操纵 XML 提供了一个简单的 API。他还是 Apache Cocoon 项目和 EJBoss EJB 服务器的活跃开发人员以及 Apache Turbine 项目的共同创始人之一。

【打印】
查看完整文章 | 频道首页 | 网站首页