| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| XPathWrapper |
|
| 1.68;1.68 | ||||
| XPathWrapper$1 |
|
| 1.68;1.68 | ||||
| XPathWrapper$MyVariableResolver |
|
| 1.68;1.68 |
| 1 | // Copyright 2008-2014 severally by the contributors | |
| 2 | // | |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 | // you may not use this file except in compliance with the License. | |
| 5 | // You may obtain a copy of the License at | |
| 6 | // | |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | // | |
| 9 | // Unless required by applicable law or agreed to in writing, software | |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 | // See the License for the specific language governing permissions and | |
| 13 | // limitations under the License. | |
| 14 | ||
| 15 | package net.sf.practicalxml.xpath; | |
| 16 | ||
| 17 | import java.util.ArrayList; | |
| 18 | import java.util.HashMap; | |
| 19 | import java.util.List; | |
| 20 | import java.util.Map; | |
| 21 | import javax.xml.namespace.QName; | |
| 22 | import javax.xml.xpath.XPath; | |
| 23 | import javax.xml.xpath.XPathConstants; | |
| 24 | import javax.xml.xpath.XPathExpression; | |
| 25 | import javax.xml.xpath.XPathExpressionException; | |
| 26 | import javax.xml.xpath.XPathFactory; | |
| 27 | import javax.xml.xpath.XPathFunction; | |
| 28 | import javax.xml.xpath.XPathVariableResolver; | |
| 29 | ||
| 30 | import org.w3c.dom.Element; | |
| 31 | import org.w3c.dom.Node; | |
| 32 | import org.w3c.dom.NodeList; | |
| 33 | ||
| 34 | import net.sf.practicalxml.DomUtil; | |
| 35 | import net.sf.practicalxml.XmlException; | |
| 36 | ||
| 37 | ||
| 38 | ||
| 39 | /** | |
| 40 | * This class simplifies the use of XPath expressions, hiding the factory and | |
| 41 | * return types, and providing a simple builder-style interface for adding | |
| 42 | * resolvers. It also maintains the expression in a compiled form, improving | |
| 43 | * reuse performance. | |
| 44 | * <p> | |
| 45 | * <strong>Warning:</strong> | |
| 46 | * <code>XPathWrapper</code>, like the underlying JDK <code>XPath</code> object, | |
| 47 | * is not thread-safe. However, separate instances may be created and evaluated | |
| 48 | * on separate threads without explicit synchronization. To make this work, we | |
| 49 | * create a new <code>XPathFactory</code> instance for each wrapper (with the | |
| 50 | * Sun JDK, this adds little overhead), synchronizing <code>newInstance()</code> | |
| 51 | * on <code>XPathWrapper.class</code>. | |
| 52 | */ | |
| 53 | 37 | public class XPathWrapper |
| 54 | implements Cloneable | |
| 55 | { | |
| 56 | private final String _expr; | |
| 57 | 201 | private final NamespaceResolver _nsResolver = new NamespaceResolver(); |
| 58 | 201 | private Map<QName,Object> _variables = new HashMap<QName,Object>(); |
| 59 | 201 | private FunctionResolver _functions = new FunctionResolver(); |
| 60 | ||
| 61 | private XPathExpression _compiled; | |
| 62 | ||
| 63 | ||
| 64 | /** | |
| 65 | * Creates a new instance, which may then be customized with various | |
| 66 | * resolvers, and used to evaluate expressions. | |
| 67 | */ | |
| 68 | public XPathWrapper(String expr) | |
| 69 | 201 | { |
| 70 | 201 | _expr = expr; |
| 71 | 201 | } |
| 72 | ||
| 73 | ||
| 74 | //---------------------------------------------------------------------------- | |
| 75 | // Public methods | |
| 76 | //---------------------------------------------------------------------------- | |
| 77 | ||
| 78 | /** | |
| 79 | * Adds a namespace binding to this expression. All bindings must be | |
| 80 | * added prior to the first call to <code>evaluate()</code>. If you add | |
| 81 | * multiple bindings with the same namespace, only the last is retained. | |
| 82 | * | |
| 83 | * @param prefix The prefix used to reference this namespace in the | |
| 84 | * XPath expression. Note that this does <em>not</em> | |
| 85 | * need to be the same prefix used by the document. | |
| 86 | * @param nsURI The namespace URI to associate with this prefix. | |
| 87 | * | |
| 88 | * @return The wrapper, so that calls may be chained. | |
| 89 | */ | |
| 90 | public XPathWrapper bindNamespace(String prefix, String nsURI) | |
| 91 | { | |
| 92 | 54 | _nsResolver.addNamespace(prefix, nsURI); |
| 93 | 54 | return this; |
| 94 | } | |
| 95 | ||
| 96 | ||
| 97 | /** | |
| 98 | * Sets the default namespace binding: this will be applied to all | |
| 99 | * expressions that do not explicitly specify a prefix (although | |
| 100 | * they must use the colon separating the non-existent prefix from | |
| 101 | * the element name). | |
| 102 | * | |
| 103 | * @param nsURI The default namespace for this document. | |
| 104 | * | |
| 105 | * @return The wrapper, so that calls may be chained. | |
| 106 | * | |
| 107 | * @deprecated In practice, this method isn't particularly useful: | |
| 108 | * if you're going to specify ":" on a term, you might | |
| 109 | * as well specify a single-character prefix. It won't | |
| 110 | * be removed, but a future implementation might parse | |
| 111 | * an expression sans colons, and insert references. | |
| 112 | ||
| 113 | */ | |
| 114 | @Deprecated | |
| 115 | public XPathWrapper bindDefaultNamespace(String nsURI) | |
| 116 | { | |
| 117 | 3 | _nsResolver.setDefaultNamespace(nsURI); |
| 118 | 3 | return this; |
| 119 | } | |
| 120 | ||
| 121 | ||
| 122 | /** | |
| 123 | * Binds a value to a variable, replacing any previous value for that | |
| 124 | * variable. Unlike other configuration methods, this may be called | |
| 125 | * after calling <code>evaluate()</code>; the new values will be used | |
| 126 | * for subsequent evaluations. | |
| 127 | * | |
| 128 | * @param name The name of the variable; this is turned into a | |
| 129 | * <code>QName</code> without namespace. | |
| 130 | * @param value The value of the variable; the XPath evaluator must | |
| 131 | * be able to convert this value into a type usable in | |
| 132 | * an XPath expression. | |
| 133 | * | |
| 134 | * @return The wrapper, so that calls may be chained. | |
| 135 | */ | |
| 136 | public XPathWrapper bindVariable(String name, Object value) | |
| 137 | { | |
| 138 | 3 | return bindVariable(new QName(name), value); |
| 139 | } | |
| 140 | ||
| 141 | ||
| 142 | /** | |
| 143 | * Binds a value to a variable, replacing any previous value for that | |
| 144 | * variable. Unlike other configuration methods, this may be called | |
| 145 | * after calling <code>evaluate()</code>; the new values will be used | |
| 146 | * for subsequent evaluations. | |
| 147 | * | |
| 148 | * @param name The fully-qualified name of the variable. | |
| 149 | * @param value The value of the variable; the XPath evaluator must | |
| 150 | * be able to convert this value into a type usable in | |
| 151 | * an XPath expression. | |
| 152 | * | |
| 153 | * @return The wrapper, so that calls may be chained. | |
| 154 | */ | |
| 155 | public XPathWrapper bindVariable(QName name, Object value) | |
| 156 | { | |
| 157 | 18 | _variables.put(name, value); |
| 158 | 18 | return this; |
| 159 | } | |
| 160 | ||
| 161 | ||
| 162 | /** | |
| 163 | * Binds a self-describing function to this expression. Subsequent calls | |
| 164 | * with the same name/arity will silently replace the binding. | |
| 165 | * <p> | |
| 166 | * Per the JDK documentation, user-defined functions must occupy their | |
| 167 | * own namespace. You must bind a namespace for this function, and use | |
| 168 | * the bound prefix to reference it in your expression. Alternatively, | |
| 169 | * you can call the variant of <code>bindFunction()</code> that binds | |
| 170 | * a prefix to the function's namespace. | |
| 171 | * | |
| 172 | * @param func The function. | |
| 173 | * | |
| 174 | * @return The wrapper, so that calls may be chained. | |
| 175 | */ | |
| 176 | public XPathWrapper bindFunction(FunctionResolver.SelfDescribingFunction func) | |
| 177 | { | |
| 178 | 1 | _functions.addFunction(func); |
| 179 | 1 | return this; |
| 180 | } | |
| 181 | ||
| 182 | ||
| 183 | /** | |
| 184 | * Binds a self-describing function to this expression, along with the | |
| 185 | * prefix used to access that function. This also establishes a namespace | |
| 186 | * binding for that prefix. | |
| 187 | * <p> | |
| 188 | * Per the JDK documentation, user-defined functions must occupy their | |
| 189 | * own namespace. This method will retrieve the namespace from the | |
| 190 | * function, and bind it to the passed prefix. | |
| 191 | * | |
| 192 | * @param func The function. | |
| 193 | * @param prefix The prefix to bind to this function's namespace. | |
| 194 | * | |
| 195 | * @return The wrapper, so that calls may be chained. | |
| 196 | */ | |
| 197 | public XPathWrapper bindFunction(FunctionResolver.SelfDescribingFunction func, String prefix) | |
| 198 | { | |
| 199 | 19 | _functions.addFunction(func); |
| 200 | 19 | return bindNamespace(prefix, func.getNamespaceUri()); |
| 201 | } | |
| 202 | ||
| 203 | ||
| 204 | /** | |
| 205 | * Binds a standard <code>XPathFunction</code> to this expression, | |
| 206 | * handling any number of arguments. Subsequent calls to this method | |
| 207 | * with the same name will silently replace the binding. | |
| 208 | * <p> | |
| 209 | * Per the JDK documentation, user-defined functions must occupy their | |
| 210 | * own namespace. If the qualified name that you pass to this method | |
| 211 | * includes a prefix, the associated namespace will be bound to that | |
| 212 | * prefix. If not, you must bind the namespace explicitly. In either | |
| 213 | * case, you must refer to the function in your expression using a | |
| 214 | * bound prefix. | |
| 215 | * | |
| 216 | * @param name The qualified name for this function. Must contain | |
| 217 | * a name and namespace, may contain a prefix. | |
| 218 | * @param func The function to bind to this name. | |
| 219 | * | |
| 220 | * @return The wrapper, so that calls may be chained. | |
| 221 | */ | |
| 222 | public XPathWrapper bindFunction(QName name, XPathFunction func) | |
| 223 | { | |
| 224 | 3 | return bindFunction(name, func, 0, Integer.MAX_VALUE); |
| 225 | } | |
| 226 | ||
| 227 | ||
| 228 | /** | |
| 229 | * Binds a standard <code>XPathFunction</code> to this expression, | |
| 230 | * handling a specific number of arguments. Subsequent calls to this | |
| 231 | * method with the same name and arity will silently replace the binding. | |
| 232 | * <p> | |
| 233 | * Per the JDK documentation, user-defined functions must occupy their | |
| 234 | * own namespace. If the qualified name that you pass to this method | |
| 235 | * includes a prefix, the associated namespace will be bound to that | |
| 236 | * prefix. If not, you must bind the namespace explicitly. In either | |
| 237 | * case, you must refer to the function in your expression using a | |
| 238 | * bound prefix. | |
| 239 | * | |
| 240 | * @param name The qualified name for this function. Must contain | |
| 241 | * a name and namespace, may contain a prefix. | |
| 242 | * @param func The function to bind to this name. | |
| 243 | * @param arity The number of arguments accepted by this function. | |
| 244 | * | |
| 245 | * @return The wrapper, so that calls may be chained. | |
| 246 | */ | |
| 247 | public XPathWrapper bindFunction(QName name, XPathFunction func, int arity) | |
| 248 | { | |
| 249 | 2 | return bindFunction(name, func, arity, arity); |
| 250 | } | |
| 251 | ||
| 252 | ||
| 253 | /** | |
| 254 | * Binds a standard <code>XPathFunction</code> to this expression, | |
| 255 | * handling a specific range of arguments. Subsequent calls to this | |
| 256 | * method with the same name and range of arguments will silently | |
| 257 | * replace the binding. | |
| 258 | * <p> | |
| 259 | * Per the JDK documentation, user-defined functions must occupy their | |
| 260 | * own namespace. If the qualified name that you pass to this method | |
| 261 | * includes a prefix, the associated namespace will be bound to that | |
| 262 | * prefix. If not, you must bind the namespace explicitly. In either | |
| 263 | * case, you must refer to the function in your expression using a | |
| 264 | * bound prefix. | |
| 265 | * | |
| 266 | * @param name The qualified name for this function. Must contain | |
| 267 | * a name and namespace, may contain a prefix. | |
| 268 | * @param func The function to bind to this name. | |
| 269 | * @param minArity The minimum number of arguments accepted by this | |
| 270 | * function. | |
| 271 | * @param maxArity The maximum number of arguments accepted by this | |
| 272 | * function. | |
| 273 | * | |
| 274 | * @return The wrapper, so that calls may be chained. | |
| 275 | */ | |
| 276 | public XPathWrapper bindFunction(QName name, XPathFunction func, | |
| 277 | int minArity, int maxArity) | |
| 278 | { | |
| 279 | 5 | _functions.addFunction(func, name, minArity, maxArity); |
| 280 | 5 | if (!"".equals(name.getPrefix())) |
| 281 | 3 | bindNamespace(name.getPrefix(), name.getNamespaceURI()); |
| 282 | 5 | return this; |
| 283 | } | |
| 284 | ||
| 285 | ||
| 286 | /** | |
| 287 | * Attaches a pre-build {@link FunctionResolver} to this wrapper, | |
| 288 | * replacing the existing resolver (and removing all bound functions). | |
| 289 | * This method is intended for use by {@link XPathWrapperFactory}, but | |
| 290 | * is exposed for applications that wish to manage their own XPaths. | |
| 291 | * | |
| 292 | * @since 1.1.12 | |
| 293 | */ | |
| 294 | public XPathWrapper setFunctionResolver(FunctionResolver resolver) | |
| 295 | { | |
| 296 | 20 | _functions = resolver; |
| 297 | 20 | return this; |
| 298 | } | |
| 299 | ||
| 300 | ||
| 301 | /** | |
| 302 | * Applies this expression to the the specified node, converting the | |
| 303 | * resulting <code>NodeList</code> into a <code>java.util.List</code>. | |
| 304 | */ | |
| 305 | public List<Node> evaluate(Node context) | |
| 306 | { | |
| 307 | 79 | return DomUtil.asList( |
| 308 | evaluate(context, XPathConstants.NODESET, NodeList.class), | |
| 309 | Node.class); | |
| 310 | } | |
| 311 | ||
| 312 | ||
| 313 | /** | |
| 314 | * Applies this expression to the the specified node, converting the | |
| 315 | * resulting <code>NodeList</code> into a <code>java.util.List</code> | |
| 316 | * containing only nodes of the specified type. | |
| 317 | */ | |
| 318 | public <T> List<T> evaluate(Node context, Class<T> klass) | |
| 319 | { | |
| 320 | 3 | return DomUtil.filter( |
| 321 | evaluate(context, XPathConstants.NODESET, NodeList.class), | |
| 322 | klass); | |
| 323 | } | |
| 324 | ||
| 325 | ||
| 326 | /** | |
| 327 | * Applies this expression to the the specified node, and returning the | |
| 328 | * first <code>Element</code> from the resulting nodelist. Returns | |
| 329 | * <code>null</code> if there is no element in the list. | |
| 330 | * | |
| 331 | * @since 1.1.11 | |
| 332 | */ | |
| 333 | public Element evaluateAsElement(Node context) | |
| 334 | { | |
| 335 | 3 | NodeList nodelist = evaluate(context, XPathConstants.NODESET, NodeList.class); |
| 336 | 3 | int size = nodelist.getLength(); |
| 337 | 5 | for (int ii = 0 ; ii < size ; ii++) |
| 338 | { | |
| 339 | 4 | Node node = nodelist.item(ii); |
| 340 | 4 | if (node instanceof Element) |
| 341 | 2 | return (Element)node; |
| 342 | } | |
| 343 | 1 | return null; |
| 344 | } | |
| 345 | ||
| 346 | ||
| 347 | /** | |
| 348 | * Applies this expression to the specified node, requesting the | |
| 349 | * <code>STRING</code> return type. | |
| 350 | */ | |
| 351 | public String evaluateAsString(Node context) | |
| 352 | { | |
| 353 | 113 | return evaluate(context, XPathConstants.STRING, String.class); |
| 354 | } | |
| 355 | ||
| 356 | ||
| 357 | /** | |
| 358 | * Applies this expression to the specified node, requesting the | |
| 359 | * <code>NODESET</code> return type, and then retrieving the text | |
| 360 | * content for each returned node. | |
| 361 | * <p> | |
| 362 | * Note: this method calls <code>Node.getTextContent()</code> in | |
| 363 | * keeping with normal XPath behavior. If you want to get | |
| 364 | * only the immediate child text nodes, evaluate as a list | |
| 365 | * of elements and use <code>DomUtil.getText()</code>. | |
| 366 | * | |
| 367 | * @since 1.1.3 | |
| 368 | */ | |
| 369 | public List<String> evaluateAsStringList(Node context) | |
| 370 | { | |
| 371 | 1 | List<Node> nodes = evaluate(context); |
| 372 | 1 | List<String> result = new ArrayList<String>(nodes.size()); |
| 373 | 1 | for (Node node : nodes) |
| 374 | 2 | result.add(node.getTextContent()); |
| 375 | 1 | return result; |
| 376 | } | |
| 377 | ||
| 378 | ||
| 379 | /** | |
| 380 | * Applies this expression to the specified node, requesting the | |
| 381 | * <code>NUMBER</code> return type. | |
| 382 | */ | |
| 383 | public Number evaluateAsNumber(Node context) | |
| 384 | { | |
| 385 | 1 | return evaluate(context, XPathConstants.NUMBER, Number.class); |
| 386 | } | |
| 387 | ||
| 388 | ||
| 389 | /** | |
| 390 | * Applies this expression to the the specified node, requesting the | |
| 391 | * <code>BOOLEAN</code> return type. | |
| 392 | */ | |
| 393 | public Boolean evaluateAsBoolean(Node context) | |
| 394 | { | |
| 395 | 3 | return evaluate(context, XPathConstants.BOOLEAN, Boolean.class); |
| 396 | } | |
| 397 | ||
| 398 | ||
| 399 | //---------------------------------------------------------------------------- | |
| 400 | // Overrides of Object | |
| 401 | //---------------------------------------------------------------------------- | |
| 402 | ||
| 403 | /** | |
| 404 | * Two instances are considered equal if they have the same expression, | |
| 405 | * namespace mappings, variable mappings, and function mappings. Note | |
| 406 | * that instances with function mappings are all but guaranteed to be | |
| 407 | * not-equal, unless you override the <code>equals()</code> method on | |
| 408 | * the function implementation class. | |
| 409 | */ | |
| 410 | @Override | |
| 411 | public final boolean equals(Object obj) | |
| 412 | { | |
| 413 | 17 | if (obj instanceof XPathWrapper) |
| 414 | { | |
| 415 | 15 | XPathWrapper that = (XPathWrapper)obj; |
| 416 | 15 | return this._expr.equals(that._expr) |
| 417 | && this._nsResolver.equals(that._nsResolver) | |
| 418 | && this._variables.equals(that._variables) | |
| 419 | && this._functions.equals(that._functions); | |
| 420 | } | |
| 421 | 2 | return false; |
| 422 | } | |
| 423 | ||
| 424 | ||
| 425 | /** | |
| 426 | * Hash code is driven by the expression, ignoring differences between | |
| 427 | * namespaces, variables, and functions. | |
| 428 | */ | |
| 429 | @Override | |
| 430 | public int hashCode() | |
| 431 | { | |
| 432 | 18 | return _expr.hashCode(); |
| 433 | } | |
| 434 | ||
| 435 | ||
| 436 | /** | |
| 437 | * The string value is the expression. | |
| 438 | */ | |
| 439 | @Override | |
| 440 | public String toString() | |
| 441 | { | |
| 442 | 52 | return _expr; |
| 443 | } | |
| 444 | ||
| 445 | ||
| 446 | /** | |
| 447 | * Creates a <em>shallow</em> clone of this object, omitting the compiled | |
| 448 | * XPath expression. This method exists to support thread-local instances, | |
| 449 | * which will recompile the expression. | |
| 450 | */ | |
| 451 | @Override | |
| 452 | protected XPathWrapper clone() throws CloneNotSupportedException | |
| 453 | { | |
| 454 | 1 | XPathWrapper newInstance = (XPathWrapper)super.clone(); |
| 455 | 1 | newInstance._compiled = null; |
| 456 | 1 | return newInstance; |
| 457 | } | |
| 458 | ||
| 459 | ||
| 460 | //---------------------------------------------------------------------------- | |
| 461 | // Internals | |
| 462 | //---------------------------------------------------------------------------- | |
| 463 | ||
| 464 | /** | |
| 465 | * Compiles the expression, if it has not already been compiled. This is | |
| 466 | * called from the various <code>evaluate</code> methods, and ensures | |
| 467 | * that the caller has completely configured the wrapper prior to use. | |
| 468 | */ | |
| 469 | private void compileIfNeeded() | |
| 470 | { | |
| 471 | 202 | if (_compiled != null) |
| 472 | 17 | return; |
| 473 | ||
| 474 | try | |
| 475 | { | |
| 476 | 185 | XPathFactory fact = null; |
| 477 | 185 | synchronized (XPathFactory.class) |
| 478 | { | |
| 479 | 185 | fact = XPathFactory.newInstance(); |
| 480 | 185 | } |
| 481 | 185 | XPath xpath = fact.newXPath(); |
| 482 | 185 | xpath.setNamespaceContext(_nsResolver); |
| 483 | 185 | xpath.setXPathVariableResolver(new MyVariableResolver()); |
| 484 | 185 | xpath.setXPathFunctionResolver(_functions); |
| 485 | 185 | _compiled = xpath.compile(_expr); |
| 486 | } | |
| 487 | 1 | catch (XPathExpressionException ee) |
| 488 | { | |
| 489 | 1 | throw new XmlException("unable to compile: " + _expr, ee); |
| 490 | 184 | } |
| 491 | 184 | } |
| 492 | ||
| 493 | ||
| 494 | /** | |
| 495 | * The base evaluation method: compiles the XPath, applies it as instructed | |
| 496 | * by the caller, and casts the result (also as instructed). | |
| 497 | */ | |
| 498 | private <T> T evaluate(Node context, QName returnType, Class<T> castTo) | |
| 499 | { | |
| 500 | 202 | compileIfNeeded(); |
| 501 | try | |
| 502 | { | |
| 503 | 201 | return castTo.cast(_compiled.evaluate(context, returnType)); |
| 504 | } | |
| 505 | 2 | catch (Exception ee) |
| 506 | { | |
| 507 | 2 | throw new XmlException("unable to evaluate: " + _expr, ee); |
| 508 | } | |
| 509 | } | |
| 510 | ||
| 511 | ||
| 512 | /** | |
| 513 | * Resolver for variable references. | |
| 514 | */ | |
| 515 | 370 | private class MyVariableResolver |
| 516 | implements XPathVariableResolver | |
| 517 | { | |
| 518 | public Object resolveVariable(QName name) | |
| 519 | { | |
| 520 | 37 | return _variables.get(name); |
| 521 | } | |
| 522 | } | |
| 523 | } |