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 }