1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 /* 19 * $Id: CachedXPathAPI.java 524811 2007-04-02 15:51:59Z zongaro $ 20 */ 21 package org.apache.xpath; 22 23 import javax.xml.transform.TransformerException; 24 25 import org.apache.xml.utils.PrefixResolver; 26 import org.apache.xml.utils.PrefixResolverDefault; 27 import org.apache.xpath.objects.XObject; 28 29 import org.w3c.dom.Document; 30 import org.w3c.dom.Node; 31 import org.w3c.dom.NodeList; 32 import org.w3c.dom.traversal.NodeIterator; 33 34 /** 35 * The methods in this class are convenience methods into the 36 * low-level XPath API. 37 * 38 * These functions tend to be a little slow, since a number of objects must be 39 * created for each evaluation. A faster way is to precompile the 40 * XPaths using the low-level API, and then just use the XPaths 41 * over and over. 42 * 43 * This is an alternative for the old XPathAPI class, which provided 44 * static methods for the purpose but had the drawback of 45 * instantiating a new XPathContext (and thus building a new DTMManager, 46 * and new DTMs) each time it was called. XPathAPIObject instead retains 47 * its context as long as the object persists, reusing the DTMs. This 48 * does have a downside: if you've changed your source document, you should 49 * obtain a new XPathAPIObject to continue searching it, since trying to use 50 * the old DTMs will probably yield bad results or malfunction outright... and 51 * the cached DTMs may consume memory until this object and its context are 52 * returned to the heap. Essentially, it's the caller's responsibility to 53 * decide when to discard the cache. 54 * 55 * @see <a href="http://www.w3.org/TR/xpath">XPath Specification</a> 56 * */ 57 public class CachedXPathAPI 58 { 59 /** XPathContext, and thus the document model system (DTMs), persists through multiple 60 calls to this object. This is set in the constructor. 61 */ 62 protected XPathContext xpathSupport; 63 64 /** 65 * <p>Default constructor. Establishes its own {@link XPathContext}, and hence 66 * its own {@link org.apache.xml.dtm.DTMManager}. 67 * Good choice for simple uses.</p> 68 * <p>Note that any particular instance of {@link CachedXPathAPI} must not be 69 * operated upon by multiple threads without synchronization; we do 70 * not currently support multithreaded access to a single 71 * {@link org.apache.xml.dtm.DTM}.</p> 72 */ 73 public CachedXPathAPI() 74 { 75 // Create an XPathContext that doesn't support pushing and popping of 76 // variable resolution scopes. Sufficient for simple XPath 1.0 expressions. 77 xpathSupport = new XPathContext(false); 78 } 79 80 /** 81 * <p>This constructor shares its {@link XPathContext} with a pre-existing 82 * {@link CachedXPathAPI}. That allows sharing document models 83 * ({@link org.apache.xml.dtm.DTM}) and previously established location 84 * state.</p> 85 * <p>Note that the original {@link CachedXPathAPI} and the new one should 86 * not be operated upon concurrently; we do not support multithreaded access 87 * to a single {@link org.apache.xml.dtm.DTM} at this time. Similarly, 88 * any particular instance of {@link CachedXPathAPI} must not be operated 89 * upon by multiple threads without synchronization.</p> 90 * <p>%REVIEW% Should this instead do a clone-and-reset on the XPathSupport object?</p> 91 * 92 */ 93 public CachedXPathAPI(CachedXPathAPI priorXPathAPI) 94 { 95 xpathSupport = priorXPathAPI.xpathSupport; 96 } 97 98 99 /** Returns the XPathSupport object used in this CachedXPathAPI 100 * 101 * %REVIEW% I'm somewhat concerned about the loss of encapsulation 102 * this causes, but the xml-security folks say they need it. 103 * */ 104 public XPathContext getXPathContext() 105 { 106 return this.xpathSupport; 107 } 108 109 110 /** 111 * Use an XPath string to select a single node. XPath namespace 112 * prefixes are resolved from the context node, which may not 113 * be what you want (see the next method). 114 * 115 * @param contextNode The node to start searching from. 116 * @param str A valid XPath string. 117 * @return The first node found that matches the XPath, or null. 118 * 119 * @throws TransformerException 120 */ 121 public Node selectSingleNode(Node contextNode, String str) 122 throws TransformerException 123 { 124 return selectSingleNode(contextNode, str, contextNode); 125 } 126 127 /** 128 * Use an XPath string to select a single node. 129 * XPath namespace prefixes are resolved from the namespaceNode. 130 * 131 * @param contextNode The node to start searching from. 132 * @param str A valid XPath string. 133 * @param namespaceNode The node from which prefixes in the XPath will be resolved to namespaces. 134 * @return The first node found that matches the XPath, or null. 135 * 136 * @throws TransformerException 137 */ 138 public Node selectSingleNode( 139 Node contextNode, String str, Node namespaceNode) 140 throws TransformerException 141 { 142 143 // Have the XObject return its result as a NodeSetDTM. 144 NodeIterator nl = selectNodeIterator(contextNode, str, namespaceNode); 145 146 // Return the first node, or null 147 return nl.nextNode(); 148 } 149 150 /** 151 * Use an XPath string to select a nodelist. 152 * XPath namespace prefixes are resolved from the contextNode. 153 * 154 * @param contextNode The node to start searching from. 155 * @param str A valid XPath string. 156 * @return A NodeIterator, should never be null. 157 * 158 * @throws TransformerException 159 */ 160 public NodeIterator selectNodeIterator(Node contextNode, String str) 161 throws TransformerException 162 { 163 return selectNodeIterator(contextNode, str, contextNode); 164 } 165 166 /** 167 * Use an XPath string to select a nodelist. 168 * XPath namespace prefixes are resolved from the namespaceNode. 169 * 170 * @param contextNode The node to start searching from. 171 * @param str A valid XPath string. 172 * @param namespaceNode The node from which prefixes in the XPath will be resolved to namespaces. 173 * @return A NodeIterator, should never be null. 174 * 175 * @throws TransformerException 176 */ 177 public NodeIterator selectNodeIterator( 178 Node contextNode, String str, Node namespaceNode) 179 throws TransformerException 180 { 181 182 // Execute the XPath, and have it return the result 183 XObject list = eval(contextNode, str, namespaceNode); 184 185 // Have the XObject return its result as a NodeSetDTM. 186 return list.nodeset(); 187 } 188 189 /** 190 * Use an XPath string to select a nodelist. 191 * XPath namespace prefixes are resolved from the contextNode. 192 * 193 * @param contextNode The node to start searching from. 194 * @param str A valid XPath string. 195 * @return A NodeIterator, should never be null. 196 * 197 * @throws TransformerException 198 */ 199 public NodeList selectNodeList(Node contextNode, String str) 200 throws TransformerException 201 { 202 return selectNodeList(contextNode, str, contextNode); 203 } 204 205 /** 206 * Use an XPath string to select a nodelist. 207 * XPath namespace prefixes are resolved from the namespaceNode. 208 * 209 * @param contextNode The node to start searching from. 210 * @param str A valid XPath string. 211 * @param namespaceNode The node from which prefixes in the XPath will be resolved to namespaces. 212 * @return A NodeIterator, should never be null. 213 * 214 * @throws TransformerException 215 */ 216 public NodeList selectNodeList( 217 Node contextNode, String str, Node namespaceNode) 218 throws TransformerException 219 { 220 221 // Execute the XPath, and have it return the result 222 XObject list = eval(contextNode, str, namespaceNode); 223 224 // Return a NodeList. 225 return list.nodelist(); 226 } 227 228 /** 229 * Evaluate XPath string to an XObject. Using this method, 230 * XPath namespace prefixes will be resolved from the namespaceNode. 231 * @param contextNode The node to start searching from. 232 * @param str A valid XPath string. 233 * @return An XObject, which can be used to obtain a string, number, nodelist, etc, should never be null. 234 * @see org.apache.xpath.objects.XObject 235 * @see org.apache.xpath.objects.XNull 236 * @see org.apache.xpath.objects.XBoolean 237 * @see org.apache.xpath.objects.XNumber 238 * @see org.apache.xpath.objects.XString 239 * @see org.apache.xpath.objects.XRTreeFrag 240 * 241 * @throws TransformerException 242 */ 243 public XObject eval(Node contextNode, String str) 244 throws TransformerException 245 { 246 return eval(contextNode, str, contextNode); 247 } 248 249 /** 250 * Evaluate XPath string to an XObject. 251 * XPath namespace prefixes are resolved from the namespaceNode. 252 * The implementation of this is a little slow, since it creates 253 * a number of objects each time it is called. This could be optimized 254 * to keep the same objects around, but then thread-safety issues would arise. 255 * 256 * @param contextNode The node to start searching from. 257 * @param str A valid XPath string. 258 * @param namespaceNode The node from which prefixes in the XPath will be resolved to namespaces. 259 * @return An XObject, which can be used to obtain a string, number, nodelist, etc, should never be null. 260 * @see org.apache.xpath.objects.XObject 261 * @see org.apache.xpath.objects.XNull 262 * @see org.apache.xpath.objects.XBoolean 263 * @see org.apache.xpath.objects.XNumber 264 * @see org.apache.xpath.objects.XString 265 * @see org.apache.xpath.objects.XRTreeFrag 266 * 267 * @throws TransformerException 268 */ 269 public XObject eval(Node contextNode, String str, Node namespaceNode) 270 throws TransformerException 271 { 272 273 // Since we don't have a XML Parser involved here, install some default support 274 // for things like namespaces, etc. 275 // (Changed from: XPathContext xpathSupport = new XPathContext(); 276 // because XPathContext is weak in a number of areas... perhaps 277 // XPathContext should be done away with.) 278 279 // Create an object to resolve namespace prefixes. 280 // XPath namespaces are resolved from the input context node's document element 281 // if it is a root node, or else the current context node (for lack of a better 282 // resolution space, given the simplicity of this sample code). 283 PrefixResolverDefault prefixResolver = new PrefixResolverDefault( 284 (namespaceNode.getNodeType() == Node.DOCUMENT_NODE) 285 ? ((Document) namespaceNode).getDocumentElement() : namespaceNode); 286 287 // Create the XPath object. 288 XPath xpath = new XPath(str, null, prefixResolver, XPath.SELECT, null); 289 290 // Execute the XPath, and have it return the result 291 // return xpath.execute(xpathSupport, contextNode, prefixResolver); 292 int ctxtNode = xpathSupport.getDTMHandleFromNode(contextNode); 293 294 return xpath.execute(xpathSupport, ctxtNode, prefixResolver); 295 } 296 297 /** 298 * Evaluate XPath string to an XObject. 299 * XPath namespace prefixes are resolved from the namespaceNode. 300 * The implementation of this is a little slow, since it creates 301 * a number of objects each time it is called. This could be optimized 302 * to keep the same objects around, but then thread-safety issues would arise. 303 * 304 * @param contextNode The node to start searching from. 305 * @param str A valid XPath string. 306 * @param prefixResolver Will be called if the parser encounters namespace 307 * prefixes, to resolve the prefixes to URLs. 308 * @return An XObject, which can be used to obtain a string, number, nodelist, etc, should never be null. 309 * @see org.apache.xpath.objects.XObject 310 * @see org.apache.xpath.objects.XNull 311 * @see org.apache.xpath.objects.XBoolean 312 * @see org.apache.xpath.objects.XNumber 313 * @see org.apache.xpath.objects.XString 314 * @see org.apache.xpath.objects.XRTreeFrag 315 * 316 * @throws TransformerException 317 */ 318 public XObject eval( 319 Node contextNode, String str, PrefixResolver prefixResolver) 320 throws TransformerException 321 { 322 323 // Since we don't have a XML Parser involved here, install some default support 324 // for things like namespaces, etc. 325 // (Changed from: XPathContext xpathSupport = new XPathContext(); 326 // because XPathContext is weak in a number of areas... perhaps 327 // XPathContext should be done away with.) 328 // Create the XPath object. 329 XPath xpath = new XPath(str, null, prefixResolver, XPath.SELECT, null); 330 331 // Create an XPathContext that doesn't support pushing and popping of 332 // variable resolution scopes. Sufficient for simple XPath 1.0 expressions. 333 XPathContext xpathSupport = new XPathContext(false); 334 335 // Execute the XPath, and have it return the result 336 int ctxtNode = xpathSupport.getDTMHandleFromNode(contextNode); 337 338 return xpath.execute(xpathSupport, ctxtNode, prefixResolver); 339 } 340 } 341