View Javadoc

1   /* 
2   * This program is licensed under Common Public License Version 0.5.
3   *
4   * For License Information and conditions of use, see "LICENSE" in packaged
5   * 
6   * Change History (based on CVS versions):
7   * 
8   * Version:      Date:       Author:         Description:
9   * 1.1           2002/09/02  yuquing_wang    Initial revision
10  * 1.2           2003/10/28  ckoelle         Method getNodeValuesAsHashtable added. 
11  *                                           Imports restructured. 
12  *                                           @since-Tags added.
13  * 1.3           2003/11/14  ckoelle         Refactoring of _init method.
14  *                                           Get validation switchable.
15  * 1.4           2003/11/27  ckoelle         Get xml-File as resource with the help of the class loader
16  * 1.5			2005/01/15	ckoelle			Added getAttrValuesHashedByNamedAttrs method
17  * 1.6			2005/02/27	ckoelle			Allow schema validation with Xerces 2 parser
18  */
19  
20  package net.wangs.xmlutil;
21  
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.text.DateFormat;
27  import java.util.Hashtable;
28  import java.util.Vector;
29  
30  import javax.xml.parsers.DocumentBuilder;
31  import javax.xml.parsers.DocumentBuilderFactory;
32  import javax.xml.parsers.ParserConfigurationException;
33  
34  import org.w3c.dom.Document;
35  import org.w3c.dom.Element;
36  import org.w3c.dom.NamedNodeMap;
37  import org.w3c.dom.Node;
38  import org.w3c.dom.NodeList;
39  import org.xml.sax.SAXException;
40  
41  /***
42  * <pre>
43  * XMLUtil constructs from either an xml file or a node, and returns detail information of 
44  * node by directory style path into easy-to-use data formats, such as String, int, Hashtable 
45  * of String, etc. 
46  *
47  * Demonstration of the usage of XMLUtil:
48  *
49  *          try {
50  *                String path = "/AAA/BBB/CCC";
51  *
52  *                // You can directly construct an XMLUtil object from XPath like below:
53  *                // XMLUtil util = new XMLUtil("./config/sample.xml");
54  *                // Vector nodes = util.getNodes(path); 
55  *
56  *                // Or you can construct an XMLUtil object from a node (normally also from XMLUtil)
57  *                Vector nodes = XMLUtil.getNodes(path);
58  *                for(int i=0; i&lt;nodes.size(); i++) {
59  *                        Node node = (Node)nodes.elementAt(i);
60  *                        XMLUtil xMLUtil = new XMLUtil(node);
61  *                        String val = xMLUtil.getNodeValue("/BBB/CCC", 1);
62  *                        Hashtable hash = xMLUtil.getChildNodeHashValues("/CCC");
63  *                }
64  *          } catch (Exception e) {
65  *                e.printStackTrace();
66  *          }
67  * </pre>
68  * @author <a href="mailto:yuqingwang_99@yahoo.com">Yuqing Wang</a>
69  */
70  
71  public class XMLUtil {
72  
73      /***
74      * Return all nodes for given node name with path.
75      * @param nodeName
76      * @return Vector of Node
77      * @throws XMLUtilException
78      * @since 1.0
79      */
80      public Vector getNodes(String nodeName) throws XMLUtilException {
81              return _getNodesByString(_configElem, nodeName);
82      }
83  
84      /***
85      * Return all node values for given node name with path.
86      * @return Vector of String
87      * @since 1.0
88      */
89      public Vector getNodeValues(String nodeName) throws XMLUtilException {
90              Vector vNodes = _getNodesByString(_configElem, nodeName);
91  
92              if(vNodes == null) {
93                      String msg = "***Error: Unknown error. node name may not be supplied"; 
94                      throw new XMLUtilException(msg);
95              }
96  
97              if(vNodes.size() == 0) {
98                      String msg = "***Error: No such node " + nodeName; 
99                      throw new XMLUtilException(msg);
100             }
101 
102             Vector vString = new Vector();
103         
104             for (int i=0; i<vNodes.size(); i++) {
105                 Node node = (Node)vNodes.elementAt(i);
106                 if(node.hasChildNodes() && node.getNodeType() == Node.ELEMENT_NODE) {
107                     Node aNode = node.getFirstChild();
108                     if(aNode != null && aNode.getNodeType() == Node.TEXT_NODE) {
109                         vString.add(aNode.getNodeValue().trim());
110                     }
111                 }
112             }
113             return vString;
114     }
115 
116     /***
117     * Return all node values in a Hashtable for given node name with path.
118     * The values are converted in their origin type.
119     * @return Hashtable of String for keys and Object for values
120     * @since 1.1
121     */
122     public Hashtable getNodeValuesAsHashtable(String nodeName) throws XMLUtilException {
123         Vector vNodes = _getNodesByString(_configElem, nodeName);
124 
125         if(vNodes == null) {
126                 String msg = "***Error: Unknown error. node name may not be supplied"; 
127                 throw new XMLUtilException(msg);
128         }
129 
130         if(vNodes.size() == 0) {
131                 String msg = "***Error: No such node " + nodeName; 
132                 throw new XMLUtilException(msg);
133         }
134 
135         Hashtable vString = new Hashtable();
136         
137         for (int i=0; i<vNodes.size(); i++) {
138             Node node = (Node)vNodes.elementAt(i);
139             XMLUtil util = new XMLUtil((Element)node);
140             String key = util.getNamedAttrValue("item", "name", 1);
141             String value = util.getNodeValue("item/value", 1);
142             String type = util.getNamedAttrValue("item", "type", 1);
143             // Default: String (Falls type nicht gesetzt !)
144             if (type == null)
145                 type = "String";
146             Object convertedValue = convertType(value, type);
147             vString.put(key, convertedValue);
148         }
149         return vString;
150     }
151         
152     /***
153     * Construct a new object in a given type based on a given object value. 
154     * 
155     * For primative type, please use keywords "int", "short", "long", "double", "char", "byte", 
156     * and "boolean", and the value in return Hashtable is the corresponding Object type: 
157     * "Integer", "Short", "Long", "Double", "Character", "Byte", and "Boolean". 
158     * 
159     * For "java.lang.String" the short forms "string" and "String" are also valid.
160     * For "java.util.Date" the short forms "date" and "Date" are also valid. The format of
161     * a valid date field depends on the current locale.
162     * 
163     * For the other data type, please specify the full Java type name, such as "java.lang.XXX".
164     *
165     */
166     private Object convertType(String val, String type) throws XMLUtilException{
167        Object newVal = null;
168        DateFormat lDateFormat = DateFormat.getDateInstance();
169 
170           // constructing from string if type is primative, otherwise using reflection
171           try {
172              if (type.equals("int"))
173                 newVal = new Integer(val);
174              else if (type.equals("short"))
175                 newVal = new Short(val);
176              else if (type.equals("long"))
177                 newVal = new Long(val);
178              else if (type.equals("char"))
179                 newVal = new Character(val.charAt(0));
180              else if (type.equals("byte"))
181                 newVal = new Byte(val);
182              else if (type.equals("double"))
183                 newVal = new Double(val);
184              else if (type.equals("boolean"))
185                 newVal = new Boolean(val);
186              else if (type.equals("java.lang.String") || type.equals("string") || type.equals("String"))
187                 newVal = val;
188              else if (type.equals("java.util.Date") || type.equals("date") || type.equals("Date")) {
189                 newVal = lDateFormat.parse(val);
190              }
191              else {
192                 Class objClass = Class.forName(type);
193                 newVal = objClass.getConstructor(new Class[] {(type).getClass()}).newInstance(new Object[] {val});
194              }
195           } catch (Exception e) {
196              throw new XMLUtilException("***Error: error constructing params: type=" + type + " value=" + val + "\nError message: " + e.getMessage());
197           } 
198        return newVal;
199     }
200 
201 
202     /***
203     * Return node for given node name and index
204     * @param nodename - name of the node with full path
205     * @param index - the index number of node that satisfies the nodeName, started from 1, top-down
206     * @since 1.0
207     */
208     public Node getNode(String nodeName, int index) throws XMLUtilException {
209             Vector nodes = getNodes(nodeName);
210             if (nodes.size() < index) {
211                     String msg = "***Error: Node's index out of bound: " + nodeName; 
212                     throw new XMLUtilException(msg);
213             }
214 
215             return (Node)nodes.elementAt(index-1);
216     }
217 
218 
219     /***
220     * Return Vector of nodes for given node name and one given attribute values
221     * More complicated case is to give a hashtable of attribute name-value pairs, but
222     * we are not going to provide this since it's rarely used.
223     * @param nodeName - name of the node with full path
224     * @param attrName - name of attribute in the node
225     * @param attrValue - value of the given attribute
226     * @since 1.0
227     */
228     public Vector getNodesByAttrValue(String nodeName, String attrName, String attrValue) throws XMLUtilException {
229     Vector result = new Vector();
230             Vector nodes = getNodes(nodeName);
231 
232             Element elemNode = null;
233             for (int i=0; i<nodes.size(); i++) {
234                     elemNode = (Element)nodes.elementAt(i);
235                     if (!elemNode.hasAttribute(attrName)) {
236                     // this node does not fit our need
237                     }
238 
239                     if (elemNode.getAttribute(attrName).equals(attrValue))
240                             result.add(elemNode);
241             }
242 
243             if (result == null) {
244                     String msg = "***Error: Wrong values for node=" + nodeName + ", attribute name=" + attrName + ", attribute value=" + attrValue; 
245                     throw new XMLUtilException(msg);
246             }
247 
248             return result;
249     }
250 
251 
252     /***
253     * Return node for given node name and one given attribute values
254     * <pre>
255     * If there are multiple nodes satisfied this condition, the first one is returned.
256     * A common usage of this method is: to obtain a particular node from a set of sibling nodes.
257     * All nodes have the same tag name and attribute names. The only way to differentiate one
258     * from them is the attribute value. For example,
259     *       &lt;service name="ServiceAAA"&gt;
260     *               // data belongs to ServiceAAA
261     *       &lt;/service&gt;
262     *       &lt;service name="ServiceBBB"&gt;
263     *               // data belongs to ServiceBBB
264     *       &lt;/service&gt;
265     *       &lt;service name="ServiceCCC"&gt;
266     *               // data belongs to ServiceCCC
267     *       &lt;/service&gt;
268     * More complicated case is to give a hashtable of attribute name-value pairs, but
269     * we are not going to provide this since it's rarely used.
270     * </pre>
271     * @param nodeName - name of the node with full path
272     * @param attrName - name of attribute in the node
273     * @param attrValue - value of the given attribute
274     * @since 1.0
275     */
276     public Node getNodeByAttrValue(String nodeName, String attrName, String attrValue) throws XMLUtilException {
277             Vector nodes = getNodes(nodeName);
278 
279             Element elemNode = null;
280             for (int i=0; i<nodes.size(); i++) {
281                     elemNode = (Element)nodes.elementAt(i);
282                     if (!elemNode.hasAttribute(attrName)) {
283                     // this node does not fit our need
284                     }
285 
286                     if (elemNode.getAttribute(attrName).equals(attrValue))
287                             return elemNode;
288             }
289 
290             // values not match, throw exception
291             String msg = "SFWK:: Wrong values for node=" + nodeName + ", attribute name=" + attrName + ", attribute value=" + attrValue; 
292             throw new XMLUtilException(msg);
293 
294     }
295 
296 
297     /***
298     * Return Vector of nodes for given node name and one given attribute values
299     * <pre>
300     * A common usage of this method is: to obtain a subset of nodes from a set of sibling nodes.
301     * All nodes have the same tag name and attribute names. The only way to differentiate desired 
302     * ones from them is the attribute value. For example,
303     *       &lt;service name="ServiceAAA"&gt;
304     *               // data belongs to ServiceAAA
305     *       &lt;/service&gt;
306     *       &lt;service name="ServiceBBB"&gt;
307     *               // data belongs to ServiceBBB
308     *       &lt;/service&gt;
309     *       &lt;service name="ServiceCCC"&gt;
310     *               // data belongs to ServiceCCC
311     *       &lt;/service&gt;
312     * More complicated case is to give a hashtable of attribute name-value pairs, but
313     * we are not going to provide this since it's rarely used.
314     * </pre>
315     * @param nodeName - name of the node with full path
316     * @param attrName - name of attribute in the node
317     * @param attrValue - value of the given attribute
318     * @since 1.0
319     */
320     public String getNodeValueByAttrValue(String nodeName, String attrName, String attrValue) 
321         throws XMLUtilException {
322             Node node = getNodeByAttrValue(nodeName, attrName, attrValue);
323 
324             if(node.hasChildNodes() && node.getNodeType() == Node.ELEMENT_NODE) {
325                     Node aNode = node.getFirstChild();
326                     if(aNode != null && aNode.getNodeType() == Node.TEXT_NODE) {
327                             return aNode.getNodeValue().trim();
328                     }
329             }
330 
331             // not an element node, return null
332             return null;
333     }
334 
335 
336     /***
337     * For a known node, get its value.
338     * If more than one nodes with the same name under
339     * given path, the one in the index (start from "1" Top-Down) is retrieved.
340     * @param nodename - name of the node with full path
341     * @param index - the index number of node that satisfies the nodeName
342     * @return String - string value of node
343     * @exception XMLUtilException
344     * @since 1.0
345     */
346     public String getNodeValue(String nodeName, int index) throws XMLUtilException {
347             Vector vNodes = _getNodesByString(_configElem, nodeName);
348 
349             if(vNodes == null) {
350                 String msg = "***Error: Unknown error. node name may not be supplied"; 
351                 throw new XMLUtilException(msg);
352             }
353 
354             if(vNodes.size() == 0) {
355                 String msg = "***Error: No such node " + nodeName; 
356                 throw new XMLUtilException(msg);
357             }
358 
359             Node node = (Node)vNodes.elementAt(index - 1);
360             if(node.hasChildNodes() && node.getNodeType() == Node.ELEMENT_NODE) {
361                 Node aNode = node.getFirstChild();
362                 if(aNode != null && aNode.getNodeType() == Node.TEXT_NODE) {
363                     return aNode.getNodeValue().trim();
364                 }
365             }
366 
367             return null; 
368     }
369 
370     
371     /*** 
372     * Given a node name (with path), returns number of nodes with the same
373     * name under the same path.
374     * @param nodename - name of the node with full path
375     * @return int 
376     * @exception XMLUtilException
377     * @since 1.0
378     */
379     public  int getNodeCount(String nodeName) throws XMLUtilException {
380         Vector vNodes = _getNodesByString(_configElem, nodeName);
381 
382         if(vNodes == null) {
383             String msg = "***Error: Unknown error. node name may not be supplied"; 
384             throw new XMLUtilException(msg);
385         }
386 
387         return vNodes.size();
388     }
389 
390 
391     /*** 
392     * Given a node name (with path), returns all attributes of this node
393     * with name-value pairs. If more than one nodes with the same name under 
394     * given path, the one in the index (start from "1" Top-Down) is retrieved. 
395     *
396     * @param nodename - name of the node with full path
397     * @param index - the index number of node that satisfies the nodeName
398     * @return String - string value of node
399     * @exception XMLUtilException
400     * @since 1.0
401     */ 
402     public  Hashtable getAttrValues(String nodeName, int index) throws XMLUtilException {
403 
404             Vector vNodes = _getNodesByString(_configElem, nodeName);
405 
406             if(vNodes == null) {
407                     String msg = "***Error: Unknown error. node name may not be supplied"; 
408                     throw new XMLUtilException(msg);
409             }
410 
411             if(vNodes.size() == 0) {
412                 String msg = "***Error: No such node " + nodeName; 
413                 throw new XMLUtilException(msg);
414             }
415 
416         Node node = (Node)vNodes.elementAt(index - 1);
417         if (node == null) {
418             String msg = "***Error: Index is wrong"; 
419             throw new XMLUtilException(msg);
420             // return null;
421         }
422 
423         NamedNodeMap attrMap = node.getAttributes();
424         if (attrMap == null) {
425             return null;
426         }
427 
428         Hashtable attrs = new Hashtable(); 
429         for (int i=0; i<attrMap.getLength(); i++) {
430             Node attrItem = attrMap.item(i);
431             attrs.put(attrItem.getNodeName(), attrItem.getNodeValue());
432         }
433 
434         return attrs;
435     }
436 
437     /*** 
438     * Given a node name (with path) and atrribute name, returns attribute value.
439     * If more than one nodes with the same name under given path, the one in the 
440       * index (start from "1" Top-Down) is retrieved. 
441     *
442     * @param nodename - name of the node with full path
443     * @param attrName - name of the attribute in this node
444     * @param index - the index number of node that satisfies the nodeName
445     * @return String - string value of node
446     * @exception XMLUtilException
447     * @since 1.0
448     */ 
449     public  String getNamedAttrValue(String nodeName, String attrName, int index) 
450                     throws XMLUtilException {
451             Vector vNodes = _getNodesByString(_configElem, nodeName);
452 
453             if(vNodes == null) {
454                     String msg = "***Error: Unknown error. node name may not be supplied"; 
455                     throw new XMLUtilException(msg);
456             }
457 
458             if(vNodes.size() == 0) {
459                     String msg = "***Error: No such node " + nodeName; 
460                     throw new XMLUtilException(msg);
461             }
462 
463             Node node = (Node)vNodes.elementAt(index - 1);
464             if (node == null) {
465                     String msg = "***Error: Index is wrong";
466                     throw new XMLUtilException(msg);
467                     // return null;
468             }
469 
470             NamedNodeMap attrMap = node.getAttributes();
471             if (attrMap == null) {
472                     String msg = "***Error: No attributes for the element " + nodeName; 
473                     throw new XMLUtilException(msg);
474                     // return null;
475             }        
476 
477             Node attrNode = attrMap.getNamedItem(attrName);
478 
479             if (attrNode == null) {
480                     String msg = "***Error: No such attribute: " + attrName; 
481                     throw new XMLUtilException(msg);
482                     // return null;
483             }
484 
485             return attrNode.getNodeValue();
486 
487     }
488 
489 
490     /***
491     * Given a node name (with path) and attributes array, returns MultiKeyHashtable of all nodes with
492     * the same name in the same path. The Hash Key is String array of all attributes values. 
493     * For example, a node
494     * <pre>
495     *       "/AAA/BBB/CCC"
496     * is defined as:
497     *       ...
498     *       &lt;CCC&gt;
499     *               &lt;DDD attrA="a1" attrB="b1"&gt;
500     *                       &lt;EEE&gt;Value1 of EEE&lt;/EEE&gt;
501     *                       &lt;FFF/&gt;
502     *               &lt;/DDD&gt;
503     *               &lt;DDD attrA="a1" attrB="b2"&gt;
504     *                       &lt;EEE&gt;Value2 of EEE&lt;/EEE&gt;
505     *                       &lt;FFF/&gt;
506     *               &lt;/DDD&gt;
507     *               &lt;DDD attrA="a2" attrB="b1"&gt;
508     *                       &lt;EEE&gt;Value3 of EEE&lt;/EEE&gt;
509     *                       &lt;FFF/&gt;
510     *               &lt;/DDD&gt;
511     *       &lt;/CCC&gt;
512     *       ...
513     * Then
514     *       getNodeValuesHashedByNamedAttrs("/AAA/BBB/CCC", new String[] {"attrA", "attrB"})
515     * returns:
516     *       Keys                                    Values
517     *       --------------------------              -------------------------
518     *       {"a1", "b1"}                            first node of DDD
519     *       {"a1", "b2"}                            second node of DDD
520     *       {"a2", "b1"}                            third node of DDD
521     * 
522     * </pre>
523     * @since 1.0   
524     */
525             
526     public  MultiKeyHashtable getNodesHashedByNamedAttrs(String nodeName, String[] attrNames) 
527                     throws XMLUtilException {
528             Vector vNodes = _getNodesByString(_configElem, nodeName);
529 
530             if(vNodes == null) {
531                     String msg = "***Error: Unknown error. node name may not be supplied"; 
532                     throw new XMLUtilException(msg);
533             }
534 
535             if(vNodes.size() == 0) {
536                     String msg = "***Error: No such node " + nodeName; 
537                     throw new XMLUtilException(msg);
538             }
539 
540             MultiKeyHashtable result = new MultiKeyHashtable();
541 
542             for (int i=0; i<vNodes.size(); i++) {
543                     Node node = (Node)vNodes.elementAt(i);
544                     NamedNodeMap attrMap = node.getAttributes();
545                     if (attrMap.getLength() == 0 || attrMap == null) {
546                             // This node has no attributes
547                     } else {
548                         String[] keys = new String[attrNames.length];
549                         for (int j=0; j<attrNames.length; j++) {
550                             Node attrNode = attrMap.getNamedItem(attrNames[j]);
551                             if (attrNode != null) 
552                                 keys[j] = attrNode.getNodeValue().trim();
553                             else 
554                                 keys[j] = "";
555                         }
556                         result.put(keys, node);
557                     }
558             }
559 
560             return result; 
561     }
562 
563 
564     /*** 
565     * Given a node name (with path) and attributes array, returns all node values with the same name
566     * in the same path. The Hash Key is String array of all attributes values.
567     * For example, a node <p>
568     *    "/AAA/BBB/CCC"<p>
569     * is defined as:<p>
570     * <pre>
571     *    ...
572     *    &lt;CCC&gt;
573     *    &lt;DDD attrA="a1" attrB="b1"&gt;v1&lt;/DDD&gt;
574     *    &lt;DDD attrA="a2" attrB="b2"&gt;v2&lt;/DDD&gt;
575     *    &lt;DDD attrA="a3" attrB="b3"&gt;v3&lt;/DDD&gt;
576     *    &lt;DDD attrA="a4" attrB="b4"&gt;v4&lt;/DDD&gt;
577     *    &lt;DDD attrA="a5" attrB="b5"&gt;v5&lt;/DDD&gt;
578     *    &lt;/CCC&gt;
579     *    ...
580     * </pre>
581     * Then <p>
582     *     getNodeValuesHashedByNamedAttrs("/AAA/BBB/CCC",
583     *                     new String[] {"attrA", "attrB"})<p>
584     * returns:<p>
585     * <pre>
586     *    Keys                              Values    
587     *    --------------------------        -------------------------
588     *    {"a1",  "b1"}                     v1
589     *    {"a2",  "b2"}                     v2
590     *    {"a3",  "b3"}                     v3
591     *    {"a4",  "b4"}                     v4
592     *    {"a5",  "b5"}                     v5
593     * </pre>   
594     *
595     * This method is a sub case of getNodesHashedByNamedAttrs(), in case the node is leaf.
596     * @since 1.0
597     */
598         
599     public  MultiKeyHashtable getNodeValuesHashedByNamedAttrs(String nodeName, String[] attrNames) 
600             throws XMLUtilException {
601         Vector vNodes = _getNodesByString(_configElem, nodeName);
602 
603         if(vNodes == null) {
604             String msg = "***Error: Unknown error. node name may not be supplied"; 
605             throw new XMLUtilException(msg);
606         }
607 
608         if(vNodes.size() == 0) {
609             String msg = "***Error: No such node " + nodeName; 
610             throw new XMLUtilException(msg);
611         }
612         
613         MultiKeyHashtable result = new MultiKeyHashtable();
614         
615         for (int i=0; i<vNodes.size(); i++) {
616             Node node = (Node)vNodes.elementAt(i);
617             NamedNodeMap attrMap = node.getAttributes();
618             if (attrMap.getLength() == 0 || attrMap == null) {
619                 // This node has no attributes            
620             } else {
621                 String[] keys = new String[attrNames.length];
622                 for (int j=0; j<attrNames.length; j++) {
623                     Node attrNode = attrMap.getNamedItem(attrNames[j]);
624                     if (attrNode != null) 
625                         keys[j] = attrNode.getNodeValue().trim();
626                     else 
627                         keys[j] = "";
628                 }
629                 String value = "";
630                 if(node.hasChildNodes() && node.getNodeType() == Node.ELEMENT_NODE) {
631                 Node aNode = node.getFirstChild();
632                 if(aNode != null && aNode.getNodeType() == Node.TEXT_NODE) {
633                     value = aNode.getNodeValue().trim();
634                 }
635                     result.put(keys, value);
636                 }
637             }
638         }
639 
640         return result; 
641     }
642 
643 
644     /***
645     * Given a node name (with path) and one attribute, returns Hashtable of all nodes with
646     * the same name in the same path. The Hash Key is String value of given attribute value. 
647     *
648     * This method is a simplified version of getNodeValuesHashedByNamedAttrs(). It takes
649     * only one attribute, and returns Hashtable, instead of MultiKeyHashtable.
650     *
651     * For example, a node
652     * <pre>
653     *       "/AAA/some-tag"
654     * is defined as:
655     *       ...
656     *       &lt;some-tag&gt;
657     *               &lt;some-node attrA="a1"&gt;
658     *                       &lt;sub-node&gt;value1&lt;/sub-node&gt;
659     *               &lt;/some-node&gt;
660     *               &lt;some-node attrA="a2"&gt;
661     *                       &lt;sub-node&gt;value2&lt;/sub-node&gt;
662     *               &lt;/some-node&gt;
663     *               &lt;some-node attrA="a3"&gt;
664     *                       &lt;sub-node&gt;value3&lt;/sub-node&gt;
665     *               &lt;/some-node&gt;
666     *       &lt;/some-tag&gt;
667     *       ...
668     * Then
669     *       getNodeValuesHashedByNamedAttr("/AAA/some-tag", "attrA")
670     * returns:
671     *       Keys                          Values
672     *       --------------------------    -------------------------
673     *       a1                            first node of some-node
674     *       a2                            second node of some-node
675     *       a3                            third node of some-node
676     * 
677     * </pre>   
678     * @since 1.0
679     */
680     public Hashtable getNodesHashedByNamedAttr(String nodeName, String attrName) 
681     throws XMLUtilException {
682          Vector vNodes = _getNodesByString(_configElem, nodeName);
683 
684          if(vNodes == null) {
685                  String msg = "***Error: Unknown error. node name may not be supplied";
686                  throw new XMLUtilException(msg);
687          }
688 
689          if(vNodes.size() == 0) {
690                  String msg = "***Error: No such node " + nodeName; 
691                  throw new XMLUtilException(msg);
692          }
693 
694          Hashtable result = new Hashtable();
695 
696          for (int i=0; i<vNodes.size(); i++) {
697                  Node node = (Node)vNodes.elementAt(i);
698                  NamedNodeMap attrMap = node.getAttributes();
699                  if (attrMap.getLength() == 0 || attrMap == null) {
700                          // This node has no attributes
701                  } else {
702                          String key = "";
703                          Node attrNode = attrMap.getNamedItem(attrName);
704                          if (attrNode != null) {
705                              key = attrNode.getNodeValue().trim();
706                          }
707                          result.put(key, node);
708                  }
709          }
710 
711          return result; 
712     }
713 
714 
715     /***
716     * Given a node name (with path) and one attribute, returns Hashtable of all nodes with
717     * the same name in the same path. The Hash Key is String value of given attribute value.
718     *
719     * This method is a simplified version of getNodeValuesHashedByNamedAttrs(). It takes
720     * only one attribute, and returns Hashtable, instead of MultiKeyHashtable.
721     *
722     * For example, a node
723     * <pre>
724     *       "/AAA/some-tag"
725     * is defined as:
726     *       ...
727     *       &lt;some-tag&gt;
728     *               &lt;some-node attrA="a1"&gt;
729     *                       &lt;sub-node&gt;value1&lt;/sub-node&gt;
730     *               &lt;/some-node&gt;
731     *               &lt;some-node attrA="a2"&gt;
732     *                       &lt;sub-node&gt;value2&lt;/sub-node&gt;
733     *               &lt;/some-node&gt;
734     *               &lt;some-node attrA="a3"&gt;
735     *                       &lt;sub-node&gt;value3&lt;/sub-node&gt;
736     *               &lt;/some-node&gt;
737     *       &lt;/some-tag&gt;
738     *       ...
739     * Then
740     *       getNodeValuesHashedByNamedAttr("/AAA/some-tag", "attrA")
741     * returns:
742     *       Keys                                    Values
743     *       --------------------------              -------------------------
744     *       a1                                      value1 
745     *       a2                                      value2 
746     *       a3                                      value3 
747     *
748     * </pre>
749     * @since 1.0  
750     */
751     public  Hashtable getNodeValuesHashedByNamedAttr(String nodeName, String attrName) 
752             throws XMLUtilException {
753         Vector vNodes = _getNodesByString(_configElem, nodeName);
754         
755          if(vNodes == null) {
756                     String msg = "***Error: Unknown error. node name may not be supplied";
757                     throw new XMLUtilException(msg);
758         }
759 
760         if(vNodes.size() == 0) {
761                     String msg = "***Error: No such node " + nodeName; 
762                     throw new XMLUtilException(msg);
763         }
764         
765         Hashtable result = new Hashtable();
766         
767         for (int i=0; i<vNodes.size(); i++) {
768             Node node = (Node)vNodes.elementAt(i);
769             NamedNodeMap attrMap = node.getAttributes();
770             if (attrMap.getLength() == 0 || attrMap == null) {
771                 // This node has no attributes            
772             } else {
773                 String key = ""; 
774                 Node attrNode = attrMap.getNamedItem(attrName);
775                 if (attrNode != null) {
776                     key = attrNode.getNodeValue().trim();
777                 }
778                 String value = "";
779                     if(node.hasChildNodes() && node.getNodeType() == Node.ELEMENT_NODE) {
780                     Node aNode = node.getFirstChild();
781                     if(aNode != null && aNode.getNodeType() == Node.TEXT_NODE) {
782                         value = aNode.getNodeValue().trim();
783                     }
784                     result.put(key, value);
785                 }
786             }
787         }
788 
789         return result; 
790     }
791 
792 
793     /***
794     * Given node name (with path) and two attribute's names, returns a Hashtable with key is one attribute's value
795     * and value is another attribute's value.
796     *
797     * @param nodeName: the name of node with path
798     * @param val_attrName: name of attribute which value is treated as hash value
799     * @param key_attrName: name of attribute which value is treated as hash key
800     * @return Hashtable
801     * @since 1.0
802     */
803     public Hashtable getAttrValuesHashedByNamedAttr(String nodeName, String val_attrName, String key_attrName)
804                     throws XMLUtilException {
805             Vector vNodes = _getNodesByString(_configElem, nodeName);
806 
807             if(vNodes == null) {
808                     String msg = "SFWK:: Unknown error. node name may not be supplied";
809                     throw new XMLUtilException(msg);
810             }
811 
812             if(vNodes.size() == 0) {
813                     String msg = "SFWK:: No such node " + nodeName;
814                     throw new XMLUtilException(msg);
815             }
816 
817             Hashtable result = new Hashtable();
818 
819             for (int i=0; i<vNodes.size(); i++) {
820                     Node node = (Node)vNodes.elementAt(i);
821                     NamedNodeMap attrMap = node.getAttributes();
822                     if (attrMap.getLength() == 0 || attrMap == null) {
823                             // This node has no attributes
824                     } else {
825                             String key = "";
826                             Node key_attrNode = attrMap.getNamedItem(key_attrName);
827                             if (key_attrNode != null) {
828                                     key = key_attrNode.getNodeValue().trim();
829                             }
830                             if (!key.equals("")) {  // only consider when key attr is not empty
831                                     String value = "";
832                                     Node val_attrNode = attrMap.getNamedItem(val_attrName);
833                                     if (val_attrNode != null) {
834                                             value = val_attrNode.getNodeValue().trim();
835                                     }
836                                     result.put(key, value);
837                             }
838                     }
839             }
840 
841             return result;
842     }
843 
844 
845     /***
846      * Given node name (with path), an attribute name and an array of attribute names, returns a MultiKeyHashtable 
847      * with keys from two or more attributes and value from another attribute.
848      *
849      * @param nodeName: the name of node with path
850      * @param val_attrName: name of attribute which value is treated as hash value
851      * @param key_attrName: array of names of attributes which values are treated as hash keys
852      * @return MultiKeyHashtable with results
853      * @since 1.0.3
854      */
855      public MultiKeyHashtable getAttrValuesHashedByNamedAttrs(String nodeName,
856             String val_attrName, String[] key_attrName) throws XMLUtilException {
857 
858         Vector vNodes = _getNodesByString(_configElem, nodeName);
859 
860         if (vNodes == null) {
861             String msg = "SFWK:: Unknown error. node name may not be supplied";
862             throw new XMLUtilException(msg);
863         }
864 
865         if (vNodes.size() == 0) {
866             String msg = "SFWK:: No such node " + nodeName;
867             throw new XMLUtilException(msg);
868         }
869 
870         MultiKeyHashtable result = new MultiKeyHashtable();
871 
872         for (int i = 0; i < vNodes.size(); i++) {
873             Node node = (Node) vNodes.elementAt(i);
874             NamedNodeMap attrMap = node.getAttributes();
875             if (attrMap.getLength() == 0 || attrMap == null) {
876                 // This node has no attributes
877             } else {
878                 String[] keys = new String[key_attrName.length];
879                 for (int j = 0; j < key_attrName.length; j++) {
880                     Node key_attrNode = attrMap.getNamedItem(key_attrName[j]);
881                     if (key_attrNode != null) {
882                         keys[j] = key_attrNode.getNodeValue().trim();
883                     }
884                 }
885                 if (!keys[0].equals("")) { // only consider when first key attr
886                                            // is not empty
887                     String value = "";
888                     Node val_attrNode = attrMap.getNamedItem(val_attrName);
889                     if (val_attrNode != null) {
890                         value = val_attrNode.getNodeValue().trim();
891                     }
892                     result.put(keys, value);
893                 }
894             }
895         }
896 
897         return result;
898     }
899 
900     /***
901      * Given a node name, return all child nodes' name-value pairs into a
902      * hashtable. Node names are hashkeys, node String values are hashvalues.
903      * 
904      * @return Hashtable. Node names are hashkeys, node String values are
905      *         hashvalues.
906      * @since 1.0
907      */
908     public  Hashtable getChildNodeHashValues(String nodeName) throws XMLUtilException {
909         Vector vTag = _getNodesByString(_configElem, nodeName);
910         if (vTag == null ) {
911             String msg = "***Error: No such node: " + nodeName; 
912             throw new XMLUtilException(msg);
913         }
914         
915         if (vTag.size() == 0) {
916             return null;
917         }
918 
919         Hashtable result = new Hashtable();
920 
921             Node node = (Node)vTag.elementAt(0);
922         NodeList children = node.getChildNodes();
923         
924         for(int i=0; i<children.getLength(); i++) {
925             Node child = children.item(i);
926             String name = child.getNodeName();
927             String value = "";
928             if(child.getNodeType() == Node.ELEMENT_NODE) {
929                 if(child.hasChildNodes()) {    
930                     Node aNode  = child.getFirstChild();
931                     if(aNode != null && aNode.getNodeType() == Node.TEXT_NODE) {
932                         value = aNode.getNodeValue().trim();
933                     }
934                 }
935                 result.put(name, value);
936             }
937         }
938 
939         return result;
940     }
941 
942 
943     /***
944      * Loading XML file to DOM tree.
945      * It first tries to get file location from absolute path,
946      * if not found, then search from classpath (relative path).
947      * During load the XML file is validated. The validation 
948      * can be switched off by providing the system property
949      * "xmlutil.script.validation=false" 
950      * @param fileName Name of XML file
951      * @throws XMLUtilException 
952      */
953     private void _init(String fileName) throws XMLUtilException {
954         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
955         String validate = System.getProperty("xmlutil.script.validation");
956         if (validate != null && validate.compareTo("false") == 0) {
957             factory.setValidating(false);
958         } else {
959             factory.setValidating(true);
960             if (System.getProperty("xmlutil.parser.version") != null && System.getProperty("xmlutil.parser.version").compareTo("Xerces2") == 0) {
961                 factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
962             }
963         }
964         factory.setNamespaceAware(true);
965 
966         DocumentBuilder domBuilder = null;
967         try {
968             domBuilder = factory.newDocumentBuilder();
969         } catch (ParserConfigurationException e) {
970             String msg = "***Error: " + e.getMessage();
971             throw new XMLUtilException(msg); 
972         }
973         SAXErrorHandlerImpl error = new SAXErrorHandlerImpl();
974         domBuilder.setErrorHandler(error);
975 
976         Document configDOM = null;
977 
978         try {
979             InputStream is = new FileInputStream(fileName);
980             configDOM = domBuilder.parse(is);
981             is.close();
982         } catch (FileNotFoundException e) {
983         // fails absolute path, will try relative path based on classpath
984         } catch (IOException e) {
985             String msg = "***Error: " + e.getMessage();
986             throw new XMLUtilException(msg); 
987         } catch (SAXException e) {
988             String msg = "***Error: " + e.getMessage();
989             throw new XMLUtilException(msg); 
990         }
991 
992         if (configDOM == null) {
993             InputStream is = getClass().getClassLoader().getResourceAsStream( fileName );
994 
995             if ( is == null ) {
996                 String msg = "***Error: " + fileName + " is missing";
997                     throw new XMLUtilException(msg);
998             }
999 
1000             // clear previous parsing errors
1001             error.clearErrors();
1002             try {
1003                 configDOM = domBuilder.parse(is);
1004                 is.close();
1005             } catch (FileNotFoundException e) {
1006             // fails absolute path, will try relative path based on classpath
1007             } catch (IOException e) {
1008                 String msg = "***Error: " + e.getMessage();
1009                 throw new XMLUtilException(msg); 
1010             } catch (SAXException e) {
1011                 String msg = "***Error: " + e.getMessage();
1012                 throw new XMLUtilException(msg); 
1013             }
1014         } 
1015 
1016         if (configDOM == null) {
1017             String msg = "***Error: DOM tree is null. Please verify if " + fileName + " is under either absolute path or classpath.";
1018             throw new XMLUtilException(msg);
1019         }
1020 
1021         if (!configDOM.isSupported("Traversal", "2.0")) {
1022             String msg = "***Error: This DOM Document does not support Traversal. This might be caused by the fact that the XML parser does not support Traversal. Use a more recent parser (e.g. Xerces 1.4.4 or higher).";
1023             throw new XMLUtilException(msg);
1024         }
1025 
1026         if ((error.getErrors()).size() > 0) {
1027             String msg = "***Error: xml file " + fileName + " is INVALID! ";  
1028             throw new XMLUtilException(msg);
1029         }                
1030 
1031         _configElem = configDOM.getDocumentElement();
1032 
1033     }    
1034 
1035 
1036     /*** 
1037     * Recursive function <p>
1038     * Start from given Element, search all nodes that fits given path. 
1039     * @return Vector of nodes.
1040     */
1041     private Vector _getNodesByString(Element elem, String path) {
1042             Vector vTag = XStringParser.parseXString(path);
1043             Vector vNode = new Vector();
1044             String tmpPath = path;
1045 
1046             if(vTag.size() == 0) {
1047                     return null;
1048             }
1049 
1050             // Ending condition
1051             if(vTag.size() == 1) {
1052                     if((elem.getTagName()).equals((String)vTag.elementAt(0))) {
1053                            vNode.addElement((Node)elem);
1054                     }
1055                     return vNode;
1056             }
1057 
1058             int index = tmpPath.indexOf(XStringParser.getPathToken(), 1);
1059             if (index == -1) {
1060                 return null;
1061             }
1062             tmpPath = tmpPath.substring(index + 1);
1063             
1064 /* No, don't use this: it will retrieve ALL nodes in ALL levels down...
1065             NodeList subNodes = elem.getElementsByTagName((String)vTag.elementAt(1));
1066             for (int i=0; i<subNodes.getLength(); i++) {
1067                     Vector vTmp = _getNodesByString((Element)subNodes.item(i), tmpPath);
1068                     // append recursive result
1069                     if (vTmp.size() != 0) {
1070                         vNode.addAll(vTmp);
1071                     }
1072             } 
1073 */
1074 
1075             NodeList childNodes = elem.getChildNodes();
1076             if (childNodes == null)
1077                 return null;
1078 
1079             for (int i=0; i<childNodes.getLength(); i++) {
1080                 Node child = childNodes.item(i);
1081                 if(child.getNodeType() == Node.ELEMENT_NODE) {
1082                     String tagName = ((Element)child).getTagName();
1083                     if (tagName.equals((String)vTag.elementAt(1))) {
1084                         Vector vTmp = _getNodesByString((Element)childNodes.item(i), tmpPath);
1085                         // append recursive result
1086                         if (vTmp.size() != 0) {
1087                             vNode.addAll(vTmp);
1088                         }
1089                     }
1090                 }
1091             }
1092             
1093             return vNode;
1094         }
1095 
1096 
1097     /***
1098     * Constructor: constructing internal element from Node object.
1099     * This Node object must be either DOCUMENT_NODE or ELEMENT_NODE type
1100     * @since 1.0
1101     */
1102     public XMLUtil(Node node) throws XMLUtilException {
1103         if(node.getNodeType() == Node.DOCUMENT_NODE) {
1104             _configElem = ((Document)node).getDocumentElement();
1105         } else if (node.getNodeType() == Node.ELEMENT_NODE) {
1106             _configElem = (Element)node; 
1107         } else {
1108             throw new XMLUtilException("***Error: Error constructing XMLUtil instance. Node is neither Document nor Element type.");
1109         }
1110     }
1111 
1112 
1113     /***
1114     * Constructor: constructing internal element from XML file
1115     * @since 1.0
1116     */
1117     public XMLUtil(String fileName) throws XMLUtilException {
1118         _init(fileName);
1119     }
1120 
1121     private Element _configElem;
1122 
1123 }
1124