001/* 002 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 003 * 004 * This software is open source. 005 * See the bottom of this file for the licence. 006 * 007 * $Id: DefaultXPath.java,v 1.24 2002/03/13 03:29:55 jstrachan Exp $ 008 */ 009 010package org.dom4j.xpath; 011 012import org.dom4j.Document; 013import org.dom4j.Element; 014import org.dom4j.InvalidXPathException; 015import org.dom4j.Node; 016import org.dom4j.NodeFilter; 017import org.dom4j.XPath; 018import org.dom4j.XPathException; 019 020import org.dom4j.rule.Pattern; 021 022import org.jaxen.BaseXPath; 023import org.jaxen.FunctionContext; 024import org.jaxen.JaxenException; 025import org.jaxen.NamespaceContext; 026import org.jaxen.SimpleNamespaceContext; 027import org.jaxen.VariableContext; 028 029import org.saxpath.XPathReader; 030import org.saxpath.SAXPathException; 031import org.saxpath.helpers.XPathReaderFactory; 032 033import java.io.Serializable; 034import java.io.StringReader; 035 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.Comparator; 039import java.util.HashMap; 040import java.util.HashSet; 041import java.util.Iterator; 042import java.util.List; 043import java.util.Map; 044 045 046/** <p>Default implementation of {@link org.dom4j.XPath} which uses the 047 * <a href="http://jaxen.org">Jaxen</a> project.</p> 048 * 049 * @author bob mcwhirter (bob @ werken.com) 050 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 051 */ 052public class DefaultXPath implements org.dom4j.XPath, NodeFilter, Serializable { 053 054 private String text; 055 private BaseXPath xpath; 056 private NamespaceContext namespaceContext; 057 058 059 /** Construct an XPath 060 */ 061 public DefaultXPath(String text) throws InvalidXPathException { 062 this.text = text; 063 this.xpath = parse( text ); 064 } 065 066 public String toString() { 067 return "[XPath: " + xpath + "]"; 068 } 069 070 071 // XPath interface 072 073 /** Retrieve the textual XPath string used to initialize this Object 074 * 075 * @return The XPath string 076 */ 077 public String getText() { 078 return text; 079 } 080 081 public FunctionContext getFunctionContext() { 082 return xpath.getFunctionContext(); 083 } 084 085 public void setFunctionContext(FunctionContext functionContext) { 086 xpath.setFunctionContext(functionContext); 087 } 088 089 public NamespaceContext getNamespaceContext() { 090 return namespaceContext; 091 } 092 093 public void setNamespaceURIs(Map map) { 094 setNamespaceContext( new SimpleNamespaceContext( map ) ); 095 } 096 097 public void setNamespaceContext(NamespaceContext namespaceContext) { 098 this.namespaceContext = namespaceContext; 099 xpath.setNamespaceContext(namespaceContext); 100 } 101 102 public VariableContext getVariableContext() { 103 return xpath.getVariableContext(); 104 } 105 106 public void setVariableContext(VariableContext variableContext) { 107 xpath.setVariableContext(variableContext); 108 } 109 110 public Object evaluate(Object context) { 111 try { 112 setNSContext(context); 113 List answer = xpath.selectNodes( context ); 114 if ( answer != null && answer.size() == 1 ) { 115 return answer.get(0); 116 } 117 return answer; 118 } 119 catch (JaxenException e) { 120 handleJaxenException(e); 121 return null; 122 } 123 } 124 125 public Object selectObject(Object context) { 126 return evaluate(context); 127 } 128 129 public List selectNodes(Object context) { 130 try { 131 setNSContext(context); 132 return xpath.selectNodes( context ); 133 } 134 catch (JaxenException e) { 135 handleJaxenException(e); 136 return Collections.EMPTY_LIST; 137 } 138 } 139 140 141 public List selectNodes(Object context, org.dom4j.XPath sortXPath) { 142 List answer = selectNodes( context ); 143 sortXPath.sort( answer ); 144 return answer; 145 } 146 147 public List selectNodes(Object context, org.dom4j.XPath sortXPath, boolean distinct) { 148 List answer = selectNodes( context ); 149 sortXPath.sort( answer, distinct ); 150 return answer; 151 } 152 153 public Node selectSingleNode(Object context) { 154 try { 155 setNSContext(context); 156 Object answer = xpath.selectSingleNode( context ); 157 if ( answer instanceof Node ) { 158 return (Node) answer; 159 } 160 if ( answer == null ) { 161 return null; 162 } 163 throw new XPathException( 164 "The result of the XPath expression is not a Node. It was: " 165 + answer + " of type: " + answer.getClass().getName() 166 + ". You might want to use a different method such as selectObject() to evaluate this XPath expression" 167 ); 168 } 169 catch (JaxenException e) { 170 handleJaxenException(e); 171 return null; 172 } 173 } 174 175 176 public String valueOf(Object context) { 177 try { 178 setNSContext(context); 179 return xpath.valueOf( context ); 180 } 181 catch (JaxenException e) { 182 handleJaxenException(e); 183 return ""; 184 } 185 } 186 187 public Number numberValueOf(Object context) { 188 try { 189 setNSContext(context); 190 return xpath.numberValueOf( context ); 191 } 192 catch (JaxenException e) { 193 handleJaxenException(e); 194 return null; 195 } 196 } 197 198 /** <p><code>sort</code> sorts the given List of Nodes 199 * using this XPath expression as a {@link Comparator}.</p> 200 * 201 * @param list is the list of Nodes to sort 202 */ 203 public void sort( List list ) { 204 sort( list, false ); 205 } 206 207 /** <p><code>sort</code> sorts the given List of Nodes 208 * using this XPath expression as a {@link Comparator} 209 * and optionally removing duplicates.</p> 210 * 211 * @param list is the list of Nodes to sort 212 * @param distinct if true then duplicate values (using the sortXPath for 213 * comparisions) will be removed from the List 214 */ 215 public void sort( List list, boolean distinct ) { 216 if ( list != null && ! list.isEmpty() ) { 217 int size = list.size(); 218 HashMap sortValues = new HashMap( size ); 219 for ( int i = 0; i < size; i++ ) { 220 Object object = list.get(i); 221 if ( object instanceof Node ) { 222 Node node = (Node) object; 223 Object expression = getCompareValue(node); 224 sortValues.put(node, expression); 225 } 226 } 227 sort( list, sortValues ); 228 229 if (distinct) { 230 removeDuplicates( list, sortValues ); 231 } 232 } 233 } 234 235 public boolean matches( Node node ) { 236 try { 237 setNSContext(node); 238 List answer = xpath.selectNodes( node ); 239 if ( answer != null && answer.size() > 0 ) { 240 Object item = answer.get(0); 241 if ( item instanceof Boolean ) { 242 return ((Boolean) item).booleanValue(); 243 } 244 return answer.contains( node ); 245 } 246 return false; 247 } 248 catch (JaxenException e) { 249 handleJaxenException(e); 250 return false; 251 } 252 } 253 254 /** Sorts the list based on the sortValues for each node 255 */ 256 protected void sort( List list, final Map sortValues ) { 257 Collections.sort( 258 list, 259 new Comparator() { 260 public int compare(Object o1, Object o2) { 261 o1 = sortValues.get(o1); 262 o2 = sortValues.get(o2); 263 if ( o1 == o2 ) { 264 return 0; 265 } 266 else if ( o1 instanceof Comparable ) { 267 Comparable c1 = (Comparable) o1; 268 return c1.compareTo(o2); 269 } 270 else if ( o1 == null ) { 271 return 1; 272 } 273 else if ( o2 == null ) { 274 return -1; 275 } 276 else { 277 return o1.equals(o2) ? 0 : -1; 278 } 279 } 280 } 281 ); 282 } 283 284 // Implementation methods 285 286 287 288 /** Removes items from the list which have duplicate values 289 */ 290 protected void removeDuplicates( List list, Map sortValues ) { 291 // remove distinct 292 HashSet distinctValues = new HashSet(); 293 for ( Iterator iter = list.iterator(); iter.hasNext(); ) { 294 Object node = iter.next(); 295 Object value = sortValues.get(node); 296 if ( distinctValues.contains( value ) ) { 297 iter.remove(); 298 } 299 else { 300 distinctValues.add( value ); 301 } 302 } 303 } 304 305 /** @return the node expression used for sorting comparisons 306 */ 307 protected Object getCompareValue(Node node) { 308 return valueOf( node ); 309 } 310 311 protected static BaseXPath parse(String text) { 312 try { 313 return new org.jaxen.dom4j.XPath( text ); 314 } 315 catch (SAXPathException e) { 316 throw new InvalidXPathException( text, e.getMessage() ); 317 } 318 catch (RuntimeException e) { 319 } 320 throw new InvalidXPathException( text ); 321 } 322 323 protected void setNSContext(Object context) { 324 if ( namespaceContext == null ) { 325 xpath.setNamespaceContext( DefaultNamespaceContext.create(context) ); 326 } 327 } 328 329 protected void handleJaxenException(JaxenException e) throws XPathException { 330 throw new XPathException(text, e); 331 } 332} 333 334 335 336 337/* 338 * Redistribution and use of this software and associated documentation 339 * ("Software"), with or without modification, are permitted provided 340 * that the following conditions are met: 341 * 342 * 1. Redistributions of source code must retain copyright 343 * statements and notices. Redistributions must also contain a 344 * copy of this document. 345 * 346 * 2. Redistributions in binary form must reproduce the 347 * above copyright notice, this list of conditions and the 348 * following disclaimer in the documentation and/or other 349 * materials provided with the distribution. 350 * 351 * 3. The name "DOM4J" must not be used to endorse or promote 352 * products derived from this Software without prior written 353 * permission of MetaStuff, Ltd. For written permission, 354 * please contact dom4j-info@metastuff.com. 355 * 356 * 4. Products derived from this Software may not be called "DOM4J" 357 * nor may "DOM4J" appear in their names without prior written 358 * permission of MetaStuff, Ltd. DOM4J is a registered 359 * trademark of MetaStuff, Ltd. 360 * 361 * 5. Due credit should be given to the DOM4J Project 362 * (http://dom4j.org/). 363 * 364 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS 365 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 366 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 367 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 368 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 369 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 370 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 371 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 372 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 373 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 374 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 375 * OF THE POSSIBILITY OF SUCH DAMAGE. 376 * 377 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 378 * 379 * $Id: DefaultXPath.java,v 1.24 2002/03/13 03:29:55 jstrachan Exp $ 380 */