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