Subversion Repositories HomeAutomation

Rev

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

  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. }
  960.