xpath api_Java XPath API

如果您派人去购买一加仑的牛奶,您想告诉那个人什么? “请去买一加仑的牛奶。” 或者,“通过前门离开房屋。在人行道上左转。向右走3个街区。向右走。半个街区。向右转进入商店。转到第4走道。沿着走道向下走5米。 ”拿起一加仑水罐的牛奶。把它带到结帐柜台。付钱。然后往后退一步。” 这是荒谬的。 大多数成年人都足够聪明,可以自行购买牛奶,而无需像“请去购买一加仑牛奶”这样的指导。

查询语言和计算机搜索相似。 说“查找Cryptonomicon的副本”要比编写用于搜索某些数据库的详细逻辑要容易得多。 因为搜索操作的逻辑非常相似,所以您可以发明一些通用的语言,使您可以发表诸如“查找Neal Stephenson的所有书籍”之类的语句,然后编写一个引擎来处理针对某些数据存储的那些查询。

XPath

在许多查询语言中,结构化查询语言(SQL)是为查询某些类型的关系数据库而设计和优化的语言。 其他不太熟悉的查询语言包括对象查询语言(OQL)和XQuery。 但是,本文的主题是XPath,这是一种用于查询XML文档的查询语言。 例如,一个简单的XPath查询将查找作者为Neal Stephenson的文档中所有书籍的标题,如下所示:

//book[author="Neal Stephenson"]/title

相比之下,对相同信息的纯DOM搜索将类似于清单1

清单1.查找Neal Stephenson的书的所有标题元素的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表达式那样通用或健壮。 您更愿意编写,调试和维护哪个? 我认为答案很明显。

但是,XPath实际上是表达性的,它不是Java语言-实际上,XPath不是完整的编程语言。 在XPath中,您无法说很多话,甚至您也无法查询。 例如,XPath找不到国际标准书号(ISBN)校验数字不匹配的所有书籍,或者外部帐户数据库显示应缴纳版权费的所有作者。 幸运的是,可以将XPath集成到Java程序中,从而使您两全其美:Java是Java的优势,而XPath是XPath的优势。

直到最近,Java程序用来进行XPath查询的确切应用程序接口(API)随XPath引擎而变化。 Xalan有一个API,Saxon有另一个,其他引擎有其他API。 这意味着您的代码倾向于将您锁定在一个产品中。 理想情况下,您希望能够试验具有不同性能特征的不同引擎,而不会造成不必要的麻烦或代码重写。

因此,Java 5引入了javax.xml.xpath包,以提供独立于引擎和对象模型的XPath库。 如果单独安装用于XML处理的Java API(JAXP)1.3,则此包在Java 1.3和更高版本中也可用。 在其他产品中,Xalan 2.7和Saxon 8包括该库的实现。

一个简单的例子

我将首先演示如何在实践中实际工作。 然后,我将深入研究一些细节。 假设您要查询一本书籍清单以查找由尼尔·斯蒂芬森(Neal Stephenson)撰写的书籍。 特别是,假设该列表采用清单2所示的形式:

清单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>

查找所有书籍的XPath查询非常简单: //book[author="Neal Stephenson"] 。 要查找这些书的书名,只需再增加一个步骤,使表达式变为//book[author="Neal Stephenson"]/title 。 最后,您真正想要的是title元素的文本节点子代。 这需要再执行一步,因此完整的表达式为//book[author="Neal Stephenson"]/title/text()

现在,我将生成一个简单的程序,该程序从Java语言执行此搜索,然后打印出找到的所有书籍的标题。 首先,您需要将文档加载到DOM Document对象中。 为简单起见,我假设该文档位于当前工作目录的books.xml文件中。 这是一个简单的代码片段,用于解析文档并构造相应的Document对象:

清单3.用JAXP解析文档
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); // never forget this! DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse("books.xml");

到目前为止,这只是标准的JAXP和DOM,没有什么真正的新鲜事物。

接下来,创建一个XPathFactory

XPathFactory factory = XPathFactory.newInstance();

然后,您可以使用此工厂创建XPath对象:

XPath xpath = factory.newXPath();

XPath对象编译XPath表达式:

XPathExpression expr = xpath.compile("//book[author='Neal Stephenson']/title/text()");

最后,您对XPath表达式求值以获得结果。 相对于特定上下文节点(在这种情况下为整个文档)评估表达式。 还必须指定返回类型。 在这里,我要求返回一个节点:

Object result = expr.evaluate(doc, XPathConstants.NODESET);

然后,您可以将结果NodeList转换为DOM NodeList并对其进行迭代以找到所有标题:

NodeList nodes = (NodeList) result; for (int i = 0; i < nodes.getLength(); i++) { System.out.println(nodes.item(i).getNodeValue()); }

清单4将所有这些放到一个程序中。 还要注意,这些方法可能引发一些必须在throws子句中声明的已检查异常,尽管我在上面对它们进行了介绍:

清单4.使用固定的XPath表达式查询XML文档的完整程序
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("//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)时,都需要将它们粘合在一起的明显缝隙。 并非所有内容都恰到好处。 XPath和Java语言没有相同的类型系统。 XPath 1.0只有四种基本数据类型:

  • 节点集
  • 布尔值

Java语言当然还有更多,包括用户定义的对象类型。

大多数XPath表达式,尤其是位置路径,都会返回节点集。 但是,还有其他可能性。 例如,XPath表达式count(//book)返回文档中的书数。 XPath表达式count(//book[@author="Neal Stephenson"]) > 10 count(//book[@author="Neal Stephenson"]) > 10返回布尔值:如果文档中Neal Stephenson的书籍多于十本,则为true;如果少于十本,则为false。

声明了evaluate()方法以返回Object 。 它实际返回的内容取决于XPath表达式的结果以及您要求的类型。 一般来说,XPath

  • 数字映射到java.lang.Double
  • 字符串映射到java.lang.String
  • 布尔值映射到java.lang.Boolean
  • 节点集映射到org.w3c.dom.NodeList

当您使用Java评估XPath表达式时,第二个参数指定所需的返回类型。 有五种可能性, javax.xml.xpath.XPathConstants类中的所有命名常量:

  • XPathConstants.NODESET
  • XPathConstants.BOOLEAN
  • XPathConstants.NUMBER
  • XPathConstants.STRING
  • XPathConstants.NODE

最后一个XPathConstants.NODE实际上与XPath类型不匹配。 当您知道XPath表达式仅返回一个节点或您不希望有多个节点时,可以使用它。 如果XPath表达式确实返回了多个节点,并且您已指定XPathConstants.NODE ,则XPathConstants.NODE evaluate() XPathConstants.NODE文档顺序返回第一个节点。 如果XPath表达式选择一个空集,并且您已指定XPathConstants.NODE ,则XPathConstants.NODE evaluate()返回null。

如果无法进行请求的转换,则XPathException evaluate()抛出XPathException

命名空间上下文

如果XML文档中的元素在名称空间中,则用于查询该文档的XPath表达式必须使用相同的名称空间。 XPath表达式不需要使用相同的前缀,而只需使用相同的名称空间URI。 实际上,当XML文档使用默认名称空间时,即使目标文档没有使用,XPath表达式也必须使用前缀。

但是,Java程序不是XML文档,因此常规名称空间解析不适用。 而是提供一个将前缀映射到名称空间URI的对象。 该对象是javax.xml.namespace.NamespaceContext接口的实例。 例如,假设将books文档放置在http://www.example.com/books命名空间中,如清单5所示

清单5.使用默认名称空间的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。 在Java软件开发工具包(JDK)或JAXP中没有NamespaceContext接口没有默认实现的做法有点愚蠢,但是没有。 但是,实现自己并不难。 清单6演示了仅针对此名称空间的简单实现。 您还应该映射xml前缀。

清单6.用于绑定单个名称空间和默认名称的简单上下文
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.com/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(); } }

使用映射存储绑定并添加允许更可重用的命名空间上下文的setter方法并不难。

创建NamespaceContext对象后,请在编译表达式之前将其安装在XPath对象上。 从那时起,您可以像以前一样使用这些前缀进行查询。 例如:

清单7.使用名称空间的XPath查询
XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); xpath.setNamespaceContext(new PersonalNamespaceContext()); XPathExpression expr = xpath.compile("//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()); }

功能解析器

有时,用Java语言定义扩展功能以在XPath表达式中使用很有用。 这些功能执行的任务很难用纯XPath完成。 但是,它们应该是真正的函数,而不是简单的任意方法。 也就是说,它们应该没有副作用。 (可以按任意顺序和任意次数评估XPath函数。)

通过Java XPath API访问的扩展功能必须实现javax.xml.xpath.XPathFunction接口。 此接口声明一个方法,求值:

public Object evaluate(List args) throws XPathFunctionException

此方法应返回Java语言可以转换为XPath的五种类型之一:

  • String
  • Double
  • Boolean
  • Nodelist
  • Node

例如, 清单8显示了一个扩展函数,该函数在ISBN中验证校验和并返回Boolean 。 此校验和的基本规则是,前九个数字中的每个数字都乘以其位置(即,第一个数字乘以一,第二个数字乘以二,依此类推)。 这些值相加,然后除以十一。 如果余数是十,则最后一位是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; } }

下一步是使扩展功能可用于Java程序。 为此,请在编译表达式之前在XPath对象中安装javax.xml.xpath.XPathFunctionResolver 。 函数解析器将函数的XPath名称和名称空间URI映射到实现该函数的Java类。 清单9是一个简单的函数解析器,它将带有名称空间http://www.example.com/books的扩展功能valid-isbn映射到清单8中的类。 例如,XPath表达式//book[not(pre:valid-isbn(isbn))]查找所有ISBN校验和不匹配的书。

清单9.识别有效的isbn扩展功能的函数上下文
import javax.xml.namespace.QName; import javax.xml.xpath.*; public class ISBNFunctionContext implements XPathFunctionResolver { private static final QName name = new QName("http://www.example.com/books", "valid-isbn"); public XPathFunction resolveFunction(QName name, int arity) { if (name.equals(ISBNFunctionContext.name) && arity == 1) { return new ISBNValidator(); } return null; } }

因为扩展功能必须在名称空间中,所以即使要查询的文档根本不使用名称空间,也必须在评估包含扩展功能的表达式时使用NamespaceResolver 。 由于XPathFunctionResolverXPathFunctionNamespaceResolver是接口,如果方便的话,甚至可以将它们全部放在同一个类中。

结论

用声明性语言(如SQL和XPath)编写查询要比用命令式语言(如Java和C)编写查询要容易得多。用图灵完整的语言(如Java和C)编写复杂的逻辑比用Java语言编写更复杂得多。声明性语言,例如SQL和XPath。 幸运的是,可以使用诸如Java数据库连接(JDBC)和javax.xml.xpath API来混合两者。 随着世界上越来越多的数据转移到XML, javax.xml.xpath将变得与java.sql一样重要。


翻译自: https://www.ibm.com/developerworks/java/library/x-javaxpathapi/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值