/*--
$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
* 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
* This will create a new
* This will create an
* This will create a new
* This will create a new
* This returns the (local) name of the
*
* This sets the (local) name of the
* This will return this
* This sets this
* This returns the namespace prefix
* of the
* This returns the URI mapped to this
* 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.
*
* This returns the full name of the
*
* 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.
*
* 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.
*
* This will return any namespace declarations on this element
* that exist, excluding the namespace of the element
* itself, which can be obtained through
*
* This will return the parent of this
* This will set the parent of this
* This detaches the element from its parent, or does nothing if the
* element has no parent.
*
* This returns a
* This sets the
* This retrieves the owning
* 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
* 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.
*
* This convenience method returns the textual content of the named
* child element, or returns an empty
* This convenience method returns the trimmed textual content of the
* named child element, or returns null if there's no such child.
* See
* This convenience method returns the textual content of the named
* child element, or returns null if there's no such child.
*
* This convenience method returns the trimmed textual content of the
* named child element, or returns null if there's no such child.
* See
* 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
* 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
* This returns the full content of the element as a List which
* may contain objects of type
* This sets the content of the element. The passed in List should
* contain only objects of type
* This returns a
* This performs no recursion, so elements nested two levels
* deep would have to be obtained with:
* 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.
* 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;
/**
* 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.
* 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.
* String name of element.
* @namespace Namespace to put element in.
*/
public Element(String name, Namespace namespace) {
setName(name);
setNamespace(namespace);
document = null;
}
/**
* Element in no
* {@link Namespace}.
* String name of element.
*/
public Element(String name) {
this(name, (Namespace) null);
}
/**
* 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).
* 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));
}
/**
* Element with
* the supplied (local) name, and specifies the prefix and URI
* of the {@link Namespace} the Element
* should be in.
* 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));
}
/**
* Element, without any
* namespace prefix, if one exists.
* String - element name.
*/
public String getName() {
return name;
}
/**
* Element.
* 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;
}
/**
* Element's
* {@link Namespace}.
* Namespace - Namespace object for this
* Element
*/
public Namespace getNamespace() {
return namespace;
}
/**
* Element's {@link Namespace}.
* If the provided namespace is null, the element will have no namespace.
* Element - the element modified.
*/
public Element setNamespace(Namespace namespace) {
if (namespace == null) {
namespace = Namespace.NO_NAMESPACE;
}
this.namespace = namespace;
return this;
}
/**
* Element, if
* one exists. Otherwise, an empty
* String is returned.
* String - namespace prefix.
*/
public String getNamespacePrefix() {
return namespace.getPrefix();
}
/**
* Element's
* prefix (or the default namespace if no prefix). If no
* mapping is found, an empty String is returned.
* String - namespace URI for this
* Element.
*/
public String getNamespaceURI() {
return namespace.getURI();
}
/**
* 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;
}
/**
* Element, in the form
* [namespacePrefix]:[localName]. If
* no namespace prefix exists for the
* Element, simply the
* local name is returned.
* String - full name of element.
*/
public String getQualifiedName() {
if (namespace.getPrefix().equals("")) {
return getName();
}
return new StringBuffer(namespace.getPrefix())
.append(":")
.append(name)
.toString();
}
/**
* 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);
}
/**
* Namespace to remove.
*/
public void removeNamespaceDeclaration(Namespace additionalNamespace) {
if (additionalNamespaces == null) {
return;
}
additionalNamespaces.remove(additionalNamespace);
}
/**
* {@link #getNamespace()}. If there are no additional
* declarations, this returns an empty list.
* List - the additional namespace declarations.
*/
public List getAdditionalNamespaces() {
if (additionalNamespaces == null) {
return Collections.EMPTY_LIST;
}
else {
return additionalNamespaces;
}
}
/**
* 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.
* Element.
*/
public Element getParent() {
return parent;
}
/**
* Element.
* Element to be new parent.
* @return Element - this Element modified.
*/
protected Element setParent(Element parent) {
this.parent = parent;
return this;
}
/**
* 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;
}
/**
* 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.
* boolean - whether this is a root element.
*/
public boolean isRootElement() {
return document != null;
}
/**
* {@link Document} parent of this element
* and makes it the root element.
* Document parent
* @return Document this Element modified
*/
protected Element setDocument(Document document) {
this.document = document;
return this;
}
/**
* {@link Document} for
* this Element, or null if not a currently a member of a
* {@link Document}.
* 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;
}
/**
* String ("") is returned.
* String ("")
* if the child has no textual content. However, if the child does
* not exist, null is returned.
* {@link #getTextTrim()} for details of text trimming.
* {@link #getTextTrim()} for
* details of text trimming.
* {@link #setMixedContent} instead.
* Setting a null text value is equivalent to setting an empty string
* value.
* true,
* {@link #getMixedContent} should be used for getting
* element data.
* 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;
}
/**
* 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.
* 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;
}
/**
* 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.
* 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.
*
*
*
* Iterator itr = currentElement.getChildren().iterator();
* while (itr.hasNext()) {
* Element oneLevelDeep = (Element)nestedElements.next();
* List twoLevelsDeep = oneLevelDeep.getChildren();
* // Do something with these children
* }
*
*
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.
*
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.
*
* 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.
*
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 nsNamespace 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().
*
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 elementElement 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 piProcessingInstruction 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 entityEntity 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 cdataCDATA 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 commentComment 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 nsNamespace 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 nsNamespace 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.
*
* 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 nsNamespace 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 nsNamespace 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.
*
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 attributeAttribute 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.
*
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.
*
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.
*
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.
*
* 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.
*
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.
*
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.
*
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.
*
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}.
*
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.
*
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 attributeAttribute 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; }
}