级别: 初级 Nicholas Chase (nicholas@nicholaschase.com), 总裁
2002 年 11 月 01 日 本文是由我们的热心读者马欲为大家翻译整理,无偿奉献出来与大家分享。XML的Document Object Model(文档对象模型,简称DOM)提供了一系列的对象和方法以便于开发者遍历一个文档所包含的树状结构。但一般而言,该处理过程包含了NodeList和递归循环的方法调用,这使得我们很容易在文档的数据结构中迷失当前的位置。DOM Level 2 Traversal module 提供了一个新的TreeWalker对象,能够简化原先的过程并使得操作更为可靠。本文描述了如何确定TreeWalker可用性的过程以及如何使用该对象来从文档中获取相应的信息。
注:本文使用了JAXP,但所举的样例程序也可以在Xerces-Java 2中工作,其核心思想同样能够在其它的XML解析环境中得到应用。
DOM提供了像Node这样包含了诸如getFirstChild() 和 getNextSibling()方法的接口。和getChildNodes()等相结合,这些方法提供了一个遍历XML文档的途径,即通过递归来分析每一个子节点的集合。
TreeWalker的工作机制与此相似,但更为规范。该对象实际上使用了nextNode()方法代替递归调用来对整个结构进行浏览。每次要移动到下一个节点的时候,currentNode属性会指向新的节点,这样TreeWalker对文档当前节点的位置就会了然于胸。举例来说,在清单1中,有一个包含紧急情况下联系雇员列表的文档:
清单1. 源文档
<?xmlversion="1.0"?>
<personnel>
<employee empid="332">
<status>contact</status>
<deptid>24</deptid>
<Shift>night</shift>
<name>Jenny Berman</name>
</employee>
<!-- Other employees listed here -->
</personnel>
|
一个TreeWalker对象能够检验当前节点status元素的文本取值。在移动到下一个employee节点之前,我们也可以不分析当前employee其它子元素的信息,而选择返回到上一层的employee元素取得empid的数值。
创建TreeWalker对象
DocumentTraversal对象是遍历模型中所有对象的类工厂。创建TreeWalker对象同样需要它的支持。实际上正如清单 2中所示那样,实例化一个TreeWalker需要四个参数。
清单2. 初始化TreeWalker对象
...
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.TreeWalker;
import org.w3c.dom.traversal.NodeFilter;
public class ShowDocument {
public static void main (String args[]) {
...
DOMImplementation domimpl = doc.getImplementation();
if (domimpl.hasFeature("Traversal", "2.0")) {
Node root = doc.getDocumentElement();
int whattoshow = NodeFilter.SHOW_ALL;
NodeFilter nodefilter = null;
boolean expandreferences = false;
DocumentTraversal traversal = (DocumentTraversal)doc;
TreeWalker walker = traversal.createTreeWalker(root,
whattoshow,
nodefilter,
expandreferences);
} else {
System.out.println("The Traversal module isn't supported.");
}
}
}
|
注:因为遍历模型只是实现DOM规范时的一个可选项,所以最好在使用之前先行确定它是否被当前XML解析器所支持。
在创建时,TreeWalker需要知道四个信息:
- 从什么地方开始
- 寻找的目标是什么
- 是否需要设置过滤条件
- 将实体展开还是简单地将其看作单纯的引用。
将根节点元素设为起始节点是个最直接的方法。类似地,通过一个简单的布尔值也可以决定是否要展开对实体的引用。
判断什么节点对TreeWalker对象可见是个有点复杂的问题。在开始阶段,你可以决定TreeWalker是否能够看到所有的节点还是某些特定类型的节点。如这里所示,whattoshow参数的值能够很容易地被NodeFilter接口所定义的常数改变。最常用的取值是NodeFilter.SHOW_ALL,而像NodeFilter.SHOW_ELEMENT和NodeFilter.SHOW_TEXT这样的常量值则是为其对应的节点类型所准备的。
除此之外,NodeFilter也能够使用相当复杂的筛选条件。我们可以自定义只传送某些值到TreeWalker 的NodeFilter。举例来说,一个NodeFilter能够只传递当前节点的employees部分给TreeWalker对象。在这里,因为应用程序初始化时会设为能够访问所有的节点,所以没有任何NodeFilter被传递到TreeWalker对象。
遍历树状结构
现在TreeWalker可以遍历文档的树状结构了。最简单的例子是walker对象从根节点开始向下移动,如清单3所示。
清单3. TreeWalker开始遍历树状结构的过程
...
TreeWalker walker = traversal.createTreeWalker(root,
whattoshow,
nodefilter,
expandreferences);
Node thisNode = null;
thisNode = walker.nextNode();
while (thisNode != null) {
System.out.println(thisNode.getNodeName()+":"
+thisNode.getNodeValue());
thisNode = walker.nextNode();
}
} else {
System.out.println("The Traversal module isn't supported.");
}
}
}
|
Walker对象创建时以根节点作为currentNode(当前节点),所以首次调用nextNode()方法时,它将移动到personnel元素的第一个文本子节点并返回那个节点。接着,while循环检查currentNode是否成功地指向正确的位置。如果结果正确,那么节点的名字和值将被输出,而后walker对象会移动到下一个节点,也就是另一个employee元素将被作为thisNode返回。在下一次的循环中,walker对象会继续移动到employee的首个文本子节点,以此类推。
清单4. walker对象返回null
#text:
employee: null
#text:
deptid: null
#text: 24
#text:
shift: null
#text: night
#text:
status: null
#text: contact
#text:
...
|
这个处理过程将会一直持续下去,直到walker不能再移动到下个节点而返回null为止。
在树状结构中自由移动
遍历文档是很简单的过程,也能够被其它方法所实现。对于TreeWalker对象,它的能力还表现在处理文档的同时能够记住这个文档的层次结构。例如,walker对象知道自己处于树状结构中的具体位置,所以当遇到某个status元素的时候,它就有能力检查该文本子节点的值并可以从它的双亲节点返回其相应的属性。
清单5. walker对象对status节点的反应
...
thisNode = walker.nextNode();
while (thisNode != null) {
if (thisNode.getNodeName().equals("status")){
Node status = walker.firstChild();
if (status.getNodeValue().equals("contact")){
Node statusNode = walker.parentNode();
Node employeeNode = walker.parentNode();
Element employeeElement = (Element)employeeNode;
String empId = employeeElement.getAttribute("empid");
System.out.println("Now contacting employee number " + empId);
walker.nextSibling();
}
}
thisNode = walker.nextNode();
}
} else {
...
|
在这种情况下,walker仍然在遍历根节点,但当遇到名为status的节点时,它将显式地移动到该节点的第一个文本子节点。此时,该文本节点将会成为walker的currentNode(当前节点)。如果文本节点的值等于contact,那么walker对象将返回到currentNode的上层节点,也就是status元素,然后再继续移动到status元素的上层节点,employee元素。从那里,我们可以像往常一样得到所需的属性值。当完成相应的操作之后,就可以直接移动到当前employee的兄弟节点继续遍历的过程。
参考资料
关于作者  | |  | Nicholas Chase 参与了 Lucent Technologies、Sun Microsystems、Oracle 和 Tampa Bay Buccaneers 等公司的网站开发。Nick 曾是一名高中物理教师、放射性废物设备初级管理人员、在线科幻小说杂志编辑、多媒体工程师和 Oracle 讲师。最近,他成为 Site Dynamics Interactive Communications(位于佛罗里达州克利尔沃特市)的首席技术官,而且还是三本有关 Web 开发书籍的作者,包括 Java and XML From Scratch(Que)和即将出版的 Primer Plus XML Programming(Sams)。他乐于听取读者的意见,可以通过
nicholas@nicholaschase.com与他联系。
|
对本文的评价
|