XPath 表达式比J琐的文档对象模型(DOMQ导航代码要Ҏ~写得多。如果需要从 XML 文档中提取信息,最快捷、最单的办法是?Java?E序中嵌?XPath 表达式。Java 5 推出?javax.xml.xpath 包,q是一个用?XPath 文档查询的独立于 XML 对象模型的库?/blockquote>
如果要告诉别Z一加仑牛奶Q您会怎么_“请M一加仑牛奶回来?q是 “从前门出去Q向左{Q走三个街区向右转,再走半个街区向右转进入商店。走向四号通道Q沿通道Cc_左,拿一瓶一加仑装的牛奶然后到收银台付款。再沿原路回家。?直太可笑了。只要在 “请M一加仑牛奶回来?的基上稍加指C,多数成h都能自己买回牛奶来?
查询语言和计机搜烦与此cM。直接说 “找一?Cryptonomicon 的副本?要比~写搜烦某个数据库的详细逻辑Ҏ得多。由于搜索操作的逻辑非常怼Q可以发明一U通用语言让您使用 “找?Neal Stephenson 的所有著作?q样的命令,然后~写对特定数据存储执行此cL询的引擎?
XPath
在众多查询语a之中Q结构化查询语言QSQLQ是一U针Ҏ询特定类型的关系库而设计和优化的语a。其他不那么常见的查询语aq有对象查询语言QOQLQ和 XQuery。但本文的主题是 XPathQ一Uؓ查询 XML 文档而设计的查询语言。比如,下面q个单的 XPath 查询可以在文档中扑ֈ作者ؓ Neal Stephenson 的所有图书的标题Q?/p>
//book[author="Neal Stephenson"]/title
|
作ؓ对照Q查询同样信息的U?DOM 搜烦代码?清单 1 所C:
清单 1. 扑ֈ Neal Stephenson 所有著?title 元素?DOM 代码
ArrayList result = new ArrayList();
NodeList books = doc.getElementsByTagName("book");
for (int i = 0; i < books.getLength(); i++) {
Element book = (Element) books.item(i);
NodeList authors = book.getElementsByTagName("author");
boolean stephenson = false;
for (int j = 0; j < authors.getLength(); j++) {
Element author = (Element) authors.item(j);
NodeList children = author.getChildNodes();
StringBuffer sb = new StringBuffer();
for (int k = 0; k < children.getLength(); k++) {
Node child = children.item(k);
// really should to do this recursively
if (child.getNodeType() == Node.TEXT_NODE) {
sb.append(child.getNodeValue());
}
}
if (sb.toString().equals("Neal Stephenson")) {
stephenson = true;
break;
}
}
if (stephenson) {
NodeList titles = book.getElementsByTagName("title");
for (int j = 0; j < titles.getLength(); j++) {
result.add(titles.item(j));
}
}
}
|
不论您是否相信,清单 1 中的 DOM 昄不如单的 XPath 表达式通用或者健壮。您愿意~写、调试和l护哪一个?我想{案很明显?
但是虽然有很强的表达能力QXPath q不?Java 语言Q事实上 XPath 不是一U完整的~程语言。有很多东西?XPath 表达不出来,甚至有些查询也无法表达。比方说QXPath 不能查找国际标准图书~码QISBNQ检验码不匹配的所有图书,或者找出境外帐h据库昄Ơ帐的所有作者。幸q的是,可以?XPath l合?Java E序中,q样p发挥两者的优势了:Java ?Java 所擅长的,XPath ?XPath 所擅长的?
直到最q,Java E序执行 XPath 查询所需要的应用E序~程接口QAPIQ还因Ş形色色的 XPath 引擎而各不相同。Xalan 有一U?APIQSaxon 使用另一U,其他引擎则用其他的 API。这意味着代码往往把您限制CU品上。理x况下Q最好能够试验具有不同性能特点的各U引擎,而不会带来不适当的麻烦或者重新编写代码?
于是QJava 5 推出?javax.xml.xpath
包,提供一个引擎和对象模型独立?XPath 库。这个包也可用于 Java 1.3 及以后的版本Q但需要单独安?Java API for XML Processing (JAXP) 1.3。Xalan 2.7 ?Saxon 8 以及其他产品包含了这个库的实现?
一个简单的例子
我将举例说明如何使用它。然后再讨论一些细节问题。假设要查询一个图书列表,L Neal Stephenson 的著作。具体来_q个图书列表的Ş式如 清单 2 所C:
清单 2. 包含图书信息?XML 文档
<inventory>
<book year="2000">
<title>Snow Crash</title>
<author>Neal Stephenson</author>
<publisher>Spectra</publisher>
<isbn>0553380958</isbn>
<price>14.95</price>
</book>
<book year="2005">
<title>Burning Tower</title>
<author>Larry Niven</author>
<author>Jerry Pournelle</author>
<publisher>Pocket</publisher>
<isbn>0743416910</isbn>
<price>5.99</price>
<book>
<book year="1995">
<title>Zodiac</title>
<author>Neal Stephenson<author>
<publisher>Spectra</publisher>
<isbn>0553573862</isbn>
<price>7.50</price>
<book>
<!-- more books... -->
</inventory>
|
|
抽象工厂
XPathFactory 是一个抽象工厂。抽象工厂设计模式得这一U?API 能够支持不同的对象模型,?DOM、JDOM ?XOM。ؓ了选择不同的模型,需要向 XPathFactory.newInstance() Ҏ传递标识对象模型的l一资源标识W(URIQ。比?http://xom.nu/ 可以选择 XOM。但实际上,到目前ؓ?DOM 是该 API 支持的惟一对象模型?
|
|
查找所有图书的 XPath 查询非常单://book[author="Neal Stephenson"]
。ؓ了找些图书的标题Q只要增加一步,表达式就变成?//book[author="Neal Stephenson"]/title
。最后,真正需要的?title
元素的文本节点孩子。这p求再增加一步,完整的表辑ּ是 //book[author="Neal Stephenson"]/title/text()
?
现在我提供一个简单的E序Q它?Java 语言中执行这个查询,然后把找到的所有图书的标题打印出来。首先,需要将文档加蝲C?DOM Document
对象中。ؓ了简化v见,假设该文档在当前工作目录?books.xml 文g中。下面的单代码片D解析文档ƈ建立对应?Document
对象Q?
清单 3. ?JAXP 解析文档
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); // never forget this!
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("books.xml");
|
到目前ؓ止,q仅仅是标准?JAXP ?DOMQ没有什么新鲜的?
接下来创?XPathFactory
Q?/p>
XPathFactory factory = XPathFactory.newInstance();
|
然后使用q个工厂创徏 XPath
对象Q?
XPath xpath = factory.newXPath();
|
XPath
对象~译 XPath 表达式:
PathExpression expr = xpath.compile("http://book[author='Neal Stephenson']/title/text()");
|
|
直接求?/b>
如果 XPath 表达式只使用一ơ,可以跌~译步骤直接?XPath 对象调用 evaluate() Ҏ。但是,如果同一个表辑ּ要重复用多ơ,~译可能更快一些?
|
|
最后,计算 XPath 表达式得到结果。表辑ּ是针对特定的上下文节点计的Q在q个例子中是整个文档。还必须指定q回cd。这里要求返回一个节炚wQ?
Object result = expr.evaluate(doc, XPathConstants.NODESET);
|
可以结果强制{化成 DOM NodeList
Q然后遍历列表得到所有的标题Q?
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(nodes.item(i).getNodeValue());
}
|
清单 4
把上q片D늻合到了一个程序中。还要注意,q些Ҏ可能抛出一些检查异常,q些异常必须?throws
子句中声明,但是我在上面把它们掩盖v来了Q?
清单 4. 用固定的 XPath 表达式查?XML 文档的完整程?/b>
import java.io.IOException;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
public class XPathExample {
public static void main(String[] args)
throws ParserConfigurationException, SAXException,
IOException, XPathExpressionException {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true); // never forget this!
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse("books.xml");
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
XPathExpression expr
= xpath.compile("http://book[author='Neal Stephenson']/title/text()");
Object result = expr.evaluate(doc, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(nodes.item(i).getNodeValue());
}
}
}
|
XPath 数据模型
每当混合使用诸如 XPath ?Java q样两种不同的语aӞ必定会有某些两者粘合在一L明显接缝。ƈ非一切都很合拍。XPath ?Java 语言没有同样的类型系l。XPath 1.0 只有四种基本数据cdQ?/p>
- node-set
- number
- boolean
- string
当然QJava 语言有更多的数据cdQ包括用户定义的对象cd?/p>
多数 XPath 表达式,特别是位|\径,都返回节炚w。但是还有其他可能。比如,XPath 表达?count(//book)
q回文档中的图书数量。XPath 表达?count(//book[@author="Neal Stephenson"]) > 10
q回一个布|如果文档?Neal Stephenson 的著作超q?10 本则q回 trueQ否则返?false?
evaluate()
Ҏ被声明ؓq回 Object
。实际返回什么依赖于 XPath 表达式的l果以及要求的类型。一般来_XPath ?/p>
- number 映射?
java.lang.Double
- string 映射?
java.lang.String
- boolean 映射?
java.lang.Boolean
- node-set 映射?
org.w3c.dom.NodeList
|
XPath 2
前面一直假设您使用的是 XPath 1.0。XPath 2 大大扩展和修改了cdpȝ。Java XPath API 支持 XPath 2 所需的主要修Ҏ?XPath 2 新数据类型增加常量?
|
|
?Java 中计?XPath 表达式时Q第二个参数指定需要的q回cd。有五种可能Q都?javax.xml.xpath.XPathConstants
cM命名了常量:
-
XPathConstants.NODESET
-
XPathConstants.BOOLEAN
-
XPathConstants.NUMBER
-
XPathConstants.STRING
-
XPathConstants.NODE
最后一?XPathConstants.NODE
实际上没有匹配的 XPath cd。只有知?XPath 表达式只q回一个节Ҏ者只需要一个节Ҏ才用它。如?XPath 表达式返回了多个节点q且指定?XPathConstants.NODE
Q则 evaluate()
按照文档序q回W一个节炏V如?XPath 表达式选择了一个空集ƈ指定?XPathConstants.NODE
Q则 evaluate()
q回 null?
如果不能完成要求的{换,evaluate()
抛?XPathException
?
名称I间上下?/font>
?XML 文档中的元素在名U空间中Q查询该文档?XPath 表达式必M用相同的名称I间。XPath 表达式不一定要使用相同的前~Q只需要名U空?URI 相同卛_。事实上Q如?XML 文档使用默认名称I间Q那么尽目标文档没有用前~QXPath 表达式也必须使用前缀?
但是QJava E序不是 XML 文档Q因此不能用一般的名称I间解析。必L供一个对象将前缀映射到名U空?URI。该对象?javax.xml.namespace.NamespaceContext
接口的实例。比如,假设图书文档攑֜ http://www.example.com/books 名称I间中,?清单 5 所C:
清单 5. 使用默认名称I间?XML 文档
<inventory xmlns="http://www.example.com/books">
<book year="2000">
<title>Snow Crash</title>
<author>Neal Stephenson</author>
<publisher>Spectra</publisher>
<isbn>0553380958</isbn>
<price>14.95<price>
</book>
<!-- more books... -->
<inventory>
|
查找 Neal Stephenson 全部著作标题?XPath 表达式就要改?//pre:book[pre:author="Neal Stephenson"]/pre:title/text()
。但是,必须前~ pre
映射?URI http://www.example.com/books?code>NamespaceContext 接口?Java 软g开发工LQJDKQ或 JAXP 中没有默认实C乎有点笨Q但实如此。不q,自己实现也不难?a >清单 6 对一个名U空间给Z单的实现。还需要映?xml
前缀?
清单 6. l定一个名U空间和默认名称I间的简单上下文
import java.util.Iterator;
import javax.xml.*;
import javax.xml.namespace.NamespaceContext;
public class PersonalNamespaceContext implements NamespaceContext {
public String getNamespaceURI(String prefix) {
if (prefix == null) throw new NullPointerException("Null prefix");
else if ("pre".equals(prefix)) return "http://www.example.org/books";
else if ("xml".equals(prefix)) return XMLConstants.XML_NS_URI;
return XMLConstants.NULL_NS_URI;
}
// This method isn't necessary for XPath processing.
public String getPrefix(String uri) {
throw new UnsupportedOperationException();
}
// This method isn't necessary for XPath processing either.
public Iterator getPrefixes(String uri) {
throw new UnsupportedOperationException();
}
}
|
使用映射存储l定和增?setter Ҏ实现名称I间上下文的重用也不难?
创徏 NamespaceContext
对象后,在编译表辑ּ之前其安装?XPath
对象上。以后就可以像以前一h用这些前~查询了。比如:
清单 7. 使用名称I间?XPath 查询
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
xpath.setNamespaceContext(new PersonalNamespaceContext());
XPathExpression expr
= xpath.compile("http://pre:book[pre:author='Neal Stephenson']/pre:title/text()");
Object result = expr.evaluate(doc, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(nodes.item(i).getNodeValue());
}
|
函数求解?/font>
有时候,?Java 语言中定义用?XPath 表达式的扩展函数很有用。这些函数可以执行用U?XPath 很难或者无法执行的d。不q必L真正的函敎ͼ而不是随意的Ҏ。就是说不能有副作用。(XPath 函数可以按照L的顺序求gQ意多ơ。)
通过 Java XPath API 讉K的扩展函数必d?javax.xml.xpath.XPathFunction
接口。这个接口只声明了一个方?evaluateQ?
public Object evaluate(List args) throws XPathFunctionException
|
该方法必返?Java 语言能够转换?XPath 的五U类型之一Q?
-
String
-
Double
-
Boolean
-
Nodelist
-
Node
比如Q?a >清单 8 昄了一个扩展函敎ͼ它检?ISBN 的校验和q返?Boolean
。这个校验和的基本规则是前九位数的每一位乘上它的位|(即第一位数乘上 1Q第二位C?2Q依ơ类推)。将q些数加h然后取除?11 的余数。如果余数是 10Q那么最后一位数是 X?
清单 8. ?ISBN ?XPath 扩展函数
import java.util.List;
import javax.xml.xpath.*;
import org.w3c.dom.*;
public class ISBNValidator implements XPathFunction {
// This class could easily be implemented as a Singleton.
public Object evaluate(List args) throws XPathFunctionException {
if (args.size() != 1) {
throw new XPathFunctionException("Wrong number of arguments to valid-isbn()");
}
String isbn;
Object o = args.get(0);
// perform conversions
if (o instanceof String) isbn = (String) args.get(0);
else if (o instanceof Boolean) isbn = o.toString();
else if (o instanceof Double) isbn = o.toString();
else if (o instanceof NodeList) {
NodeList list = (NodeList) o;
Node node = list.item(0);
// getTextContent is available in Java 5 and DOM 3.
// In Java 1.4 and DOM 2, you'd need to recursively
// accumulate the content.
isbn= node.getTextContent();
}
else {
throw new XPathFunctionException("Could not convert argument type");
}
char[] data = isbn.toCharArray();
if (data.length != 10) return Boolean.FALSE;
int checksum = 0;
for (int i = 0; i < 9; i++) {
checksum += (i+1) * (data[i]-'0');
}
int checkdigit = checksum % 11;
if (checkdigit + '0' == data[9] || (data[9] == 'X' && checkdigit == 10)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
|
下一步让q个扩展函数能够?Java E序中用。ؓ此,需要在~译表达式之前向 XPath 对象安装 javax.xml.xpath.XPathFunctionResolver
。函数求解器函数的 XPath 名称和名U空?URI 映射到实现该函数?Java cR?a >清单 9 是一个简单的函数求解器,扩展函?valid-isbn
和名U空?http://www.example.org/books 映射?清单 8 中的cR比如,XPath 表达?//book[not(pre:valid-isbn(isbn))]
可以扑ֈ ISBN 校验和不匚w的所有图书?
清单 9. 识别 valid-isbn 扩展函数的上下文
iimport javax.xml.namespace.QName;
import javax.xml.xpath.*;
public class ISBNFunctionContext implements XPathFunctionResolver {
private static final QName name
= new QName("http://www.example.org/books", "valid-isbn");
public XPathFunction resolveFunction(QName name, int arity) {
if (name.equals(ISBNFunctionContext.name) && arity == 1) {
return new ISBNValidator();
}
return null;
}
}
|
׃扩展函数必须有名U空_所以计包含扩展函数的表达式时必须使用 NamespaceResolver
Q即便查询的文档没有使用M名称I间。由?XPathFunctionResolver
?code>XPathFunction ?NamespaceResolver
都是接口Q如果方便的话可以将它们攑֜所有的cM?
l束?/font>
?SQL ?XPath q样的声明性语a~写查询Q要比?Java ?C q样的命令式语言Ҏ得多。但是,?Java ?C q样的图灵完整语a~写复杂的逻辑Q又?SQL ?XPath q样的声明性语aҎ得多。所q的是,通过使用 Java Database Connectivity (JDBC) ?javax.xml.xpath
之类?API 可以两者结合v来。随着世界上越来越多的数据转向 XMLQ?code>javax.xml.xpath 与 java.sql
一样变得越来越重要?/p>

]]>