|
22 | 22 | import java.io.OutputStream; |
23 | 23 | import java.io.UnsupportedEncodingException; |
24 | 24 |
|
| 25 | +import javax.xml.namespace.QName; |
25 | 26 | import javax.xml.parsers.DocumentBuilderFactory; |
26 | 27 | import javax.xml.parsers.ParserConfigurationException; |
| 28 | +import javax.xml.xpath.XPath; |
| 29 | +import javax.xml.xpath.XPathConstants; |
| 30 | +import javax.xml.xpath.XPathExpression; |
| 31 | +import javax.xml.xpath.XPathExpressionException; |
| 32 | +import javax.xml.xpath.XPathFactory; |
27 | 33 |
|
28 | 34 | import org.slf4j.Logger; |
29 | 35 | import org.slf4j.LoggerFactory; |
30 | 36 | import org.w3c.dom.DOMException; |
31 | 37 | import org.w3c.dom.Document; |
| 38 | +import org.w3c.dom.Node; |
| 39 | +import org.w3c.dom.NodeList; |
32 | 40 | import org.w3c.dom.ls.DOMImplementationLS; |
33 | 41 | import org.w3c.dom.ls.LSException; |
34 | 42 | import org.w3c.dom.ls.LSInput; |
@@ -60,6 +68,7 @@ public class DOMHandle |
60 | 68 | private LSResourceResolver resolver; |
61 | 69 | private Document content; |
62 | 70 | private DocumentBuilderFactory factory; |
| 71 | + private XPath xpathProcessor; |
63 | 72 |
|
64 | 73 | /** |
65 | 74 | * Creates a factory to create a DOMHandle instance for a DOM document. |
@@ -221,6 +230,133 @@ protected DocumentBuilderFactory makeDocumentBuilderFactory() throws ParserConfi |
221 | 230 | return factory; |
222 | 231 | } |
223 | 232 |
|
| 233 | + /** |
| 234 | + * Get the processor used to evaluate XPath expressions. |
| 235 | + * You might get the XPath processor to configure it. For instance, |
| 236 | + * you can configure the XPath processor to declare namespace |
| 237 | + * bindings or to set a function or variable resolver. |
| 238 | + * @see com.marklogic.client.util.EditableNamespaceContext |
| 239 | + * @return the XPath expression processor |
| 240 | + */ |
| 241 | + public XPath getXPathProcessor() { |
| 242 | + if (xpathProcessor == null) |
| 243 | + xpathProcessor = makeXPathProcessorFactory().newXPath(); |
| 244 | + return xpathProcessor; |
| 245 | + } |
| 246 | + /** |
| 247 | + * Specifies the processor used to evaluate XPath expressions against |
| 248 | + * the document. |
| 249 | + * @param xpathProcessor the XPath expression processor |
| 250 | + */ |
| 251 | + public void setXPathProcessor(XPath xpathProcessor) { |
| 252 | + this.xpathProcessor = xpathProcessor; |
| 253 | + } |
| 254 | + protected XPathFactory makeXPathProcessorFactory() { |
| 255 | + return XPathFactory.newInstance(); |
| 256 | + } |
| 257 | + |
| 258 | + /** |
| 259 | + * Evaluate a string XPath expression against the retrieved document. |
| 260 | + * An XPath expression can return a Node or subinterface such as |
| 261 | + * Element or Text, a NodeList, or a Boolean, Number, or String value. |
| 262 | + * @param xpathExpression the XPath expression as a string |
| 263 | + * @param as the type of the value |
| 264 | + * @return the value produced by the XPath expression |
| 265 | + */ |
| 266 | + public <T> T evaluateXPath(String xpathExpression, Class<T> as) |
| 267 | + throws XPathExpressionException { |
| 268 | + return evaluateXPath(xpathExpression, get(), as); |
| 269 | + } |
| 270 | + /** |
| 271 | + * Evaluate a string XPath expression relative to a node such as a node |
| 272 | + * returned by a previous XPath expression. |
| 273 | + * An XPath expression can return a Node or subinterface such as |
| 274 | + * Element or Text, a NodeList, or a Boolean, Number, or String value. |
| 275 | + * @param xpathExpression the XPath expression as a string |
| 276 | + * @param context the node for evaluating the expression |
| 277 | + * @param as the type of the value |
| 278 | + * @return the value produced by the XPath expression |
| 279 | + */ |
| 280 | + public <T> T evaluateXPath(String xpathExpression, Node context, Class<T> as) |
| 281 | + throws XPathExpressionException { |
| 282 | + checkContext(context); |
| 283 | + return castAs( |
| 284 | + getXPathProcessor().evaluate(xpathExpression, context, returnXPathConstant(as)), |
| 285 | + as |
| 286 | + ); |
| 287 | + } |
| 288 | + /** |
| 289 | + * Compile an XPath string expression for efficient evaluation later. |
| 290 | + * @param xpathExpression the XPath expression as a string |
| 291 | + * @return the compiled XPath expression |
| 292 | + */ |
| 293 | + public XPathExpression compileXPath(String xpathExpression) |
| 294 | + throws XPathExpressionException { |
| 295 | + return getXPathProcessor().compile(xpathExpression); |
| 296 | + } |
| 297 | + /** |
| 298 | + * Evaluate a compiled XPath expression against the retrieved document. |
| 299 | + * An XPath expression can return a Node or subinterface such as |
| 300 | + * Element or Text, a NodeList, or a Boolean, Number, or String value. |
| 301 | + * @param xpathExpression an XPath expression compiled previously |
| 302 | + * @param as the type of the value |
| 303 | + * @return the value produced by the XPath expression |
| 304 | + */ |
| 305 | + public <T> T evaluateXPath(XPathExpression xpathExpression, Class<T> as) |
| 306 | + throws XPathExpressionException { |
| 307 | + return evaluateXPath(xpathExpression, get(), as); |
| 308 | + } |
| 309 | + /** |
| 310 | + * Evaluate a compiled XPath expression relative to a node such as a node |
| 311 | + * returned by a previous XPath expression. |
| 312 | + * An XPath expression can return a Node or subinterface such as |
| 313 | + * Element or Text, a NodeList, or a Boolean, Number, or String value. |
| 314 | + * @param xpathExpression an XPath expression compiled previously |
| 315 | + * @param context the node for evaluating the expression |
| 316 | + * @param as the type of the value |
| 317 | + * @return the value produced by the XPath expression |
| 318 | + */ |
| 319 | + public <T> T evaluateXPath(XPathExpression xpathExpression, Node context, Class<T> as) |
| 320 | + throws XPathExpressionException { |
| 321 | + checkContext(context); |
| 322 | + return castAs( |
| 323 | + xpathExpression.evaluate(context, returnXPathConstant(as)), |
| 324 | + as |
| 325 | + ); |
| 326 | + } |
| 327 | + protected void checkContext(Node context) { |
| 328 | + if (context == null) { |
| 329 | + throw new IllegalStateException("Cannot process empty context"); |
| 330 | + } |
| 331 | + } |
| 332 | + protected QName returnXPathConstant(Class<?> as) { |
| 333 | + if (as == null) { |
| 334 | + throw new IllegalArgumentException("cannot execute XPath as null"); |
| 335 | + } else if (Node.class.isAssignableFrom(as)) { |
| 336 | + return XPathConstants.NODE; |
| 337 | + } else if (NodeList.class.isAssignableFrom(as)) { |
| 338 | + return XPathConstants.NODESET; |
| 339 | + } else if (String.class.isAssignableFrom(as)) { |
| 340 | + return XPathConstants.STRING; |
| 341 | + } else if (Number.class.isAssignableFrom(as)) { |
| 342 | + return XPathConstants.NUMBER; |
| 343 | + } else if (Boolean.class.isAssignableFrom(as)) { |
| 344 | + return XPathConstants.BOOLEAN; |
| 345 | + } |
| 346 | + throw new IllegalArgumentException("cannot execute XPath as "+as.getName()); |
| 347 | + } |
| 348 | + protected <T> T castAs(Object result, Class<?> as) { |
| 349 | + if (result == null) { |
| 350 | + return null; |
| 351 | + } |
| 352 | + if (!as.isAssignableFrom(result.getClass())) { |
| 353 | + throw new IllegalArgumentException("cannot cast "+result.getClass().getName()+" to "+as.getName()); |
| 354 | + } |
| 355 | + @SuppressWarnings("unchecked") |
| 356 | + T typedResult = (T) result; |
| 357 | + return typedResult; |
| 358 | + } |
| 359 | + |
224 | 360 | @Override |
225 | 361 | protected Class<InputStream> receiveAs() { |
226 | 362 | return InputStream.class; |
|
0 commit comments