~ubuntu-branches/ubuntu/precise/xom/precise

« back to all changes in this revision

Viewing changes to src/nu/xom/Document.java

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2007-11-25 15:50:40 UTC
  • Revision ID: james.westby@ubuntu.com-20071125155040-r75ikcqf1vu0cei7
Tags: upstream-1.1
ImportĀ upstreamĀ versionĀ 1.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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@metalab.unc.edu. 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
     */
 
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
     */
 
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
 
 
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
 
 
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
     */
 
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
     */
 
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
     */
 
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
     */
 
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
     */
 
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
     */
 
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
     */
 
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
     */
 
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
     */
 
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
     */
 
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
     */
 
411
    public final String toXML() {
 
412
    
 
413
        StringBuffer result = new StringBuffer();
 
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
     */
 
436
    public Node copy() {
 
437
        return new Document(this);
 
438
    }
 
439
 
 
440
    
 
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
     */
 
455
    public final String toString() {
 
456
        return "[" + getClass().getName() + ": " 
 
457
          + getRootElement().getQualifiedName() + "]"; 
 
458
    }
 
459
 
 
460
    
 
461
}
 
 
b'\\ No newline at end of file'