内容


实用数据绑定

使用 XPath 作为数据绑定工具,第 2 部分

使用 JAXP API 执行 XPath 查询

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 实用数据绑定

敬请期待该系列的后续内容。

此内容是该系列的一部分:实用数据绑定

敬请期待该系列的后续内容。

本系列的 第 1 部分 介绍了 XPath 语言及其语法。对于不熟悉 XSLT 的 Java 程序员来说可能有一点儿压力。XPath 语法可能看起来有点儿奇怪,和基于 XML 的语法相比更像是 UNIX 的目录路径 —— 又稍微增加了一点儿东西。但是您很快就看到了 XPath 使得选择 XML 文档的特定部分非常容易。

实用数据绑定 系列文章中讨论 XPath,原因就在于这些选择都使用逻辑名(请参阅 突出的逻辑性)。比方说,不必选择根元素第二个子元素的第一个属性,可以使用 XPath 表达式 /cds/cd[@title='August and Everything After']。从一定意义上说,这就是 数据绑定,因为可以使用 XML 标记而不是结构来访问数据。

本文主要讨论使用 XPath 作为通过逻辑名从 XML 中访问数据的方法,如 cdtableperson,而不用 firstElementparent.getChildren(2)。最终得到的代码非常类似于数据绑定,使用逻辑名而非语义名,但是又没有传统数据绑定解决方案的初始类生成和类路径问题。

节点知道

第 1 部分 中提到,XPath 选择总是返回一个节点 集。和其他任何集合一样,其中可能有零个、一个或多个成员。这里重要的概念是节点。节点可以是一段文本(比如元素的内容)、一个元素、一条处理指令或者注释。当然,节点集就是这类事物的任意组合。

如果您是一位 DOM 程序员,就不会被节点这个概念难倒。DOM 根据其 Node 接口看待一切事物,因此您也就习惯于通过 Node 接口来处理注释、元素和文本数据。如果不习惯 DOM,应该花点儿时间来熟悉节点。节点对于使用 XPath 至关重要。执行 XPath 表达式时,对表达式结果进行操作的代码必须对节点操作,而不深究 XML 语义。文档内的导航是用 XPath 表达式进行的,而不是使用 DOM 树转移方法从一个子节点移动到另一个。

JAXP 和 XPath 基础

Java 编程语言的最新版本 Java 5.0 中,对 Java API for XML Processing(JAXP)和 XPath 的支持已成为标准。您可能已经熟悉 JAXP 以及 SAX、DOM 和 XSL 编程中使用的 javax.xml.parsersjavax.xml.transform 包。(如果不熟悉的话,请参阅本文后面列出的一些 参考资料。)对 XPath 的支持主要来自于一个新的包 javax.xml.xpath

检查 JAXP 和 Java 版本

JAXP 1.3 包含在所有的 Java 5.0 下载包中。为了确保安装了 JAXP 1.3,请在命令提示符下输入以下命令:

java -version

应该看到类似于 清单 1 所示的结果。

清单 1. 命令行提示符下的 Java 5.0
java version "1.5.0_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_02-56)
Java HotSpot(TM) Client VM (build 1.5.0_02-36, mixed mode, sharing)

如果版本是 1.5 或更高,则已经有了 JAXP 1.3,可以确信您有了本文中使用的 XPath API。

简单的 XML

还需要一些供操作的 XML。本文中所有的例子都将使用 清单 2 中所示的简单 XML 文档。这个文档并不复杂,但是足以说明 90% 的 XPath 编程中所需要的各种 XPath 函数。

清单 2. XPath 代码中使用的 XML 示例文档
<?xml version="1.0"?>
<cds>
 <cd title="August and Everything After" artistId="23">
  <style>rock</style>
  <style>alternative</style>
  <tracks>
   <track id="1">Round Here</track>
   <track id="2">Omaha</track>
  </tracks>
 </cd>
 <cd title="This Desert Life" artistId="23">
  <style>rock</style>
  <style>alternative</style>
  <tracks>
   <track id="1">Hangin' Around</track>
   <track id="2">Mrs. Potter's Lullaby</track>
  </tracks>
 </cd>
 <cd title="Crash" artistId="46">
  <style>alternative</style>
  <style>jazz</style>
  <style>rock</style>
  <tracks>
   <track id="5">#41</track>
   <track id="3">Crash Into Me</track>
  </tracks>
 </cd>
 <artists>
  <artist id="23" type="group">
   <group-name>Counting Crows</group-name>
   <website>http://www.countingcrows.com</website>
   <member type="lead-singer">
    <firstName>Adam</firstName>
    <lastName>Duritz</lastName>
    <website>http://adam.countingcrows.com</website>
   </member>
  </artist>
  <artist id="46" type="group">
   <group-name>Dave Matthews Band</group-name>
   <website>http://www.dmband.com</website>
   <member type="lead-singer">
    <firstName>Dave</firstName>
    <lastName>Matthews</lastName>
   </member>
   <member type="instrumentalist">
    <firstName>Boyd</firstName>
    <lastName>Tinsley</lastName>
    <instrument>violin</instrument>
    <instrument>viola</instrument>
   </member>
  </artist>
  <artist id="101" type="solo">
   <website>http://www.patdonohue.com</website>
   <member>
    <firstName>Pat</firstName>
    <lastName>Donohue</lastName>
    <instrument>acoustic guitar</instrument>
   </member>
  </artist>
 </artists>
</cds>

将该文档保存在本地机器上便于从 Java 代码中访问的地方(请参阅 下载)。如果希望使用自己的 XML 文档也完全可以,只要调整示例代码和文档的结构与逻辑名匹配即可。

再回到 DOM

DOM 程序员已经熟悉了节点的概念,这是理解 XPath 表达式和这些表达式的返回值不可或缺的。使用 XPath 时,DOM 程序员还有一个优势,即在应用任何 XPath 表达式之前实际使用 DOM 获得对 XML 文档的访问。这毫不奇怪:XPath 现在是 JAXP 的核心部分,而 JAXP 也提供了完整的 DOM 支持。

将 XML 转化成 DOM 树

使用 XPath 的第一步是将目标 XML 文档作为 DOM 树读入,如 清单 3 所示。

清单 3. 将 XML 加载到 DOM 树中
package com.ibm.dw.xpath;
import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class SimpleXPath {
  File xmlInput;
  public SimpleXPath(String xmlFilename) {
    xmlInput = new File(xmlFilename);
    if (!xmlInput.exists()) 
      throw new RuntimeException("Specified file does not exist!");
  }
  public void test() throws Exception {
    Document doc = buildDocument();
  }
  private Document buildDocument() throws Exception {
    DocumentBuilder builder = 
      DocumentBuilderFactory.newInstance()
                            .newDocumentBuilder();
    return builder.parse(xmlInput);
  }
  public static void main(String[] args) {
    if (args.length < 1) {
      System.err.println("Usage: java com.ibm.dw.xpath.SimpleXPath " +
                         "[XML filename]");
      return;
    }
    try { 
      SimpleXPath tester = new SimpleXPath(args[0]);
      tester.test();
    } catch (Exception e) {
      System.err.println("Exception occurred: " + e.getMessage());
      e.printStackTrace(System.err);
      return;
    }
  }
}

清单 3 中的代码没有多少值得注意的地方。它仅仅从命令行接收 XML,然后使用 JAXP DOM API 将其加载到 DOM 树中。如果曾经使用过 JAXP,或者在没有 JAXP helper 类的情况下使用过 DOM,就会非常熟悉这种操作。(如果这些代码看起来有些吓人,那么可能需要查阅一下 参考资料 中列出的 DOM 文章,然后再回到本文。)

设置 XPath 上下文

第 1 部分 中提到,在对任何 XPath 表达式求值之前,必须知道开始的上下文。比方说,选择 ID 为 23artist 节点,XPath 表达式取决于上下文是 XML 文档的根节点,还是第三个 cd 元素的第二个 track 元素。

显然,需要能够在 XML 文档中移动来设置上下文,并相应地修改 XPath 表达式。从这里开始,真正的数据绑定 API 和 XPath 的界线渐渐分明了。在 XPath 中,虽然表达式是纯逻辑性的(使用 cdartist 之类的名称),但是仍然需要使用 DOM 在 XML 中移动位置。这意味着仍然需要使用 ElementText 这样的 XML 结构成分,仍然需要适当的树遍历。当然,如果读入 XML 文档,像 清单 3 那样,那么最初的上下文就是文档根节点,如果从根开始编写表达式就不用太担心树的遍历。

我认为这就是数据绑定!

有些读者可能已经在挠头了。本文是讨论如何将 XPath 用于数据绑定的,但是到现在说的一直是 DOM 和结构。XPath 是数据绑定很好的替身,但是这种 API 天生具有一定程度的结构。事实上,甚至位置路径也在某种程度上使用结构://cds/cd[@title='This Desert Life'] 假设 cds 元素是目标文档的根元素,cd 元素是根的子元素。这和使用 getCDs().getCD("This Desert Life") 之类的代码没有多少差别。这两种数据绑定方法没有显著的不同。

至于更明显的差异,就是在 XPath 代码中必须处理 DOM。但是,这点不同也能够在很大程度上隐藏起来。再看一看 清单 3,加载文档过程中所有和 DOM 有关的工作都通过 buildDocument() 方法抽象出来。返回的 DOM Document 对象是 DOM 专有的,但是没有多少地方需要直接操作这个对象。因此,不需要传统数据绑定方法所需要的一些类生成代码,XPath 就提供了数据绑定的多数 好处:

  • 从 DOM 和 SAX 这样的 API 中抽象出来
  • 使用逻辑命名法而不是结构命名法
  • XML 文档和 Java 对象之间的宽松映射

这就使其成为 XML 编程中一种非常轻量的数据绑定方法。

使用 XPath 工厂

您已经看到,稍微了解一点儿 DOM 知识可以大大方便对 XPath 的学习。如果在 DOM 的基础上再熟悉 JAXP API,那么您就遥遥领先了。比方说,在 JAXP 中无论是用 SAX 还是 DOM ,基本步骤都是:

  1. 创建新的解析器工厂。
  2. 创建新的解析器或者文档构造器(builder)。
  3. 解析/构造目标 XML。

同样的步骤也适用于 XPath 编程:

  1. 创建新的 XPath 工厂。
  2. 创建新的 XPath 求值程序(evaluator)。
  3. 创建和计算 XPath 表达式。

如果掌握了这些 SAX 或 DOM 背景知识,可能只需看一眼 XPath API 就能开始编程了。不过先等一等。还需要知道 XPath 一些特殊的地方,虽然您会发现本节的内容熟悉而且非常简单。

JAXP 和工厂

如果不熟悉 JAXP 或者使用时间不长,可能就不会理解在这种 API 中工厂模式的重要性和价值。JAXP 实际上不是一种 API 而更像是一种抽象,它允许不必编写厂商专用的代码,就可以使用特定厂商的 SAX、DOM 和 XPath 实现。为此,需要加载一个工厂实例,如 SAXParserFactoryDocumentBuilderFactory,然后 JAXP 就会把这个类连接到这些结构的厂商实现。因此虽然在代码中直接使用 SAXParserFactory,JAXP 必须在幕后使用 Apache Xerces 来处理真正的文档解析。

同样的抽象在 XPath 中更具有高瞻远瞩的性质。和丰富的 XML 解析与转换工具相比,目前还没有很多 XPath 处理程序。但是,通过使用工厂模式,就会发现以后如果需要可以更方便地替换 XPath 实现。具体来说,XPath 工厂允许使用不同的对象模型。默认情况下,节点集是从 DOM NodeNodeSet 中的 XPath 表达式返回的(在稍后的 使用 XPath 表达式 一节中将会看到)。但是,有可能希望使用 JDOM 结构或者 dom4j 提供的对象模型。虽然这些 Java/XML API 还没有用于 JAXP 的 XPath 引擎,但是可能很快就会出现。JAXP 工厂模型意味着很容易插入这些模型。因此不要为使用工厂模式所要求的额外步骤感到恼火,它们的存在为了提供便利,增加灵活性。

创建 XPath 工厂

您已经知道,使用 XPath 时最主要的包是 javax.xml.xpath。毫不奇怪,我们需要的工厂被称作 XPathFactory,也在这个包中。JAXP 老兵可能已经猜到,要使用 newInstance() 创建新的工厂,如 清单 4 所示(添加到 清单 3 中所示的代码中)。

清单 4. 创建新的 XPath 工厂
  public void test() throws Exception {
    Document doc = buildDocument();
    
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
  }

清单 4 中没有什么特别之处。仅仅使用默认的对象(即 DOM,至少在 1.3 版中如此,而且近期不大可能改变)创建了一个新的 XPath 工厂实例。

使用其他对象模型

因为 JAXP 已经开始接受其他 XML 对象模型,您可能希望不使用 JAXP 默认的 DOM 模型。这种情况下可以为 newInstance() 指定一个字符串统一资源标识符(Uniform Resource Identifier,URI),如 清单 5 所示。

清单 5. 指定字符串 URI
  public void test() throws Exception {
    Document doc = buildDocument();
    
    // Create a new XPath factory with an alternate object model
    XPathFactory factory = XPathFactory.newInstance(
        "http://jdom.org/jaxp/xpath/jdom");
  }

默认情况下,该 URI 被指定为 javax.xml.xpath.XPathConstants.DOM_OBJECT_MODEL 中的 http://java.sun.com/jaxp/xpath/dom。因此 清单 5 中的代码等价于 清单 6 中所示的代码。

清单 6. 创建新的 XPath 工厂,比较麻烦的方法
  public void test() throws Exception {
    Document doc = buildDocument();
    
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance(
        XPathConstants.DOM_OBJECT_MODEL);
  }

无论采用何种方法创建,工厂都是使用 XPath 时的第一步。因此测试类中一定要包含工厂创建代码,以便能够使用 XPath 类。

使用 XPath 类

创建工厂之后,还需要从工厂转移到能够真正计算表达式并与 XPath 环境交互的对象。XPathFactory不能 让您计算表达式(至少不能直接计算),它仅仅是从代码中获得厂商的 XPath 引擎和对象模型,而不需要很多厂商专用代码的一种方式。因此,建立这种联系后,还需要获得一个能够 处理 XPath 表达式求值的对象。这并非其他对象,正是 XPath 对象登台亮相了。

创建 XPath 对象

可以使用工厂的 newXPath() 方法创建新的 XPath清单 7 表明这个步骤是多么简单。

清单 7. 创建 XPath 类的新实例
  public void test() throws Exception {
    Document doc = buildDocument();
    
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
    // Create a new XPath instance
    XPath xpath = factory.newXPath();
  }

如果这些代码看起来简单得可笑,那就对了。所有其他的 XPath 处理工作都要从这个类开始,虽然看起来简单,但是应该习惯于输入这些代码。

配置和重置 XPath 对象

一旦得到了 XPath 的实例,就可以开始通过改变上下文、处理名称空间和计算表达式来配置它。其中一些主题超出了本文的范围,但是如果遇到 XPath 代码不工作的情况,可能是因为使用了错误配置的 XPath 对象。如果是这种情况,或者怀疑是这样,可以通过调用 xpath.reset() 重新将该对象恢复为初始配置。这样通常能够消除看到的问题,在调试过程中非常有用。

使用 XPath 表达式

现在您可能已经为使用 XPath 表达式作好了充分的准备。所幸的是,到现在为止您做的准备工作使这项任务变得很容易了。只需要用字符串创建一个新的表达式(要记住,这些都只是在 XML 文档中定位的引用,常常被称为位置路径)。

创建 XPath 表达式

清单 8 将一个 XPath 表达式赋给了一个字符串变量,这里没有戏法,也没有特殊的语法。仅仅使用一般的 Java String,然后将 XPath 表达式赋给它去求值。

清单 8. 创建 XPath 表达式
  public void test() throws Exception {
    Document doc = buildDocument();
    
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
    // Create a new XPath instance
    XPath xpath = factory.newXPath();
    // Create a new XPath expression
    String expr = "//cds/cd[@title='August and Everything After']";
  }

因为 清单 8 没有包含 XPath 专用的步骤,必须非常小心。其中也没有任何错误检查。有可能漏掉 title 属性中的 @ 符号(我第一次编写这段代码时就这样做过!),不会显示任何错误,只不过计算表达式时返回值为空。因此在计算和使用表达式之前一定要反复检查。

计算 XPath 表达式

得到表达式后必须对其求值。这正是出现 XPath 对象的原因。可以使用 evaluate() 方法计算表达式。这个方法有点儿奇特,因此让我们看看 清单 9 中的例子,了解每个参数的意义。

清单 9. 计算 XPath 表达式
  public void test() throws Exception {
    Document doc = buildDocument();
    
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
    // Create a new XPath instance
    XPath xpath = factory.newXPath();
    // Create a new XPath expression
    String expr = "//cds/cd[@title='August and Everything After']";
    Node cd = (Node)xpath.evaluate(expr, doc, XPathConstants.NODE);
  }

evaluate() 的第一个参数是表达式本身,第二个是表达式求值开始的上下文。清单 9 中,传入了通过调用 buildDocument() 获得的 DOM 文档。如果需要不同的上下文,可以传入文档中的某个 Node。但通常不这样做,传入 DOM 文档元素,然后从这里开始编写表达式是最简单的办法。

最后一个参数看起来有点儿奇怪,除非您非常熟悉 Java Naming and Directory Interface(JNDI)。该参数告诉 XPath 引擎期望表达式返回什么类型的值。XPathConstants 类中定义了几种可能的值:

  • XPathConstants.BOOLEAN 用于返回布尔数据类型的表达式(映射到 Java Boolean 数据类型)
  • XPathConstants.NODE 用于返回节点数据类型的表达式(映射到 DOM org.w3c.dom.Node 数据类型)
  • XPathConstants.NODESET 用于返回节点集数据类型的表达式(映射到 DOM org.w3c.dom.NodeList 数据类型)
  • XPathConstants.NUMBER 用于返回数值数据类型的表达式(映射到 Java Double 数据类型)
  • XPathConstants.STRING 用于返回字符串数据类型的表达式(映射到 Java String 数据类型)

这些类型涵盖了所有表达式。清单 9 中的表达式(//cds/cd[@title='August and Everything After'])应该返回一个且只有一个节点,因此类型指定为 XPathConstants.NODE。如果表达式返回多个节点,比如 //cds/cd 返回所有 cd 元素,应该使用 XPathConstants.NODESET

evaluate() 的返回值是一个 Java Object,因此需要将其强制转换成指定的类型。然后可以对 NodeNodeSet 或者返回的其他类型进行操作。

预编译 XPath 表达式

在讨论不同类型的 XPath 表达式之前,有必要提一提 XPath 对象的另一种特性。如果准备反复使用同一个 XPath 表达式,无论是一个 XML 文档还是具有相同结构的多个文档,当然不希望重复编译表达式的字节码版本。

为了避免重复这一过程,XPath 提供了一个 compile() 方法,只有一个参数,即字符串 XPath 表达式。它编译该表达式并将其作为 XPathExpression 类的实例返回。然后这个实例就代替了 XPath,可以对 XPathExpression 实例而不是 XPath 调用 evaluate()XPathExpression 版本的 evaluate() 有两个参数,即上下文(多数情况下就是 DOM 文档对象)和返回类型,操作和 XPath 对象的版本一样。请看 清单 10 中的简单例子。

清单 10. 预编译 XPath 表达式
  public void test() throws Exception {
    Document doc = buildDocument();
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
    // Create a new XPath instance
    XPath xpath = factory.newXPath();
    // Create a new XPath expression
    String expr = "//cds/cd[@title='August and Everything After']";
    XPathExpression xexpr = xpath.compile(expr);
    Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
  }

经过这样简单的修改,您的工作有很好的回报。因为无论如何都要付出将表达式编译成字节码的代价,所以修改后实际增加的成本只是多了一个需要管理的对象,程序流稍微变得不那么直观。(强调的是稍微,只要略有 Java 和 JAXP 经验,这些代码仍然很清晰。)除非确信表达式只使用一次,否则对表达式进行预编译总是有益的。

处理表达式结果

处理 XPath 表达式结果的方法仅受限于您的编程知识。得到结果集后,可以做任何需要的处理。比方说,为了验证 清单 9清单 10 中的代码确实检索到了正确的 CD,可以增加 清单 11 中所示的代码。

清单 11. 检查结果
  public void test() throws Exception {
    Document doc = buildDocument();
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
    // Create a new XPath instance
    XPath xpath = factory.newXPath();
    // Create a new XPath expression
    String expr = "//cds/cd[@title='August and Everything After']";
    XPathExpression xexpr = xpath.compile(expr);
    Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
    if (cd != null) {
      Element el = (Element)cd;
      System.out.println(el.getAttribute("title"));
    } else {
      System.err.println("Error! Node is null!");
    }
  }

返回的结果本身没有多少用处,但关键是可以使用 DOM 对 XPath 表达式求值返回的 Node 进行操作。也可将其转化成其他 Java/XML 格式(如 SAX)并提供事件处理程序,或者将节点序列化到磁盘中。也可将数据提取出来供应用程序的其他部分处理、迭代其跟踪路径或者想到的其他方法。XPath 专用的处理已经完成,因此不需要再担心了。

当然也可将返回的这个 Node 作为新 XPath 表达式的上下文,如 清单 12 所示。

清单 12. 多表达式求值
  public void test() throws Exception {
    Document doc = buildDocument();
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
    // Create a new XPath instance
    XPath xpath = factory.newXPath();
    // Create a new XPath expression
    String expr = "//cds/cd[@title='August and Everything After']";
    XPathExpression xexpr = xpath.compile(expr);
    Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
    Node track2 = (Node)xpath.evaluate(
      "tracks/track[@id='2']", cd, XPathConstants.NODE);
    Element e = (Element)track2;
    // Test the returned value
    System.out.println(e.getFirstChild().getNodeValue());
  }

这就是使用上下文可能非常有意义的地方,可以从前一个表达式的结果开始新的 XPath 表达式, 需要编写大量的 DOM 代码就能非常方便地在文档中导航。

插入 XPath 表达式

如果理解了 XPath 并能使用不同类型的表达式,事情就更有趣了。第 1 部分 已经介绍了 XPath 必须提供的大多数功能。现在我们将在 Java 代码中使用这些功能。

处理节点集

最常见的操作之一是处理表达式返回的节点集。再看一看 清单 2 中的示例 XML,假设需要查看 Counting Crows 的所有唱片。只要两个 XPath 表达式就能很容易地实现,如 清单 13 所示。

清单 13. 处理节点集
  public void test() throws Exception {
    Document doc = buildDocument();
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
    // Create a new XPath instance
    XPath xpath = factory.newXPath();
    // Create a new XPath expression
    String expr = "//cds/cd[@title='August and Everything After']";
    XPathExpression xexpr = xpath.compile(expr);
    Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
    expr = "//cds/artists/artist[group-name='Counting Crows']/@id";
    String crowsID = 
      (String)xpath.evaluate(expr, doc, XPathConstants.STRING);
    expr = "//cds/cd[@artistId='" + crowsID + "']";
    NodeList crowsCDs =
      (NodeList)xpath.evaluate(expr, doc, XPathConstants.NODESET);
    expr = "@title";
    XPathExpression title = xpath.compile(expr);
    for (int i = 0; i < crowsCDs.getLength(); i++) {
      Node current = crowsCDs.item(i);
      System.out.println("Found CD titled '" +
        title.evaluate(current) + "'");
    }
  }

清单 13 中使用几种不同返回类型的 XPath 表达式。首先使用返回字符串(实际上是一个数字,不过从文本转化成数字并没有什么好处,因为后面的代码还要将其作为字符串使用)的 XPath 表达式检索 Counting Crows 的 ID。然后使用该表达式的结果创建一个新的表达式,选择具有此 ID 的所有 cd 元素。

第二个表达式的结果是一个节点集,映射到 DOM org.w3c.dom.NodeList 类型。循环遍历 org.w3c.dom.NodeList 非常简单,很容易将列表中的每个 Node 强制转换成 org.w3c.dom.Element,然后获取子元素(文本节点)并输出该文本节点的值。还使用了另外一个 XPath 表达式,这仅仅是为了强调 XPath 的方便性,说明 XPath 能够多么轻易地避开基本上任何 DOM 树遍历代码。该表达式取得当前节点的 title 属性(要记住,在这里上下文很重要)并返回其值,然后输出这个值。通过使用 NodeList 中的当前 Node 作为上下文,可以很好地代替 DOM 获取每个 CD 的标题。

关于缩写形式

细心的读者可能注意到清单 13 中的一句代码:

title.evaluate(current)

这个版本的 evaluate() 也需要上下文参数,但是 需要返回类型,它似乎返回一个字符串。情况确实如此,这个版本的 evaluate() 在任何情况下都返回一个字符串。有时候,比如计算的表达式返回一个节点集或者一个节点,这可能造成很大的混乱。不过在其他时候,如果确实需要一个文本表达式,这是一种很好的缩写形式。

增加布尔表达式

XPath 的另一个特点是能够计算布尔表达式。比如,可使用 清单 14 中的代码来实现和 清单 13 类似的效果,这一次在迭代全部 CD 的过程中使用了 XPath 布尔表达式。

清单 14. 用 XPath 表达式返回布尔值
  public void test() throws Exception {
    Document doc = buildDocument();
    // Create a new XPath factory
    XPathFactory factory = XPathFactory.newInstance();
    // Create a new XPath instance
    XPath xpath = factory.newXPath();
    // Create a new XPath expression
    String expr = "//cds/cd[@title='August and Everything After']";
    XPathExpression xexpr = xpath.compile(expr);
    Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
    expr = "//cds/artists/artist[group-name='Counting Crows']/@id";
    String crowsID = (String)xpath.evaluate(expr, doc, XPathConstants.STRING);
    expr = "//cds/cd";
    NodeList allCDs =  
      (NodeList)xpath.evaluate(expr, doc, XPathConstants.NODESET);
 
    String expr1 = "@artistId='" + crowsID + "'";
    String expr2 = "@title";
    XPathExpression artist = xpath.compile(expr1);
    XPathExpression title = xpath.compile(expr2);
    for (int i = 0; i < allCDs.getLength(); i++) {
      Node current = allCDs.item(i);
      Boolean crowsCD = 
        (Boolean)artist.evaluate(current, XPathConstants.BOOLEAN);
      if (crowsCD)
        System.out.println("Found CD titled '" +
          title.evaluate(current) + "'");
    }
  }

清单 13清单 14 无所谓优劣,仅仅是获得同样信息的不同方法。不过,这两个例子说明了 XPath 的灵活性。这些例子以及本文中的其他例子,说明了 XPath 如何处理各种数据类型,可以为您自己的编程工作带来启迪。

结束语

和以前相比 Java 开发人员拥有了更多的 API、技术和工具箱。但是丰富的编程能力也带来一种危险 —— 某种 API 偏执症。很容易认定只有那些明确贴上数据绑定标签的 API 才能用于以逻辑而非语义的方式获取 XML 数据。贴上 XPath 标签的 API 只能用于传统上使用 XPath 的应用程序中,这就是偏执症的观念。同样的问题也适用于其他 API,无论是解析 XML、读取属性文件还是在内存中创建树状结构的 API。

但是,当稍微创造性地选择 API 和编程方式时,Java 语言的强大之处常常最明显。不要被那些随意的标签(如数据绑定)所蒙蔽,要看看 API 到底提供了什么。如果发现一种 API 似乎提供了有用的功能,就将其添加到代码中,无论它是用来干什么的。和遵守所有规则但是笨拙、缓慢、功能有限的应用程序相比,以这种方式使用(或滥用)API 的能够工作的应用程序更应该受到赞赏。

通过本系列的两篇文章,您看到了如何将 XPath API 作为一种相对容易的数据绑定 API 来使用,虽然一般认为 XPath 仅在 XSLT 和 XPointer 世界中出现。更好的是,Java 语言在 JAXP 中提供了一种 XPath API。Java 和 XML 程序员有谁能够抗拒这种诱惑呢?放弃标签的框框吧,不仅仅 JAXB 能用于数据绑定,XPath 是一个很好的起点。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Java technology
ArticleID=102452
ArticleTitle=实用数据绑定: 使用 XPath 作为数据绑定工具,第 2 部分
publish-date=01232006