/*-- $Id: Element.java,v 1.68 2001/04/18 07:04:17 jhunter Exp $ Copyright (C) 2000 Brett McLaughlin & Jason Hunter. 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 license@jdom.org. 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 (pm@jdom.org). 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 Brett McLaughlin and Jason Hunter . For more information on the JDOM Project, please see . */ package org.jdom; import java.io.*; import java.util.*; /** *

Element defines behavior for an XML * element, modeled in Java. Methods allow the user * to obtain the value of the element's textual content, * obtain its attributes, and get its children. *

* * * @author Brett McLaughlin * @author Jason Hunter * @author Lucas Gonze * @author Kevin Regan * @author Dan Schaffer * @author Yusuf Goolamabbas * @version 1.0 */ public class Element implements Serializable, Cloneable { /** Determines whether isAncestor() is called when adding child elements */ protected static boolean testAncestors = true; /** The local name of the Element */ protected String name; /** The {@link Namespace} of the Element */ protected transient Namespace namespace; /** Additional {@link Namespace} declarations on this element */ protected transient LinkedList additionalNamespaces; /** Parent element, or null if none */ protected Element parent; // See http://lists.denveronline.net/lists/jdom-interest/2000-September/003030.html // for a possible memory optimization here (using a RootElement subclass) /** The Document containing this element, if it is the root element */ protected Document document; /** The attributes of the Element */ protected List attributes; /** The mixed content of the Element */ protected LinkedList content; /** *

* This protected constructor is provided in order to support an Element * subclass that wants full control over variable initialization. It * intentionally leaves all instance variables null, allowing a * lightweight subclass implementation. The subclass is responsible for * ensuring all the get and set methods on Element behave as documented. *

* *

* When implementing an Element subclass which doesn't * require full control over variable initialization, be aware that * simply calling super() (or letting the compiler add the implicit * super() call) will not initialize the instance variables which will * cause many of the methods to throw a * NullPointerException. Therefore, the * constructor for these subclasses should call one of the public * constructors so variable initialization is handled automatically. *

*/ protected Element() { } /** *

* This will create a new Element * with the supplied (local) name, and define * the {@link Namespace} to be used. * If the provided namespace is null, the element will have * no namespace. *

* * @param name String name of element. * @namespace Namespace to put element in. */ public Element(String name, Namespace namespace) { setName(name); setNamespace(namespace); document = null; } /** *

* This will create an Element in no * {@link Namespace}. *

* * @param name String name of element. */ public Element(String name) { this(name, (Namespace) null); } /** *

* This will create a new Element with * the supplied (local) name, and specifies the URI * of the {@link Namespace} the Element * should be in, resulting it being unprefixed (in the default * namespace). *

* * @param name String name of element. * @param uri String URI for Namespace element * should be in. */ public Element(String name, String uri) { this(name, Namespace.getNamespace("", uri)); } /** *

* This will create a new Element with * the supplied (local) name, and specifies the prefix and URI * of the {@link Namespace} the Element * should be in. *

* * @param name String name of element. * @param uri String URI for Namespace element * should be in. */ public Element(String name, String prefix, String uri) { this(name, Namespace.getNamespace(prefix, uri)); } /** *

* This returns the (local) name of the * Element, without any * namespace prefix, if one exists. *

* * @return String - element name. */ public String getName() { return name; } /** *

* This sets the (local) name of the Element. *

* * @return Element - the element modified. */ public Element setName(String name) { String reason; if ((reason = Verifier.checkElementName(name)) != null) { throw new IllegalNameException(name, "element", reason); } this.name = name; return this; } /** *

* This will return this Element's * {@link Namespace}. *

* * @return Namespace - Namespace object for this * Element */ public Namespace getNamespace() { return namespace; } /** *

* This sets this Element's {@link Namespace}. * If the provided namespace is null, the element will have no namespace. *

* * @return Element - the element modified. */ public Element setNamespace(Namespace namespace) { if (namespace == null) { namespace = Namespace.NO_NAMESPACE; } this.namespace = namespace; return this; } /** *

* This returns the namespace prefix * of the Element, if * one exists. Otherwise, an empty * String is returned. *

* * @return String - namespace prefix. */ public String getNamespacePrefix() { return namespace.getPrefix(); } /** *

* This returns the URI mapped to this Element's * prefix (or the default namespace if no prefix). If no * mapping is found, an empty String is returned. *

* * @return String - namespace URI for this * Element. */ public String getNamespaceURI() { return namespace.getURI(); } /** *

* This returns the Namespace in scope on this element for the given * prefix (this involves searching up the tree, so the results depend * on the current location of the element). It returns null if there * is no Namespace in scope with the given prefix at this point in * the document. *

* * @param prefix namespace prefix to look up * @return Namespace - namespace in scope for the given * prefix on this Element, or null if none. */ public Namespace getNamespace(String prefix) { if (prefix == null) { return null; } // Check if the prefix is the prefix for this element if (prefix.equals(getNamespacePrefix())) { return getNamespace(); } // Scan the additional namespaces List addl = getAdditionalNamespaces(); if (addl.size() > 0) { Iterator itr = addl.iterator(); while (itr.hasNext()) { Namespace ns = (Namespace) itr.next(); if (prefix.equals(ns.getPrefix())) { return ns; } } } // If we still don't have a match, ask the parent Element parent = getParent(); if (parent != null) { return parent.getNamespace(prefix); } return null; } /** *

* This returns the full name of the * Element, in the form * [namespacePrefix]:[localName]. If * no namespace prefix exists for the * Element, simply the * local name is returned. *

* * @return String - full name of element. */ public String getQualifiedName() { if (namespace.getPrefix().equals("")) { return getName(); } return new StringBuffer(namespace.getPrefix()) .append(":") .append(name) .toString(); } /** *

* This will add a namespace declarations to this element. * This should not be used to add the declaration for * this element itself; that should be assigned in the construction * of the element. Instead, this is for adding namespace * declarations on the element not relating directly to itself. *

* * @param additionalNamespace Namespace to add. * @throws IllegalAddException if the namespace prefix collides with * another namespace prefix on the element */ public void addNamespaceDeclaration(Namespace additionalNamespace) { // Verify the new namespace prefix doesn't collide with another // declared namespace, an attribute prefix, or this element's prefix String prefix = additionalNamespace.getPrefix(); String uri = additionalNamespace.getURI(); if (prefix.equals(getNamespacePrefix()) && !uri.equals(getNamespaceURI())) { throw new IllegalAddException(this, additionalNamespace, "The namespace prefix \"" + prefix + "\" collides " + "with the element namespace prefix"); } if (additionalNamespaces != null && additionalNamespaces.size() > 0) { Iterator itr = additionalNamespaces.iterator(); while (itr.hasNext()) { Namespace ns = (Namespace) itr.next(); if (prefix.equals(ns.getPrefix()) && !uri.equals(ns.getURI())) { throw new IllegalAddException(this, additionalNamespace, "The namespace prefix \"" + prefix + "\" collides with an additional namespace declared " + "by the element"); } } } if (attributes != null && attributes.size() > 0) { Iterator itr = attributes.iterator(); while (itr.hasNext()) { Namespace ns = (Namespace) ((Attribute)itr.next()).getNamespace(); if (prefix.equals(ns.getPrefix()) && !uri.equals(ns.getURI())) { throw new IllegalAddException(this, additionalNamespace, "The namespace prefix \"" + prefix + "\" collides with an attribute namespace on " + "the element"); } } } if (additionalNamespaces == null) { additionalNamespaces = new LinkedList(); } additionalNamespaces.add(additionalNamespace); } /** *

* This will remove a namespace declarations from this element. * This should not be used to remove the declaration for * this element itself; that should be handled in the construction * of the element. Instead, this is for removing namespace * declarations on the element not relating directly to itself. * If the declaration is not present, this method does nothing. *

* * @param additionalNamespace Namespace to remove. */ public void removeNamespaceDeclaration(Namespace additionalNamespace) { if (additionalNamespaces == null) { return; } additionalNamespaces.remove(additionalNamespace); } /** *

* This will return any namespace declarations on this element * that exist, excluding the namespace of the element * itself, which can be obtained through * {@link #getNamespace()}. If there are no additional * declarations, this returns an empty list. *

* * @return List - the additional namespace declarations. */ public List getAdditionalNamespaces() { if (additionalNamespaces == null) { return Collections.EMPTY_LIST; } else { return additionalNamespaces; } } /** *

* This will return the parent of this Element. * If there is no parent, then this returns null. * Also note that on its own, this is not 100% sufficient to * see if the Element is not in use - this should * be used in tandem with {@link #isRootElement} * to determine this. *

* * @return parent of this Element. */ public Element getParent() { return parent; } /** *

* This will set the parent of this Element. *

* * @param parent Element to be new parent. * @return Element - this Element modified. */ protected Element setParent(Element parent) { this.parent = parent; return this; } /** *

* This detaches the element from its parent, or does nothing if the * element has no parent. *

* * @return Element - this Element modified. */ public Element detach() { Element p = getParent(); if (p != null) { p.removeContent(this); } else { Document d = getDocument(); if (d != null) { d.setRootElement(new Element("placeholder")); } } return this; } /** *

* This returns a boolean value indicating * whether this Element is a root * Element for a JDOM {@link Document}. * This should be used in tandem with * {@link #getParent} to determine * if an Element has no "attachments" to * parents. *

* * @return boolean - whether this is a root element. */ public boolean isRootElement() { return document != null; } /** *

* This sets the {@link Document} parent of this element * and makes it the root element. *

* * @param document Document parent * @return Document this Element modified */ protected Element setDocument(Document document) { this.document = document; return this; } /** *

* This retrieves the owning {@link Document} for * this Element, or null if not a currently a member of a * {@link Document}. *

* * @return Document owning this Element, or null. */ public Document getDocument() { if (this.isRootElement()) { return this.document; } if (this.getParent() != null) { return this.getParent().getDocument(); } return null; } /** *

* This returns the textual content directly held under this * element. This will include all text within * this single element, including whitespace and CDATA * sections if they exist. It's essentially the concatenation of * all String nodes returned by getMixedContent(). The call does not * recurse into child elements. If no textual value exists for the * element, an empty String ("") is returned. *

* * @return text content for this element, or empty string if none */ public String getText() { if ((content == null) || (content.size() < 1) || (content.get(0) == null)) { return ""; } // If we hold only a String, return it directly if ((content.size() == 1) && (content.get(0) instanceof String)) { return (String)content.get(0); } // Else build String up StringBuffer textContent = new StringBuffer(); boolean hasText = false; Iterator i = content.iterator(); while (i.hasNext()) { Object obj = i.next(); if (obj instanceof String) { textContent.append((String)obj); hasText = true; } else if (obj instanceof CDATA) { textContent.append(((CDATA)obj).getText()); hasText = true; } } if (!hasText) { return ""; } else { return textContent.toString(); } } /** *

* This returns the textual content of this element with all * surrounding whitespace removed and internal whitespace normalized * to a single space. If no textual value exists for the element, * or if only whitespace exists, the empty string is * returned. *

* * @return normalized text content for this element, or empty string * if none */ public String getTextTrim() { String text = getText(); StringBuffer textContent = new StringBuffer(); StringTokenizer tokenizer = new StringTokenizer(text); while (tokenizer.hasMoreTokens()) { String str = tokenizer.nextToken(); textContent.append(str); if (tokenizer.hasMoreTokens()) { textContent.append(" "); // separator } } return textContent.toString(); } /** *

* This convenience method returns the textual content of the named * child element, or returns an empty String ("") * if the child has no textual content. However, if the child does * not exist, null is returned. *

* * @param name the name of the child * @return text content for the named child, or null if none */ public String getChildText(String name) { Element child = getChild(name); if (child == null) { return null; } else { return child.getText(); } } /** *

* This convenience method returns the trimmed textual content of the * named child element, or returns null if there's no such child. * See {@link #getTextTrim()} for details of text trimming. *

* * @param name the name of the child * @return trimmed text content for the named child, or null if none */ public String getChildTextTrim(String name) { Element child = getChild(name); if (child == null) { return null; } else { return child.getTextTrim(); } } /** *

* This convenience method returns the textual content of the named * child element, or returns null if there's no such child. *

* * @param name the name of the child * @param ns the namespace of the child * @return text content for the named child, or null if none */ public String getChildText(String name, Namespace ns) { Element child = getChild(name, ns); if (child == null) { return null; } else { return child.getText(); } } /** *

* This convenience method returns the trimmed textual content of the * named child element, or returns null if there's no such child. * See {@link #getTextTrim()} for * details of text trimming. *

* * @param name the name of the child * @param ns the namespace of the child * @return trimmed text content for the named child, or null if none */ public String getChildTextTrim(String name, Namespace ns) { Element child = getChild(name, ns); if (child == null) { return null; } else { return child.getTextTrim(); } } /** *

* This sets the content of the element to be the text given. * All existing text content and non-text context is removed. * If this element should have both textual content and nested * elements, use {@link #setMixedContent} instead. * Setting a null text value is equivalent to setting an empty string * value. *

* * @param text new content for the element * @return this element modified */ public Element setText(String text) { if (content != null) { content.clear(); } else { content = new LinkedList(); } if (text != null) { content.add(text); } return this; } /** *

* This will indicate whether the element has mixed content or not. * Mixed content is when an element contains both textual and * element data within it. When this evaluates to true, * {@link #getMixedContent} should be used for getting * element data. *

* * @return boolean - indicating whether there * is mixed content (both textual data and elements). */ public boolean hasMixedContent() { if (content == null) { return false; } Class prevClass = null; Iterator i = content.iterator(); while (i.hasNext()) { Object obj = i.next(); Class newClass = obj.getClass(); if (newClass != prevClass) { if (prevClass != null) { return true; } prevClass = newClass; } } return false; } /** *

* This returns the full content of the element as a List which * may contain objects of type String, Element, * Comment, ProcessingInstruction, * CDATA, and Entity. * When there is technically no mixed content and * all contents are of the same type, then all objects returned in the * List will be of the same type. The List returned is "live" and * modifications to it affect the element's actual contents. Whitespace * content is returned in its entirety. *

* * @return a List containing the mixed content of the * element: may contain String, * {@link Element}, {@link Comment}, * {@link ProcessingInstruction}, and * {@link Entity} objects. */ public List getMixedContent() { if (content == null) { content = new LinkedList(); } PartialList result = new PartialList(content, this); result.addAllPartial(content); return result; } /** *

* This sets the content of the element. The passed in List should * contain only objects of type String, Element, * Comment, ProcessingInstruction, * CDATA, and Entity. Passing a null or * empty List clears the existing content. In event of an exception * the original content will be unchanged and the items in the added * content will be unaltered. *

* * @return this element modified * @throws IllegalAddException if the List contains objects of * illegal types */ public Element setMixedContent(List mixedContent) { if (mixedContent == null) { return this; } // Save list with original content and create a new list LinkedList oldContent = content; content = new LinkedList(); RuntimeException ex = null; int itemsAdded = 0; try { for (Iterator i = mixedContent.iterator(); i.hasNext(); ) { Object obj = i.next(); if (obj instanceof Element) { addContent((Element)obj); } else if (obj instanceof String) { addContent((String)obj); } else if (obj instanceof Comment) { addContent((Comment)obj); } else if (obj instanceof ProcessingInstruction) { addContent((ProcessingInstruction)obj); } else if (obj instanceof CDATA) { addContent((CDATA)obj); } else if (obj instanceof Entity) { addContent((Entity)obj); } else { throw new IllegalAddException( "An Element may directly contain only objects of type " + "String, Element, Comment, CDATA, Entity, and " + "ProcessingInstruction: " + (obj == null ? "null" : obj.getClass().getName()) + " is not allowed"); } itemsAdded++; } } catch (RuntimeException e) { ex = e; } finally { if (ex != null) { // Restore the original state content = oldContent; // Unmodify all modified elements. DO NOT change any later // elements tho because they may already have parents! Iterator i = mixedContent.iterator(); while (itemsAdded-- > 0) { Object obj = i.next(); if (obj instanceof Element) { ((Element)obj).setParent(null); } else if (obj instanceof Comment) { ((Comment)obj).setParent(null); } else if (obj instanceof ProcessingInstruction) { ((ProcessingInstruction)obj).setParent(null); } else if (obj instanceof Entity) { ((Entity)obj).setParent(null); } } throw ex; } } // Remove parentage on the old content Iterator itr = oldContent.iterator(); while (itr.hasNext()) { Object obj = itr.next(); if (obj instanceof Element) { ((Element)obj).setParent(null); } else if (obj instanceof Comment) { ((Comment)obj).setParent(null); } else if (obj instanceof ProcessingInstruction) { ((ProcessingInstruction)obj).setParent(null); } else if (obj instanceof Entity) { ((Entity)obj).setParent(null); } } return this; } public boolean hasChildren() { if (content == null || content.size() == 0) { return false; } Iterator i = content.iterator(); while (i.hasNext()){ if (i.next() instanceof Element) { return true; } } return false; } /** *

* This returns a List of all the child elements * nested directly (one level deep) within this element, as * Element objects. If this target element has no nested * elements, an empty List is returned. The returned list is "live" * and changes to it affect the element's actual contents. *

*

* This performs no recursion, so elements nested two levels * deep would have to be obtained with: *

     * 
     *   Iterator itr = currentElement.getChildren().iterator();
     *   while (itr.hasNext()) {
     *     Element oneLevelDeep = (Element)nestedElements.next();
     *     List twoLevelsDeep = oneLevelDeep.getChildren();
     *     // Do something with these children
     *   }
     * 
     * 
*

* * @return list of child Element objects for this element */ public List getChildren() { if (content == null) { content = new LinkedList(); } PartialList elements = new PartialList(content, this); Iterator i = content.iterator(); while (i.hasNext()) { Object obj = i.next(); if (obj instanceof Element) { elements.addPartial(obj); } } return elements; } /** *

* This sets the content of the element to be the List of * Element objects within the supplied List. * All existing element and non-element content of the element is removed. * In event of an exception the children may be partially added. *

* * @param children List of Element objects to add * @return this element modified */ public Element setChildren(List children) { return setMixedContent(children); } /** *

* This returns a List of all the child elements * nested directly (one level deep) within this element with the given * local name and belonging to no namespace, returned as * Element objects. If this target element has no nested * elements with the given name outside a namespace, an empty List * is returned. The returned list is "live" * and changes to it affect the element's actual contents. *

*

* Please see the notes for {@link #getChildren} * for a code example. *

* * @param name local name for the children to match * @return all matching child elements */ public List getChildren(String name) { return getChildren(name, Namespace.NO_NAMESPACE); } /** *

* This returns a List of all the child elements * nested directly (one level deep) within this element with the given * local name and belonging to the given Namespace, returned as * Element objects. If this target element has no nested * elements with the given name in the given Namespace, an empty List * is returned. The returned list is "live" * and changes to it affect the element's actual contents. *

*

* Please see the notes for {@link #getChildren} * for a code example. *

* * @param name local name for the children to match * @param ns Namespace to search within * @return all matching child elements */ public List getChildren(String name, Namespace ns) { PartialList children = new PartialList(getChildren(), this); if (content != null) { String uri = ns.getURI(); Iterator i = content.iterator(); while (i.hasNext()) { Object obj = i.next(); if (obj instanceof Element) { Element element = (Element)obj; if ((element.getNamespaceURI().equals(uri)) && (element.getName().equals(name))) { children.addPartial(element); } } } } return children; } /** *

* This returns the first child element within this element with the * given local name and belonging to the given namespace. * If no elements exist for the specified name and namespace, null is * returned. *

* * @param name local name of child element to match * @param ns Namespace to search within * @return the first matching child element, or null if not found */ public Element getChild(String name, Namespace ns) { if (content == null) { return null; } String uri = ns.getURI(); Iterator i = content.iterator(); while (i.hasNext()) { Object obj = i.next(); if (obj instanceof Element) { Element element = (Element)obj; if ((element.getNamespaceURI().equals(uri)) && (element.getName().equals(name))) { return element; } } } // If we got here, none found return null; } /** *

* This returns the first child element within this element with the * given local name and belonging to no namespace. * If no elements exist for the specified name and namespace, null is * returned. *

* * @param name local name of child element to match * @return the first matching child element, or null if not found */ public Element getChild(String name) { return getChild(name, Namespace.NO_NAMESPACE); } /** *

* This adds text content to this element. It does not replace the * existing content as does setText(). *

* * @param text String to add * @return this element modified */ public Element addContent(String text) { if (content == null) { content = new LinkedList(); } if (content.size() > 0) { Object ob = content.getLast(); if (ob instanceof String) { text = (String)ob + text; content.removeLast(); } } content.add(text); return this; } /** *

* This adds element content to this element. *

* * @param element Element to add * @return this element modified */ public Element addContent(Element element) { if ( Element.testAncestors ) { if (element.isRootElement()) { throw new IllegalAddException(this, element, "The element already has an existing parent " + "(the document root)"); } else if (element.getParent() != null) { throw new IllegalAddException(this, element, "The element already has an existing parent \"" + element.getParent().getQualifiedName() + "\""); } else if (element == this) { throw new IllegalAddException(this, element, "The element cannot be added to itself"); } else if ( isAncestor(element) ) { throw new IllegalAddException(this, element, "The element cannot be added as a descendent of itself"); } } if (content == null) { content = new LinkedList(); } element.setParent(this); content.add(element); return this; } // Scan ancestry looking for an element private boolean isAncestor(Element e) { Element parent = getParent(); while (parent != null) { if (parent == e) { return true; } parent = parent.getParent(); } return false; } /** *

* This adds a processing instruction as content to this element. *

* * @param pi ProcessingInstruction to add * @return this element modified */ public Element addContent(ProcessingInstruction pi) { if (pi.getParent() != null) { throw new IllegalAddException(this, pi, "The PI already has an existing parent \"" + pi.getParent().getQualifiedName() + "\""); } else if (pi.getDocument() != null) { throw new IllegalAddException(this, pi, "The PI already has an existing parent " + "(the document root)"); } if (content == null) { content = new LinkedList(); } content.add(pi); pi.setParent(this); return this; } /** *

* This adds entity content to this element. *

* * @param entity Entity to add * @return this element modified */ public Element addContent(Entity entity) { if (entity.getParent() != null) { throw new IllegalAddException(this, entity, "The entity already has an existing parent \"" + entity.getParent().getQualifiedName() + "\""); } if (content == null) { content = new LinkedList(); } content.add(entity); entity.setParent(this); return this; } /** *

* This adds a CDATA section as content to this element. *

* * @param cdata CDATA to add * @return this element modified */ public Element addContent(CDATA cdata) { if (content == null) { content = new LinkedList(); } content.add(cdata); return this; } /** *

* This adds a comment as content to this element. *

* * @param comment Comment to add * @return this element modified */ public Element addContent(Comment comment) { if (comment.getParent() != null) { throw new IllegalAddException(this, comment, "The comment already has an existing parent \"" + comment.getParent().getQualifiedName() + "\""); } else if (comment.getDocument() != null) { throw new IllegalAddException(this, comment, "The comment already has an existing parent " + "(the document root)"); } if (content == null) { content = new LinkedList(); } content.add(comment); comment.setParent(this); return this; } /** *

* This removes the first child element (one level deep) with the * given local name and belonging to no namespace. * Returns true if a child was removed. *

* * @param name the name of child elements to remove * @return whether deletion occurred */ public boolean removeChild(String name) { return removeChild(name, Namespace.NO_NAMESPACE); } /** *

* This removes the first child element (one level deep) with the * given local name and belonging to the given namespace. * Returns true if a child was removed. *

* * @param name the name of child element to remove * @param ns Namespace to search within * @return whether deletion occurred */ public boolean removeChild(String name, Namespace ns) { if (content == null) { return false; } String uri = ns.getURI(); Iterator i = content.iterator(); while (i.hasNext()) { Object obj = i.next(); if (obj instanceof Element) { Element element = (Element)obj; if ((element.getNamespaceURI().equals(uri)) && (element.getName().equals(name))) { element.setParent(null); i.remove(); return true; } } } // If we got here, none found return false; } /** *

* This removes all child elements (one level deep) with the * given local name and belonging to no namespace. * Returns true if any were removed. *

* * @param name the name of child elements to remove * @return whether deletion occurred */ public boolean removeChildren(String name) { return removeChildren(name, Namespace.NO_NAMESPACE); } /** *

* This removes all child elements (one level deep) with the * given local name and belonging to the given namespace. * Returns true if any were removed. *

* * @param name the name of child elements to remove * @param ns Namespace to search within * @return whether deletion occurred */ public boolean removeChildren(String name, Namespace ns) { if (content == null) { return false; } String uri = ns.getURI(); boolean deletedSome = false; Iterator i = content.iterator(); while (i.hasNext()) { Object obj = i.next(); if (obj instanceof Element) { Element element = (Element)obj; if ((element.getNamespaceURI().equals(uri)) && (element.getName().equals(name))) { element.setParent(null); i.remove(); deletedSome = true; } } } return deletedSome; } /** *

* This removes all child elements. Returns true if any were removed. *

* * @return whether deletion occurred */ public boolean removeChildren() { boolean deletedSome = false; if (content != null) { Iterator i = content.iterator(); while (i.hasNext()) { Object obj = i.next(); if (obj instanceof Element) { Element element = (Element)obj; i.remove(); element.setParent(null); deletedSome = true; } } } return deletedSome; } /** *

* This returns the complete set of attributes for this element, as a * List of Attribute objects in no particular * order, or an empty list if there are none. * The returned list is "live" and changes to it affect the * element's actual attributes. *

* * @return attributes for the element */ public List getAttributes() { if (attributes == null) { attributes = new LinkedList(); } PartialList atts = new PartialList(attributes, this); atts.addAllPartial(attributes); return atts; } /** *

* This returns the attribute for this element with the given name * and within no namespace. *

* * @param name name of the attribute to return * @return attribute for the element */ public Attribute getAttribute(String name) { return getAttribute(name, Namespace.NO_NAMESPACE); } /** *

* This returns the attribute for this element with the given name * and within the given Namespace. *

* * @param name name of the attribute to return * @param ns Namespace to search within * @return attribute for the element */ public Attribute getAttribute(String name, Namespace ns) { if (attributes == null) { return null; } String uri = ns.getURI(); Iterator i = attributes.iterator(); while (i.hasNext()) { Attribute att = (Attribute)i.next(); if ((att.getNamespaceURI().equals(uri)) && (att.getName().equals(name))) { return att; } } // If we got here, nothing found return null; } /** *

* This returns the attribute value for the attribute with the given name * and within no namespace, null if there is no such attribute, and the * empty string if the attribute value is empty. *

* * @param name name of the attribute whose value to be returned * @return the named attribute's value, or null if no such attribute */ public String getAttributeValue(String name) { return getAttributeValue(name, Namespace.NO_NAMESPACE); } /** *

* This returns the attribute value for the attribute with the given name * and within the given Namespace, null if there is no such attribute, and * the empty string if the attribute value is empty. *

* * @param name name of the attribute whose valud is to be returned * @param ns Namespace to search within * @return the named attribute's value, or null if no such attribute */ public String getAttributeValue(String name, Namespace ns) { Attribute attrib = getAttribute(name, ns); if (attrib == null) { return null; } else { return attrib.getValue(); } } /** *

* This sets all the attributes for this element to be those * in the given List; all existing attributes are removed. *

* * @param attributes List of attributes to set * @return this element modified */ public Element setAttributes(List attributes) { // XXX Verify attributes are all parentless first this.attributes = attributes; return this; } /** *

* This sets an attribute value for this element. Any existing attribute * with the same name and namespace URI is removed. (TODO: Code the * replacement logic.) *

* * @param attribute Attribute to add * @return this element modified * @throws IllegalAddException if an attribute by the same name/namespace * already exists, if the attribute being added already has a parent, * or if the attribute namespace prefix collides with another namespace * prefix on the element */ public Element setAttribute(Attribute attribute) { if (getAttribute(attribute.getName(), attribute.getNamespace()) != null) { throw new IllegalAddException( this, attribute, "Duplicate attributes are not allowed"); } if (attribute.getParent() != null) { throw new IllegalAddException(this, attribute, "The attribute already has an existing parent \"" + attribute.getParent().getQualifiedName() + "\""); } // Verify the attribute's namespace prefix doesn't collide with // another attribute prefix or this element's prefix. // This is unfortunately pretty heavyweight but we don't need to do it // for attributes without a namespace, and a little testing shows no // real difference in build times anyway. String prefix = attribute.getNamespace().getPrefix(); if (!prefix.equals("")) { String uri = attribute.getNamespace().getURI(); if (prefix.equals(getNamespacePrefix()) && !uri.equals(getNamespaceURI())) { throw new IllegalAddException(this, attribute, "The attribute namespace prefix \"" + prefix + "\" collides with the element namespace prefix"); } if (additionalNamespaces != null && additionalNamespaces.size() > 0) { Iterator itr = additionalNamespaces.iterator(); while (itr.hasNext()) { Namespace ns = (Namespace) itr.next(); if (prefix.equals(ns.getPrefix()) && !uri.equals(ns.getURI())) { throw new IllegalAddException(this, attribute, "The attribute namespace prefix \"" + prefix + "\" collides with a namespace declared by the element"); } } } if (attributes != null && attributes.size() > 0) { Iterator itr = attributes.iterator(); while (itr.hasNext()) { Namespace ns = (Namespace) ((Attribute)itr.next()).getNamespace(); if (prefix.equals(ns.getPrefix()) && !uri.equals(ns.getURI())) { throw new IllegalAddException(this, attribute, "The attribute namespace prefix \"" + prefix + "\" collides with another attribute namespace on " + "the element"); } } } } if (attributes == null) { attributes = new LinkedList(); } attributes.add(attribute); attribute.setParent(this); return this; } /** *

* This adds an attribute to this element with the given name and value. * To add attributes in namespaces using addAttribute(Attribute). *

* * @param name name of the attribute to add * @param value value of the attribute to add * @return this element modified */ public Element addAttribute(String name, String value) { return addAttribute(new Attribute(name, value)); } /** *

* This removes the attribute with the given name and within the * given namespace URI. *

* * @param name name of attribute to remove * @param uri namespace URI of attribute to remove * @return whether the attribute was removed */ public boolean removeAttribute(String name, String uri) { Iterator i = attributes.iterator(); while (i.hasNext()) { Attribute att = (Attribute)i.next(); if ((att.getNamespaceURI().equals(uri)) && (att.getName().equals(name))) { i.remove(); att.setParent(null); return true; } } return false; } /** *

* This removes the attribute with the given name and within no * namespace. *

* * @param name name of attribute to remove * @return whether the attribute was removed */ public boolean removeAttribute(String name) { return removeAttribute(name, Namespace.NO_NAMESPACE); } /** *

* This removes the attribute with the given name and within the * given Namespace. *

* * @param name name of attribute to remove * @param ns namespace URI of attribute to remove * @return whether the attribute was removed */ public boolean removeAttribute(String name, Namespace ns) { if (attributes == null) { return false; } String uri = ns.getURI(); Iterator i = attributes.iterator(); while (i.hasNext()) { Attribute att = (Attribute)i.next(); if ((att.getNamespaceURI().equals(uri)) && (att.getName().equals(name))) { i.remove(); att.setParent(null); return true; } } return false; } /** *

* This returns a String representation of the * Element, suitable for debugging. If the XML * representation of the Element is desired, * {@link #getSerializedForm} should be used. *

* * @return String - information about the * Element */ public String toString() { StringBuffer stringForm = new StringBuffer(64) .append("[Element: <") .append(getQualifiedName()); String nsuri = getNamespaceURI(); if (!nsuri.equals("")) { stringForm .append(" [Namespace: ") .append(nsuri) .append("]"); } stringForm.append("/>]"); return stringForm.toString(); } /** *

* This will return the Element in XML format, * usable in an XML document. *

* * @return String - the serialized form of the * Element. */ public final String getSerializedForm() { throw new RuntimeException( "Element.getSerializedForm() is not yet implemented"); } /** *

* This tests for equality of this Element to the supplied * Object, explicitly using the == operator. *

* * @param ob Object to compare to * @return whether the elements are equal */ public final boolean equals(Object ob) { return (this == ob); } /** *

* This returns the hash code for this Element. *

* * @return inherited hash code */ public final int hashCode() { return super.hashCode(); } /** *

* This returns a deep clone of this element. * The new element is detached from its parent, and getParent() * on the clone will return null. *

* * @return the clone of this element */ public Object clone() { Element element = null; try { element = (Element) super.clone(); } catch (CloneNotSupportedException ce) { // Can't happen } // name and namespace are references to imutable objects // so super.clone() handles them ok // Reference to parent and document are copied by super.clone() // (Object.clone()) so we have to remove it element.parent = null; element.document = null; // Reference to content list and attribute lists are copyed by // super.clone() so we set it new lists if the original had lists element.content = (this.content == null) ? null : new LinkedList(); element.attributes = (this.attributes == null) ? null : new LinkedList(); // Cloning attributes if (attributes != null) { for (Iterator i = attributes.iterator(); i.hasNext(); ) { Attribute a = (Attribute)((Attribute)i.next()).clone(); a.setParent(element); element.attributes.add(a); } } // Cloning content // No need to clone CDATA or String since they're immutable if (content != null) { for (Iterator i = content.iterator(); i.hasNext(); ) { Object obj = i.next(); if (obj instanceof String) { element.content.add(obj); } else if (obj instanceof Element) { Element e = (Element)((Element)obj).clone(); e.setParent(element); element.content.add(e); } else if (obj instanceof Comment) { Comment c = (Comment)((Comment)obj).clone(); c.setParent(element); element.content.add(c); } else if (obj instanceof CDATA) { element.content.add(obj); } else if (obj instanceof ProcessingInstruction) { ProcessingInstruction p = (ProcessingInstruction) ((ProcessingInstruction)obj).clone(); element.content.add(p); } else if (obj instanceof Entity) { Entity e = (Entity)((Entity)obj).clone(); e.setParent(element); element.content.add(e); } } } // Handle additional namespaces if (additionalNamespaces != null) { element.additionalNamespaces = (LinkedList) additionalNamespaces.clone(); } return element; } // Support a custom Namespace serialization so no two namespace // object instances may exist for the same prefix/uri pair private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // We use writeObject() and not writeUTF() to minimize space // This allows for writing pointers to already written strings out.writeObject(namespace.getPrefix()); out.writeObject(namespace.getURI()); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); namespace = Namespace.getNamespace( (String)in.readObject(), (String)in.readObject()); } /** *

* This removes the specified Element. *

* * @param child Element to delete * @return whether deletion occurred */ public boolean removeContent(Element element) { if (content == null) { return false; } if (content.remove(element)) { element.setParent(null); return true; } else { return false; } } /** *

* This removes the specified ProcessingInstruction. *

* * @param child ProcessingInstruction to delete * @return whether deletion occurred */ public boolean removeContent(ProcessingInstruction pi) { if (content == null) { return false; } if (content.remove(pi)) { pi.setParent(null); return true; } else { return false; } } /** *

* This removes the specified Entity. *

* * @param child Entity to delete * @return whether deletion occurred */ public boolean removeContent(Entity entity) { if (content == null) { return false; } if (content.remove(entity)) { entity.setParent(null); return true; } else { return false; } } /** *

* This removes the specified Comment. *

* * @param comment Comment to delete * @return whether deletion occurred */ public boolean removeContent(Comment comment) { if (content == null) { return false; } if (content.remove(comment)) { comment.setParent(null); return true; } else { return false; } } /** *

* This creates a copy of this Element, with the new * name specified, and in the specified {@link Namespace}. *

* * @param name String name of new Element copy. * @param ns Namespace to put copy in. * @return Element copy of this Element. * * @deprecated Deprecated in beta7, use clone(), setName(), and * setNamespace() instead */ public Element getCopy(String name, Namespace ns) { Element clone = (Element)clone(); clone.namespace = ns; clone.name = name; return clone; } /** *

* This creates a copy of this Element, with the new * name specified, and in no namespace. *

* * @param name String name of new Element copy. * * @deprecated Deprecated in beta7, use clone() and setName() instead */ public Element getCopy(String name) { return getCopy(name, Namespace.NO_NAMESPACE); } /** *

* This adds an attribute to this element. Any existing attribute with * the same name and namespace URI is removed. (TODO: Code the * replacement logic.) *

* * @param attribute Attribute to add * @return this element modified * * @deprecated Deprecated in beta7, use setAttribute(Attribute) instead */ public Element addAttribute(Attribute attribute) { if (getAttribute(attribute.getName(), attribute.getNamespace()) != null) { throw new IllegalAddException( this, attribute, "Duplicate attributes are not allowed"); } if (attribute.getParent() != null) { throw new IllegalAddException(this, attribute, "The attribute already has an existing parent \"" + attribute.getParent().getQualifiedName() + "\""); } // XXX Should verify attribute ns prefix doesn't collide with // another attribute prefix or this element's prefix if (attributes == null) { attributes = new LinkedList(); } attributes.add(attribute); attribute.setParent(this); return this; } public static void performAncestryTests( boolean test ) { Element.testAncestors = test; } public static boolean isTestingAncestry() { return Element.testAncestors; } }