1
2
3
4
5
6
7
8
9
10 package org.dom4j.xpath;
11
12 import java.io.Serializable;
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20
21 import org.dom4j.InvalidXPathException;
22 import org.dom4j.Node;
23 import org.dom4j.NodeFilter;
24 import org.dom4j.XPathException;
25 import org.jaxen.FunctionContext;
26 import org.jaxen.JaxenException;
27 import org.jaxen.NamespaceContext;
28 import org.jaxen.SimpleNamespaceContext;
29 import org.jaxen.VariableContext;
30 import org.jaxen.XPath;
31 import org.jaxen.dom4j.Dom4jXPath;
32
33
34 /*** <p>Default implementation of {@link org.dom4j.XPath} which uses the
35 * <a href="http://jaxen.org">Jaxen</a> project.</p>
36 *
37 * @author bob mcwhirter (bob @ werken.com)
38 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
39 */
40 public class DefaultXPath implements org.dom4j.XPath, NodeFilter, Serializable {
41
42 private String text;
43 private XPath xpath;
44 private NamespaceContext namespaceContext;
45
46
47 /*** Construct an XPath
48 */
49 public DefaultXPath(String text) throws InvalidXPathException {
50 this.text = text;
51 this.xpath = parse( text );
52 }
53
54 public String toString() {
55 return "[XPath: " + xpath + "]";
56 }
57
58
59
60
61 /*** Retrieve the textual XPath string used to initialize this Object
62 *
63 * @return The XPath string
64 */
65 public String getText() {
66 return text;
67 }
68
69 public FunctionContext getFunctionContext() {
70 return xpath.getFunctionContext();
71 }
72
73 public void setFunctionContext(FunctionContext functionContext) {
74 xpath.setFunctionContext(functionContext);
75 }
76
77 public NamespaceContext getNamespaceContext() {
78 return namespaceContext;
79 }
80
81 public void setNamespaceURIs(Map map) {
82 setNamespaceContext( new SimpleNamespaceContext( map ) );
83 }
84
85 public void setNamespaceContext(NamespaceContext namespaceContext) {
86 this.namespaceContext = namespaceContext;
87 xpath.setNamespaceContext(namespaceContext);
88 }
89
90 public VariableContext getVariableContext() {
91 return xpath.getVariableContext();
92 }
93
94 public void setVariableContext(VariableContext variableContext) {
95 xpath.setVariableContext(variableContext);
96 }
97
98 public Object evaluate(Object context) {
99 try {
100 setNSContext(context);
101 List answer = xpath.selectNodes( context );
102 if ( answer != null && answer.size() == 1 ) {
103 return answer.get(0);
104 }
105 return answer;
106 }
107 catch (JaxenException e) {
108 handleJaxenException(e);
109 return null;
110 }
111 }
112
113 public Object selectObject(Object context) {
114 return evaluate(context);
115 }
116
117 public List selectNodes(Object context) {
118 try {
119 setNSContext(context);
120 return xpath.selectNodes( context );
121 }
122 catch (JaxenException e) {
123 handleJaxenException(e);
124 return Collections.EMPTY_LIST;
125 }
126 }
127
128
129 public List selectNodes(Object context, org.dom4j.XPath sortXPath) {
130 List answer = selectNodes( context );
131 sortXPath.sort( answer );
132 return answer;
133 }
134
135 public List selectNodes(Object context, org.dom4j.XPath sortXPath, boolean distinct) {
136 List answer = selectNodes( context );
137 sortXPath.sort( answer, distinct );
138 return answer;
139 }
140
141 public Node selectSingleNode(Object context) {
142 try {
143 setNSContext(context);
144 Object answer = xpath.selectSingleNode( context );
145 if ( answer instanceof Node ) {
146 return (Node) answer;
147 }
148 if ( answer == null ) {
149 return null;
150 }
151 throw new XPathException(
152 "The result of the XPath expression is not a Node. It was: "
153 + answer + " of type: " + answer.getClass().getName()
154 + ". You might want to use a different method such as selectObject() to evaluate this XPath expression"
155 );
156 }
157 catch (JaxenException e) {
158 handleJaxenException(e);
159 return null;
160 }
161 }
162
163
164 public String valueOf(Object context) {
165 try {
166 setNSContext(context);
167 return xpath.valueOf( context );
168 }
169 catch (JaxenException e) {
170 handleJaxenException(e);
171 return "";
172 }
173 }
174
175 public Number numberValueOf(Object context) {
176 try {
177 setNSContext(context);
178 return xpath.numberValueOf( context );
179 }
180 catch (JaxenException e) {
181 handleJaxenException(e);
182 return null;
183 }
184 }
185
186 public boolean booleanValueOf(Object context) {
187 try {
188 setNSContext(context);
189 return xpath.booleanValueOf(context);
190 }
191 catch(JaxenException e) {
192 handleJaxenException(e);
193 return false;
194 }
195 }
196
197 /*** <p><code>sort</code> sorts the given List of Nodes
198 * using this XPath expression as a {@link Comparator}.</p>
199 *
200 * @param list is the list of Nodes to sort
201 */
202 public void sort( List list ) {
203 sort( list, false );
204 }
205
206 /*** <p><code>sort</code> sorts the given List of Nodes
207 * using this XPath expression as a {@link Comparator}
208 * and optionally removing duplicates.</p>
209 *
210 * @param list is the list of Nodes to sort
211 * @param distinct if true then duplicate values (using the sortXPath for
212 * comparisions) will be removed from the List
213 */
214 public void sort( List list, boolean distinct ) {
215 if ( list != null && ! list.isEmpty() ) {
216 int size = list.size();
217 HashMap sortValues = new HashMap( size );
218 for ( int i = 0; i < size; i++ ) {
219 Object object = list.get(i);
220 if ( object instanceof Node ) {
221 Node node = (Node) object;
222 Object expression = getCompareValue(node);
223 sortValues.put(node, expression);
224 }
225 }
226 sort( list, sortValues );
227
228 if (distinct) {
229 removeDuplicates( list, sortValues );
230 }
231 }
232 }
233
234 public boolean matches( Node node ) {
235 try {
236 setNSContext(node);
237 List answer = xpath.selectNodes( node );
238 if ( answer != null && answer.size() > 0 ) {
239 Object item = answer.get(0);
240 if ( item instanceof Boolean ) {
241 return ((Boolean) item).booleanValue();
242 }
243 return answer.contains( node );
244 }
245 return false;
246 }
247 catch (JaxenException e) {
248 handleJaxenException(e);
249 return false;
250 }
251 }
252
253 /*** Sorts the list based on the sortValues for each node
254 */
255 protected void sort( List list, final Map sortValues ) {
256 Collections.sort(
257 list,
258 new Comparator() {
259 public int compare(Object o1, Object o2) {
260 o1 = sortValues.get(o1);
261 o2 = sortValues.get(o2);
262 if ( o1 == o2 ) {
263 return 0;
264 }
265 else if ( o1 instanceof Comparable ) {
266 Comparable c1 = (Comparable) o1;
267 return c1.compareTo(o2);
268 }
269 else if ( o1 == null ) {
270 return 1;
271 }
272 else if ( o2 == null ) {
273 return -1;
274 }
275 else {
276 return o1.equals(o2) ? 0 : -1;
277 }
278 }
279 }
280 );
281 }
282
283
284
285
286
287 /*** Removes items from the list which have duplicate values
288 */
289 protected void removeDuplicates( List list, Map sortValues ) {
290
291 HashSet distinctValues = new HashSet();
292 for ( Iterator iter = list.iterator(); iter.hasNext(); ) {
293 Object node = iter.next();
294 Object value = sortValues.get(node);
295 if ( distinctValues.contains( value ) ) {
296 iter.remove();
297 }
298 else {
299 distinctValues.add( value );
300 }
301 }
302 }
303
304 /*** @return the node expression used for sorting comparisons
305 */
306 protected Object getCompareValue(Node node) {
307 return valueOf( node );
308 }
309
310 protected static XPath parse(String text) {
311 try {
312 return new Dom4jXPath( text );
313 }
314 catch (JaxenException e) {
315 throw new InvalidXPathException( text, e.getMessage() );
316 }
317 catch (RuntimeException e) {
318 }
319 throw new InvalidXPathException( text );
320 }
321
322 protected void setNSContext(Object context) {
323 if ( namespaceContext == null ) {
324 xpath.setNamespaceContext( DefaultNamespaceContext.create(context) );
325 }
326 }
327
328 protected void handleJaxenException(JaxenException e) throws XPathException {
329 throw new XPathException(text, e);
330 }
331 }
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379