1 /* Copyright 2002-2004 Elliotte Rusty Harold 2 3 This library is free software; you can redistribute it and/or modify 4 it under the terms of version 2.1 of the GNU Lesser General Public 5 License as published by the Free Software Foundation. 6 7 This library is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 GNU Lesser General Public License for more details. 11 12 You should have received a copy of the GNU Lesser General Public 13 License along with this library; if not, write to the 14 Free Software Foundation, Inc., 59 Temple Place, Suite 330, 15 Boston, MA 02111-1307 USA 16 17 You can contact Elliotte Rusty Harold by sending e-mail to 18 elharo@ibiblio.org. Please include the word "XOM" in the 19 subject line. The XOM home page is located at http://www.xom.nu/ 20 */ 21 22 package nu.xom; 23 24 /** 25 * <p> 26 * The <code>Document</code> class represents 27 * a complete XML document including its root element, 28 * prolog, and epilog. 29 * </p> 30 * 31 * @author Elliotte Rusty Harold 32 * @version 1.1b5 33 * 34 */ 35 public class Document extends ParentNode { 36 37 /** 38 * <p> 39 * Creates a new <code>Document</code> object with the 40 * specified root element. 41 * </p> 42 * 43 * @param root the root element of this document 44 * 45 * @throws NullPointerException if <code>root</code> is null 46 * @throws MultipleParentException if <code>root</code> already 47 * has a parent 48 */ Document(Element root)49 public Document(Element root) { 50 _insertChild(root, 0); 51 } 52 53 54 /** 55 * <p> 56 * Creates a copy of this document. 57 * </p> 58 * 59 * @param doc the document to copy 60 * 61 * @throws NullPointerException if <code>doc</code> is null 62 */ Document(Document doc)63 public Document(Document doc) { 64 65 insertChild(doc.getRootElement().copy(), 0); 66 int count = doc.getChildCount(); 67 for (int i = 0; i < count; i++) { 68 Node child = doc.getChild(i); 69 if (!(child.isElement())) { 70 this.insertChild(child.copy(), i); 71 } 72 } 73 this.actualBaseURI = doc.actualBaseURI; 74 75 } 76 77 insertionAllowed(Node child, int position)78 final void insertionAllowed(Node child, int position) { 79 80 if (child == null) { 81 throw new NullPointerException( 82 "Tried to insert a null child in the document"); 83 } 84 else if (child.getParent() != null) { 85 throw new MultipleParentException("Child already has a parent."); 86 } 87 else if (child.isComment() || child.isProcessingInstruction()) { 88 return; 89 } 90 else if (child.isDocType()) { 91 if (position <= getRootPosition()) { 92 DocType oldDocType = getDocType(); 93 if (oldDocType != null) { 94 throw new IllegalAddException( 95 "Tried to insert a second DOCTYPE" 96 ); 97 } 98 return; 99 } 100 else { 101 throw new IllegalAddException( 102 "Cannot add a document type declaration " 103 + "after the root element" 104 ); 105 } 106 } 107 else if (child.isElement()) { 108 if (getChildCount() == 0) return; 109 else { 110 throw new IllegalAddException( 111 "Cannot add a second root element to a Document." 112 ); 113 } 114 } 115 else { 116 throw new IllegalAddException("Cannot add a " 117 + child.getClass().getName() + " to a Document."); 118 } 119 120 } 121 122 getRootPosition()123 private int getRootPosition() { 124 125 // This looks like an infinite loop but it isn't 126 // because all documents have root elements 127 for (int i = 0; ; i++) { 128 Node child = getChild(i); 129 if (child.isElement()) { 130 return i; 131 } 132 } 133 134 } 135 136 137 /** 138 * <p> 139 * Returns this document's document type declaration, 140 * or null if it doesn't have one. 141 * </p> 142 * 143 * @return the document type declaration 144 * 145 * @see #setDocType 146 * 147 */ getDocType()148 public final DocType getDocType() { 149 150 for (int i = 0; i < getChildCount(); i++) { 151 Node child = getChild(i); 152 if (child.isDocType()) { 153 return (DocType) child; 154 } 155 } 156 return null; 157 158 } 159 160 161 /** 162 * <p> 163 * Sets this document's document type declaration. 164 * If this document already has a document type declaration, 165 * then it's inserted at that position. Otherwise, it's inserted 166 * at the beginning of the document. 167 * </p> 168 * 169 * @param doctype the document type declaration 170 * 171 * @throws MultipleParentException if <code>doctype</code> belongs 172 * to another document 173 * @throws NullPointerException if <code>doctype</code> is null 174 * 175 */ setDocType(DocType doctype)176 public void setDocType(DocType doctype) { 177 178 DocType oldDocType = getDocType(); 179 if (doctype == null) { 180 throw new NullPointerException("Null DocType"); 181 } 182 else if (doctype == oldDocType) return; 183 else if (doctype.getParent() != null) { 184 throw new MultipleParentException("DocType belongs to another document"); 185 } 186 187 if (oldDocType == null) insertChild(doctype, 0); 188 else { 189 int position = indexOf(oldDocType); 190 super.removeChild(position); 191 fastInsertChild(doctype, position); 192 oldDocType.setParent(null); 193 doctype.setParent(this); 194 } 195 196 } 197 198 199 /** 200 * <p> 201 * Returns this document's root element. 202 * This is guaranteed to be non-null. 203 * </p> 204 * 205 * @return the root element 206 */ getRootElement()207 public final Element getRootElement() { 208 209 // This looks like an infinite loop but it isn't because 210 // all documents have root elements. 211 for (int i = 0; ; i++) { 212 Node child = getChild(i); 213 if (child.isElement()) { 214 return (Element) child; 215 } 216 } 217 218 } 219 220 221 /** 222 * <p> 223 * Replaces the current root element with a different root element. 224 * </p> 225 * 226 * @param root the new root element 227 * 228 * @throws MultipleParentException if root has a parent 229 * @throws NullPointerException if root is null 230 */ setRootElement(Element root)231 public void setRootElement(Element root) { 232 233 Element oldRoot = this.getRootElement(); 234 if (root == oldRoot) return; 235 else if (root == null) { 236 throw new NullPointerException("Root element cannot be null"); 237 } 238 else if (root.getParent() != null) { 239 throw new MultipleParentException(root.getQualifiedName() 240 + " already has a parent"); 241 } 242 243 fillInBaseURI(oldRoot); 244 int index = indexOf(oldRoot); 245 246 oldRoot.setParent(null); 247 children[index] = root; 248 root.setParent(this); 249 250 } 251 252 253 /** 254 * <p> 255 * Sets the URI from which this document was loaded, and 256 * against which relative URLs in this document will be resolved. 257 * Setting the base URI to null or the empty string removes any 258 * existing base URI. 259 * </p> 260 * 261 * @param URI the base URI of this document 262 * 263 * @throws MalformedURIException if <code>URI</code> is 264 * not a legal absolute URI 265 */ setBaseURI(String URI)266 public void setBaseURI(String URI) { 267 setActualBaseURI(URI); 268 } 269 270 271 /** 272 * <p> 273 * Returns the absolute URI from which this document was loaded. 274 * This method returns the empty string if the base URI is not 275 * known; for instance if the document was created in memory with 276 * a constructor rather than by parsing an existing document. 277 * </p> 278 * 279 * @return the base URI of this document 280 */ getBaseURI()281 public final String getBaseURI() { 282 return getActualBaseURI(); 283 } 284 285 286 /** 287 * <p> 288 * Removes the child of this document at the specified position. 289 * Indexes begin at 0 and count up to one less than the number 290 * of children of this document. The root element cannot be 291 * removed. Instead, use <code>setRootElement</code> to replace 292 * the existing root element with a different element. 293 * </p> 294 * 295 * @param position index of the node to remove 296 * 297 * @return the node which was removed 298 * 299 * @throws IndexOutOfBoundsException if the index is negative or 300 * greater than the number of children of this document - 1 301 * @throws WellformednessException if the index points 302 * to the root element 303 */ removeChild(int position)304 public Node removeChild(int position) { 305 306 if (position == getRootPosition()) { 307 throw new WellformednessException( 308 "Cannot remove the root element" 309 ); 310 } 311 return super.removeChild(position); 312 313 } 314 315 316 /** 317 * <p> 318 * Removes the specified child from this document. 319 * The root element cannot be removed. 320 * Instead, use <code>setRootElement</code> to replace the 321 * existing root element with a different element. 322 * </p> 323 * 324 * @param child node to remove 325 * 326 * @return the node which was removed 327 * 328 * @throws NoSuchChildException if the node is not a 329 * child of this node 330 * @throws WellformednessException if child is the root element 331 */ removeChild(Node child)332 public Node removeChild(Node child) { 333 334 if (child == getRootElement()) { 335 throw new WellformednessException( 336 "Cannot remove the root element"); 337 } 338 return super.removeChild(child); 339 340 } 341 342 343 /** 344 * <p> 345 * Replaces an existing child with a new child node. 346 * If <code>oldChild</code> is not a child of this node, 347 * then a <code>NoSuchChildException</code> is thrown. 348 * The root element can only be replaced by another element. 349 * </p> 350 * 351 * @param oldChild the node removed from the tree 352 * @param newChild the node inserted into the tree 353 * 354 * @throws MultipleParentException if <code>newChild</code> already 355 * has a parent 356 * @throws NoSuchChildException if <code>oldChild</code> 357 * is not a child of this node 358 * @throws NullPointerException if either argument is null 359 * @throws IllegalAddException if <code>newChild</code> is an 360 * attribute or a text node 361 * @throws WellformednessException if <code>newChild</code> 362 * <code>oldChild</code> is an element and 363 * <code>newChild</code> is not 364 */ replaceChild(Node oldChild, Node newChild)365 public void replaceChild(Node oldChild, Node newChild) { 366 367 if (oldChild == getRootElement() 368 && newChild != null && newChild.isElement()) { 369 setRootElement((Element) newChild); 370 } 371 else if (oldChild == getDocType() 372 && newChild != null && newChild.isDocType()) { 373 setDocType((DocType) newChild); 374 } 375 else { 376 super.replaceChild(oldChild, newChild); 377 } 378 379 } 380 381 382 /** 383 * <p> 384 * Returns the value of the document as defined by XPath 1.0. 385 * This is the same as the value of the root element, which 386 * is the complete PCDATA content of the root element, without 387 * any tags, comments, or processing instructions after all 388 * entity and character references have been resolved. 389 * </p> 390 * 391 * @return value of the root element of this document 392 * 393 */ getValue()394 public final String getValue() { 395 return getRootElement().getValue(); 396 } 397 398 399 /** 400 * <p> 401 * Returns the actual complete, well-formed XML document as a 402 * <code>String</code>. Significant white space is preserved. 403 * Insignificant white space in tags, the prolog, the epilog, 404 * and the internal DTD subset is not preserved. 405 * Entity and character references are not preserved. 406 * The entire document is contained in this one string. 407 * </p> 408 * 409 * @return a string containing this entire XML document 410 */ toXML()411 public final String toXML() { 412 413 StringBuffer result = new StringBuffer(64); 414 415 // XML declaration 416 result.append("<?xml version=\"1.0\"?>\n"); 417 418 // children 419 for (int i = 0; i < getChildCount(); i++) { 420 result.append(getChild(i).toXML()); 421 result.append("\n"); 422 } 423 424 return result.toString(); 425 426 } 427 428 429 /** 430 * <p> 431 * Returns a complete copy of this document. 432 * </p> 433 * 434 * @return a deep copy of this <code>Document</code> object 435 */ copy()436 public Node copy() { 437 return new Document(this); 438 } 439 440 isDocument()441 boolean isDocument() { 442 return true; 443 } 444 445 446 /** 447 * <p> 448 * Returns a string representation of this document suitable 449 * for debugging and diagnosis. This is <em>not</em> 450 * the XML representation of this document. 451 * </p> 452 * 453 * @return a non-XML string representation of this document 454 */ toString()455 public final String toString() { 456 return "[" + getClass().getName() + ": " 457 + getRootElement().getQualifiedName() + "]"; 458 } 459 460 461 }