Java认证辅导之关于Java语言的XPathAPI(5)9 L6 P7 ~. t6 j. x! O' s a
名称空间上下文3 B; t' E. z7 R3 m! c$ X& z) W9 z; \
若 XML 文档中的元素在名称空间中,查询该文档的 XPath 表达式必须使用相同的名称空间。XPath 表达式不一定要使用相同的前缀,只需要名称空间 URI 相同即可。事实上,如果 XML 文档使用默认名称空间,那么尽管目标文档没有使用前缀,XPath 表达式也必须使用前缀。
4 E( k# k8 b3 S& k: w但是,Java 程序不是 XML 文档,因此不能用一般的名称空间解析。必须提供一个对象将前缀映射到名称空间 URI。该对象是javax.xml.namespace.NamespaceContext 接口的实例。比如,假设图书文档放在 http://www.example.com/books 名称空间中,如 清单 5 所示:/ t) J1 P2 b9 d7 y8 `
清单 5. 使用默认名称空间的 XML 文档3 A: @. Z! M; y/ k# j7 o$ N
3 j I- B0 c, V1 m6 r& {# F0 P4 o 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 前缀。
8 i/ ^9 k& G' n2 R清单 6. 绑定一个名称空间和默认名称空间的简单上下文; \+ t/ X. O" |
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(); } } 用映射存储绑定和增加 setter 方法实现名称空间上下文的重用也不难。创建 NamespaceContext 对象后,在编译表达式之前将其安装到 XPath 对象上。以后就可以像以前一样是用这些前缀查询了。比如:
: T" G8 o* e( Q6 _: f/ U清单 7. 使用名称空间的 XPath 查询
9 i; W. u' ~$ h+ c; w" x7 v; w. _4 S
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()); } 函数求解器! r" y$ a0 X* m1 S" f% l
有时候,在 Java 语言中定义用于 XPath 表达式的扩展函数很有用。这些函数可以执行用纯 XPath 很难或者无法执行的任务。不过必须是真正的函数,而不是随意的方法。就是说不能有副作用。(XPath 函数可以按照任意的顺序求值任意多次。)
% s3 K7 J8 Q' _7 P; N7 |通过 Java XPath API 访问的扩展函数必须实现 javax.xml.xpath.XPathFunction 接口。这个接口只声明了一个方法 evaluate:% u" A2 g, b- k5 |" L
# T0 B! w# X9 E4 h2 S: Cpublic Object evaluate(List args) throws XPathFunctionException 该方法必须返回 Java 语言能够转换到 XPath 的五种类型之一:
* S$ T, j8 S8 Q# |* N5 \◆ String
; g7 x4 _- M2 l2 f6 ~* o1 Q) t3 u& f◆ Double
: g. b- c! F+ R! [: m& [9 B6 [◆ Boolean. h0 }6 L* P8 T9 b
◆ Nodelist" Y5 @8 G. |- Z! R; Z0 F* b
◆ Node
3 t6 w9 o; h) h/ g/ O; B比如,清单 8 显示了一个扩展函数,它检查 ISBN 的校验和并返回 Boolean。这个校验和的基本规则是前九位数的每一位乘上它的位置(即第一位数乘上 1,第二位数乘上 2,依次类推)。将这些数加起来然后取除以 11 的余数。如果余数是 10,那么最后一位数就是 X。! D: {7 V: J! G$ _- m) V
清单 8. 检查 ISBN 的 XPath 扩展函数1 n$ k: ? g( _; u {
' p6 Y G, Y, |, q6 P8 Y8 c' ~
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 校验和不匹配的所有图书。% M4 [' ^9 G' U) r$ c' l
清单 9. 识别 valid-isbn 扩展函数的上下文3 s2 j6 e' \) S% B# P' K6 W: K
3 q7 h ^; _& | ~: G/ w% A
import 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 都是接口,如果方便的话可以将它们放在所有的类中。 |