Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: nanoxml/kXMLElement.java


1   /*
2    *  This file is part of NanoXML.
3    *
4    *  $Revision: 1.2 $
5    *  $Date: 2002/06/05 01:27:16 $
6    *  $Name: build_20030502 $
7    *
8    *  Copyright (C) 2000 Marc De Scheemaecker, All Rights Reserved.
9    *
10   *  This software is provided 'as-is', without any express or implied warranty.
11   *  In no event will the authors be held liable for any damages arising from the
12   *  use of this software.
13   *
14   *  Permission is granted to anyone to use this software for any purpose,
15   *  including commercial applications, and to alter it and redistribute it
16   *  freely, subject to the following restrictions:
17   *
18   *  1. The origin of this software must not be misrepresented; you must not
19   *  claim that you wrote the original software. If you use this software in
20   *  a product, an acknowledgment in the product documentation would be
21   *  appreciated but is not required.
22   *
23   *  2. Altered source versions must be plainly marked as such, and must not be
24   *  misrepresented as being the original software.
25   *
26   *  3. This notice may not be removed or altered from any source distribution.
27   */
28  
29  package nanoxml;
30  
31  import java.io.IOException;
32  import java.io.Reader;
33  import java.io.Writer;
34  import java.util.Enumeration;
35  import java.util.Hashtable;
36  import java.util.Vector;
37  
38  
39  /**
40   * kXMLElement is a representation of an XML object. The object is able to
41   * parse XML code. <P>
42   *
43   * Note that NanoXML is not 100% XML 1.0 compliant:
44   * <UL>
45   *   <LI> The parser is non-validating.
46   *   <LI> The DTD is fully ignored, including <CODE>&lt;!ENTITY...&gt;</CODE>.
47   *
48   *   <LI> There is no support for mixed content (elements containing both
49   *   subelements and CDATA elements)
50   * </UL>
51   * <P>
52   *
53   * You can opt to use a SAX compatible API, by including both <CODE>nanoxml.jar</CODE>
54   * and <CODE>nanoxml-sax.jar</CODE> in your classpath and setting the property
55   * <CODE>org.xml.sax.parser</CODE> to <CODE>nanoxml.sax.SAXParser</CODE> <P>
56   *
57   * $Revision: 1.2 $<BR>
58   * $Date: 2002/06/05 01:27:16 $<P>
59   *
60   *
61   *
62   * @author    Marc De Scheemaecker &lt;<A HREF="mailto:Marc.DeScheemaecker@advalvas.be"
63   *      >Marc.DeScheemaecker@advalvas.be</A> &gt;
64   * @created   May 28, 2002
65   * @see       nanoxml.kXMLParseException
66   * @version   1.6
67   */
68  public class kXMLElement {
69      /**
70       * Major version of NanoXML.
71       */
72      public final static int NANOXML_MAJOR_VERSION = 1;
73  
74      /**
75       * Minor version of NanoXML.
76       */
77      public final static int NANOXML_MINOR_VERSION = 6;
78  
79      /**
80       * The attributes given to the object.
81       */
82      private Hashtable attributes;
83  
84      /**
85       * Parent of the object.
86       */
87      private kXMLElement parent;
88  
89      /**
90       * Subobjects of the object. The subobjects are of class kXMLElement
91       * themselves.
92       */
93      private Vector children;
94  
95      /**
96       * The class of the object (the name indicated in the tag).
97       */
98      private String tagName;
99  
100     /**
101      * The #PCDATA content of the object. If there is no such content, this
102      * field is null.
103      */
104     private String contents;
105 
106     /**
107      * Conversion table for &amp;...; tags.
108      */
109     private Hashtable conversionTable;
110 
111     /**
112      * Whether to skip leading whitespace in CDATA.
113      */
114     private boolean skipLeadingWhitespace;
115 
116     /**
117      * The line number where the element starts.
118      */
119     private int lineNr;
120 
121     /**
122      * Whether the parsing is case sensitive.
123      */
124     private boolean ignoreCase;
125 
126 
127     /**
128      * Creates a new XML element. The following settings are used:
129      * <DL>
130      *   <DT> Conversion table</DT>
131      *   <DD> Minimal XML conversions: <CODE>&amp;amp; &amp;lt; &amp;gt;
132      *         &amp;apos; &amp;quot;</CODE></DD>
133      *   <DT> Skip whitespace in contents</DT>
134      *   <DD> <CODE>false</CODE></DD>
135      *   <DT> Ignore Case</DT>
136      *   <DD> <CODE>true</CODE></DD>
137      * </DL>
138      *
139      *
140      * @see   nanoxml.kXMLElement#kXMLElement(java.util.Hashtable)
141      * @see   nanoxml.kXMLElement#kXMLElement(boolean)
142      * @see   nanoxml.kXMLElement#kXMLElement(java.util.Hashtable,boolean)
143      */
144     public kXMLElement() {
145         this(new Hashtable(), false, true, true);
146     }
147 
148 
149     /**
150      * Creates a new XML element. The following settings are used:
151      * <DL>
152      *   <DT> Conversion table</DT>
153      *   <DD> <I>conversionTable</I> combined with the minimal XML
154      *   conversions: <CODE>&amp;amp; &amp;lt; &amp;gt;
155      *         &amp;apos; &amp;quot;</CODE></DD>
156      *   <DT> Skip whitespace in contents</DT>
157      *   <DD> <CODE>false</CODE></DD>
158      *   <DT> Ignore Case</DT>
159      *   <DD> <CODE>true</CODE></DD>
160      * </DL>
161      *
162      *
163      * @param conversionTable  Description of the Parameter
164      * @see                    nanoxml.kXMLElement#kXMLElement()
165      * @see                    nanoxml.kXMLElement#kXMLElement(boolean)
166      * @see                    nanoxml.kXMLElement#kXMLElement(java.util.Hashtable,boolean)
167      */
168     public kXMLElement(Hashtable conversionTable) {
169         this(conversionTable, false, true, true);
170     }
171 
172 
173     /**
174      * Creates a new XML element. The following settings are used:
175      * <DL>
176      *   <DT> Conversion table</DT>
177      *   <DD> Minimal XML conversions: <CODE>&amp;amp; &amp;lt; &amp;gt;
178      *         &amp;apos; &amp;quot;</CODE></DD>
179      *   <DT> Skip whitespace in contents</DT>
180      *   <DD> <I>skipLeadingWhitespace</I> </DD>
181      *   <DT> Ignore Case</DT>
182      *   <DD> <CODE>true</CODE></DD>
183      * </DL>
184      *
185      *
186      * @param skipLeadingWhitespace  Description of the Parameter
187      * @see                          nanoxml.kXMLElement#kXMLElement()
188      * @see                          nanoxml.kXMLElement#kXMLElement(java.util.Hashtable)
189      * @see                          nanoxml.kXMLElement#kXMLElement(java.util.Hashtable,boolean)
190      */
191     public kXMLElement(boolean skipLeadingWhitespace) {
192         this(new Hashtable(), skipLeadingWhitespace, true, true);
193     }
194 
195 
196     /**
197      * Creates a new XML element. The following settings are used:
198      * <DL>
199      *   <DT> Conversion table</DT>
200      *   <DD> <I>conversionTable</I> combined with the minimal XML
201      *   conversions: <CODE>&amp;amp; &amp;lt; &amp;gt;
202      *         &amp;apos; &amp;quot;</CODE></DD>
203      *   <DT> Skip whitespace in contents</DT>
204      *   <DD> <I>skipLeadingWhitespace</I> </DD>
205      *   <DT> Ignore Case</DT>
206      *   <DD> <CODE>true</CODE></DD>
207      * </DL>
208      *
209      *
210      * @param conversionTable        Description of the Parameter
211      * @param skipLeadingWhitespace  Description of the Parameter
212      * @see                          nanoxml.kXMLElement#kXMLElement()
213      * @see                          nanoxml.kXMLElement#kXMLElement(boolean)
214      * @see                          nanoxml.kXMLElement#kXMLElement(java.util.Hashtable)
215      */
216     public kXMLElement(Hashtable conversionTable,
217                        boolean skipLeadingWhitespace) {
218         this(conversionTable, skipLeadingWhitespace, true, true);
219     }
220 
221 
222     /**
223      * Creates a new XML element. The following settings are used:
224      * <DL>
225      *   <DT> Conversion table</DT>
226      *   <DD> <I>conversionTable</I> , eventually combined with the minimal
227      *   XML conversions: <CODE>&amp;amp; &amp;lt; &amp;gt;
228      *         &amp;apos; &amp;quot;</CODE> (depending on <I>
229      *   fillBasicConversionTable</I> )</DD>
230      *   <DT> Skip whitespace in contents</DT>
231      *   <DD> <I>skipLeadingWhitespace</I> </DD>
232      *   <DT> Ignore Case</DT>
233      *   <DD> <I>ignoreCase</I> </DD>
234      * </DL>
235      * <P>
236      *
237      * This constructor should <I>only</I> be called from kXMLElement itself
238      * to create child elements.
239      *
240      * @param conversionTable        Description of the Parameter
241      * @param skipLeadingWhitespace  Description of the Parameter
242      * @param ignoreCase             Description of the Parameter
243      * @see                          nanoxml.kXMLElement#kXMLElement()
244      * @see                          nanoxml.kXMLElement#XMLElement(boolean)
245      * @see                          nanoxml.kXMLElement#XMLElement(java.util.Hashtable)
246      * @see                          nanoxml.kXMLElement#XMLElement(java.util.Hashtable,boolean)
247      */
248     public kXMLElement(Hashtable conversionTable,
249                        boolean skipLeadingWhitespace,
250                        boolean ignoreCase) {
251         this(conversionTable, skipLeadingWhitespace, true, ignoreCase);
252     }
253 
254 
255     /**
256      * Creates a new XML element. The following settings are used:
257      * <DL>
258      *   <DT> Conversion table</DT>
259      *   <DD> <I>conversionTable</I> , eventually combined with the minimal
260      *   XML conversions: <CODE>&amp;amp; &amp;lt; &amp;gt;
261      *         &amp;apos; &amp;quot;</CODE> (depending on <I>
262      *   fillBasicConversionTable</I> )</DD>
263      *   <DT> Skip whitespace in contents</DT>
264      *   <DD> <I>skipLeadingWhitespace</I> </DD>
265      *   <DT> Ignore Case</DT>
266      *   <DD> <I>ignoreCase</I> </DD>
267      * </DL>
268      * <P>
269      *
270      * This constructor should <I>only</I> be called from kXMLElement itself
271      * to create child elements.
272      *
273      * @param conversionTable           Description of the Parameter
274      * @param skipLeadingWhitespace     Description of the Parameter
275      * @param fillBasicConversionTable  Description of the Parameter
276      * @param ignoreCase                Description of the Parameter
277      * @see                             nanoxml.kXMLElement#XMLElement()
278      * @see                             nanoxml.kXMLElement#XMLElement(boolean)
279      * @see                             nanoxml.kXMLElement#XMLElement(java.util.Hashtable)
280      * @see                             nanoxml.kXMLElement#XMLElement(java.util.Hashtable,boolean)
281      */
282     protected kXMLElement(Hashtable conversionTable,
283                           boolean skipLeadingWhitespace,
284                           boolean fillBasicConversionTable,
285                           boolean ignoreCase) {
286         this.ignoreCase = ignoreCase;
287         this.skipLeadingWhitespace = skipLeadingWhitespace;
288         this.tagName = null;
289         this.contents = "";
290         this.attributes = null;
291         this.children = null;
292         this.conversionTable = conversionTable;
293         this.lineNr = 0;
294 
295         if (fillBasicConversionTable) {
296             this.conversionTable.put("lt", "<");
297             this.conversionTable.put("gt", ">");
298             this.conversionTable.put("quot", "\"");
299             this.conversionTable.put("apos", "'");
300             this.conversionTable.put("amp", "&");
301         }
302     }
303 
304 
305     /**
306      * Gets its parent.
307      *
308      * @return   The parent value
309      */
310     public kXMLElement getParent() {
311         return parent;
312     }
313 
314 
315     /**
316      * Sets the parent of this instance.
317      *
318      * @param parent  The new parent value
319      */
320     private void setParent(kXMLElement parent) {
321         this.parent = parent;
322     }
323 
324 
325     /**
326      * Adds a subobject.
327      *
328      * @param child  The feature to be added to the Child attribute
329      */
330     public void addChild(kXMLElement child) {
331         child.setParent(this);
332         getChildren().addElement(child);
333     }
334 
335 
336     /**
337      * Gets the attributes attribute of the kXMLElement object
338      *
339      * @return   The attributes value
340      */
341     private Hashtable getAttributes() {
342         if (attributes == null) {
343             attributes = new Hashtable();
344         }
345         return attributes;
346     }
347 
348 
349     /**
350      * Adds a property. If the element is case insensitive, the property name
351      * is capitalized.
352      *
353      * @param key    The feature to be added to the Property attribute
354      * @param value  The feature to be added to the Property attribute
355      */
356     public void addProperty(String key,
357                             Object value) {
358         if (ignoreCase) {
359             key = key.toUpperCase();
360         }
361 
362         getAttributes().put(key, value.toString());
363     }
364 
365 
366     /**
367      * Adds a property. If the element is case insensitive, the property name
368      * is capitalized.
369      *
370      * @param key    The feature to be added to the Property attribute
371      * @param value  The feature to be added to the Property attribute
372      */
373     public void addProperty(String key,
374                             int value) {
375         if (ignoreCase) {
376             key = key.toUpperCase();
377         }
378 
379         getAttributes().put(key, Integer.toString(value));
380     }
381 
382 
383     /**
384      * Returns the number of subobjects of the object.
385      *
386      * @return   Description of the Return Value
387      */
388     public int countChildren() {
389         return (children != null) ? children.size() : 0;
390     }
391 
392 
393     /**
394      * Enumerates the attribute names.
395      *
396      * @return   Description of the Return Value
397      */
398     public Enumeration enumeratePropertyNames() {
399         return getAttributes().keys();
400     }
401 
402 
403     /**
404      * Enumerates the subobjects of the object.
405      *
406      * @return   Description of the Return Value
407      */
408     public Enumeration enumerateChildren() {
409         return getChildren().elements();
410     }
411 
412 
413     /**
414      * Returns the subobjects of the object.
415      *
416      * @return   The children value
417      */
418     public Vector getChildren() {
419         if (children == null) {
420             children = new Vector();
421         }
422         return children;
423     }
424 
425 
426     /**
427      * Returns the #PCDATA content of the object. If there is no such content,
428      * <CODE>null</CODE> is returned.
429      *
430      * @return   The contents value
431      */
432     public String getContents() {
433         return contents;
434     }
435 
436 
437     /**
438      * Returns the line nr on which the element is found.
439      *
440      * @return   The lineNr value
441      */
442     public int getLineNr() {
443         return lineNr;
444     }
445 
446 
447     /**
448      * Returns a property by looking up a key in a hashtable. If the property
449      * doesn't exist, the value corresponding to defaultValue is returned.
450      *
451      * @param key           Description of the Parameter
452      * @param valueSet      Description of the Parameter
453      * @param defaultValue  Description of the Parameter
454      * @return              The intProperty value
455      */
456     public int getIntProperty(String key,
457                               Hashtable valueSet,
458                               String defaultValue) {
459         String val = getProperty(attributes, key);
460         Integer result;
461 
462         if (ignoreCase) {
463             key = key.toUpperCase();
464         }
465 
466         if (val == null) {
467             val = defaultValue;
468         }
469 
470         try {
471             result = (Integer) (valueSet.get(val));
472         } catch (ClassCastException e) {
473             throw invalidValueSet(key);
474         }
475 
476         if (result == null) {
477             throw invalidValue(key, val, lineNr);
478         }
479 
480         return result.intValue();
481     }
482 
483 
484     /**
485      * @param h    Description of the Parameter
486      * @param key  Description of the Parameter
487      * @return     The property value
488      */
489 
490     public static String getProperty(Hashtable h, String key) {
491         if (h == null) {
492             return null;
493         }
494         return (String) h.get(key);
495     }
496 
497 
498     /**
499      * @param h    Description of the Parameter
500      * @param key  Description of the Parameter
501      * @param def  Description of the Parameter
502      * @return     The property value
503      */
504 
505     public static String getProperty(Hashtable h, String key, String def) {
506         if (h == null) {
507             return def;
508         }
509         String val = (String) h.get(key);
510         return (val != null) ? val : def;
511     }
512 
513 
514     /**
515      * Returns a property of the object. If there is no such property, this
516      * method returns <CODE>null</CODE>.
517      *
518      * @param key  Description of the Parameter
519      * @return     The property value
520      */
521     public String getProperty(String key) {
522         if (ignoreCase) {
523             key = key.toUpperCase();
524         }
525 
526         return getProperty(attributes, key);
527     }
528 
529 
530     /**
531      * Returns a property of the object. If the property doesn't exist, <I>
532      * defaultValue</I> is returned.
533      *
534      * @param key           Description of the Parameter
535      * @param defaultValue  Description of the Parameter
536      * @return              The property value
537      */
538     public String getProperty(String key,
539                               String defaultValue) {
540         if (ignoreCase) {
541             key = key.toUpperCase();
542         }
543 
544         return getProperty(attributes, key, defaultValue);
545     }
546 
547 
548     /**
549      * Returns an integer property of the object. If the property doesn't
550      * exist, <I>defaultValue</I> is returned.
551      *
552      * @param key           Description of the Parameter
553      * @param defaultValue  Description of the Parameter
554      * @return              The property value
555      */
556     public int getProperty(String key,
557                            int defaultValue) {
558         if (ignoreCase) {
559             key = key.toUpperCase();
560         }
561 
562         String val = getProperty(attributes, key);
563 
564         if (val == null) {
565             return defaultValue;
566         } else {
567             try {
568                 return Integer.parseInt(val);
569             } catch (NumberFormatException e) {
570                 throw invalidValue(key, val, lineNr);
571             }
572         }
573     }
574 
575 
576     /**
577      * Returns a boolean property of the object. If the property is missing,
578      * <I>defaultValue</I> is returned.
579      *
580      * @param key           Description of the Parameter
581      * @param trueValue     Description of the Parameter
582      * @param falseValue    Description of the Parameter
583      * @param defaultValue  Description of the Parameter
584      * @return              The property value
585      */
586     public boolean getProperty(String key,
587                                String trueValue,
588                                String falseValue,
589                                boolean defaultValue) {
590         if (ignoreCase) {
591             key = key.toUpperCase();
592         }
593 
594         String val = getProperty(attributes, key);
595 
596         if (val == null) {
597             return defaultValue;
598         } else if (val.equals(trueValue)) {
599             return true;
600         } else if (val.equals(falseValue)) {
601             return false;
602         } else {
603             throw invalidValue(key, val, lineNr);
604         }
605     }
606 
607 
608     /**
609      * Returns a property by looking up a key in the hashtable <I>valueSet</I>
610      * If the property doesn't exist, the value corresponding to <I>
611      * defaultValue</I> is returned.
612      *
613      * @param key           Description of the Parameter
614      * @param valueSet      Description of the Parameter
615      * @param defaultValue  Description of the Parameter
616      * @return              The property value
617      */
618     public Object getProperty(String key,
619                               Hashtable valueSet,
620                               String defaultValue) {
621         if (ignoreCase) {
622             key = key.toUpperCase();
623         }
624 
625         String val = getProperty(attributes, key);
626 
627         if (val == null) {
628             val = defaultValue;
629         }
630 
631         Object result = valueSet.get(val);
632 
633         if (result == null) {
634             throw invalidValue(key, val, lineNr);
635         }
636 
637         return result;
638     }
639 
640 
641     /**
642      * Returns a property by looking up a key in the hashtable <I>valueSet</I>
643      * . If the property doesn't exist, the value corresponding to <I>
644      * defaultValue</I> is returned.
645      *
646      * @param key           Description of the Parameter
647      * @param valueSet      Description of the Parameter
648      * @param defaultValue  Description of the Parameter
649      * @return              The stringProperty value
650      */
651     public String getStringProperty(String key,
652                                     Hashtable valueSet,
653                                     String defaultValue) {
654         if (ignoreCase) {
655             key = key.toUpperCase();
656         }
657 
658         String val = getProperty(attributes, key);
659         String result;
660 
661         if (val == null) {
662             val = defaultValue;
663         }
664 
665         try {
666             result = (String) (valueSet.get(val));
667         } catch (ClassCastException e) {
668             throw invalidValueSet(key);
669         }
670 
671         if (result == null) {
672             throw invalidValue(key, val, lineNr);
673         }
674 
675         return result;
676     }
677 
678 
679     /**
680      * Returns a property by looking up a key in the hashtable <I>valueSet</I>
681      * . If the value is not defined in the hashtable, the value is considered
682      * to be an integer. If the property doesn't exist, the value
683      * corresponding to <I>defaultValue</I> is returned.
684      *
685      * @param key           Description of the Parameter
686      * @param valueSet      Description of the Parameter
687      * @param defaultValue  Description of the Parameter
688      * @return              The specialIntProperty value
689      */
690     public int getSpecialIntProperty(String key,
691                                      Hashtable valueSet,
692                                      String defaultValue) {
693         if (ignoreCase) {
694             key = key.toUpperCase();
695         }
696 
697         String val = getProperty(attributes, key);
698         Integer result;
699 
700         if (val == null) {
701             val = defaultValue;
702         }
703 
704         try {
705             result = (Integer) (valueSet.get(val));
706         } catch (ClassCastException e) {
707             throw invalidValueSet(key);
708         }
709 
710         if (result == null) {
711             try {
712                 return Integer.parseInt(val);
713             } catch (NumberFormatException e) {
714                 throw invalidValue(key, val, lineNr);
715             }
716         }
717 
718         return result.intValue();
719     }
720 
721 
722     /**
723      * Returns the class (i.e. the name indicated in the tag) of the object.
724      *
725      * @return   The tagName value
726      */
727     public String getTagName() {
728         return tagName;
729     }
730 
731 
732     /**
733      * Checks whether a character may be part of an identifier.
734      *
735      * @param ch  Description of the Parameter
736      * @return    The identifierChar value
737      */
738     private boolean isIdentifierChar(char ch) {
739         return (((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z'))
740              || ((ch >= '0') && (ch <= '9')) || (".-_:".indexOf(ch) >= 0));
741     }
742 
743 
744     /**
745      * Reads an XML definition from a java.io.Reader and parses it.
746      *
747      * @param reader                          Description of the Parameter
748      * @exception IOException                 Description of the Exception
749      * @exception kXMLParseException          Description of the Exception
750      */
751     public void parseFromReader(Reader reader)
752          throws IOException, kXMLParseException {
753         parseFromReader(reader, 1);
754     }
755 
756 
757     /**
758      * Reads an XML definition from a java.io.Reader and parses it.
759      *
760      * @param reader                          Description of the Parameter
761      * @param startingLineNr                  Description of the Parameter
762      * @exception IOException                 Description of the Exception
763      * @exception kXMLParseException          Description of the Exception
764      */
765     public void parseFromReader(Reader reader,
766                                 int startingLineNr)
767          throws IOException, kXMLParseException {
768         int blockSize = 4096;
769         char[] input = null;
770         int size = 0;
771 
772         for (; ; ) {
773             if (input == null) {
774                 input = new char[blockSize];
775             } else {
776                 char[] oldInput = input;
777                 input = new char[input.length + blockSize];
778                 System.arraycopy(oldInput, 0, input, 0, oldInput.length);
779             }
780 
781             int charsRead = reader.read(input, size, blockSize);
782 
783             if (charsRead < 0) {
784                 break;
785             }
786 
787             size += charsRead;
788         }
789 
790         parseCharArray(input, 0, size, startingLineNr);
791     }
792 
793 
794     /**
795      * Parses an XML definition.
796      *
797      * @param string                          Description of the Parameter
798      * @exception kXMLParseException          Description of the Exception
799      */
800     public void parseString(String string)
801          throws kXMLParseException {
802         parseCharArray(string.toCharArray(), 0, string.length(), 1);
803     }
804 
805 
806     /**
807      * Parses an XML definition starting at <I>offset</I> .
808      *
809      * @param string                          Description of the Parameter
810      * @param offset                          Description of the Parameter
811      * @return                                the offset of the string
812      *      following the XML data
813      * @exception kXMLParseException          Description of the Exception
814      */
815     public int parseString(String string,
816                            int offset)
817          throws kXMLParseException {
818         return parseCharArray(string.toCharArray(), offset,
819             string.length(), 1);
820     }
821 
822 
823     /**
824      * Parses an XML definition starting at <I>offset</I> .
825      *
826      * @param string                          Description of the Parameter
827      * @param offset                          Description of the Parameter
828      * @param end                             Description of the Parameter
829      * @return                                the offset of the string
830      *      following the XML data (<= end)
831      * @exception kXMLParseException          Description of the Exception
832      */
833     public int parseString(String string,
834                            int offset,
835                            int end)
836          throws kXMLParseException {
837         return parseCharArray(string.toCharArray(), offset, end, 1);
838     }
839 
840 
841     /**
842      * Parses an XML definition starting at <I>offset</I> .
843      *
844      * @param string                          Description of the Parameter
845      * @param offset                          Description of the Parameter
846      * @param end                             Description of the Parameter
847      * @param startingLineNr                  Description of the Parameter
848      * @return                                the offset of the string
849      *      following the XML data (<= end)
850      * @exception kXMLParseException          Description of the Exception
851      */
852     public int parseString(String string,
853                            int offset,
854                            int end,
855                            int startingLineNr)
856          throws kXMLParseException {
857         return parseCharArray(string.toCharArray(), offset, end,
858             startingLineNr);
859     }
860 
861 
862     /**
863      * Parses an XML definition starting at <I>offset</I> .
864      *
865      * @param input                           Description of the Parameter
866      * @param offset                          Description of the Parameter
867      * @param end                             Description of the Parameter
868      * @return                                the offset of the array
869      *      following the XML data (<= end)
870      * @exception kXMLParseException          Description of the Exception
871      */
872     public int parseCharArray(char[] input,
873                               int offset,
874                               int end)
875          throws kXMLParseException {
876         return parseCharArray(input, offset, end, 1);
877     }
878 
879 
880     /**
881      * Parses an XML definition starting at <I>offset</I> .
882      *
883      * @param input                           Description of the Parameter
884      * @param offset                          Description of the Parameter
885      * @param end                             Description of the Parameter
886      * @param startingLineNr                  Description of the Parameter
887      * @return                                the offset of the array
888      *      following the XML data (<= end)
889      * @exception kXMLParseException          Description of the Exception
890      */
891     public int parseCharArray(char[] input,
892                               int offset,
893                               int end,
894                               int startingLineNr)
895          throws kXMLParseException {
896         int[] lineNr = new int[1];
897         lineNr[0] = startingLineNr;
898         return parseCharArray(input, offset, end, lineNr);
899     }
900 
901 
902     /**
903      * Parses an XML definition starting at <I>offset</I> .
904      *
905      * @param input                           Description of the Parameter
906      * @param offset                          Description of the Parameter
907      * @param end                             Description of the Parameter
908      * @param currentLineNr                   Description of the Parameter
909      * @return                                the offset of the array
910      *      following the XML data (<= end)
911      * @exception kXMLParseException          Description of the Exception
912      */
913     private int parseCharArray(char[] input,
914                                int offset,
915                                int end,
916                                int[] currentLineNr)
917          throws kXMLParseException {
918         this.lineNr = currentLineNr[0];
919         this.tagName = null;
920         this.contents = null;
921         this.attributes = null;
922         this.children = null;
923 
924         try {
925             offset = this.skipWhitespace(input, offset, end, currentLineNr);
926         } catch (kXMLParseException e) {
927             return offset;
928         }
929 
930         offset = skipPreamble(input, offset, end, currentLineNr);
931         offset = scanTagName(input, offset, end, currentLineNr);
932         lineNr = currentLineNr[0];
933         offset = scanAttributes(input, offset, end, currentLineNr);
934         int[] contentOffset = new int[1];
935         int[] contentSize = new int[1];
936         int contentLineNr = currentLineNr[0];
937         offset = scanContent(input, offset, end,
938             contentOffset, contentSize, currentLineNr);
939 
940         if (contentSize[0] > 0) {
941             scanChildren(input, contentOffset[0], contentSize[0],
942                 contentLineNr);
943 
944             if (children != null && children.size() > 0) {
945                 contents = null;
946             } else {
947                 processContents(input, contentOffset[0],
948                     contentSize[0], contentLineNr);
949 
950                 for (int i = 0; i < contents.length(); i++) {
951                     if (contents.charAt(i) > ' ') {
952                         return offset;
953                     }
954                 }
955 
956                 contents = null;
957             }
958         }
959 
960         return offset;
961     }
962 
963 
964     /**
965      * Decodes the entities in the contents and, if skipLeadingWhitespace is
966      * <CODE>true</CODE>, removes extraneous whitespaces after newlines and
967      * convert those newlines into spaces.
968      *
969      * @param input                           Description of the Parameter
970      * @param contentOffset                   Description of the Parameter
971      * @param contentSize                     Description of the Parameter
972      * @param contentLineNr                   Description of the Parameter
973      * @exception kXMLParseException          Description of the Exception
974      * @see                                   nanoxml.kXMLElement#decodeString
975      */
976     private void processContents(char[] input,
977                                  int contentOffset,
978                                  int contentSize,
979                                  int contentLineNr)
980          throws kXMLParseException {
981         int[] lineNr = new int[1];
982         lineNr[0] = contentLineNr;
983 
984         if (!skipLeadingWhitespace) {
985             String str = new String(input, contentOffset, contentSize);
986             contents = decodeString(str, lineNr[0]);
987             return;
988         }
989 
990         StringBuffer result = new StringBuffer(contentSize);
991         int end = contentSize + contentOffset;
992 
993         for (int i = contentOffset; i < end; i++) {
994             char ch = input[i];
995 
996             // The end of the contents is always a < character, so there's
997             // no danger for bounds violation
998             while ((ch == '\r') || (ch == '\n')) {
999                 lineNr[0]++;
1000                result.append(ch);
1001
1002                i++;
1003                ch = input[i];
1004
1005                if (ch != '\n') {
1006                    result.append(ch);
1007                }
1008
1009                do {
1010                    i++;
1011                    ch = input[i];
1012                } while ((ch == ' ') || (ch == '\t'));
1013            }
1014
1015            if (i < end) {
1016                result.append(input[i]);
1017            }
1018        }
1019
1020        contents = decodeString(result.toString(), lineNr[0]);
1021    }
1022
1023
1024    /**
1025     * Removes a child object. If the object is not a child, nothing happens.
1026     *
1027     * @param child  Description of the Parameter
1028     */
1029    public void removeChild(kXMLElement child) {
1030        if (children != null) {
1031            children.removeElement(child);
1032        }
1033    }
1034
1035
1036    /**
1037     * Removes an attribute.
1038     *
1039     * @param key  Description of the Parameter
1040     */
1041    public void removeChild(String key) {
1042        if (attributes != null) {
1043            if (ignoreCase) {
1044                key = key.toUpperCase();
1045            }
1046
1047            attributes.remove(key);
1048        }
1049    }
1050
1051
1052    /**
1053     * Scans the attributes of the object.
1054     *
1055     * @param input                           Description of the Parameter
1056     * @param offset                          Description of the Parameter
1057     * @param end                             Description of the Parameter
1058     * @param lineNr                          Description of the Parameter
1059     * @return                                the offset in the string
1060     *      following the attributes, so that input[offset] in { '/', '>' }
1061     * @exception kXMLParseException          Description of the Exception
1062     * @see                                   nanoxml.kXMLElement#scanOneAttribute
1063     */
1064    private int scanAttributes(char[] input,
1065                               int offset,
1066                               int end,
1067                               int[] lineNr)
1068         throws kXMLParseException {
1069        String key;
1070        String value;
1071
1072        for (; ; ) {
1073            offset = skipWhitespace(input, offset, end, lineNr);
1074
1075            char ch = input[offset];
1076
1077            if ((ch == '/') || (ch == '>')) {
1078                break;
1079            }
1080
1081            offset = scanOneAttribute(input, offset, end, lineNr);
1082        }
1083
1084        return offset;
1085    }
1086
1087
1088    /**
1089     * !!! Searches the content for child objects. If such objects exist, the
1090     * content is reduced to <CODE>null</CODE>.
1091     *
1092     * @param input                           Description of the Parameter
1093     * @param contentOffset                   Description of the Parameter
1094     * @param contentSize                     Description of the Parameter
1095     * @param contentLineNr                   Description of the Parameter
1096     * @exception kXMLParseException          Description of the Exception
1097     * @see                                   nanoxml.kXMLElement#parseCharArray
1098     */
1099    protected void scanChildren(char[] input,
1100                                int contentOffset,
1101                                int contentSize,
1102                                int contentLineNr)
1103         throws kXMLParseException {
1104        int end = contentOffset + contentSize;
1105        int offset = contentOffset;
1106        int lineNr[] = new int[1];
1107        lineNr[0] = contentLineNr;
1108
1109        while (offset < end) {
1110            try {
1111                offset = skipWhitespace(input, offset, end, lineNr);
1112            } catch (kXMLParseException e) {
1113                return;
1114            }
1115
1116            if ((input[offset] != '<')
1117                 || ((input[offset + 1] == '!') && (input[offset + 2] == '['))) {
1118                return;
1119            }
1120
1121            kXMLElement child = createAnotherElement();
1122            offset = child.parseCharArray(input, offset, end, lineNr);
1123            addChild(child);
1124        }
1125    }
1126
1127
1128    /**
1129     * Creates a new XML element.
1130     *
1131     * @return   Description of the Return Value
1132     */
1133    protected kXMLElement createAnotherElement() {
1134        return new kXMLElement(conversionTable,
1135            skipLeadingWhitespace,
1136            false,
1137            ignoreCase);
1138    }
1139
1140
1141    /**
1142     * Scans the content of the object.
1143     *
1144     * @param input                           Description of the Parameter
1145     * @param offset                          Description of the Parameter
1146     * @param end                             Description of the Parameter
1147     * @param contentOffset                   Description of the Parameter
1148     * @param contentSize                     Description of the Parameter
1149     * @param lineNr                          Description of the Parameter
1150     * @return                                the offset after the XML
1151     *      element; contentOffset points to the start of the content section;
1152     *      contentSize is the size of the content section
1153     * @exception kXMLParseException          Description of the Exception
1154     */
1155    private int scanContent(char[] input,
1156                            int offset,
1157                            int end,
1158                            int[] contentOffset,
1159                            int[] contentSize,
1160                            int[] lineNr)
1161         throws kXMLParseException {
1162        if (input[offset] == '/') {
1163            contentSize[0] = 0;
1164
1165            if (input[offset + 1] != '>') {
1166                throw expectedInput("'>'", lineNr[0]);
1167            }
1168
1169            return offset + 2;
1170        }
1171
1172        if (input[offset] != '>') {
1173            throw expectedInput("'>'", lineNr[0]);
1174        }
1175
1176        if (skipLeadingWhitespace) {
1177            offset = skipWhitespace(input, offset + 1, end, lineNr);
1178        } else {
1179            offset++;
1180        }
1181
1182        int begin = offset;
1183        contentOffset[0] = offset;
1184        int level = 0;
1185        char[] tag = tagName.toCharArray();
1186        end -= (tag.length + 2);
1187
1188        while ((offset < end) && (level >= 0)) {
1189            if (input[offset] == '<') {
1190                boolean ok = true;
1191
1192                if ((offset < (end - 3)) && (input[offset + 1] == '!')
1193                     && (input[offset + 2] == '-')
1194                     && (input[offset + 3] == '-')) {
1195                    offset += 3;
1196
1197                    while ((offset < end)
1198                         && ((input[offset - 2] != '-')
1199                         || (input[offset - 1] != '-')
1200                         || (input[offset - 0] != '>'))) {
1201                        offset++;
1202                    }
1203
1204                    offset++;
1205                    continue;
1206                }
1207
1208                if ((offset < (end - 1)) && (input[offset + 1] == '!')
1209                     && (input[offset + 2] == '[')) {
1210                    offset++;
1211                    continue;
1212                }
1213
1214                for (int i = 0; ok && (i < tag.length); i++) {
1215                    ok &= (input[offset + (i + 1)] == tag[i]);
1216                }
1217
1218                ok &= !isIdentifierChar(input[offset + tag.length + 1]);
1219
1220                if (ok) {
1221                    while ((offset < end) && (input[offset] != '>')) {
1222                        offset++;
1223                    }
1224
1225                    if (input[offset - 1] != '/') {
1226                        level++;
1227                    }
1228
1229                    continue;
1230                } else if (input[offset + 1] == '/') {
1231                    ok = true;
1232
1233                    for (int i = 0; ok && (i < tag.length); i++) {
1234                        ok &= (input[offset + (i + 2)] == tag[i]);
1235                    }
1236
1237                    if (ok) {
1238                        contentSize[0] = offset - contentOffset[0];
1239                        offset += tag.length + 2;
1240
1241                        try {
1242                            offset = skipWhitespace(input, offset,
1243                                end + tag.length
1244                                 + 2,
1245                                lineNr);
1246                        } catch (kXMLParseException e) {
1247                            // ignore
1248                        }
1249
1250                        if (input[offset] == '>') {
1251                            level--;
1252                            offset++;
1253                        }
1254
1255                        continue;
1256                    }
1257                }
1258            }
1259
1260            if (input[offset] == '\r') {
1261                lineNr[0]++;
1262
1263                if ((offset != end) && (input[offset + 1] == '\n')) {
1264                    offset++;
1265                }
1266            } else if (input[offset] == '\n') {
1267                lineNr[0]++;
1268            }
1269
1270            offset++;
1271        }
1272
1273        if (level >= 0) {
1274            throw unexpectedEndOfData(lineNr[0]);
1275        }
1276
1277        if (skipLeadingWhitespace) {
1278            int i = contentOffset[0] + contentSize[0] - 1;
1279
1280            while ((contentSize[0] >= 0) && (input[i] <= ' ')) {
1281                i--;
1282                contentSize[0]--;
1283            }
1284        }
1285
1286        return offset;
1287    }
1288
1289
1290    /**
1291     * Scans an identifier.
1292     *
1293     * @param input   Description of the Parameter
1294     * @param offset  Description of the Parameter
1295     * @param end     Description of the Parameter
1296     * @return        the identifier, or <CODE>null</CODE> if offset doesn't
1297     *      point to an identifier
1298     */
1299    private String scanIdentifier(char[] input,
1300                                  int offset,
1301                                  int end) {
1302        int begin = offset;
1303
1304        while ((offset < end) && (isIdentifierChar(input[offset]))) {
1305            offset++;
1306        }
1307
1308        if ((offset == end) || (offset == begin)) {
1309            return null;
1310        } else {
1311            return new String(input, begin, offset - begin);
1312        }
1313    }
1314
1315
1316    /**
1317     * Scans one attribute of an object.
1318     *
1319     * @param input                           Description of the Parameter
1320     * @param offset                          Description of the Parameter
1321     * @param end                             Description of the Parameter
1322     * @param lineNr                          Description of the Parameter
1323     * @return                                the offset after the attribute
1324     * @exception kXMLParseException          Description of the Exception
1325     */
1326    private int scanOneAttribute(char[] input,
1327                                 int offset,
1328                                 int end,
1329                                 int[] lineNr)
1330         throws kXMLParseException {
1331        String key;
1332        String value;
1333
1334        key = scanIdentifier(input, offset, end);
1335
1336        if (key == null) {
1337            throw syntaxError("an attribute key", lineNr[0]);
1338        }
1339
1340        offset = skipWhitespace(input, offset + key.length(), end, lineNr);
1341
1342        if (ignoreCase) {
1343            key = key.toUpperCase();
1344        }
1345
1346        if (input[offset] != '=') {
1347            throw valueMissingForAttribute(key, lineNr[0]);
1348        }
1349
1350        offset = skipWhitespace(input, offset + 1, end, lineNr);
1351
1352        value = scanString(input, offset, end, lineNr);
1353
1354        if (value == null) {
1355            throw syntaxError("an attribute value", lineNr[0]);
1356        }
1357
1358        if ((value.charAt(0) == '"') || (value.charAt(0) == '\'')) {
1359            value = value.substring(1, (value.length() - 1));
1360            offset += 2;
1361        }
1362
1363        getAttributes().put(key, decodeString(value, lineNr[0]));
1364        return offset + value.length();
1365    }
1366
1367
1368    /**
1369     * Scans a string. Strings are either identifiers, or text delimited by
1370     * double quotes.
1371     *
1372     * @param input                           Description of the Parameter
1373     * @param offset                          Description of the Parameter
1374     * @param end                             Description of the Parameter
1375     * @param lineNr                          Description of the Parameter
1376     * @return                                the string found, without
1377     *      delimiting double quotes; or null if offset didn't point to a
1378     *      valid string
1379     * @exception kXMLParseException          Description of the Exception
1380     * @see                                   nanoxml.kXMLElement#scanIdentifier
1381     */
1382    private String scanString(char[] input,
1383                              int offset,
1384                              int end,
1385                              int[] lineNr)
1386         throws kXMLParseException {
1387        char delim = input[offset];
1388
1389        if ((delim == '"') || (delim == '\'')) {
1390            int begin = offset;
1391            offset++;
1392
1393            while ((offset < end) && (input[offset] != delim)) {
1394                if (input[offset] == '\r') {
1395                    lineNr[0]++;
1396
1397                    if ((offset != end) && (input[offset + 1] == '\n')) {
1398                        offset++;
1399                    }
1400                } else if (input[offset] == '\n') {
1401                    lineNr[0]++;
1402                }
1403
1404                offset++;
1405            }
1406
1407            if (offset == end) {
1408                return null;
1409            } else {
1410                return new String(input, begin, offset - begin + 1);
1411            }
1412        } else {
1413            return scanIdentifier(input, offset, end);
1414        }
1415    }
1416
1417
1418    /**
1419     * Scans the class (tag) name of the object.
1420     *
1421     * @param input                           Description of the Parameter
1422     * @param offset                          Description of the Parameter
1423     * @param end                             Description of the Parameter
1424     * @param lineNr                          Description of the Parameter
1425     * @return                                the position after the tag name
1426     * @exception kXMLParseException          Description of the Exception
1427     */
1428    private int scanTagName(char[] input,
1429                            int offset,
1430                            int end,
1431                            int[] lineNr)
1432         throws kXMLParseException {
1433        tagName = scanIdentifier(input, offset, end);
1434
1435        if (tagName == null) {
1436            throw syntaxError("a tag name", lineNr[0]);
1437        }
1438
1439        return offset + tagName.length();
1440    }
1441
1442
1443    /**
1444     * Changes the content string.
1445     *
1446     * @param content  The new content string.
1447     */
1448    public void setContent(String content) {
1449        this.contents = content;
1450    }
1451
1452
1453    /**
1454     * Changes the tag name.
1455     *
1456     * @param tagName  The new tag name.
1457     */
1458    public void setTagName(String tagName) {
1459        this.tagName = tagName;
1460    }
1461
1462
1463    /**
1464     * Skips a tag that don't contain any useful data: &lt;?...?&gt;,
1465     * &lt;!...&gt; and comments.
1466     *
1467     * @param input                           Description of the Parameter
1468     * @param offset                          Description of the Parameter
1469     * @param end                             Description of the Parameter
1470     * @param lineNr                          Description of the Parameter
1471     * @return                                the position after the tag
1472     */
1473    protected int skipBogusTag(char[] input,
1474                               int offset,
1475                               int end,
1476                               int[] lineNr) {
1477        int level = 1;
1478
1479        while (offset < end) {
1480            char ch = input[offset++];
1481
1482            switch (ch) {
1483                case '\r':
1484                    if ((offset < end) && (input[offset] == '\n')) {
1485                        offset++;
1486                    }
1487
1488                    lineNr[0]++;
1489                    break;
1490                case '\n':
1491                    lineNr[0]++;
1492                    break;
1493                case '<':
1494                    level++;
1495                    break;
1496                case '>':
1497                    level--;
1498
1499                    if (level == 0) {
1500                        return offset;
1501                    }
1502
1503                    break;
1504                default:
1505            }
1506        }
1507
1508        throw unexpectedEndOfData(lineNr[0]);
1509    }
1510
1511
1512    /**
1513     * Skips a tag that don't contain any useful data: &lt;?...?&gt;,
1514     * &lt;!...&gt; and comments.
1515     *
1516     * @param input                           Description of the Parameter
1517     * @param offset                          Description of the Parameter
1518     * @param end                             Description of the Parameter
1519     * @param lineNr                          Description of the Parameter
1520     * @return                                the position after the tag
1521     * @exception kXMLParseException          Description of the Exception
1522     */
1523    private int skipPreamble(char[] input,
1524                             int offset,
1525                             int end,
1526                             int[] lineNr)
1527         throws kXMLParseException {
1528        char ch;
1529
1530        do {
1531            offset = skipWhitespace(input, offset, end, lineNr);
1532
1533            if (input[offset] != '<') {
1534                expectedInput("'<'", lineNr[0]);
1535            }
1536
1537            offset++;
1538
1539            if (offset >= end) {
1540                throw unexpectedEndOfData(lineNr[0]);
1541            }
1542
1543            ch = input[offset];
1544
1545            if ((ch == '!') || (ch == '?')) {
1546                offset = skipBogusTag(input, offset, end, lineNr);
1547            }
1548        } while (!isIdentifierChar(ch));
1549
1550        return offset;
1551    }
1552
1553
1554    /**
1555     * Skips whitespace characters.
1556     *
1557     * @param input                           Description of the Parameter
1558     * @param offset                          Description of the Parameter
1559     * @param end                             Description of the Parameter
1560     * @param lineNr                          Description of the Parameter
1561     * @return                                the position after the
1562     *      whitespace
1563     */
1564    private int skipWhitespace(char[] input,
1565                               int offset,
1566                               int end,
1567                               int[] lineNr) {
1568        int startLine = lineNr[0];
1569
1570        while (offset < end) {
1571            if (((offset + 6) < end) && (input[offset + 3] == '-')
1572                 && (input[offset + 2] == '-') && (input[offset + 1] == '!')
1573                 && (input[offset] == '<')) {
1574                offset += 4;
1575
1576                while ((input[offset] != '-') || (input[offset + 1] != '-')
1577                     || (input[offset + 2] != '>')) {
1578                    if ((offset + 2) >= end) {
1579                        throw unexpectedEndOfData(startLine);
1580                    }
1581
1582                    offset++;
1583                }
1584
1585                offset += 2;
1586            } else if (input[offset] == '\r') {
1587                lineNr[0]++;
1588
1589                if ((offset != end) && (input[offset + 1] == '\n')) {
1590                    offset++;
1591                }
1592            } else if (input[offset] == '\n') {
1593                lineNr[0]++;
1594            } else if (input[offset] > ' ') {
1595                break;
1596            }
1597
1598            offset++;
1599        }
1600
1601        if (offset == end) {
1602            throw unexpectedEndOfData(startLine);
1603        }
1604
1605        return offset;
1606    }
1607
1608
1609    /**
1610     * Converts &amp;...; sequences to "normal" chars.
1611     *
1612     * @param s       Description of the Parameter
1613     * @param lineNr  Description of the Parameter
1614     * @return        Description of the Return Value
1615     */
1616    protected String decodeString(