Java认证辅导之关于Java语言的XPathAPI(5)
" j9 I& h/ _6 t% ~& j名称空间上下文
% I- ^9 S& o/ f0 N若 XML 文档中的元素在名称空间中,查询该文档的 XPath 表达式必须使用相同的名称空间。XPath 表达式不一定要使用相同的前缀,只需要名称空间 URI 相同即可。事实上,如果 XML 文档使用默认名称空间,那么尽管目标文档没有使用前缀,XPath 表达式也必须使用前缀。
( Z1 _4 w7 L0 `% Y6 M" f但是,Java 程序不是 XML 文档,因此不能用一般的名称空间解析。必须提供一个对象将前缀映射到名称空间 URI。该对象是javax.xml.namespace.NamespaceContext 接口的实例。比如,假设图书文档放在 http://www.example.com/books 名称空间中,如 清单 5 所示: ]. G* Q" f: P" ^9 v s
清单 5. 使用默认名称空间的 XML 文档
4 H- a$ T* p1 Y/ ]3 S6 O$ k5 M0 I: A7 i
Snow Crash Neal Stephenson Spectra 0553380958 14.95 查找 Neal Stephenson 全部著作标题的 XPath 表达式就要改为 //pre:book[pre:author="Neal Stephenson"]/pre:title/text()。但是,必须将前缀 pre 映射到 URI http://www.example.com/books。NamespaceContext 接口在 Java 软件开发工具箱(JDK)或 JAXP 中没有默认实现似乎有点笨,但确实如此。不过,自己实现也不难。清单 6 对一个名称空间给出了简单的实现。还需要映射xml 前缀。
6 [2 m6 Q4 q/ g# d) g( N" B+ a' V清单 6. 绑定一个名称空间和默认名称空间的简单上下文
$ ~1 b' U7 C: { {1 \* W& timport 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(); } } 用映射存储绑定和增加 setter 方法实现名称空间上下文的重用也不难。创建 NamespaceContext 对象后,在编译表达式之前将其安装到 XPath 对象上。以后就可以像以前一样是用这些前缀查询了。比如:" x) |* _5 j* c6 N, B, }
清单 7. 使用名称空间的 XPath 查询/ h5 J! R' \ G7 g
: I$ g) t# s* G' f4 x4 g6 CXPathFactory 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()); } 函数求解器
2 r% b* U" b! q% p2 @" r3 Y9 s有时候,在 Java 语言中定义用于 XPath 表达式的扩展函数很有用。这些函数可以执行用纯 XPath 很难或者无法执行的任务。不过必须是真正的函数,而不是随意的方法。就是说不能有副作用。(XPath 函数可以按照任意的顺序求值任意多次。)
! `1 g8 X0 e7 c; N. M, L通过 Java XPath API 访问的扩展函数必须实现 javax.xml.xpath.XPathFunction 接口。这个接口只声明了一个方法 evaluate:# X4 r1 V4 ^! \& s& d# F
, f' f& w5 m# R' b, o1 Y) Z( y
public Object evaluate(List args) throws XPathFunctionException 该方法必须返回 Java 语言能够转换到 XPath 的五种类型之一:
$ i1 L9 Z& z% S) q" v◆ String
3 z: {9 O; I( p◆ Double: N) m) v: M' {0 R
◆ Boolean2 f; `4 k4 m: a1 Z2 x" C
◆ Nodelist2 I% U0 l1 ?/ V- x: X, a( b( J
◆ Node" e6 k7 j" F2 |7 z
比如,清单 8 显示了一个扩展函数,它检查 ISBN 的校验和并返回 Boolean。这个校验和的基本规则是前九位数的每一位乘上它的位置(即第一位数乘上 1,第二位数乘上 2,依次类推)。将这些数加起来然后取除以 11 的余数。如果余数是 10,那么最后一位数就是 X。
* B, L0 |# p- f/ I) M, A清单 8. 检查 ISBN 的 XPath 扩展函数- e- V# Z/ R6 Q" ^
5 r `$ ~. G* }* ~: A% B- H
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-'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是一个简单的函数求解器,将扩展函数 valid-isbn 和名称空间 http://www.example.org/books 映射到 清单 8 中的类。比如,XPath 表达式 //book[not(pre:valid-isbn(isbn))] 可以找到 ISBN 校验和不匹配的所有图书。& X" e4 V% g! F s
清单 9. 识别 valid-isbn 扩展函数的上下文$ _8 q$ j0 m/ s9 x* T" I
! d! Z4 |3 V2 O) U: w9 Dimport 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; } } 由于扩展函数必须有名称空间,所以计算包含扩展函数的表达式时必须使用 NamespaceResolver,即便查询的文档没有使用任何名称空间。由于 XPathFunctionResolver、XPathFunction 和 NamespaceResolver 都是接口,如果方便的话可以将它们放在所有的类中。 |