Subversion Repositories HomeAutomation

Rev

Details | Last modification | View Log | SVN | RSS feed

Rev Author Line No. Line
915 migan 1
 
2
/*
3
   This module is free software; you can redistribute it and/or
4
   modify it under the terms of the GNU Library General Public
5
   License as publised by the Free Software Foundation; either
6
   version 2 of the License, or (at your option) any later version.
7
 
8
   This module is distributed WITHOUT ANY WARRANTY; without even
9
   the implied warranty of MERCHANTABILITY or FITNESS FOR A
10
   PARTICULAR PURPOSE.  See the GNU Library General Public License
11
   for more details.
12
 
13
   You should have received a copy of the GNU Library General Public
14
   License along with this library; if not, write to the Free
15
   Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
16
   MA 02111-1307, USA
17
 
18
   Should you need to contact the author, you can do so by email to
19
   <wsh@sprintmail.com>.
20
*/
21
import java.util.Vector;
22
import java.util.Stack;
23
import java.io.PrintWriter;
24
import java.io.FileReader;
25
import java.io.BufferedReader;
26
import java.io.IOException;
27
import java.io.Serializable;
28
 
29
/**
30
 * <p>Title: XML parser/generator</p>
31
 * <b><p>Description: WEB applet for X10 Linux Universal Device Drivers<br>
32
 * Copyright: Copyright (c) 2003<br>
33
 * License: GPL<br>
34
 * Summary:This is a poor man's version of a DOM like XML reader/writer written specifically so that it is
35
 * very small and ideal for an applet.</p></b>
36
 * <p>The format supported is as followed:</p>
37
 * <p>&lt;?xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&gt;<br>
38
 * &lt;!-- Comment --&gt;<br>
39
 * &lt;rootname&gt;<br>
40
 * &nbsp;&nbsp;&lt;tag&gt;<br>
41
 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;tag attribute1=&quot;value&quot; attribute2=&quot;value&quot;&gt;elementvalue&lt;/tag&gt;<br>
42
 * &nbsp;&nbsp;&lt;/tag&gt;<br>
43
 * &lt;/rootname&gt;</p>
44
 * <p> The following are not supported:</p>
45
 * <ul>
46
 * <li> Processing Instructions
47
 * <li> Namespace
48
 * </ul>
49
 * <hr>
50
 * <p> How to create XML tree in memory to write to disk:</p>
51
 * <ol>
52
 * <li>Create the root node with a name (myXML root = new myXML(&quot;roottag&quot;);)
53
 * <li>Create subelements
54
 * <ol>
55
 * <li>If subelement will have subelements (branch), use the form without a value
56
 * (myXML subelement = roottag.addElement(&quot;subelement&quot;);)
57
 * <li>If the subelement will not have subelements (leaf), add the element with a value
58
 * (myXML subelement = roottag.addElement(&quot;element&quot;,value);)
59
 * <li>For each subelement (branch and leaf) add attributes
60
 * (subelement.Attribute.add(&quot;name&quot;,value);) or (root.Attribute.add(&quot;name&quot;,value);)
61
 * </ol>
62
 * <li>Continue this until you have completed the tree
63
 * <li>Write it to a PrintWriter stream to generate XML
64
 * </ol>
65
 * <p> Example: </p>
66
 * <ul>
67
 * <li> myXML root = new myXML("root");
68
 * <li> myXML sub1 = root.addElement("element1",value1);
69
 * <li> sub1.Attribute.add("attribute1",att1);
70
 * <li> sub1.Attribute.add("attribute2",att2);
71
 * <li> myXML sub2 = root.addElement("element2");
72
 * <li> sub2.Attribute.add("attribute20",att20);
73
 * <li> myXML sub21 = sub1.addElement("element21");
74
 * <li> sub21.Attribute.add("attribute21",value21);
75
 * <li> root.serialize(pw);
76
 * </ul>
77
 * <p> Results in the following:</p>
78
 * <p>
79
 * &lt;?xml version=&quot;1.0&quot; standalone=&quot;yes&quot;?&gt;<br>
80
 * &lt;root&gt;<br>
81
 * &nbsp;&nbsp;&lt;element1 attribute1=&quot;att1&quot; attribute2=&quot;att2&quot;&gt;value1&lt;/element1&gt;<br>
82
 * &nbsp;&nbsp;&lt;element2 attribute20=&quot;att20&quot;&gt;<br>
83
 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;element21 attribute21=&quot;att21&quot;&gt;value21&lt;/element21&gt;<br>
84
 * &nbsp;&nbsp;&lt;/element2&gt;<br>
85
 * &lt;/root&gt;
86
 * </p>
87
 * <p> Values for Elements and Attributes can be any type of object but will be
88
 * converted to String by calling toString() before storing the value.</p>
89
 * <hr>
90
 * <p> How to load XML tree from disk:</p>
91
 * <ol>
92
 * <li> Open a BufferedReader (in = new BufferedReader(file);)
93
 * <li> Create a new myXML(BufferedReader) object passing it the BufferedReader (xmlroot = new myXML((BufferedReader)in);)
94
 * </ol>
95
 * <hr>
96
 * <p> How to walk through the XML tree:</p>
97
 * <ul>
98
 * <li> myXML.getTag():  Returns the tag name of the myXML object
99
 * <li> myXML.getValue():  Returns the value of the myXML object
100
 * <li> myXML.getElement(int):  Returns the myXML sub-object at the specified index
101
 * <li> myXML.findElement(String tag):  Returns the first myXML object with the name tag
102
 * <li> myXML.findElement(String tag, Attribute name):  Returns the first myXML object with the name tag and attribute name
103
 * <li> myXML.Attribute.find(String name):  Returns the attribute value associated with name
104
 * </ul>
105
 * @author Scott Hiles
106
 * @version 1.1.0
107
 */
108
 
109
public class myXML implements Serializable {
110
  private final boolean debug=false;
111
  private final String XMLheader = "<?xml version=\"1.0\" standalone=\"yes\"?>";
112
  private String XMLcomment = "<!-- Written by WiSH XML writer -->";
113
  private String tag;
114
  private Object element = null;                // either an Object or a Vector()
115
  public Attribute Attribute = new Attribute();
116
 
117
  /**
118
   * Create new myXML object with no subelements, no attributes, and no value
119
   *
120
   * @param tag initial name for the XML element or null for empty element
121
   */
122
  public myXML(String tag) {
123
    this.tag = tag;
124
    if (debug) System.out.println("myXML("+tag+")");
125
  }
126
  public myXML() {
127
 
128
  }
129
  /**
130
   * Set comment for XML
131
   *
132
   * @param commentText
133
   *
134
   * <b>String object</b>: for the text of the XML element
135
   */
136
  public void setComment(String headerComment) {
137
    this.XMLcomment = "<!-- " + headerComment + " -->";
138
  }
139
 
140
  /**
141
   * Retrieve the name/tag
142
   *
143
   * @return <b>null</b> if no name set<br>
144
   * <b>String object</b>: for the text of the XML element
145
   */
146
  public String getTag() {
147
    return this.tag;
148
  }
149
  /**
150
   * Retrieve the data value for this element in String form
151
   *
152
   * @return <b>null</b> if no value set<br>
153
   * <b>String object</b>: for the text of the XML element
154
   */
155
  public String getValue() {
156
    if (element == null)
157
      return null;
158
    if (this.element.getClass() == Vector.class)
159
      return null;
160
    return(String)this.element;
161
  }
162
  /**
163
   * Retrieve the number of elements attached to this element
164
   *
165
   * @return int value
166
   */
167
  public int size() {
168
    if (element == null)
169
      return 0;
170
    if (element.getClass() == Vector.class)
171
      return((Vector)element).size();
172
    return 0;
173
  }
174
  /**
175
   * Find and retrieve the first element that has the specified tag name
176
   * The search starts with this object and follows all branch and leaf nodes
177
   * in the XML tree.
178
   *
179
   * @return <b>null</b>:  no object with the specified tag found<br>
180
   * <b>myXML object</b>:  First object found with the matching tag
181
   */
182
  public myXML findElement(String tag) {
183
    if (this.tag.compareTo(tag) == 0)
184
      return this;
185
    if (element == null)
186
      return null;
187
    if (element.getClass() != Vector.class)
188
      return null;
189
 
190
    myXML xml = null;
191
    Vector v = (Vector)this.element;
192
    int i;
193
    for (i = 0; i < v.size(); i++) {
194
      xml = (myXML)v.get(i);
195
      if (xml == null)
196
        continue;
197
      if (xml.findElement(tag) != null)
198
        return xml;
199
    }
200
    return null;
201
  }
202
 
203
  /**
204
   * Remove subelement (element) from list of objects for a parent element
205
   *
206
   * @param element element to be removed from object list of parent
207
   *
208
   * @return boolean value set to true if the element was found and removed from the parent
209
   */
210
  public boolean removeElement(myXML element) throws myXMLException {
211
    if (element == null)
212
      throw new myXMLException("Cannot prune if target element is null");
213
    if (this.element == null)
214
      throw new myXMLException("No subelements");
215
    if (this.element.getClass() != Vector.class)
216
      throw new myXMLException("No subelements");
217
 
218
    Vector v = (Vector)this.element;
219
    int i;
220
    if (v.size() <= 0)
221
      return false;
222
    if (!v.remove(element))
223
      return false;
224
    if (v.size() == 0)         // go ahead and cl
225
      this.element = null;
226
    return true;
227
  }
228
 
229
  /**
230
   * Tests if the specified element is a subelement of this element
231
   *
232
   * @param element element to test
233
   *
234
   * @return boolean value set to true if element is found to be subelement
235
   */
236
  public boolean contains(myXML element) throws myXMLException {
237
    if (element == null)
238
      throw new myXMLException("Cannot prune if target element is null");
239
    if (this.element == null)
240
      throw new myXMLException("No subelements");
241
    if (this.element.getClass() != Vector.class)
242
      throw new myXMLException("No subelements");
243
 
244
    Vector v = (Vector)this.element;
245
    int i;
246
    if (v.size() <= 0)
247
      return false;
248
    return v.contains(element);
249
  }
250
 
251
  /**
252
   * Removes all subelements from the specified parent
253
   *
254
   * @return boolean value set to true if all subelements removed
255
   */
256
  public boolean removeAllElements() {
257
    if (this.element == null)
258
      return false;
259
    if (this.element.getClass() != Vector.class)
260
      return false;
261
    element = null;
262
    return true;
263
  }
264
 
265
  /**
266
   * Tests if this parent has no subelements
267
   *
268
   * @return boolean value set to true if size is zero
269
   */
270
  public boolean isEmpty() {
271
    return(this.element == null);
272
  }
273
  /**
274
   * Find and retrieve the first element that has the specified attribute name.
275
   * The search starts with this object and follows all branch and leaf nodes
276
   * in the XML tree.
277
   *
278
   * @return <b>null</b>:  No element found with the specified attribute name<br>
279
   * <b>myXML object</b>:  First object found with the matching attribute name
280
   */
281
  public myXML findattribute(String name) {
282
    if (Attribute.find(name) != null)
283
      return this;
284
    if (element == null)
285
      return null;
286
    if (element.getClass() != Vector.class)
287
      return null;
288
 
289
    myXML xml = null;
290
    Vector v = (Vector)element;
291
    int i;
292
    for (i = 0; i < v.size(); i++) {
293
      xml = (myXML)v.get(i);
294
      if (xml.findattribute(name) != null)
295
        return xml;
296
    }
297
    return null;
298
  }
299
  /**
300
   * Find and retrieve the first element that has the specified tag name and
301
   * the specified attribute name.  The search starts with this object and
302
   * follows all branch and leaf nodes in the XML tree.
303
   *
304
   * @return <b>null</b>:  No element found with the specified tag name and attribute name<br>
305
   * <b>myXML object</b>:  First object found matching both the tag name and attribute name
306
   */
307
  public myXML findElement(String tag, String attname) {
308
    if (this.tag.compareTo(tag) == 0 && this.Attribute.find(attname) != null)
309
      return this;
310
    if (element == null)
311
      return null;
312
    if (element.getClass() != Vector.class)
313
      return null;
314
 
315
    myXML xml = null;
316
    Vector v = (Vector)element;
317
    int i;
318
    for (i = 0; i < v.size(); i++) {
319
      xml = (myXML)v.get(i);
320
      if (xml.findElement(tag) != null && xml.Attribute.find(attname) != null)
321
        return xml;
322
    }
323
    return null;
324
  }
325
  /**
326
   * Retrieve the element object at the specified <b>index</b>.  The branche and
327
   * leaf nodes in the XML tree are stored in a Vector and each index represents
328
   * a node in the tree.  Indicies start at 0 and count up to the length of the
329
   * Vector.
330
   *
331
   * @param index integer indicating the branch or leaf of the this XML object
332
   *
333
   * @return <b>null</b>: if index is out of bounds<br>
334
   * <b>myXML object</b>: element at the specified index.
335
   */
336
  public myXML getElement(int index) {
337
    if (element == null)
338
      return null;
339
    if (element.getClass() != Vector.class)
340
      return null;
341
 
342
    Vector v = (Vector)element;
343
    if (index > v.size())
344
      return null;
345
    if (index < 0)
346
      return null;
347
    return(myXML)v.get(index);
348
  }
349
 
350
  /**
351
   * Set the tag for this element (private)
352
   *
353
   * @param tag String to use for tag name for element
354
   */
355
  private void setTag(String tag) {
356
    this.tag = tag;
357
    if (debug) System.out.println("myXML.setTag("+tag+")");
358
  }
359
  /**
360
   * Set the value for this element (private)
361
   *
362
   * @param value String to use for value for the element
363
   */
364
  private void setValue(String value) throws myXMLException {
365
    if (element != null)
366
      if (element.getClass() == Vector.class)
367
        throw new myXMLException("Cannot assign a value to an element with subelements");
368
    this.element = value;
369
    if (debug) System.out.println("myXML.setValue("+value+")");
370
  }
371
 
372
  /**
373
   * Add a subelement which will be used as a branch in the XML tree to the
374
   * this element of the form:<br>
375
   * <br>&lt;tag attr1=&quot;attr1value attr2=&quot;attr2value&quot; ... &gt;<br>
376
   * ...<br>
377
   * &lt;/tag&gt;
378
   *
379
   * @param tag String used to identify the element
380
   *
381
   * @return <b>myXML object</b>: the newly created element
382
   */
383
  public myXML addElement(String tag) throws myXMLException {
384
    Vector v = null;
385
 
386
    if (element != null)
387
      if (element.getClass() != Vector.class)
388
        throw new myXMLException("Cannot create a subelement to an element with a value");
389
    if (element == null) {
390
      v = new Vector(); // create the new subelement
391
      element = v;
392
    } else
393
      v = (Vector)element;
394
 
395
    myXML xml = new myXML(tag);
396
    v.add((Object)xml);
397
    if (debug) System.out.println("myXML.addElement("+tag+")");
398
    if (debug) System.out.println("elements.size() = "+v.size());
399
    return xml;
400
  }
401
  /**
402
   * Add a leaf element to the XML tree of the form:<br>
403
   * <br>&lt;tag attr1=&quot;attr1value attr2=&quot;attr2value&quot; ... &gt;value&lt;/tag&gt;
404
   *
405
   * @param tag String used to identify the element
406
   * @param value Object to use for the element value (Note that the object will
407
   * be converted to a String before storing in the XML tree.
408
   *
409
   * @return <b>myXML object</b>: the newly created element
410
   */
411
  public myXML addElement(String tag,Object value) throws myXMLException {
412
    Vector v = null;
413
 
414
    if (element != null)
415
      if (element.getClass() != Vector.class)
416
        throw new myXMLException("Cannot create a subelement to an element with a value");
417
    if (element == null) {
418
      v = new Vector();
419
      element = v;
420
    } else
421
      v = (Vector)element;
422
 
423
    myXML e = new myXML(tag);
424
    e.setValue(value.toString());
425
    v.add(e);
426
    if (debug) System.out.println("myXML.addElement("+tag+","+value+")");
427
    if (debug) System.out.println("elements.size() = "+v.size());
428
    return e;
429
  }
430
// -----------------------------------------------------------------------------
431
  /**
432
   * Attributes associated with element tags.  Multiple attributes can be added
433
   * to any element tag and will be stored between the &lt; and &gt;.  Attributes
434
   * are written to the XML tree in the form:<br>
435
   * <br>&lt;tag attr1=&quot;attr1value attr2=&quot;attr2value&quot; ... &gt;value&lt;/tag&gt;
436
   */
437
  public class Attribute {
438
    private Vector attributes = new Vector();
439
 
440
    /**
441
     * Add a named attribute to this element
442
     *
443
     * @param name String identifying the name of the attribute
444
     * @param value Object identifying the data to be associated with the attribute
445
     */
446
    public void add(String name, Object value) {
447
      attribute a = new attribute(name, value.toString());
448
      attributes.add( (Object) a);
449
      if (debug) System.out.println("myXML.Attribute.add("+name+","+value+")");
450
    }
451
    private String dumpattributes() {
452
      int i;
453
      String result = new String("");
454
      for (i = 0; i < attributes.size(); i++) {
455
        attribute o = (attribute)attributes.get(i);
456
        result = new String(result+" "+o.name+"=\""+cleanup(o.value)+"\"");
457
      }
458
      return result;
459
    }
460
    /**
461
     * Retrieve the value associated with the attribute identified
462
     *
463
     * @param name The name of the attribute to find
464
     *
465
     * @return <b>String object</b>: String form of the value associated with the attribute
466
     */
467
    public String find(String name) {
468
      int i;
469
      if (attributes.size() == 0)
470
        return null;
471
      for (i = 0; i < attributes.size(); i++) {
472
        attribute a = (attribute)attributes.get(i);
473
        if (a.name == null)
474
          continue;
475
        else if (a.name.compareTo(name) == 0)
476
          return a.value;
477
      }
478
      return null;
479
    }
480
    private class attribute {
481
      public String name = null;
482
      public String value = null;
483
      attribute(String name,String value){
484
        this.name = name;
485
        this.value = value;
486
      }
487
    }
488
  }
489
// -----------------------------------------------------------------------------
490
// XML writer (serializer)
491
// -----------------------------------------------------------------------------
492
// Simply recurse through the tree printing out the tags, attributes, and
493
// subelements as you go.  Every node in the tree is an instance of myXML
494
// so recursion is fairly trivial.
495
//
496
// This is all original code, so don't stone me if I have a mistake.
497
// -----------------------------------------------------------------------------
498
  /**
499
   * Write the XML tree to an output stream in text form.
500
   *
501
   * @param out A PrintWriter object to be used as the output stream for the XML tree.
502
   */
503
  public void serialize(PrintWriter out) throws IOException {
504
    int depth = 0;
505
    out.println(XMLheader);
506
    out.println(XMLcomment);
507
    dumpelements("",out);
508
  }
509
  private void dumpelements(String indent,PrintWriter out) {
510
    int i;
511
    out.print(indent+"<"+tag+Attribute.dumpattributes());
512
    if (element == null) {
513
      out.println("/>");
514
      return;
515
    }
516
    if (element.getClass() == Vector.class) {
517
      Vector v = (Vector)element;
518
      out.println(">");
519
      for (i = 0; i < v.size(); i++)
520
        ((myXML)v.get(i)).dumpelements(new String(indent+"  "),out);
521
      out.println(indent+"</"+tag+">");
522
    } else {                       // we have children so we can't have any elements
523
      out.println(">"+getValue()+"</"+tag+">");
524
    }
525
  }
526
 
527
// Here is a cute little method that you have to use before you write any
528
// user specified names out.  If the user embeds the characters &, <, >, ", or '
529
// in the name of an object and it is written as is, the XML reader will choke.
530
//
531
// The order that the characters are replaced is very important in so much as
532
// the & character must be replaced first since the other replacements add
533
// & characters.
534
//
535
// This method is private as it is only needed as you serialize the objects.
536
// The application (user) shouldn't have to worry about the characters that
537
// are used in the application or entered by the user.
538
  private Object cleanup(Object o)
539
  {
540
    if (o.getClass() == String.class) {
541
      String s = (String)o;
542
      s = s.replaceAll("&","&amp;");
543
      s = s.replaceAll("<","&lt;");
544
      s = s.replaceAll(">","&gt;");
545
      s = s.replaceAll("\"","&quot;");
546
      s = s.replaceAll("\'","&apos;");
547
      s = s.replaceAll("/","&#47;");
548
//      s = s.replaceAll("?","&#63;");
549
      s = s.replaceAll("!","&#33;");
550
      return s;
551
    }
552
    return o;
553
  }
554
 
555
// -----------------------------------------------------------------------------
556
// XML parser/reader
557
// -----------------------------------------------------------------------------
558
// The XML reader/parser state machine technique was extracted from
559
// Steven Brandt's article in JavaWorld
560
// (http://www.javaworld.com/javaworld/javatips/jw-javatip128.html)
561
//
562
// Steven's state machine makes calls like a SAX parser; however, this
563
// adaption acts more like a DOM parser to load the entire document into
564
// memory exactly in the form that the writer expects so that the application
565
// can query the object tree to find information.  Variable names and
566
// programming technique differs from Steven's so that I could figure out
567
// what his state machine was trying to do.
568
//
569
// All values for Elements and Attributes are stored as String objects and
570
// Should be interpreted by the application.
571
// -----------------------------------------------------------------------------
572
  /**
573
   * Read the XML tree in from the specified stream.  The stream can be anything
574
   * that can be opened as a BufferedReader which includes URLs and files.  The XML
575
   * tree will be stored in a tree of myXML objects and the root object will be returned.
576
   *
577
   * @param in BufferedReader input stream
578
   */
579
  public myXML(BufferedReader in) throws myXMLException, myXMLEncodingException, IOException {
580
    myFileReader f = new myFileReader(in);
581
    try {
582
      readXML(this, f);
583
    } catch (myXMLEncodingException e) {
584
      throw e;
585
    } catch (IOException e) {
586
      throw e;
587
    } catch (myXMLException e) {
588
      throw e;
589
    }
590
    if (debug) {
591
      System.out.println("XML file successfully loaded");
592
//      System.exit(0);
593
    }
594
  }
595
 
596
  private static int popState(Stack st) {
597
    if (!st.empty())
598
      return((Integer)st.pop()).intValue();
599
    else
600
      return PRE;
601
  }
602
  private final static int  // Steven Brandt's original states
603
  TEXT = 1,
604
  ENTITY = 2,
605
  OPEN_TAG = 3,
606
  CLOSE_TAG = 4,
607
  START_TAG = 5,
608
  ATTRIBUTE_LVALUE = 6,
609
  ATTRIBUTE_EQUAL = 9,
610
  ATTRIBUTE_RVALUE = 10,
611
  QUOTE = 7,
612
  IN_TAG = 8,
613
  SINGLE_TAG = 12,
614
  COMMENT = 13,
615
  DONE = 11,
616
  DOCTYPE = 14,
617
  PRE = 15,
618
  CDATA = 16;
619
  private int line = 1, col = 0;
620
  private boolean eol = false;
621
 
622
// This is a very simple method which allows me to unread characters that
623
// have been read from the file stream.  This one is critical to the XMLreader
624
// so that you can find the <tag> markers and push the "<" back into the reader
625
// to allow you to recursively call the reader as if the <tag> were a new root
626
// object.
627
  private class myFileReader implements Serializable {
628
    Stack stack = new Stack();
629
    BufferedReader f = null;
630
    myFileReader(BufferedReader in) {
631
      f = in;
632
    }
633
    void unread(int c) {
634
      stack.push(new Integer(c));
635
    }
636
    int read() throws IOException {
637
      if (stack.empty())
638
        try {
639
          return f.read();
640
        } catch (IOException e) {
641
          throw e;
642
        } else {
643
        int c = ((Integer)stack.pop()).intValue();
644
        return c;
645
      }
646
    }
647
  }
648
 
649
// Steven's code had this embedded but I took it out to clean up the recursive
650
// method.  This just takes out the linefeeds and carriage returns
651
  private boolean checkforlinefeed(int c) {
652
    // We need to map \r, \r\n, and \n to \n
653
    // See XML spec section 2.11
654
    if (c == '\n' && eol) {
655
      eol = false;
656
      return true;
657
    } else if (eol) {
658
      eol = false;
659
    } else if (c == '\n') {
660
      line++;
661
      col = 0;
662
    } else if (c == '\r') {
663
      eol = true;
664
      c = '\n';
665
      line++;
666
      col = 0;
667
    } else {
668
      col++;
669
    }
670
    return false;
671
  }
672
 
673
//------------------------------------------------------------------------------
674
// OK, here is your workhorse for the parser.  It implements the same kind of
675
// character reader based state machine that Steven Brandt demonstrated in
676
// http://www.javaworld.com/javaworld/javatips/jw-javatip128.html. But, the
677
// significant change here is that the reader calls itself recursively to
678
// build the document tree and doesn't make document handler calls.  The
679
// states have been reordered and turned into a switch() statement but they
680
// behave mostly the same.
681
//------------------------------------------------------------------------------
682
  private void readXML(myXML xml,myFileReader in) throws myXMLException, myXMLEncodingException, IOException {
683
    int c;
684
    int state = PRE;
685
    Stack stack = new Stack();
686
    StringBuffer sb = new StringBuffer();
687
    StringBuffer etag = new StringBuffer();
688
    String lvalue = null, rvalue = null;
689
    int quotec = '"';
690
 
691
    if (debug) System.out.println("readXML starting");
692
    while ((c = in.read()) != -1) {
693
      if (checkforlinefeed(c))
694
        continue;
695
      switch (state) {
696
      case PRE:
697
        if (debug) System.out.println("readXML: state PRE, c="+(char)c);
698
        if (c == '<') {                        // we are outside the root tag element
699
          stack.push(new Integer(TEXT));
700
          state = START_TAG;
701
        }
702
        break;
703
      case START_TAG:
704
        if (debug) System.out.println("readXML: state START_TAG, c="+(char)c);
705
        state = popState(stack);
706
        if (c == '/') {
707
          stack.push(new Integer(state));
708
          state = CLOSE_TAG;
709
        } else if (c == '?') {
710
          state = DOCTYPE;
711
        } else if (xml.getTag() != null) {
712
          in.unread(c);
713
          in.unread('<');
714
          try {
715
            readXML(xml.addElement(null), in);
716
          } catch (myXMLException e) {
717
            throw e;
718
          }
719
          state = popState(stack);
720
        } else {
721
          stack.push(new Integer(state));
722
          state = OPEN_TAG;
723
          sb.append((char)c);
724
        }
725
        break;
726
      case OPEN_TAG:
727
        if (debug) System.out.println("readXML: state OPEN_TAG, c="+(char)c);
728
        if (c == '>') {
729
          if (xml.getTag() == null)
730
            xml.setTag(sb.toString());
731
          else
732
            xml.addElement(sb.toString());
733
          sb.setLength(0);
734
          state = popState(stack);
735
        } else if (c == '/') {
736
          state = SINGLE_TAG;
737
        } else if (c == '-' && sb.toString().equals("!-")) {
738
          state = COMMENT;
739
        } else if (c == '[' && sb.toString().equals("![CDATA")) {
740
          state = CDATA;
741
          sb.setLength(0);
742
        } else if (c == 'E' && sb.toString().equals("!DOCTYP")) {
743
          sb.setLength(0);
744
          state = DOCTYPE;
745
        } else if (Character.isWhitespace((char)c)) {
746
          xml.setTag(sb.toString());
747
          sb.setLength(0);
748
          state = IN_TAG;
749
        } else {
750
          sb.append((char)c);
751
        }
752
        break;
753
      case TEXT:
754
        if (Character.isWhitespace((char)c) && sb.length() < 1)
755
          break;
756
        if (debug) System.out.println("readXML: state TEXT, c="+(char)c);
757
        if (c == '<') {                       // we are currently reading a value and get the start of a tag
758
          stack.push(new Integer(state));
759
          state = START_TAG;
760
          if (sb.length() > 0) {              // we had a value, so the "<" must belong to a </close>
761
            xml.setValue(sb.toString());
762
            sb.setLength(0);
763
          }
764
        } else if (c == '&') {                // encoded items such as &lt
765
          stack.push(new Integer(state));
766
          state = ENTITY;
767
          etag.setLength(0);
768
        } else {
769
          sb.append((char)c);
770
        }
771
        break;
772
      case CLOSE_TAG:
773
        if (debug) System.out.println("readXML: state CLOSE_TAG, c="+(char)c);
774
        if (c == '>') {                       // currently processing endtag like </tag>
775
          state = popState(stack);
776
          String endTag = sb.toString();
777
          if (endTag.compareTo(xml.getTag()) != 0)
778
            throw new myXMLEncodingException("Mismatched close tag near line "+line+", column "+col);
779
          sb.setLength(0);
780
          if (debug) System.out.println("readXML: state CLOSE_TAG - Element complete...returning");
781
          return;
782
        } else {
783
          sb.append((char)c);
784
        }
785
        break;
786
      case IN_TAG:
787
        if (debug) System.out.println("readXML: state IN_TAG, c="+(char)c);
788
        if (c == '>') {
789
          state = popState(stack);
790
        } else if (c == '/') {
791
          state = SINGLE_TAG;
792
        } else if (Character.isWhitespace((char)c)) {
793
          ;
794
        } else {
795
          state = ATTRIBUTE_LVALUE;
796
          sb.append((char)c);
797
        }
798
        break;
799
      case DONE:                             // due to recursion, this will never happen
800
        if (debug) System.out.println("readXML: state DONE, c="+(char)c);
801
        return;
802
      case CDATA:
803
        if (debug) System.out.println("readXML: state CDATA, c="+(char)c);
804
        if (c == '>' && sb.toString().endsWith("]]")) {
805
          sb.setLength(sb.length()-2);
806
          xml.setValue(sb.toString());
807
          sb.setLength(0);
808
          state = popState(stack);
809
        } else {
810
          sb.append((char)c);
811
        }
812
        break;
813
      case COMMENT:                          // Inside <!-- ... -->
814
        if (debug) System.out.println("readXML: state COMMENT, c="+(char)c);
815
        if (c == '>' && sb.toString().endsWith("--")) {  // found the end of a comment
816
          sb.setLength(0);
817
          state = popState(stack);
818
        } else {
819
          sb.append((char)c);
820
        }
821
        break;
822
      case DOCTYPE:
823
        if (debug) System.out.println("readXML: state DOCTYPE, c="+(char)c);
824
        if (c == '>') {                        // currently processing <? ... ?>
825
          state = popState(stack);
826
          if (state == TEXT)
827
            state = PRE;
828
        }
829
        break;
830
      case ENTITY:
831
        if (debug) System.out.println("readXML: state ENTITY, c="+(char)c);
832
        if (c == ';') {                        // we are completing an entity like &gt;, &lt;
833
          state = popState(stack);
834
          String cent = etag.toString();
835
          etag.setLength(0);
836
          if (cent.equals("lt"))
837
            sb.append('<');
838
          else if (cent.equals("gt"))          // &gt; = "<"
839
            sb.append('>');
840
          else if (cent.equals("amp"))         // &amp; = "&"
841
            sb.append('&');
842
          else if (cent.equals("quot"))        // &quot; = """
843
            sb.append('"');
844
          else if (cent.equals("apos"))        // &apos; = "'"
845
            sb.append('\'');
846
          else if (cent.startsWith("#"))       // &#33; = "!", &#63; = "?", &#47; = "/", &#59; = ";"
847
            sb.append((char)Integer.parseInt(cent.substring(1)));
848
          else
849
            throw new myXMLEncodingException("Unknown entity: &"+cent+"; near line "+line+", column "+col);
850
        } else {
851
          etag.append((char)c);
852
        }
853
        break;
854
      case SINGLE_TAG:                        // Inside <tag/>
855
        if (debug) System.out.println("readXML: state SINGLE_TAG, c="+(char)c);
856
        if (xml.getTag() == null) {
857
          xml.setTag(sb.toString());
858
          sb.setLength(0);
859
        }
860
        if (c != '>')
861
          throw new myXMLEncodingException("Expected > for tag <"+xml.getTag()+"/> near line "+line+", column "+col);
862
        return;
863
      case QUOTE:
864
        if (debug) System.out.println("readXML: state QUOTE, c="+(char)c);
865
        if (c == quotec) {
866
          rvalue = sb.toString();
867
          sb.setLength(0);
868
          xml.Attribute.add(lvalue,rvalue);
869
          state = IN_TAG;
870
          // See section the XML spec, section 3.3.3
871
          // on normalization processing.
872
        } else if (" \r\n\u0009".indexOf(c)>=0) {
873
          sb.append(' ');
874
        } else if (c == '&') {
875
          stack.push(new Integer(state));
876
          state = ENTITY;
877
          etag.setLength(0);
878
        } else {
879
          sb.append((char)c);
880
        }
881
        break;
882
      case ATTRIBUTE_RVALUE:
883
        if (debug) System.out.println("readXML: state ATTRIBUTE_RVALUE, c="+(char)c);
884
        if (c == '"' || c == '\'') {
885
          quotec = c;
886
          state = QUOTE;
887
        } else if (Character.isWhitespace((char)c)) {
888
          ;
889
        } else {
890
          throw new myXMLEncodingException("Error in attribute processing near line "+line+", column "+col);
891
        }
892
        break;
893
      case ATTRIBUTE_LVALUE:
894
        if (debug) System.out.println("readXML: state ATTRIBUTE_LVALUE, c="+(char)c);
895
        if (Character.isWhitespace( (char) c)) {
896
          lvalue = sb.toString();
897
          sb.setLength(0);
898
          state = ATTRIBUTE_EQUAL;
899
        } else if (c == '=') {
900
          lvalue = sb.toString();
901
          sb.setLength(0);
902
          state = ATTRIBUTE_RVALUE;
903
        } else {
904
          sb.append( (char) c);
905
        }
906
        break;
907
      case ATTRIBUTE_EQUAL:
908
        if (debug) System.out.println("readXML: state ATTRIBUTE_EQUAL, c="+(char)c);
909
        if (c == '=') {
910
          state = ATTRIBUTE_RVALUE;
911
        } else if (Character.isWhitespace((char)c)) {
912
          ;
913
        } else {
914
          throw new myXMLEncodingException("Error in attribute processing near line "+line+", column "+col);
915
        }
916
        break;
917
      default:
918
        throw new myXMLEncodingException("State exception near line "+line+", column "+col);
919
      }
920
    }
921
    if (state == DONE)
922
      return;
923
    else
924
      throw new myXMLEncodingException("Missing end tag near line "+line+", column "+col);
925
  }
926
  /**
927
   * Signals that the XML being parsed has an error in the encoding.
928
   */
929
  static public class myXMLEncodingException extends Exception implements Serializable {
930
    public myXMLEncodingException() {
931
      super();
932
    }
933
    public myXMLEncodingException(String s) {
934
      super(s);
935
    }
936
  }
937
  /**
938
   * Signals that a general exception has occurred.
939
   */
940
  static public class myXMLException extends Exception implements Serializable {
941
    public myXMLException() {
942
      super();
943
    }
944
    public myXMLException(String s) {
945
      super(s);
946
    }
947
  }
948
  /**
949
   * Signals that an I/O excetion has occurred.
950
   */
951
  static public class myXMLIOException extends Exception implements Serializable {
952
    public myXMLIOException() {
953
      super();
954
    }
955
    public myXMLIOException(String s) {
956
      super(s);
957
    }
958
  }
959
}