级别: 中级 Neil Graham (neilg@ca.ibm.com), XML 解析器开发经理, IBM Elena Litani (elitani@ca.ibm.com), 软件开发人员, IBM
2004 年 12 月 01 日 本文中,作者将继续
第 1 部分关于 JAXP 1.3 的讨论,介绍支持 Namespaces in XML 规范所定义概念的新增工具,描述
javax.xml.transform 包的变化。而且还将讨论新定义的 Java 类型,以及这些类型如何为 W3C XML Schema 数据类型提供完备的原生 Java 语言支持。最后将详细讨论 JAXP 独立于数据模型和厂商的 XPath API。
工具
为了完善对名称空间的支持,JAXP 1.3 引入了新的
javax.xml.namespace 包,允许通过
NamespaceContext 接口和
QName 类,来操纵和查询名称空间信息。
NamespaceContext 接口保存当前文档上下文中可用的前缀到名称空间的映射。该接口提供了获得给定前缀的名称空间 URI 的方法、获得给定名称空间 URI 的前缀的方法,或者提供了获得绑定到某一给定名称空间 URI 的所有前缀的方法。
NamespaceContext 供新的 XPath API 使用,在本文的后面部分将对此进行介绍。
QName 类表示 Namespaces in XML Recommendation(请参阅
参考资料)中规定的限定名,
第 1 部分已经提到,这个类最初是在 Java API for XML-Based RPC (JAX-RPC) 规范(请参阅
参考资料)中定义的。
QName 包含名称中的本地部分、名称空间 URI、对应的前缀(如果可用的话)。需要指出的是,在
equals() 和
hashCode() 方法的实现中,前缀值是被忽略的。
在
第 1 部分曾提到的 javax.xml 包只有一个类:
XMLConstants。这个类定义了常用的常量,比如 JAXP 能够识别的模式语言的常量值,以及与名称空间有关的常量值。
XSL 转换包的变化
在 JAXP 的这个版本中,
javax.xml.transform 包的变化主要包括错误修正,并澄清了 API 的某些部分。最重要的变化是 JAXP 1.3 参考实现更改了默认转换引擎:JAXP 1.2 使用的是解释型的转换器(Xalan),JAXP 1.3 中的默认转换引擎是编译型的转换器(XSLTC)。XSLTC 将样式表编译成 Java 字节码,称为
translet,然后使用 translet 执行 XSL 转换。这种方法改善了 XSLT 的性能,因为每个样式表只要解析和编译一次,就可以在以后的转换中反复使用。
XML Schema 到 Java 类型的映射
XML Schema 数据类型得到了广泛的认可,被用作其他很多规范的类型系统,如 Web Services Description Language(WSDL,请参阅
参考资料)。很多用 Java 语言编写的应用程序需要或者更喜欢访问表示 XML Schema 数据类型值的 Java 类型。因此,过去两年中,人们在定义 XML Schema 数据类型(如
xs:string)和 Java 类型方面进行了多次尝试,比如开放源代码 XML 数据绑定框架 Castor 和 Java Architecture for XML Binding (JAXB) 1.0 规范(请参阅
参考资料)。表 1 中可以看出,多数类型的映射非常直接。
表 1. XML Schema 数据类型到 Java 类型的映射(部分)
|
XML Schema 数据类型
|
Java 类型
|
xs:string
|
java.lang.String
|
xs:decimal
|
java.math.BigDecimal
|
xs:float
|
float
|
xs:short, xs:unsignedByte
|
short
|
xs:int
|
int
|
xs:boolean
|
boolean
|
xs:base64Binary, xs:hexBinary
|
byte[]
| | ... | ... |
但是,XML Schema Datatypes 规范中定义的某些数据类型和现有的 Java 类不存在一一对应的关系。具体而言,Java 类型系统没有和 XML Schema
xs:duration 数据类型对应的类型,也没有与其他 XML Schema 日期/时间类型(如
xs:gYear)一一对应的类。
作为 Java 平台一部分的 JAXP 1.3 增加了缺少的类型,从而完善了类型映射。表 2 是新定义的 Java 类型及其到 XML Schema 数据类型的映射。
表 2. 新的 JAXP 1.3 类型及其到 XML Schema 数据类型的映射
|
Java 类型
|
XML Schema 数据类型
|
javax.xml.datatype.XMLGregorianCalendar
|
xs:gDay, xs:gMonth, xs:gMonthDay, xs:gYear, xs:gYearMonth, xs:time, xs:dateTime, xs:date
|
javax.xml.datatype.Duration
|
xs:duration
|
javax.xml.namespace.QName
|
xs:QName 和
xs:NOTATION
|
注意,XQuery 1.0 和 XPath 2.0 规范中新定义的数据类型(
xdt:dayTimeDuration 和
xdt:yearMonthDuration)也映射到
Duration 类。
javax.xml.datatype 概要
与其他 JAXP 包相同,
javax.xml.datatype 也定义了
DatatypeFactory 类,该类允许您插入数据类型工厂的多个实现。与 DOM 与 SAX 工厂(
DOMBuilderFactory 和
SAXParserFactory)类似,
DatatypeFactory 也是一个抽象类,它的静态方法
newInstance() 可以创建
DatatypeFactory 的具体实现。使用
DatatypeFactory 的实例可以创建
Duration 和
XMLGregorianCalendar 对象。
Duration 对象是不变的,可以从各种不同的值创建,其中包括:
- 词法表示。
- 用毫秒表示的值(Java long 类型)。
- 表示时间正向或逆向的一组值:年、月、日、时、分和秒。
Duration 类提供了几种方法,其中包括:
- 用于比较
Duration 对象(按照 XML Schema 规范的定义)的方法。
- 加减两个
Duration 的方法。
- 将
Duration 加到 Java
Calendar、
Date 或
XMLGregorianCalendar 对象上的方法。
有关的更多信息,请参阅
Duration 类的 Java 文档。
XMLGregorianCalendar 对象是可以修改的,因此能够直接设置对象的日期或时间字段。这个类也提供了验证
XMLGregorianCalendar 对象、转化成 Java
GregorianCalendar 类实例和其他动作的方法。
javax.xml.datatypes 包还定义了
DatatypeConstants 工具类,以常量定义了基本的数据类型值。
清单 1 说明了如何创建和使用
Duration 及
XMLGregorianCalendar 类型。为了简便起见,不妨假设要创建一个应用程序,如果给出产品的购买日期和产品的保修期(
Duration 类型),那么就能计算出该产品什么时候超出保修期。
清单 1. 使用 JAXP 类型
// Create a data type factory
DatatypeFactory df = DatatypeFactory.newInstance();
// Create a purchase date of a product (xs:date)
XMLGregorianCalendar purchaseDate = df.newXMLGregorianCalendar();
purchaseDate.setYear(2004);
purchaseDate.setMonth(DatatypeConstants.DECEMBER);
purchaseDate.setDay(1);
// Print the purchase date
System.out.println("Purchase date: " + purchaseDate.toXMLFormat());
// Create a warranty duration (1 year)
Duration warrantyDuration = df.newDuration("P1Y");
// Now compute the warranty expiration date
purchaseDate.addDuration(warrantyDuration);
// Print out the expiration date
System.out.println("Expiration date: " +purchaseDate.toXMLFormat());
|
清单 2 是上述程序的输出结果:
清单 2. 清单 1 的输出结果
Purchase date: 2004-12-01
Expiration date: 2005-12-01
|
JAXP 1.3 XPath API
XPath 1.0 是一种 W3C 推荐标准,它定义了一种能够提取部分 XML 文档的语言。您可以抽取一组元素及其所有的后代,也可以仅仅抽取某个属性值。要选择文档的某一部分,必须规定起始节点(称为
上下文节点)和所选内容之间的
路径。通过指定路径可以选择上下文结点的某个子元素,也可以选择以上下文结点为根的整棵子树中满足复杂表达式(比如包含特定属性值的两个子元素)的所有子元素。
尽管 XPath 1.0 Recommendation 已经存在了很长时间(按照 XML 的标准,2004 年 11 月 16 日它度过了自己的第五个生日),但直到 JAXP 1.3,才最终将这种功能引入了 Java 平台。与以前的 XPath API 不同,JAXP 1.3 是完全厂商中立的,但和解析器以及转换器领域一样,它为系统发现和创建兼容的对象提供了同一类型的工厂机制。JAXP 1.3 API 也不知道底层的数据模型。从理论上说,JAXP 1.3 可以使用任何数据模型,只要精心设计好和 XPath 1.0 所定义的简单数据模型之间的映射(这样就能以确定的方式应用 XPath 表达式)。W3C Document Object Model (DOM) 是要求 JAXP 1.3 实现支持的惟一数据模型。
javax.xml.xpath 包中包含了和新的 XPath API 相关的所有接口和抽象类。毫不奇怪,用于创建 XPath 表达式求值对象的对象被称为
XPathFactory,而它创建的对象则称为
XPath。
对于特定类型的数据模型,
XPathFactory 只需要知道如何创建
XPath 对象。因此,在创建
XPathFactory 时必须指定数据模型。对于明确的 API,需要为数据模型分配一个 URI。如果没有指定 URI,则创建用于 DOM 模型的
XPathFactory。同一
XPath 对象可用于多个 DOM 树,但必须注意
XPath 对象不是线程安全的。
XPath 对象主要有两种使用方式:
- 要计算 XPath 表达式,将表达式作为简单的 String 对象传递,并给定所支持的数据模型实例中的某个节点作为上下文节点。这种方式称为解释 XPath 表达式,因为 String 被直接应用于数据模型。
- 将 XPath 表达式从 String 转化为
XPathExpression 对象,然后将该对象应用于所支持的数据模型实例中的任何节点。
XPathExpression 对象通过将 XPath 表达式的 String 表示传递给
XPath 的编译方法来创建。
XPathExpression 对象是原来的 XPath String 的编译表示,代表 XPath 表达式在内部的、经过优化的表示。事实上,在很多实现中
XPathExpression 都完全由 Java 字节码组成,很难再进一步优化。
如果两种方式都可行,就要考虑哪一种更好一些。对于 XPath 表达式,一定要记住将 XPath 表达式编译成优化的表示,其中包括字节码,需要做大量的工作;因此,如果表达式非常简单或者不经常使用,可能就不需要将其编译。但是对于复杂的表达式,特别是在应用程序中频繁使用的表达式,编译可以极大地改善性能。
XPath 和
XPathExpression 都提供了 4 种不同的方法计算 XPath 表达式,是通过重载
evaluate 实现的。对应的方法签名都是一样的,只不过
XPath 的计算方法必须以 XPath 表达式的 String 表示作为第一个参数,而
XPathExpression 可以完全省略这个参数,因为它们已经包含了特定的表达式。为了简便起见,本文只讨论
XPathExpression 的
evaluate 方法。
最常用的
evaluate 形式可能是这种:用对象表示上下文节点,以
QName 表明表达式的返回类型。返回类型可以是 4 种 XPath 1.0 基本数据类型之一:boolean、字符串、节点集和数字。所用的数据模型决定了这些 XPath 数据类型如何在 Java 代码中表示,在 DOM 模型中分别定义为
Boolean、String、org.w3c.dom.NodeList 和
Double。因此在决定如何对该方法的返回值进行强制类型转换时,必须同时考虑预期的返回类型和所用的数据模型。表示上下文节点的对象表示也必须适应数据模型,对于 DOM 可以使用任何类型的
org.w3c.dom.Node。这个包中的
XPathConstants 类为这 4 种 XPath 数据类型定义了
QName。
其他形式的
evaluate 仅仅稍有变化:
-
evaluate(Object item):String 是
evaluate(item, XPathConstants.STRING) 的缩写形式,注意返回类型是已知的。
-
evaluate(InputSource source, QName returnType): Object 将把
org.xml.sax.InputSource 解析成数据模型的一个实例,以根节点作为上下文节点,其他方面都和上一个
evaluate 方法相同。
-
evaluate(InputSource source): String 时
evaluate(source, XPathConstants.STRING) 的简写形式,是第一个
evaluate 的对称版本。
下面的例子返回
第 1 部分中所用的订单文档。这里假设订单包含多个条目,而且货物发给和付款者不同的另一个人,因此需要做特殊处理。设
fileSource 是指向订单文档的一个
org.xml.sax.InputSource,处理代码如下:
清单 3. 使用 XPath API
// XPath expressions
String shipToNameExprStr = "string(purchaseOrder/shipTo/name)";
String billToNameExprStr = "string(purchaseOrder/billTo/name)";
String itemsExprStr = "purchaseOrder//item";
try {
// First, get a DocumentBuilder
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setValidating(false);
DocumentBuilder db = dbf.newDocumentBuilder();
// Get an XPathFactory that works on DOM trees
XPathFactory xpf4dom = XPathFactory.newInstance();
XPath xpath4dom = xpf4dom.newXPath();
// Build compiled XPath expressions
XPathExpression billToNameExpr = xpath4dom.compile(billToNameExprStr);
XPathExpression shipToNameExpr = xpath4dom.compile(shipToNameExprStr);
XPathExpression itemsExpr = xpath4dom.compile(itemsExprStr);
// Parse the file and run XPaths against result
Document doc = db.parse(fileSource);
NodeList items = (NodeList)itemsExpr.evaluate(doc, XPathConstants.NODESET);
String billToName = billToNameExpr.evaluate(doc);
String shipToName = shipToNameExpr.evaluate(doc);
// Do you have special processing to do?
if(items.getLength() > 1
&& !billToName.equalsIgnoreCase(shipToName)) {
// special processing
}
} catch (Exception e) {
// Uh-oh; something bad has happened…
}
|
注意 XPath API 的其他几个细节。最重要的一点是 XPath 表达式只能引用名称空间前缀限定的元素和属性名。要编译使用名称空间的 XPath 表达式,应用程序需要注册
NamespaceContext 接口的一个实现实例,该接口属于
javax.xml.namespace 包,因为对于引用的名称空间声明没有上下文节点。
XPath 1.0 Recommendation 为了增强扩展性提供了一些有用的接口。XPath provides some useful hooks for extensibility. XPath 表达式可以包含:
-
变量:XPath 处理程序在计算表达式时将值和这些标识符联系起来。
-
函数:XPath 处理程序在计算表达式时将值和函数联系起来。
JAXP 1.3 通过定义
XPathVariableResolver 和
XPathFunctionResolver 接口实现了这些功能。应用程序可以实现这两个接口,并使用
XPathFactory 或
XPath 实例注册。
XPathVariableResolver 接口只有一个
resolveVariable 方法。该方法取得标识变量的
QName,并根据底层的数据模型返回适当的对象。
XPathFunctionResolver 包含一个类似的方法,称为
resolveFunction。但该方法接收一个
int 参数,它指定函数需要的参数个数,以及标识函数的
QName。
resolveFunction 方法返回一个
XPathFunction 对象。
XPathFunction 有一个
evaluate 方法,接受
List 参数,该参数的长度必须对应返回
XPathFunction 的
resolveFunction 回调函数中的
int 参数;与此类似,
resolveVariable 方法也按照数据模型返回适当的对象。
结束语
本文描述了 JAXP 1.3 中支持 XML Namespaces 的工具,以及
javax.xml.transform 包的细微变化。我们还描述了这种 API 如何完善了对所有 W3C XML Schema 数据类型的原生 Java 支持,最后介绍了 JAXP 1.3 中的 XPath 功能。将本文内容与本系列的第一篇文章的内容结合在一起,您应该了解了 JAXP 1.3 在性能和可用性的很多改善。
参考资料
作者简介  | |  | Neil Graham 是 IBM XML Parser Development 的经理。他是 Apache Xerces-Java 和 Xerces-C++ XML 解析器的负责人,主要从事 XML Schema、XML 1.1 和语法缓冲的实现。他还是开发 JAXP 1.3 的专家组的 IBM 代表。
|
 | |  | Elena Litani 是 IBM 的软件开发人员之一。她是 Eclipse.org 的 Eclipse Modeling Framework (EMF) 项目的主要开发人员,该项目提供了 Service Data Objects(服务数据对象,SDO)的参考实现。Elena 过去曾经作为 Apache Xerces2 项目的主要开发人员之一,从事 Xerces2 XML Schema 和 DOM Level 3 的实现和解析器性能的分析与改进。Elena 还代表 IBM 参加了 W3C DOM 工作组,参与了 DOM Level 3 规范的开发。 |
对本文的评价
|