All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdom2.Document Maven / Gradle / Ivy

Go to download

A complete, Java-based solution for accessing, manipulating, and outputting XML data

The newest version!
/*--

 Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin.
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows
    these conditions in the documentation and/or other materials
    provided with the distribution.

 3. The name "JDOM" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact .

 4. Products derived from this software may not be called "JDOM", nor
    may "JDOM" appear in their name, without prior written permission
    from the JDOM Project Management .

 In addition, we request (but do not require) that you include in the
 end-user documentation provided with the redistribution and/or in the
 software itself an acknowledgement equivalent to the following:
     "This product includes software developed by the
      JDOM Project (http://www.jdom.org/)."
 Alternatively, the acknowledgment may be graphical using the logos
 available at http://www.jdom.org/images/logos.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 This software consists of voluntary contributions made by many
 individuals on behalf of the JDOM Project and was originally
 created by Jason Hunter  and
 Brett McLaughlin .  For more information
 on the JDOM Project, please see .

 */

package org.jdom2;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.*;

import org.jdom2.filter.*;
import org.jdom2.util.IteratorIterable;

/**
 * An XML document. Methods allow access to the root element as well as the
 * {@link DocType} and other document-level information.
 *
 * @author  Brett McLaughlin
 * @author  Jason Hunter
 * @author  Jools Enticknap
 * @author  Bradley S. Huffman
 * @author  Rolf Lear
 */
public class Document extends CloneBase implements Parent {
	
	/**
	 * This document's content including comments, PIs, a possible
	 * DocType, and a root element.
	 * Subclassers have to track content using their own
	 * mechanism.
	 */
	transient ContentList content = new ContentList(this);

	/**
	 *  See http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/core.html#baseURIs-Considerations
	 */
	protected String baseURI = null;

	// Supports the setProperty/getProperty calls
	private transient HashMap propertyMap = null;

	/**
	 * Creates a new empty document.  A document must have a root element,
	 * so this document will not be well-formed and accessor methods will
	 * throw an IllegalStateException if this document is accessed before a
	 * root element is added.  This method is most useful for build tools.
	 */
	public Document() {}

	/**
	 * This will create a new Document,
	 * with the supplied {@link Element}
	 * as the root element, the supplied
	 * {@link DocType} declaration, and the specified
	 * base URI.
	 *
	 * @param rootElement Element for document root.
	 * @param docType DocType declaration.
	 * @param baseURI the URI from which this doucment was loaded.
	 * @throws IllegalAddException if the given docType object
	 *         is already attached to a document or the given
	 *         rootElement already has a parent
	 */
	public Document(Element rootElement, DocType docType, String baseURI) {
		if (rootElement != null) {
			setRootElement(rootElement);
		}
		if (docType != null) {
			setDocType(docType);
		}
		if (baseURI != null) {
			setBaseURI(baseURI);
		}
	}

	/**
	 * This will create a new Document,
	 * with the supplied {@link Element}
	 * as the root element and the supplied
	 * {@link DocType} declaration.
	 *
	 * @param rootElement Element for document root.
	 * @param docType DocType declaration.
	 * @throws IllegalAddException if the given DocType object
	 *         is already attached to a document or the given
	 *         rootElement already has a parent
	 */
	public Document(Element rootElement, DocType docType) {
		this(rootElement, docType, null);
	}

	/**
	 * This will create a new Document,
	 * with the supplied {@link Element}
	 * as the root element, and no {@link DocType}
	 * declaration.
	 *
	 * @param rootElement Element for document root
	 * @throws IllegalAddException if the given rootElement already has
	 *         a parent.
	 */
	public Document(Element rootElement) {
		this(rootElement, null, null);
	}

	/**
	 * This will create a new Document,
	 * with the supplied list of content, and a
	 * {@link DocType} declaration only if the content
	 * contains a DocType instance.  A null list is treated the
	 * same as the no-arg constructor.
	 *
	 * @param content List of starter content
	 * @throws IllegalAddException if the List contains more than
	 *         one Element or objects of illegal types.
	 */
	public Document(List content) {
		setContent(content);
	}

	@Override
	public int getContentSize() {
		return content.size();
	}

	@Override
	public int indexOf(Content child) {
		return content.indexOf(child);
	}

	//    /**
	//     * Starting at the given index (inclusive), return the index of
	//     * the first child matching the supplied filter, or -1
	//     * if none is found.
	//     *
	//     * @return index of child, or -1 if none found.
	//     */
	//    private int indexOf(int start, Filter filter) {
	//        int size = getContentSize();
	//        for (int i = start; i < size; i++) {
	//            if (filter.matches(getContent(i))) {
	//                return i;
	//            }
	//        }
	//        return -1;
	//    }

	/**
	 * This will return true if this document has a
	 * root element, false otherwise.
	 *
	 * @return true if this document has a root element,
	 *         false otherwise.
	 */
	public boolean hasRootElement() {
		return (content.indexOfFirstElement() < 0) ? false : true;
	}

	/**
	 * This will return the root Element
	 * for this Document
	 *
	 * @return Element - the document's root element
	 * @throws IllegalStateException if the root element hasn't been set
	 */
	public Element getRootElement() {
		int index = content.indexOfFirstElement();
		if (index < 0) {
			throw new IllegalStateException("Root element not set");
		}
		return (Element) content.get(index);
	}

	/**
	 * This sets the root {@link Element} for the
	 * Document. If the document already has a root
	 * element, it is replaced.
	 *
	 * @param rootElement Element to be new root.
	 * @return Document - modified Document.
	 * @throws IllegalAddException if the given rootElement already has
	 *         a parent.
	 */
	public Document setRootElement(Element rootElement) {
		int index = content.indexOfFirstElement();
		if (index < 0) {
			content.add(rootElement);
		}
		else {
			content.set(index, rootElement);
		}
		return this;
	}

	/**
	 * Detach the root {@link Element} from this document.
	 *
	 * @return removed root Element
	 */
	public Element detachRootElement() {
		int index = content.indexOfFirstElement();
		if (index < 0)
			return null;
		return (Element) removeContent(index);
	}

	/**
	 * This will return the {@link DocType}
	 * declaration for this Document, or
	 * null if none exists.
	 *
	 * @return DocType - the DOCTYPE declaration.
	 */
	public DocType getDocType() {
		int index = content.indexOfDocType();
		if (index < 0) {
			return null;
		}
		return (DocType) content.get(index);
	}

	/**
	 * This will set the {@link DocType}
	 * declaration for this Document. Note
	 * that a DocType can only be attached to one Document.
	 * Attempting to set the DocType to a DocType object
	 * that already belongs to a Document will result in an
	 * IllegalAddException being thrown.
	 *
	 * @param docType DocType declaration.
	 * @return object on which the method was invoked
	 * @throws IllegalAddException if the given docType is
	 *   already attached to a Document.
	 */
	public Document setDocType(DocType docType) {
		if (docType == null) {
			// Remove any existing doctype
			int docTypeIndex = content.indexOfDocType();
			if (docTypeIndex >= 0) content.remove(docTypeIndex);
			return this;
		}

		if (docType.getParent() != null) {
			throw new IllegalAddException(docType,
					"The DocType already is attached to a document");
		}

		// Add DocType to head if new, replace old otherwise
		int docTypeIndex = content.indexOfDocType();
		if (docTypeIndex < 0) {
			content.add(0, docType);
		}
		else {
			content.set(docTypeIndex, docType);
		}

		return this;
	}

	/**
	 * Appends the child to the end of the content list.
	 *
	 * @param child   child to append to end of content list
	 * @return        the document on which the method was called
	 * @throws IllegalAddException if the given child already has a parent.
	 */
	@Override
	public Document addContent(Content child) {
		content.add(child);
		return this;
	}

	/**
	 * Appends all children in the given collection to the end of
	 * the content list.  In event of an exception during add the
	 * original content will be unchanged and the objects in the supplied
	 * collection will be unaltered.
	 *
	 * @param c   collection to append
	 * @return    the document on which the method was called
	 * @throws IllegalAddException if any item in the collection
	 *         already has a parent or is of an illegal type.
	 */
	@Override
	public Document addContent(Collection c) {
		content.addAll(c);
		return this;
	}

	/**
	 * Inserts the child into the content list at the given index.
	 *
	 * @param index location for adding the collection
	 * @param child      child to insert
	 * @return           the parent on which the method was called
	 * @throws IndexOutOfBoundsException if index is negative or beyond
	 *         the current number of children
	 * @throws IllegalAddException if the given child already has a parent.
	 */
	@Override
	public Document addContent(int index, Content child) {
		content.add(index, child);
		return this;
	}

	/**
	 * Inserts the content in a collection into the content list
	 * at the given index.  In event of an exception the original content
	 * will be unchanged and the objects in the supplied collection will be
	 * unaltered.
	 *
	 * @param index location for adding the collection
	 * @param c  collection to insert
	 * @return            the parent on which the method was called
	 * @throws IndexOutOfBoundsException if index is negative or beyond
	 *         the current number of children
	 * @throws IllegalAddException if any item in the collection
	 *         already has a parent or is of an illegal type.
	 */
	@Override
	public Document addContent(int index, Collection c) {
		content.addAll(index, c);
		return this;
	}

	@Override
	public List cloneContent() {
		int size = getContentSize();
		List list = new ArrayList(size);
		for (int i = 0; i < size; i++) {
			Content child = getContent(i);
			list.add(child.clone());
		}
		return list;
	}

	@Override
	public Content getContent(int index) {
		return content.get(index);
	}

	//    public Content getChild(Filter filter) {
		//        int i = indexOf(0, filter);
	//        return (i < 0) ? null : getContent(i);
	//    }

	/**
	 * This will return all content for the Document.
	 * The returned list is "live" in document order and changes to it
	 * affect the document's actual content.
	 *
	 * 

* Sequential traversal through the List is best done with a Iterator * since the underlying implement of List.size() may require walking the * entire list. *

* * @return List - all Document content * @throws IllegalStateException if the root element hasn't been set */ @Override public List getContent() { if (!hasRootElement()) throw new IllegalStateException("Root element not set"); return content; } /** * Return a filtered view of this Document's content. * *

* Sequential traversal through the List is best done with a Iterator * since the underlying implement of List.size() may require walking the * entire list. *

* * @param filter Filter to apply * @return List - filtered Document content * @throws IllegalStateException if the root element hasn't been set */ @Override public List getContent(Filter filter) { if (!hasRootElement()) throw new IllegalStateException("Root element not set"); return content.getView(filter); } /** * Removes all child content from this parent. * * @return list of the old children detached from this parent */ @Override public List removeContent() { List old = new ArrayList(content); content.clear(); return old; } /** * Remove all child content from this parent matching the supplied filter. * * @param filter filter to select which content to remove * @return list of the old children detached from this parent */ @Override public List removeContent(Filter filter) { List old = new ArrayList(); Iterator itr = content.getView(filter).iterator(); while (itr.hasNext()) { F child = itr.next(); old.add(child); itr.remove(); } return old; } /** * This sets the content of the Document. The supplied * List should contain only objects of type Element, * Comment, and ProcessingInstruction. * *

* When all objects in the supplied List are legal and before the new * content is added, all objects in the old content will have their * parentage set to null (no parent) and the old content list will be * cleared. This has the effect that any active list (previously obtained * with a call to {@link #getContent}) will also * change to reflect the new content. In addition, all objects in the * supplied List will have their parentage set to this document, but the * List itself will not be "live" and further removals and additions will * have no effect on this document content. If the user wants to continue * working with a "live" list, then a call to setContent should be * followed by a call to {@link #getContent} to * obtain a "live" version of the content. *

* *

* Passing a null or empty List clears the existing content. *

* *

* In event of an exception the original content will be unchanged and * the objects in the supplied content will be unaltered. *

* * @param newContent List of content to set * @return this document modified * @throws IllegalAddException if the List contains objects of * illegal types or with existing parentage. */ public Document setContent(Collection newContent) { content.clearAndSet(newContent); return this; } /** * *

* Sets the effective URI from which this document was loaded, * and against which relative URLs in this document will be resolved. *

* * @param uri the base URI of this document */ public final void setBaseURI(String uri) { this.baseURI = uri; // XXX We don't check the URI } /** *

* Returns the URI from which this document was loaded, * or null if this is not known. *

* * @return the base URI of this document */ public final String getBaseURI() { return baseURI; } /** * Replace the current child the given index with the supplied child. *

* In event of an exception the original content will be unchanged and * the supplied child will be unaltered. *

* * @param index - index of child to replace. * @param child - child to add. * @return this document instance * @throws IllegalAddException if the supplied child is already attached * or not legal content for this parent. * @throws IndexOutOfBoundsException if index is negative or greater * than the current number of children. */ public Document setContent(int index, Content child) { content.set(index, child); return this; } /** * Replace the child at the given index whith the supplied * collection. *

* In event of an exception the original content will be unchanged and * the content in the supplied collection will be unaltered. *

* * @param index - index of child to replace. * @param collection - collection of content to add. * @return object on which the method was invoked * @throws IllegalAddException if the collection contains objects of * illegal types. * @throws IndexOutOfBoundsException if index is negative or greater * than the current number of children. */ public Document setContent(int index, Collection collection) { content.remove(index); content.addAll(index, collection); return this; } @Override public boolean removeContent(Content child) { return content.remove(child); } @Override public Content removeContent(int index) { return content.remove(index); } /** * Set this document's content to be the supplied child. *

* If the supplied child is legal content for a Document and before * it is added, all content in the current content list will * be cleared and all current children will have their parentage set to * null. *

* This has the effect that any active list (previously obtained with * a call to one of the {@link #getContent} methods will also change * to reflect the new content. In addition, all content in the supplied * collection will have their parentage set to this Document. If the user * wants to continue working with a "live" list of this Document's * child, then a call to setContent should be followed by a call to one * of the {@link #getContent} methods to obtain a "live" * version of the children. *

* Passing a null child clears the existing content. *

* In event of an exception the original content will be unchanged and * the supplied child will be unaltered. * * @param child new content to replace existing content * @return the parent on which the method was called * @throws IllegalAddException if the supplied child is already attached * or not legal content for this parent */ public Document setContent(Content child) { content.clear(); content.add(child); return this; } /** * This returns a String representation of the * Document, suitable for debugging. If the XML * representation of the Document is desired, * {@link org.jdom2.output.XMLOutputter#outputString(Document)} * should be used. * * @return String - information about the * Document */ @Override public String toString() { StringBuilder stringForm = new StringBuilder() .append("[Document: "); DocType docType = getDocType(); if (docType != null) { stringForm.append(docType.toString()) .append(", "); } else { stringForm.append(" No DOCTYPE declaration, "); } Element rootElement = hasRootElement() ? getRootElement() : null ; if (rootElement != null) { stringForm.append("Root is ") .append(rootElement.toString()); } else { stringForm.append(" No root element"); // shouldn't happen } stringForm.append("]"); return stringForm.toString(); } /** * This tests for equality of this Document to the supplied * Object. * * @param ob Object to compare to * @return boolean whether the Document is * equal to the supplied Object */ @Override public final boolean equals(Object ob) { return (ob == this); } /** * This returns the hash code for this Document. * * @return int hash code */ @Override public final int hashCode() { return super.hashCode(); } /** * This will return a deep clone of this Document. * * @return Object clone of this Document */ @Override public Document clone() { final Document doc = (Document) super.clone(); // The clone has a reference to this object's content list, so // owerwrite with a empty list doc.content = new ContentList(doc); // Add the cloned content to clone for (int i = 0; i < content.size(); i++) { Object obj = content.get(i); if (obj instanceof Element) { Element element = ((Element)obj).clone(); doc.content.add(element); } else if (obj instanceof Comment) { Comment comment = ((Comment)obj).clone(); doc.content.add(comment); } else if (obj instanceof ProcessingInstruction) { ProcessingInstruction pi = ((ProcessingInstruction)obj).clone(); doc.content.add(pi); } else if (obj instanceof DocType) { DocType dt = ((DocType)obj).clone(); doc.content.add(dt); } } return doc; } /** * Returns an iterator that walks over all descendants in document order. * * @return an iterator to walk descendants */ @Override public IteratorIterable getDescendants() { return new DescendantIterator(this); } /** * Returns an iterator that walks over all descendants in document order * applying the Filter to return only elements that match the filter rule. * With filters you can match only Elements, only Comments, Elements or * Comments, only Elements with a given name and/or prefix, and so on. * * @param filter filter to select which descendants to see * @return an iterator to walk descendants within a filter */ @Override public IteratorIterable getDescendants(final Filter filter) { return new FilterIterator(new DescendantIterator(this), filter); } /** * Always returns null, Document cannot have a parent. * @return null */ @Override public Parent getParent() { return null; // documents never have parents } /** * Always returns this Document Instance * @return 'this' because this Document is it's own Document */ @Override public Document getDocument() { return this; } /** * Assigns an arbitrary object to be associated with this document under * the given "id" string. Null values are permitted. 'id' Strings beginning * with "http://www.jdom.org/ are reserved for JDOM use. Properties set with * this method will not be serialized with the rest of this Document, should * serialization need to be done. * * @param id the id of the stored Object * @param value the Object to store */ public void setProperty(String id, Object value) { if (propertyMap == null) { propertyMap = new HashMap(); } propertyMap.put(id, value); } /** * Returns the object associated with this document under the given "id" * string, or null if there is no binding or if the binding explicitly * stored a null value. * * @param id the id of the stored Object to return * @return the Object associated with the given id */ public Object getProperty(String id) { if (propertyMap == null) { return null; } return propertyMap.get(id); } @Override public void canContainContent(Content child, int index, boolean replace) { if (child instanceof Element) { int cre = content.indexOfFirstElement(); if (replace && cre == index) { return; } if (cre >= 0) { throw new IllegalAddException( "Cannot add a second root element, only one is allowed"); } if (content.indexOfDocType() >= index) { throw new IllegalAddException( "A root element cannot be added before the DocType"); } } if (child instanceof DocType) { int cdt = content.indexOfDocType(); if (replace && cdt == index) { // It's OK to replace an existing DocType return; } if (cdt >= 0) { throw new IllegalAddException( "Cannot add a second doctype, only one is allowed"); } int firstElt = content.indexOfFirstElement(); if (firstElt != -1 && firstElt < index) { throw new IllegalAddException( "A DocType cannot be added after the root element"); } } if (child instanceof CDATA) { throw new IllegalAddException("A CDATA is not allowed at the document root"); } if (child instanceof Text) { throw new IllegalAddException("A Text is not allowed at the document root"); } if (child instanceof EntityRef) { throw new IllegalAddException("An EntityRef is not allowed at the document root"); } } /** * Get the Namespaces that are in-scope on this Document. *

* Document always has exactly two Namespaces in-scope: * {@link Namespace#NO_NAMESPACE} and {@link Namespace#XML_NAMESPACE}. *

* These namespaces are always introduced by the Document, and thus they are * both returned by {@link #getNamespacesIntroduced()}, and additionally * {@link #getNamespacesInherited()} will always be empty. *

* Description copied from * {@link NamespaceAware#getNamespacesInScope()}: *

* {@inheritDoc} */ @Override public List getNamespacesInScope() { return Collections.unmodifiableList(Arrays.asList( new Namespace[] {Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE})); } @Override public List getNamespacesIntroduced() { return Collections.unmodifiableList(Arrays.asList( new Namespace[] {Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE})); } @Override public List getNamespacesInherited() { return Collections.emptyList(); } /** * JDOM2 Serialization. In this case, DocType is simple. */ private static final long serialVersionUID = 200L; /** * Serialize out the Element. * * @serialData * Document Properties are not serialized! *

* The Stream protocol is: *

    *
  1. The BaseURI using default Serialization. *
  2. The count of child Content *
  3. The actual Child Content. *
* * @param out where to write the Element to. * @throws IOException if there is a writing problem. */ private void writeObject(final ObjectOutputStream out) throws IOException { out.defaultWriteObject(); final int cs = content.size(); out.writeInt(cs); for (int i = 0; i < cs; i++) { out.writeObject(getContent(i)); } } /** * Read an Element off the ObjectInputStream. * * @see #writeObject(ObjectOutputStream) * @param in where to read the Element from. * @throws IOException if there is a reading problem. * @throws ClassNotFoundException when a class cannot be found */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); content = new ContentList(this); int cs = in.readInt(); while (--cs >= 0) { addContent((Content)in.readObject()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy