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><?xml version="1.0" standalone="yes"?><br> |
||
| 38 | * <!-- Comment --><br> |
||
| 39 | * <rootname><br> |
||
| 40 | * <tag><br> |
||
| 41 | * <tag attribute1="value" attribute2="value">elementvalue</tag><br> |
||
| 42 | * </tag><br> |
||
| 43 | * </rootname></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("roottag");) |
||
| 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("subelement");) |
||
| 57 | * <li>If the subelement will not have subelements (leaf), add the element with a value |
||
| 58 | * (myXML subelement = roottag.addElement("element",value);) |
||
| 59 | * <li>For each subelement (branch and leaf) add attributes |
||
| 60 | * (subelement.Attribute.add("name",value);) or (root.Attribute.add("name",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 | * <?xml version="1.0" standalone="yes"?><br> |
||
| 80 | * <root><br> |
||
| 81 | * <element1 attribute1="att1" attribute2="att2">value1</element1><br> |
||
| 82 | * <element2 attribute20="att20"><br> |
||
| 83 | * <element21 attribute21="att21">value21</element21><br> |
||
| 84 | * </element2><br> |
||
| 85 | * </root> |
||
| 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><tag attr1="attr1value attr2="attr2value" ... ><br> |
||
| 376 | * ...<br> |
||
| 377 | * </tag> |
||
| 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><tag attr1="attr1value attr2="attr2value" ... >value</tag> |
||
| 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 < and >. Attributes |
||
| 434 | * are written to the XML tree in the form:<br> |
||
| 435 | * <br><tag attr1="attr1value attr2="attr2value" ... >value</tag> |
||
| 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("&","&"); |
||
| 543 | s = s.replaceAll("<","<"); |
||
| 544 | s = s.replaceAll(">",">"); |
||
| 545 | s = s.replaceAll("\"","""); |
||
| 546 | s = s.replaceAll("\'","'"); |
||
| 547 | s = s.replaceAll("/","/"); |
||
| 548 | // s = s.replaceAll("?","?"); |
||
| 549 | s = s.replaceAll("!","!"); |
||
| 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 < |
||
| 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 >, < |
||
| 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")) // > = "<" |
||
| 839 | sb.append('>'); |
||
| 840 | else if (cent.equals("amp")) // & = "&" |
||
| 841 | sb.append('&'); |
||
| 842 | else if (cent.equals("quot")) // " = """ |
||
| 843 | sb.append('"'); |
||
| 844 | else if (cent.equals("apos")) // ' = "'" |
||
| 845 | sb.append('\''); |
||
| 846 | else if (cent.startsWith("#")) // ! = "!", ? = "?", / = "/", ; = ";" |
||
| 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 | } |