KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > excalibur > xml > xpath > XPathUtil


1 /*
2  * Copyright 2002-2004 The Apache Software Foundation
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
12  * implied.
13  *
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.excalibur.xml.xpath;
18
19
20 import org.w3c.dom.*;
21
22 /**
23  * This is a simple XPath helper class. It uses a faster approach
24  * for simple XPath expressions and can create XPaths.
25  * If you know that your XPath expression is simple, you should use this
26  * helper instead.
27  *
28  * @author <a HREF="mailto:dev@avalon.apache.org">Avalon Development Team</a>
29  * @version CVS $Id: XPathUtil.java,v 1.4 2004/02/28 11:47:15 cziegeler Exp $
30 */

31 public final class XPathUtil {
32
33     /**
34      * Return the <CODE>Node</CODE> from the DOM Node <CODE>rootNode</CODE>
35      * using the XPath expression <CODE>path</CODE>.
36      * If the node does not exist, it is created and then returned.
37      * This is a very simple method for creating new nodes. If the
38      * XPath contains selectors ([,,,]) or "*" it is of course not
39      * possible to create the new node. So if you use such XPaths
40      * the node must exist beforehand.
41      * An simple exception is if the expression contains attribute
42      * tests to values (e.g. [@id = 'du' and @number = 'you'],
43      * the attributes with the given values are added. The attributes
44      * must be separated with 'and'.
45      * Another problem are namespaces: XPath requires sometimes selectors for
46      * namespaces, e.g. : /*[namespace-uri()="uri" and local-name()="name"]
47      * Creating such a node with a namespace is not possible right now as we use
48      * a very simple XPath parser which is not able to parse all kinds of selectors
49      * correctly.
50      *
51      * @param processor The XPathProcessor
52      * @param rootNode The node to start the search.
53      * @param path XPath expression for searching the node.
54      * @return The node specified by the path.
55      * @throws XPathException If no path is specified or the XPath engine fails.
56      */

57     public static Node getSingleNode(XPathProcessor processor,
58                                      Node rootNode,
59                                      String JavaDoc path)
60     throws XPathException {
61         // Now we have to parse the string
62
// First test: path? rootNode?
63
if (path == null) {
64             throw new XPathException("XPath is required.");
65         }
66         if (rootNode == null) return rootNode;
67
68         if (path.length() == 0 || path.equals("/") == true) return rootNode;
69
70         // now the first "quick" test is if the node exists using the
71
// full XPathAPI
72
Node testNode = searchSingleNode(processor, rootNode, path);
73         if (testNode != null) return testNode;
74
75         if (path.startsWith("/") == true) path = path.substring(1); // remove leading "/"
76
if (path.endsWith("/") == true) { // remove ending "/" for root node
77
path = path.substring(0, path.length() - 1);
78         }
79
80         // now step through the nodes!
81
Node parent = rootNode;
82         int pos;
83         int posSelector;
84         do {
85             pos = path.indexOf("/"); // get next separator
86
posSelector = path.indexOf("[");
87             if (posSelector != -1 && posSelector < pos) {
88                 posSelector = path.indexOf("]");
89                 pos = path.indexOf("/", posSelector);
90             }
91
92             String JavaDoc nodeName;
93             boolean isAttribute = false;
94             if (pos != -1) { // found separator
95
nodeName = path.substring(0, pos); // string until "/"
96
path = path.substring(pos+1); // rest of string after "/"
97
} else {
98                 nodeName = path;
99             }
100
101             // test for attribute spec
102
if (nodeName.startsWith("@") == true) {
103                 isAttribute = true;
104             }
105
106             Node singleNode = searchSingleNode(processor, parent, nodeName);
107
108             // create node if necessary
109
if (singleNode == null) {
110                 Node newNode;
111                 // delete XPath selectors
112
int posSelect = nodeName.indexOf("[");
113                 String JavaDoc XPathExp = null;
114                 if (posSelect != -1) {
115                     XPathExp = nodeName.substring(posSelect+1, nodeName.length()-1);
116                     nodeName = nodeName.substring(0, posSelect);
117                 }
118                 if (isAttribute == true) {
119                     try {
120                         newNode = rootNode.getOwnerDocument().createAttributeNS(null, nodeName.substring(1));
121                     } catch (DOMException local) {
122                         throw new XPathException("Unable to create new DOM node: '"+nodeName+"'.", local);
123                     }
124                 } else {
125                     try {
126                         newNode = rootNode.getOwnerDocument().createElementNS(null, nodeName);
127                     } catch (DOMException local) {
128                         throw new XPathException("Unable to create new DOM node: '"+nodeName+"'.", local);
129                     }
130                     if (XPathExp != null) {
131                         java.util.List JavaDoc attrValuePairs = new java.util.ArrayList JavaDoc(4);
132                         boolean noError = true;
133
134                         String JavaDoc attr;
135                         String JavaDoc value;
136                         // scan for attributes
137
java.util.StringTokenizer JavaDoc tokenizer = new java.util.StringTokenizer JavaDoc(XPathExp, "= ");
138                         while (tokenizer.hasMoreTokens() == true) {
139                             attr = tokenizer.nextToken();
140                             if (attr.startsWith("@") == true) {
141                                 if (tokenizer.hasMoreTokens() == true) {
142                                     value = tokenizer.nextToken();
143                                     if (value.startsWith("'") && value.endsWith("'")) value = value.substring(1, value.length()-1);
144                                     if (value.startsWith("\"") && value.endsWith("\"")) value = value.substring(1, value.length()-1);
145                                     attrValuePairs.add(attr.substring(1));
146                                     attrValuePairs.add(value);
147                                 } else {
148                                     noError = false;
149                                 }
150                             } else if (attr.trim().equals("and") == false) {
151                                 noError = false;
152                             }
153                         }
154                         if (noError == true) {
155                             for(int l=0;l<attrValuePairs.size();l=l+2) {
156                                 ((Element)newNode).setAttributeNS(null, (String JavaDoc)attrValuePairs.get(l),
157                                                                 (String JavaDoc)attrValuePairs.get(l+1));
158                             }
159                         }
160                     }
161                 }
162                 parent.appendChild(newNode);
163                 parent = newNode;
164             } else {
165                 parent = singleNode;
166             }
167         } while (pos != -1);
168         return parent;
169     }
170
171     /**
172      * Use an XPath string to select a single node. XPath namespace
173      * prefixes are resolved from the context node, which may not
174      * be what you want ({@link #getSingleNode()}).
175      *
176      * @param contextNode The node to start searching from.
177      * @param str A valid XPath string.
178      * @return The first node found that matches the XPath, or null.
179      *
180      */

181     public static Node searchSingleNode(XPathProcessor processor,
182                                         Node contextNode,
183                                         String JavaDoc str) {
184         String JavaDoc[] pathComponents = buildPathArray(str);
185         if (pathComponents == null) {
186             return processor.selectSingleNode(contextNode, str);
187         } else {
188             return getFirstNodeFromPath(contextNode, pathComponents, false);
189         }
190     }
191
192     /**
193      * Use an XPath string to select a nodelist.
194      * XPath namespace prefixes are resolved from the contextNode.
195      *
196      * @param contextNode The node to start searching from.
197      * @param str A valid XPath string.
198      * @return A NodeList, should never be null.
199      *
200      */

201     public static NodeList searchNodeList(XPathProcessor processor,
202                                           Node contextNode,
203                                           String JavaDoc str) {
204         String JavaDoc[] pathComponents = buildPathArray(str);
205         if (pathComponents == null) {
206             return processor.selectNodeList(contextNode, str);
207         } else {
208             return getNodeListFromPath(contextNode, pathComponents);
209         }
210     }
211
212     /**
213      * Build the input for the get...FromPath methods. If the XPath
214      * expression cannot be handled by the methods, <code>null</code>
215      * is returned.
216      */

217     public static String JavaDoc[] buildPathArray(String JavaDoc xpath) {
218         String JavaDoc[] result = null;
219         if (xpath != null && xpath.charAt(0) != '/') {
220             // test
221
int components = 1;
222             int i, l;
223             l = xpath.length();
224             boolean found = false;
225             i = 0;
226             while (i < l && found == false) {
227                 switch (xpath.charAt(i)) {
228                     case '[' : found = true; break;
229                     case '(' : found = true; break;
230                     case '*' : found = true; break;
231                     case '@' : found = true; break;
232                     case ':' : found = true; break;
233                     case '/' : components++;
234                     default: i++;
235                 }
236             }
237             if (found == false) {
238                 result = new String JavaDoc[components];
239                 if (components == 1) {
240                     result[components-1] = xpath;
241                 } else {
242                     i = 0;
243                     int start = 0;
244                     components = 0;
245                     while (i < l) {
246                         if (xpath.charAt(i) == '/') {
247                             result[components] = xpath.substring(start, i);
248                             start = i+1;
249                             components++;
250                         }
251                         i++;
252                     }
253                     result[components] = xpath.substring(start);
254                 }
255             }
256         }
257         return result;
258     }
259
260     /**
261      * Use a path to select the first occurence of a node. The namespace
262      * of a node is ignored!
263      * @param contextNode The node starting the search.
264      * @param path The path to search the node. The
265      * contextNode is searched for a child named path[0],
266      * this node is searched for a child named path[1]...
267      * @param create If a child with the corresponding name is not found
268      * and create is set, this node will be created.
269     */

270     public static Node getFirstNodeFromPath(Node contextNode,
271                                             final String JavaDoc[] path,
272                                             final boolean create) {
273         if (contextNode == null || path == null || path.length == 0)
274             return contextNode;
275         // first test if the node exists
276
Node item = getFirstNodeFromPath(contextNode, path, 0);
277         if (item == null && create == true) {
278             int i = 0;
279             NodeList childs;
280             boolean found;
281             int m, l;
282             while (contextNode != null && i < path.length) {
283                 childs = contextNode.getChildNodes();
284                 found = false;
285                 if (childs != null) {
286                     m = 0;
287                     l = childs.getLength();
288                     while (found == false && m < l) {
289                         item = childs.item(m);
290                         if (item.getNodeType() == Document.ELEMENT_NODE
291                             && item.getLocalName().equals(path[i]) == true) {
292                             found = true;
293                             contextNode = item;
294                         }
295                         m++;
296                     }
297                 }
298                 if (found == false) {
299                     Element e = contextNode.getOwnerDocument().createElementNS(null, path[i]);
300                     contextNode.appendChild(e);
301                     contextNode = e;
302                 }
303                 i++;
304             }
305             item = contextNode;
306         }
307         return item;
308     }
309
310     /**
311      * Private helper method for getFirstNodeFromPath()
312      */

313     private static Node getFirstNodeFromPath(final Node contextNode,
314                                              final String JavaDoc[] path,
315                                              final int startIndex) {
316         int i = 0;
317         NodeList childs;
318         boolean found;
319         int l;
320         Node item = null;
321
322         childs = contextNode.getChildNodes();
323         found = false;
324         if (childs != null) {
325             i = 0;
326             l = childs.getLength();
327             while (found == false && i < l) {
328                 item = childs.item(i);
329                 if (item.getNodeType() == Document.ELEMENT_NODE
330                     && path[startIndex].equals(item.getLocalName()!=null?item.getLocalName():item.getNodeName())) {
331                     if (startIndex == path.length-1) {
332                         found = true;
333                     } else {
334                         item = getFirstNodeFromPath(item, path, startIndex+1);
335                         if (item != null) found = true;
336                     }
337                 }
338                 if (found == false) {
339                     i++;
340                 }
341             }
342             if (found == false) {
343                 item = null;
344             }
345         }
346         return item;
347     }
348
349     /**
350      * Use a path to select all occurences of a node. The namespace
351      * of a node is ignored!
352      * @param contextNode The node starting the search.
353      * @param path The path to search the node. The
354      * contextNode is searched for a child named path[0],
355      * this node is searched for a child named path[1]...
356      */

357     public static NodeList getNodeListFromPath(Node contextNode,
358                                                String JavaDoc[] path) {
359         if (contextNode == null) return new NodeListImpl();
360         if (path == null || path.length == 0) {
361             return new NodeListImpl(new Node[] {contextNode});
362         }
363         NodeListImpl result = new NodeListImpl();
364         try {
365             getNodesFromPath(result, contextNode, path, 0);
366         } catch (NullPointerException JavaDoc npe) {
367             // this NPE is thrown because the parser is not configured
368
// to use DOM Level 2
369
throw new NullPointerException JavaDoc("XMLUtil.getNodeListFromPath() did catch a NullPointerException."+
370                           "This might be due to a missconfigured XML parser which does not use DOM Level 2."+
371                           "Make sure that you use the XML parser shipped with Cocoon.");
372         }
373         return result;
374     }
375
376     /**
377      * Helper method for getNodeListFromPath()
378      */

379     private static void getNodesFromPath(final NodeListImpl result,
380                                          final Node contextNode,
381                                          final String JavaDoc[] path,
382                                          final int startIndex) {
383         final NodeList childs = contextNode.getChildNodes();
384         int m, l;
385         Node item;
386         if (startIndex == (path.length-1)) {
387             if (childs != null) {
388                 m = 0;
389                 l = childs.getLength();
390                 while (m < l) {
391                     item = childs.item(m);
392                     if (item.getNodeType() == Document.ELEMENT_NODE) {
393                         // Work around for DOM Level 1
394
if (path[startIndex].equals(item.getLocalName()!=null?item.getLocalName():item.getNodeName()) == true) {
395                             result.addNode(item);
396                         }
397                     }
398                     m++;
399                 }
400             }
401         } else {
402             if (childs != null) {
403                 m = 0;
404                 l = childs.getLength();
405                 while (m < l) {
406                     item = childs.item(m);
407                     if (item.getNodeType() == Document.ELEMENT_NODE) {
408                         // Work around for DOM Level 1
409
if (path[startIndex].equals(item.getLocalName()!=null?item.getLocalName():item.getNodeName()) == true) {
410                             getNodesFromPath(result, item, path, startIndex+1);
411                         }
412                     }
413                     m++;
414                 }
415             }
416         }
417     }
418
419     /**
420      * Get the value of the node specified by the XPath.
421      * This works similar to xsl:value-of. If the node does not exist <CODE>null</CODE>
422      * is returned.
423      *
424      * @param root The node to start the search.
425      * @param path XPath search expression.
426      * @return The value of the node or <CODE>null</CODE>
427      */

428     public static String JavaDoc getValueOf(XPathProcessor processor,
429                                       Node root, String JavaDoc path)
430     throws XPathException {
431         if (path == null) {
432             throw new XPathException("Not a valid XPath: " + path);
433         }
434         if (root == null) return null;
435         if (path.startsWith("/") == true) path = path.substring(1); // remove leading "/"
436
if (path.endsWith("/") == true) { // remove ending "/" for root node
437
path = path.substring(0, path.length() - 1);
438         }
439
440         Node node = searchSingleNode(processor, root, path);
441         if (node != null) {
442             return getValueOfNode(processor, node);
443         }
444         return null;
445     }
446
447     /**
448      * Get the value of the node specified by the XPath.
449      * This works similar to xsl:value-of. If the node is not found
450      * the <CODE>defaultValue</CODE> is returned.
451      *
452      * @param root The node to start the search.
453      * @param path XPath search expression.
454      * @param defaultValue The default value if the node does not exist.
455      * @return The value of the node or <CODE>defaultValue</CODE>
456      */

457     public static String JavaDoc getValueOf(XPathProcessor processor,
458                                      Node root,
459                                      String JavaDoc path,
460                                      String JavaDoc defaultValue)
461     throws XPathException {
462         String JavaDoc value = getValueOf(processor, root, path);
463         if (value == null) value = defaultValue;
464
465         return value;
466     }
467
468     /**
469      * Get the boolean value of the node specified by the XPath.
470      * This works similar to xsl:value-of. If the node exists and has a value
471      * this value is converted to a boolean, e.g. "true" or "false" as value
472      * will result into the corresponding boolean values.
473      *
474      * @param root The node to start the search.
475      * @param path XPath search expression.
476      * @return The boolean value of the node.
477      * @throws ProcessingException If the node is not found.
478      */

479     public static boolean getValueAsBooleanOf(XPathProcessor processor,
480                                                 Node root,
481                                                 String JavaDoc path)
482     throws XPathException {
483         String JavaDoc value = getValueOf(processor, root, path);
484         if (value == null) {
485             throw new XPathException("No such node: " + path);
486         }
487         return Boolean.valueOf(value).booleanValue();
488     }
489
490     /**
491      * Get the boolean value of the node specified by the XPath.
492      * This works similar to xsl:value-of. If the node exists and has a value
493      * this value is converted to a boolean, e.g. "true" or "false" as value
494      * will result into the corresponding boolean values.
495      * If the node does not exist, the <CODE>defaultValue</CODE> is returned.
496      *
497      * @param root The node to start the search.
498      * @param path XPath search expression.
499      * @param defaultValue Default boolean value.
500      * @return The value of the node or <CODE>defaultValue</CODE>
501      */

502     public static boolean getValueAsBooleanOf(XPathProcessor processor,
503                                                 Node root,
504                                                 String JavaDoc path,
505                                                 boolean defaultValue)
506     throws XPathException {
507         String JavaDoc value = getValueOf(processor, root, path);
508         if (value == null) {
509             return defaultValue;
510         }
511         return Boolean.valueOf(value).booleanValue();
512     }
513
514     /**
515      * Get the value of the DOM node.
516      * The value of a node is the content of the first text node.
517      * If the node has no text nodes, <code>null</code> is returned.
518      */

519     public static String JavaDoc getValueOfNode(XPathProcessor processor,
520                                          Node node) {
521         if (node == null) return null;
522         if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
523             return node.getNodeValue();
524         } else {
525             String JavaDoc value = null;
526             node.normalize();
527             NodeList childs = node.getChildNodes();
528             int i, l;
529             i = 0;
530             l = childs.getLength();
531             while (i < l && value == null) {
532                 if (childs.item(i).getNodeType() == Node.TEXT_NODE)
533                     value = childs.item(i).getNodeValue().trim();
534                 else
535                     i++;
536             }
537             return value;
538         }
539     }
540
541     /**
542      * Get the value of the node.
543      * The value of the node is the content of the first text node.
544      * If the node has no text nodes the <CODE>defaultValue</CODE> is
545      * returned.
546      */

547     public static String JavaDoc getValueOfNode(XPathProcessor processor,
548                                          Node node, String JavaDoc defaultValue) {
549         String JavaDoc value = getValueOfNode(processor, node);
550         if (value == null) value = defaultValue;
551         return value;
552     }
553
554
555 }
556
Popular Tags