View Javadoc

1   /*
2    * Copyright (c) 2003, Henri Yandell
3    * All rights reserved.
4    * 
5    * Redistribution and use in source and binary forms, with or 
6    * without modification, are permitted provided that the 
7    * following conditions are met:
8    * 
9    * + Redistributions of source code must retain the above copyright notice, 
10   *   this list of conditions and the following disclaimer.
11   * 
12   * + Redistributions in binary form must reproduce the above copyright notice, 
13   *   this list of conditions and the following disclaimer in the documentation 
14   *   and/or other materials provided with the distribution.
15   * 
16   * + Neither the name of Genjava-Core nor the names of its contributors 
17   *   may be used to endorse or promote products derived from this software 
18   *   without specific prior written permission.
19   * 
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
23   * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
30   * POSSIBILITY OF SUCH DAMAGE.
31   */
32  package com.generationjava.io.xml;
33  
34  import java.util.Hashtable;
35  import java.util.Enumeration;
36  import java.util.Vector;
37  
38  /***
39   * An xml tag. It can be a processing instructon, an empty tag or 
40   * a normal tag. Currently, if the tag is inside a namespace then 
41   * that is a part of the name. That is, all names of tags are 
42   * fully qualified by the namespace.
43   */
44  public class XMLNode {
45  
46      private static final Enumeration EMPTY = new NullEnumeration();
47  
48      private Hashtable myAttrs;
49      private Hashtable myNodes;  // allows quick lookup
50      private Vector myNodeList;  // maintains order of myNodes
51      private String name;
52      private String value;
53      private boolean pi;
54      private boolean comment;
55      private boolean doctype;
56  
57      /***
58       * Empty Constructor.
59       */
60      public XMLNode() {
61          this("");
62      }
63      
64      /***
65       * Create a new node with this name.
66       */
67      public XMLNode(String name) {
68          this.name = name;
69      }
70      
71      /***
72       * Add a child node to this node.
73       */
74      public void addNode(XMLNode node) {
75          if(this.myNodes == null) {
76              this.myNodes = new Hashtable();
77              this.myNodeList = new Vector();
78          }
79          this.myNodeList.add(node);
80          Object obj = this.myNodes.get( node.getName() );
81          if(obj == null) {
82              this.myNodes.put( node.getName(), node );
83          } else
84          if(obj instanceof XMLNode) {
85              Vector vec = new Vector();
86              vec.addElement(obj);
87              vec.addElement(node);
88              this.myNodes.put( node.getName(), vec );
89          } else
90          if(obj instanceof Vector) {
91              Vector vec = (Vector)obj;
92              vec.addElement(node);
93          }
94      }
95  
96      // Enumerates a child node. Possibly needs renaming.
97      // That is, it enumerates a child nodes value.
98      /***
99       *
100      */
101     public Enumeration enumerateNode(String name) {
102         if(this.myNodes == null) {
103             return EMPTY;
104         }
105         Object obj = this.myNodes.get( name );
106         if(obj == null) {
107             return EMPTY;
108         } else 
109         if(obj instanceof Vector) {
110             return ((Vector)obj).elements();
111         } else {
112             return new SingleEnumeration(obj);
113         }
114     }
115 
116     /***
117      * Add an attribute with specified name and value.
118      */
119     public void addAttr(String name, String value) {
120         if(this.myAttrs == null) {
121             this.myAttrs = new Hashtable();
122         }
123         value = unescapeXml(value);
124         this.myAttrs.put( name, value );
125     }
126     
127     /***
128      * Get the attribute with the specified name.
129      */
130     public String getAttr(String name) {
131         if(myAttrs == null) {
132             return null;
133         }
134         return (String)this.myAttrs.get(name);
135     }
136     
137     /***
138      * Enumerate over all the attributes of this node.
139      * In the order they were added.
140      */
141     public Enumeration enumerateAttr() {
142         if(this.myAttrs == null) {
143             return EMPTY;
144         } else {
145             return this.myAttrs.keys();
146         }
147     }
148 
149     /***
150      * Get the node with the specified name.
151      */
152     public XMLNode getNode(String name) {
153         if(this.myNodes == null) {
154             return null;
155         }
156         Object obj = this.myNodes.get(name);
157         if(obj instanceof XMLNode) {
158             return (XMLNode)obj;
159         }
160         return null;
161     }
162     
163     /***
164      * Enumerate over all of this node's children nodes.
165      */
166     public Enumeration enumerateNode() {
167         if(this.myNodes == null) {
168             return EMPTY;
169         } else {
170 //        return this.myNodes.elements();
171         return this.myNodeList.elements();
172         }
173     }
174     
175     /***
176      * Get the name of this node. Includes the namespace.
177      */
178     public String getName() {
179         return this.name;
180     }
181 
182     /***
183      * Get the namespace of this node.
184      */
185     public String getNamespace() {
186         if(this.name.indexOf(":") != -1) {
187             return this.name.substring(0,this.name.indexOf(":"));
188         } else {
189             return "";
190         }
191     }
192 
193     /***
194      * Get the tag name of this node. Doesn't include namespace.
195      */
196     public String getTagName() {
197         if(this.name.indexOf(":") != -1) {
198             return this.name.substring(this.name.indexOf(":")+1);
199         } else {
200             return this.name;
201         }
202     }
203 
204     /***
205      * Get the appended toString's of the children of this node.
206      * For a text node, it will print out the plaintext.
207      */
208     public String getValue() {
209         if(isComment()) {
210             return "<!-- " + value + " -->";
211         }
212         if(isDocType()) {
213             return "<!DOCTYPE " + value + ">";
214         }
215         if(this.value != null) {
216             return this.value;
217         }
218         if(isInvisible()) {
219             return "";
220         }
221         // QUERY: shouldn't call toString. Needs to improve
222         if(this.myNodeList != null) {
223             StringBuffer buffer = new StringBuffer();
224             Enumeration enum = enumerateNode();
225             while(enum.hasMoreElements()) {
226                 buffer.append(enum.nextElement().toString());
227             }
228             return buffer.toString();
229         }
230         return null;
231     }
232     
233     /***
234      * Set the plaintext contained in this node.
235      */
236     public void setPlaintext(String str) {
237         this.value = unescapeXml(str);
238     }
239 
240     /***
241      * Is this a normal tag?
242      * That is, not plaintext, not comment and not a pi.
243      */
244     public boolean isTag() {
245         return !(this.pi || (this.name == null) || (this.value != null));
246     }
247 
248     /***
249      * Is it invisible
250      */
251     public boolean isInvisible() {
252         return this.name == null;
253     }
254     
255     /***
256      * Set whether this node is invisible or not.
257      */
258     public void setInvisible(boolean b) {
259         if(b) {
260             this.name = null;
261         }
262     }
263     
264     /***
265      * Is it a doctype
266      */
267     public boolean isDocType() {
268         return this.doctype;
269     }
270     
271     /***
272      * Set whether this node is a doctype or not.
273      */
274     public void setDocType(boolean b) {
275         this.doctype = b;
276     }
277     
278     /***
279      * Is it a comment
280      */
281     public boolean isComment() {
282         return this.comment;
283     }
284     
285     /***
286      * Set whether this node is a comment or not.
287      */
288     public void setComment(boolean b) {
289         this.comment = b;
290     }
291     
292     /***
293      * Is it a processing instruction    
294      */
295     public boolean isPI() {
296         return this.pi;
297     }
298     
299     /***
300      * Set whether this node is a processing instruction or not.
301      */
302     public void setPI(boolean b) {
303         this.pi = b;
304     }
305     
306     // IMPL: Assumes that you're unable to remove nodes from 
307     //          a parent node. removeNode and removeAttr is likely to 
308     //          become a needed functionality.
309     /***
310      * Is this node empty.
311      */
312     public boolean isEmpty() {
313         return (this.myNodes == null);
314     }
315 
316     /***
317      * Is this a text node.
318      */
319     public boolean isTextNode() {
320         return ((this.value != null) && !comment && !doctype && !pi);
321     }
322 
323     // not entirely necessary, but allows XMLNode's to be output 
324     // int XML by calling .toString() on the root node.
325     // Probably wants some indentation handling?
326     /***
327      * Turn this node into a String. Outputs the node as 
328      * XML. So a large amount of output.
329      */
330     public String toString() {
331         if(isComment()) {
332             return "<!-- " + value + " -->";
333         }
334         if(isDocType()) {
335             return "<!DOCTYPE " + value + ">";
336         }
337         if(value != null) {
338             return value;
339         }
340 
341         StringBuffer tmp = new StringBuffer();
342 
343         if(!isInvisible()) {
344             tmp.append("<");
345             if(isPI()) {
346                 tmp.append("?");
347             }
348             tmp.append(name);
349         }
350         
351         Enumeration enum = enumerateAttr();
352         while(enum.hasMoreElements()) {
353             tmp.append(" ");
354             String obj = (String)enum.nextElement();
355             tmp.append(obj);
356             tmp.append("=\"");
357             tmp.append(getAttr(obj));
358             tmp.append("\"");
359         }
360         if(isEmpty()) {
361             if(isPI()) {
362                 tmp.append("?>");
363             } else {
364                 if(!isInvisible()) {
365                     tmp.append("/>");
366                 }
367             }
368         } else {
369             if(!isInvisible()) {
370                 tmp.append(">");
371             }
372 
373             tmp.append(bodyToString());
374 
375             if(!isInvisible()) {
376                 tmp.append("</"+name+">\n");
377             }
378         }
379         return tmp.toString();
380     }
381             
382     /***
383      * Get the String version of the body of this tag.
384      */
385     public String bodyToString() {
386         StringBuffer tmp = new StringBuffer();
387         Enumeration enum = enumerateNode();
388         while(enum.hasMoreElements()) {
389             Object obj = enum.nextElement();
390             if(obj instanceof XMLNode) {
391                 XMLNode node = (XMLNode)obj;
392                 tmp.append(node);
393             } else
394             if(obj instanceof Vector) {
395                 Vector nodelist = (Vector)obj;
396                 Enumeration nodeEnum = nodelist.elements();
397                 while(nodeEnum.hasMoreElements()) {
398                     XMLNode node = (XMLNode)nodeEnum.nextElement();
399                     tmp.append(node);
400                 }
401             }                
402         }
403         return tmp.toString();
404     }
405 
406     private static String unescapeXml(String str) {
407         str = replace(str,"&amp;","&");
408         str = replace(str,"&lt;","<");
409         str = replace(str,"&gt;",">");
410         str = replace(str,"&quot;","\"");
411         str = replace(str,"&apos;","'");
412         return str;
413     }
414 
415     // from Commons.Lang.StringUtils
416     private static String replace(String text, String repl, String with) {   
417         int max = -1;
418         if (text == null || repl == null || with == null || repl.length() == 0 || max == 0) {
419             return text;
420         }
421         
422         StringBuffer buf = new StringBuffer(text.length());
423         int start = 0, end = 0;
424         while ((end = text.indexOf(repl, start)) != -1) {
425             buf.append(text.substring(start, end)).append(with);
426             start = end + repl.length();
427             
428             if (--max == 0) {
429                 break;
430             }
431         }
432         buf.append(text.substring(start));
433         return buf.toString();
434     }
435         
436 }
437 
438 /***
439  * An empty enumeration. Nicer to return than just plain null. 
440  */
441 class NullEnumeration implements Enumeration {
442     public Object nextElement() {
443         return null;
444     }
445  
446     public boolean hasMoreElements() {
447         return false;
448     }   
449 }
450 /***
451  * A single enumeration. Saves time on making a Vector.
452  */
453 class SingleEnumeration implements Enumeration {
454 
455     private Object obj;
456 
457     public SingleEnumeration(Object obj) {
458         this.obj = obj;
459     }
460 
461     public Object nextElement() {
462         Object tmp = this.obj;
463         this.obj = null;
464         return tmp;
465     }
466  
467     public boolean hasMoreElements() {
468         return (obj != null);
469     }   
470 }