001: /* Copyright 2002-2004 Elliotte Rusty Harold
002:
003: This library is free software; you can redistribute it and/or modify
004: it under the terms of version 2.1 of the GNU Lesser General Public
005: License as published by the Free Software Foundation.
006:
007: This library is distributed in the hope that it will be useful,
008: but WITHOUT ANY WARRANTY; without even the implied warranty of
009: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
010: GNU Lesser General Public License for more details.
011:
012: You should have received a copy of the GNU Lesser General Public
013: License along with this library; if not, write to the
014: Free Software Foundation, Inc., 59 Temple Place, Suite 330,
015: Boston, MA 02111-1307 USA
016:
017: You can contact Elliotte Rusty Harold by sending e-mail to
018: elharo@metalab.unc.edu. Please include the word "XOM" in the
019: subject line. The XOM home page is located at http://www.xom.nu/
020: */
021:
022: package nu.xom;
023:
024: /**
025: * <p>
026: * The <code>Document</code> class represents
027: * a complete XML document including its root element,
028: * prolog, and epilog.
029: * </p>
030: *
031: * @author Elliotte Rusty Harold
032: * @version 1.1b5
033: *
034: */
035: public class Document extends ParentNode {
036:
037: /**
038: * <p>
039: * Creates a new <code>Document</code> object with the
040: * specified root element.
041: * </p>
042: *
043: * @param root the root element of this document
044: *
045: * @throws NullPointerException if <code>root</code> is null
046: * @throws MultipleParentException if <code>root</code> already
047: * has a parent
048: */
049: public Document(Element root) {
050: _insertChild(root, 0);
051: }
052:
053: /**
054: * <p>
055: * Creates a copy of this document.
056: * </p>
057: *
058: * @param doc the document to copy
059: *
060: * @throws NullPointerException if <code>doc</code> is null
061: */
062: public Document(Document doc) {
063:
064: insertChild(doc.getRootElement().copy(), 0);
065: int count = doc.getChildCount();
066: for (int i = 0; i < count; i++) {
067: Node child = doc.getChild(i);
068: if (!(child.isElement())) {
069: this .insertChild(child.copy(), i);
070: }
071: }
072: this .actualBaseURI = doc.actualBaseURI;
073:
074: }
075:
076: final void insertionAllowed(Node child, int position) {
077:
078: if (child == null) {
079: throw new NullPointerException(
080: "Tried to insert a null child in the document");
081: } else if (child.getParent() != null) {
082: throw new MultipleParentException(
083: "Child already has a parent.");
084: } else if (child.isComment() || child.isProcessingInstruction()) {
085: return;
086: } else if (child.isDocType()) {
087: if (position <= getRootPosition()) {
088: DocType oldDocType = getDocType();
089: if (oldDocType != null) {
090: throw new IllegalAddException(
091: "Tried to insert a second DOCTYPE");
092: }
093: return;
094: } else {
095: throw new IllegalAddException(
096: "Cannot add a document type declaration "
097: + "after the root element");
098: }
099: } else if (child.isElement()) {
100: if (getChildCount() == 0)
101: return;
102: else {
103: throw new IllegalAddException(
104: "Cannot add a second root element to a Document.");
105: }
106: } else {
107: throw new IllegalAddException("Cannot add a "
108: + child.getClass().getName() + " to a Document.");
109: }
110:
111: }
112:
113: private int getRootPosition() {
114:
115: // This looks like an infinite loop but it isn't
116: // because all documents have root elements
117: for (int i = 0;; i++) {
118: Node child = getChild(i);
119: if (child.isElement()) {
120: return i;
121: }
122: }
123:
124: }
125:
126: /**
127: * <p>
128: * Returns this document's document type declaration,
129: * or null if it doesn't have one.
130: * </p>
131: *
132: * @return the document type declaration
133: *
134: * @see #setDocType
135: *
136: */
137: public final DocType getDocType() {
138:
139: for (int i = 0; i < getChildCount(); i++) {
140: Node child = getChild(i);
141: if (child.isDocType()) {
142: return (DocType) child;
143: }
144: }
145: return null;
146:
147: }
148:
149: /**
150: * <p>
151: * Sets this document's document type declaration.
152: * If this document already has a document type declaration,
153: * then it's inserted at that position. Otherwise, it's inserted
154: * at the beginning of the document.
155: * </p>
156: *
157: * @param doctype the document type declaration
158: *
159: * @throws MultipleParentException if <code>doctype</code> belongs
160: * to another document
161: * @throws NullPointerException if <code>doctype</code> is null
162: *
163: */
164: public void setDocType(DocType doctype) {
165:
166: DocType oldDocType = getDocType();
167: if (doctype == null) {
168: throw new NullPointerException("Null DocType");
169: } else if (doctype == oldDocType)
170: return;
171: else if (doctype.getParent() != null) {
172: throw new MultipleParentException(
173: "DocType belongs to another document");
174: }
175:
176: if (oldDocType == null)
177: insertChild(doctype, 0);
178: else {
179: int position = indexOf(oldDocType);
180: super .removeChild(position);
181: fastInsertChild(doctype, position);
182: oldDocType.setParent(null);
183: doctype.setParent(this );
184: }
185:
186: }
187:
188: /**
189: * <p>
190: * Returns this document's root element.
191: * This is guaranteed to be non-null.
192: * </p>
193: *
194: * @return the root element
195: */
196: public final Element getRootElement() {
197:
198: // This looks like an infinite loop but it isn't because
199: // all documents have root elements.
200: for (int i = 0;; i++) {
201: Node child = getChild(i);
202: if (child.isElement()) {
203: return (Element) child;
204: }
205: }
206:
207: }
208:
209: /**
210: * <p>
211: * Replaces the current root element with a different root element.
212: * </p>
213: *
214: * @param root the new root element
215: *
216: * @throws MultipleParentException if root has a parent
217: * @throws NullPointerException if root is null
218: */
219: public void setRootElement(Element root) {
220:
221: Element oldRoot = this .getRootElement();
222: if (root == oldRoot)
223: return;
224: else if (root == null) {
225: throw new NullPointerException(
226: "Root element cannot be null");
227: } else if (root.getParent() != null) {
228: throw new MultipleParentException(root.getQualifiedName()
229: + " already has a parent");
230: }
231:
232: fillInBaseURI(oldRoot);
233: int index = indexOf(oldRoot);
234:
235: oldRoot.setParent(null);
236: children[index] = root;
237: root.setParent(this );
238:
239: }
240:
241: /**
242: * <p>
243: * Sets the URI from which this document was loaded, and
244: * against which relative URLs in this document will be resolved.
245: * Setting the base URI to null or the empty string removes any
246: * existing base URI.
247: * </p>
248: *
249: * @param URI the base URI of this document
250: *
251: * @throws MalformedURIException if <code>URI</code> is
252: * not a legal absolute URI
253: */
254: public void setBaseURI(String URI) {
255: setActualBaseURI(URI);
256: }
257:
258: /**
259: * <p>
260: * Returns the absolute URI from which this document was loaded.
261: * This method returns the empty string if the base URI is not
262: * known; for instance if the document was created in memory with
263: * a constructor rather than by parsing an existing document.
264: * </p>
265: *
266: * @return the base URI of this document
267: */
268: public final String getBaseURI() {
269: return getActualBaseURI();
270: }
271:
272: /**
273: * <p>
274: * Removes the child of this document at the specified position.
275: * Indexes begin at 0 and count up to one less than the number
276: * of children of this document. The root element cannot be
277: * removed. Instead, use <code>setRootElement</code> to replace
278: * the existing root element with a different element.
279: * </p>
280: *
281: * @param position index of the node to remove
282: *
283: * @return the node which was removed
284: *
285: * @throws IndexOutOfBoundsException if the index is negative or
286: * greater than the number of children of this document - 1
287: * @throws WellformednessException if the index points
288: * to the root element
289: */
290: public Node removeChild(int position) {
291:
292: if (position == getRootPosition()) {
293: throw new WellformednessException(
294: "Cannot remove the root element");
295: }
296: return super .removeChild(position);
297:
298: }
299:
300: /**
301: * <p>
302: * Removes the specified child from this document.
303: * The root element cannot be removed.
304: * Instead, use <code>setRootElement</code> to replace the
305: * existing root element with a different element.
306: * </p>
307: *
308: * @param child node to remove
309: *
310: * @return the node which was removed
311: *
312: * @throws NoSuchChildException if the node is not a
313: * child of this node
314: * @throws WellformednessException if child is the root element
315: */
316: public Node removeChild(Node child) {
317:
318: if (child == getRootElement()) {
319: throw new WellformednessException(
320: "Cannot remove the root element");
321: }
322: return super .removeChild(child);
323:
324: }
325:
326: /**
327: * <p>
328: * Replaces an existing child with a new child node.
329: * If <code>oldChild</code> is not a child of this node,
330: * then a <code>NoSuchChildException</code> is thrown.
331: * The root element can only be replaced by another element.
332: * </p>
333: *
334: * @param oldChild the node removed from the tree
335: * @param newChild the node inserted into the tree
336: *
337: * @throws MultipleParentException if <code>newChild</code> already
338: * has a parent
339: * @throws NoSuchChildException if <code>oldChild</code>
340: * is not a child of this node
341: * @throws NullPointerException if either argument is null
342: * @throws IllegalAddException if <code>newChild</code> is an
343: * attribute or a text node
344: * @throws WellformednessException if <code>newChild</code>
345: * <code>oldChild</code> is an element and
346: * <code>newChild</code> is not
347: */
348: public void replaceChild(Node oldChild, Node newChild) {
349:
350: if (oldChild == getRootElement() && newChild != null
351: && newChild.isElement()) {
352: setRootElement((Element) newChild);
353: } else if (oldChild == getDocType() && newChild != null
354: && newChild.isDocType()) {
355: setDocType((DocType) newChild);
356: } else {
357: super .replaceChild(oldChild, newChild);
358: }
359:
360: }
361:
362: /**
363: * <p>
364: * Returns the value of the document as defined by XPath 1.0.
365: * This is the same as the value of the root element, which
366: * is the complete PCDATA content of the root element, without
367: * any tags, comments, or processing instructions after all
368: * entity and character references have been resolved.
369: * </p>
370: *
371: * @return value of the root element of this document
372: *
373: */
374: public final String getValue() {
375: return getRootElement().getValue();
376: }
377:
378: /**
379: * <p>
380: * Returns the actual complete, well-formed XML document as a
381: * <code>String</code>. Significant white space is preserved.
382: * Insignificant white space in tags, the prolog, the epilog,
383: * and the internal DTD subset is not preserved.
384: * Entity and character references are not preserved.
385: * The entire document is contained in this one string.
386: * </p>
387: *
388: * @return a string containing this entire XML document
389: */
390: public final String toXML() {
391:
392: StringBuffer result = new StringBuffer();
393:
394: // XML declaration
395: result.append("<?xml version=\"1.0\"?>\n");
396:
397: // children
398: for (int i = 0; i < getChildCount(); i++) {
399: result.append(getChild(i).toXML());
400: result.append("\n");
401: }
402:
403: return result.toString();
404:
405: }
406:
407: /**
408: * <p>
409: * Returns a complete copy of this document.
410: * </p>
411: *
412: * @return a deep copy of this <code>Document</code> object
413: */
414: public Node copy() {
415: return new Document(this );
416: }
417:
418: boolean isDocument() {
419: return true;
420: }
421:
422: /**
423: * <p>
424: * Returns a string representation of this document suitable
425: * for debugging and diagnosis. This is <em>not</em>
426: * the XML representation of this document.
427: * </p>
428: *
429: * @return a non-XML string representation of this document
430: */
431: public final String toString() {
432: return "[" + getClass().getName() + ": "
433: + getRootElement().getQualifiedName() + "]";
434: }
435:
436: }
|