/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.webdav.dom;
import java.util.Enumeration;
import java.util.Vector;
import org.eclipse.webdav.Policy;
import org.eclipse.webdav.internal.utils.URLDecoder;
import org.eclipse.webdav.internal.utils.URLEncoder;
import org.w3c.dom.*;
import org.w3c.dom.CharacterData;
/**
* Abstract superclass of all element editors. An element editor is a
* convenience for manipulating DOM elements. Specific subclasses declared
* in this package correspond to the major DOM element types for the WebDAV
* protocol; e.g., the class <code>ResponseBody</code> corresponds to
* <DAV:response>.
* <p>
* Element editors contain no state of their own; all they they hang onto
* is a single DOM <code>Element</code>; all information is stored in the DOM
* itself. Thus element editors are really just lightweight, throw-away
* wrappers.</p>
* <p>
* Element editors assume that they are working with elements that are "up to
* spec". When an element editor encounters an element that is not how it
* should be, it throws a <code>MalformedElementException</code> (a checked
* exception). The editor does not provide any further diagnosis of what's
* wrong. Clients catching this exception will generally conclude that they're
* talking to a non-compliant (or malfunctioning) WebDAV server.</p>
* <p>
* Fully-capable element editors can be written using the DOM API. No access
* is required to any concrete implementation of the DOM. The element editors
* in this package all have this property.</p>
* <p>
* This class also provides rudimentary XML namespace support and element
* navigation and manipulation methods that are useful when writing element
* editors.</p>
* <p>
* <b>Note:</b> This class/interface is part of an interim API that is still under
* development and expected to change significantly before reaching stability.
* It is being made available at this early stage to solicit feedback from pioneering
* adopters on the understanding that any code that uses this API will almost
* certainly be broken (repeatedly) as the API evolves.
* </p>
*/
public abstract class ElementEditor {
// The root element upon which this editor is based.
// This field is never <code>null</code>.
// However, it cannot be assumed that this element conforms
// to the appropriate specification (the DOM can be changed
// from underneath its editor).
protected Element root;
// The official URI for WebDAV namespace.
protected static final String DAV_NS = "DAV:"; //$NON-NLS-1$
// The official prefix for XML namespace declarations,
// namely "xmlns" (note: no colon).
protected static final String XML_PREFIX = "xmlns"; //$NON-NLS-1$
// The official XML namespace prefix.
protected static final String XML_NS_PREFIX = "xml"; //$NON-NLS-1$
// The official XML namespace name.
protected static final String XML_NS_NAME = "http://www.w3.org/XML/1998/namespace"; //$NON-NLS-1$
/**
* Constructs a new element editor for the given element. The element
* must not be <code>null</code>.
*
* @param root the element to be edited
* @throws MalformedElementException if the element is not null
*/
protected ElementEditor(Element root) throws MalformedElementException {
Assert.isNotNull(root);
this.root = root;
}
/**
* Constructs a new element editor for the given element. The element
* must not be <code>null</code>.
*
* @param root the element to be edited
* @throws MalformedElementException if the element is not null
*/
protected ElementEditor(Element root, String expectedType) throws MalformedElementException {
Assert.isNotNull(root);
Assert.isNotNull(expectedType);
this.root = root;
ensureDAVElement(root, expectedType, Policy.bind("ensure.expectingAnElmt", expectedType)); //$NON-NLS-1$
}
/**
* <p>Creates a WebDAV element with the given name and adds it as a
* child of the given parent. Returns the child element.
* <p>Children are positioned in the order specified by the given names.
* If children with the same name as the child already exist, the child
* is inserted after the rightmost child. If firstToLast is true, the
* search for the child's position starts at the parent's first child,
* otherwise, the search starts at the parent's last child.
* <p>The parent must not be <code>null</code> and must be a WebDAV
* element. The child's name must not be <code>null</code>. The
* parent's valid child names must not be <code>null</code>, and must
* contain the name of the child.
*
* @param parent the parent to which the child is added
* @param name the name of the child which is created and added
* to the parent
* @param names the ordered collection of valid child names for
* the parent
* @param firstToLast a boolean specifying the direction to search for
* the child's position among the parent's children
* @return the child element that is created
*/
public static Element addChild(Element parent, String name, String[] names, boolean firstToLast) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(name);
Assert.isNotNull(names);
String nsPrefix = getNSPrefix(parent);
String tagName = nsPrefix == null ? name : nsPrefix + ":" + name; //$NON-NLS-1$
Element child = parent.getOwnerDocument().createElement(tagName);
addChild(parent, child, names, firstToLast);
return child;
}
/**
* <p>Creates a WebDAV element with the given name and adds it as a
* child of the given parent. In addition, a text node created from the
* given data is created and appended to the child. Returns the child
* element.
* <p>Children are positioned in the order specified by the given names.
* If children with the same name as the child already exist, the child
* is inserted after the rightmost child. If firstToLast is true, the
* search for the child's position starts at the parent's first child,
* otherwise, the search starts at the parent's last child.
* <p>The parent must not be <code>null</code> and must be a WebDAV
* element. The child's name and data must not be <code>null</code>.
* The parent's valid child names must not be <code>null</code>, and
* must contain the name of the child.
*
* @param parent the parent to which the child is added
* @param name the name of the child which is created and added
* to the parent
* @param data the data of the text node which is created and
* added to the child
* @param names the ordered collection of valid child names for
* the parent
* @param firstToLast a boolean specifying the direction to search for
* the child's position among the parent's children
* @return the child element that is created
*/
public static Element addChild(Element parent, String name, String data, String[] names, boolean firstToLast) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(name);
Assert.isNotNull(data);
Assert.isNotNull(names);
Element child = addChild(parent, name, names, firstToLast);
child.appendChild(child.getOwnerDocument().createTextNode(data));
return child;
}
/**
* Adds the given child element as a child of the given parent.
* <p>
* Children are positioned in the order specified by the given names.
* If children with the same name as the child already exist, the child
* is inserted after the rightmost child. If firstToLast is true, the
* search for the child's position starts at the parent's first child,
* otherwise, the search starts at the parent's last child.</p>
* <p>
* The parent must not be <code>null</code> and must be a WebDAV
* element. The child must not be null and its namespace prefix must
* resolve to the WebDAV namespace URL in the parent. The parent's valid
* child names must not be <code>null</code>, and must contain the name
* of the child.</p>
*
* @param parent the parent to which the child is added
* @param child the child which is added to the parent
* @param names the ordered collection of valid child names for
* the parent
* @param firstToLast a boolean specifying the direction to search for
* the child's position among the parent's children
*/
public static void addChild(Element parent, Element child, String[] names, boolean firstToLast) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(child);
Assert.isTrue(DAV_NS.equals(resolve(getNSPrefix(child), parent)));
Assert.isNotNull(names);
boolean found = false;
String name = getNSLocalName(child);
for (int i = 0; !found && i < names.length; ++i)
found = names[i].equals(name);
Assert.isTrue(found);
Node sibling = getChild(parent, name, names, firstToLast);
if (firstToLast) {
if (sibling == null)
parent.appendChild(child);
else {
Node lastTwin = null;
while (isDAVElement(sibling, name)) {
lastTwin = sibling;
sibling = getTwin((Element) sibling, firstToLast);
}
if (lastTwin == null)
parent.insertBefore(child, sibling);
else {
Node refChild = lastTwin.getNextSibling();
if (refChild == null)
parent.appendChild(child);
else
parent.insertBefore(child, refChild);
}
}
} else {
Node refChild = null;
if (sibling == null) {
refChild = parent.getFirstChild();
} else {
refChild = sibling.getNextSibling();
}
if (refChild == null) {
parent.appendChild(child);
} else {
parent.insertBefore(child, refChild);
}
}
}
/**
* Creates a WebDAV element with the given name and appends it as a
* child of the given parent. Returns the child element. The parent must
* not be <code>null</code> and must be a WebDAV element. The name of
* the child must not be <code>null</code>.
*
* @param parent the parent element to which the child is added
* @param name the name of the child element that is created and added
* as a child of the parent
* @return the child element that is created
*/
public static Element appendChild(Element parent, String name) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(name);
String nsPrefix = getNSPrefix(parent);
String tagName = nsPrefix == null ? name : nsPrefix + ":" + name; //$NON-NLS-1$
Element child = parent.getOwnerDocument().createElement(tagName);
parent.appendChild(child);
return child;
}
/**
* Creates a WebDAV element with the given name and appends it as a
* child of the given parent. In addition, a text node created from the
* given data is created and appended to the child. Returns the child
* element. The parent must not be <code>null</code> and must be a
* WebDAV element. The name of the child must not be <code>null</code>.
* The data must not be <code>null</code>.
*
* @param parent the parent element to which the child is added
* @param name the name of the child element that is created and added
* as a child of the parent
* @param data the data of the text node which is created and added to
* the child
* @return the child element that is created
*/
public static Element appendChild(Element parent, String name, String data) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(name);
Assert.isNotNull(data);
Element child = appendChild(parent, name);
child.appendChild(child.getOwnerDocument().createTextNode(data));
return child;
}
/**
* Returns a clone of the given node. The given document becomes the
* owner document of the clone.
*
* @param document the owner document of the clone
* @param node the node to clone
* @return a clone of the given node
*/
public static Node cloneNode(Document document, Node node) {
Node nodeClone = null;
switch (node.getNodeType()) {
case Node.ELEMENT_NODE :
{
nodeClone = document.createElement(((Element) node).getTagName());
NamedNodeMap namedNodeMap = node.getAttributes();
for (int i = 0; i < namedNodeMap.getLength(); ++i) {
Attr attr = (Attr) namedNodeMap.item(i);
Attr attrClone = document.createAttribute(attr.getName());
attrClone.setValue(attr.getValue());
((Element) nodeClone).setAttributeNode(attrClone);
}
}
break;
case Node.TEXT_NODE :
nodeClone = document.createTextNode(((CharacterData) node).getData());
break;
case Node.CDATA_SECTION_NODE :
nodeClone = document.createCDATASection(((CharacterData) node).getData());
break;
case Node.ENTITY_REFERENCE_NODE :
nodeClone = document.createEntityReference(node.getNodeName());
break;
case Node.PROCESSING_INSTRUCTION_NODE :
nodeClone = document.createProcessingInstruction(((ProcessingInstruction) node).getTarget(), ((ProcessingInstruction) node).getData());
break;
case Node.COMMENT_NODE :
nodeClone = document.createComment(((CharacterData) node).getData());
break;
case Node.DOCUMENT_FRAGMENT_NODE :
nodeClone = document.createDocumentFragment();
break;
case Node.DOCUMENT_NODE :
case Node.DOCUMENT_TYPE_NODE :
case Node.NOTATION_NODE :
case Node.ATTRIBUTE_NODE :
case Node.ENTITY_NODE :
Assert.isTrue(false, Policy.bind("assert.notSupported")); //$NON-NLS-1$
break;
default :
Assert.isTrue(false, Policy.bind("assert.unknownNodeType")); //$NON-NLS-1$
}
return nodeClone;
}
/**
* Creates a WebDAV element with the given name and adds it as the root
* of the given document. In addition, the WebDAV namespace is declared
* on the new element. Returns the new element. The document must not be
* <code>null</code> and must not already have a root element. The name
* of the element to be created must not be <code>null</code>.
*
* @param document the document in which the new element is rooted
* @param name the name of the element to be created
* @return the rooted element
*/
public static Element create(Document document, String name) {
Assert.isNotNull(document);
Assert.isTrue(document.getDocumentElement() == null);
Assert.isNotNull(name);
Element element = document.createElement(name);
declareNS(element, null, DAV_NS);
document.appendChild(element);
return element;
}
/**
* Adds a namespace declaration to the given element. If only the prefix
* is <code>null</code>, a default namespace is declared. If the prefix
* and the namespaceUrl are <code>null</code> the default namespace is
* removed. The element must not be <code>null</code>. If the
* namespaceUrl is <code>null</code>, the <code>prefix</code> must also
* be <code>null</code>.
*
* @param element the element to receive the namespace attribute
* @param prefix the prefix to be used (without a colon); for
* example, "D", or <code>null</code>
* @param namespaceUrl the URL of the namespace; for example, "DAV:", or
* <code>null</code>
*/
public static void declareNS(Element element, String prefix, String namespaceUrl) {
Assert.isNotNull(element);
Assert.isTrue(namespaceUrl != null || prefix == null && namespaceUrl == null);
String name = XML_PREFIX + (prefix == null ? "" : ":" + prefix); //$NON-NLS-1$ //$NON-NLS-2$
String value = namespaceUrl == null ? "" : namespaceUrl; //$NON-NLS-1$
element.setAttribute(name, value);
}
/**
* Decodes the given href from a form that is safe for transport.
*
* @param href the href to be decoded
*/
public static String decodeHref(String href) {
return URLDecoder.decode(href);
}
/**
* Encodes the given href to a form that is safe for transport.
*
* @param href the href to be encoded
*/
public static String encodeHref(String href) {
return URLEncoder.encode(href);
}
/**
* Ensures that the given value is <code>true</code>.
*
* @param message the message for the exception that is thrown if the
* given value is <code>false</code>
* @param value a boolean indicating whether or not a malformed
* element exception should be thrown
* @throws MalformedElementException with the given message if
* the given value is <code>false</code>
* @deprecated
*/
protected static void ensure(String message, boolean value) throws MalformedElementException {
if (!value)
throw new MalformedElementException(message);
}
/**
* Ensures that the given value is <code>true</code>.
*
* @param message the message for the exception that is thrown if the
* given value is <code>false</code>
* @param value a boolean indicating whether or not a malformed
* element exception should be thrown
* @throws MalformedElementException with the given message if
* the given value is <code>false</code>
*/
protected static void ensure(boolean value, String message) throws MalformedElementException {
if (!value)
throw new MalformedElementException(message);
}
/**
* Ensures that the given node is a WebDAV element with the given name,
* returning it as an <code>Element</code> if it is.
*
* @param message the message for the exception that is thrown if the
* node is not a WebDAV element with the given name
* @param node the node, or <code>null</code>
* @param name the expected local name of the WebDAV element
* @returns the node as an <code>Element</code>
* @throws MalformedElementException with the given message if it
* is not a WebDAV element with the given name
* @deprecated
*/
protected static Element ensureDAVElement(String message, Node node, String name) throws MalformedElementException {
Assert.isNotNull(name);
if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
throw new MalformedElementException(message);
Element element = (Element) node;
if (!name.equals(getNSLocalName(element)) || !DAV_NS.equals(getNSName(element)))
throw new MalformedElementException(message);
return element;
}
/**
* Ensures that the given node is a WebDAV element with the given name.
*
* @param node the node, or <code>null</code>
* @param name the expected local name of the WebDAV element
* @param message the message for the exception that is thrown if the
* node is not a WebDAV element with the given name
* @throws MalformedElementException with the given message if it
* is not a WebDAV element with the given name
*/
protected static void ensureDAVElement(Node node, String name, String message) throws MalformedElementException {
Assert.isNotNull(name);
if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
throw new MalformedElementException(message);
Element element = (Element) node;
if (!name.equals(getNSLocalName(element)) || !DAV_NS.equals(getNSName(element)))
throw new MalformedElementException(message);
}
/**
* Ensures that the given object is not <code>null</code>.
*
* @param message the message for the exception that is thrown if the
* object is <code>null</code>
* @param object the object, or <code>null</code>
* @throws MalformedElementException if the given object is
* <code>null</code>
*/
protected static void ensureNotNull(String message, Object object) throws MalformedElementException {
if (object == null)
throw new MalformedElementException(message);
}
/**
* Ensures that the given object is <code>null</code>.
*
* @param message the message for the exception that is thrown if the
* object is not <code>null</code>
* @param object the object, or <code>null</code>
* @throws MalformedElementException if the given object is not
* <code>null</code>
*/
protected static void ensureNull(String message, Object object) throws MalformedElementException {
if (object != null)
throw new MalformedElementException(message);
}
/**
* Ensures that the given node is a text node, returning it as a
* <code>Text</code> node if it is.
*
* @param message the message for the exception that is thrown if the
* node is not a <code>Text</code> node
* @param node the node, or <code>null</code>
* @returns the node as a <code>Text</code> node
* @exception MalformedElementException if the node is
* <code>null</code> or not a text node
*/
protected static Text ensureText(String message, Node node) throws MalformedElementException {
if (node == null || node.getNodeType() != Node.TEXT_NODE)
throw new MalformedElementException(message);
return (Text) node;
}
/**
* Clones the given element, and its subtrees, and sets it as the root
* of the given document. Returns the cloned element. The document must
* not have a root and must not be <code>null</code>. The element must
* not be <code>null</code>.
*
* @param document the document that will become the owner and parent
* of the cloned element
* @param element the element to be cloned
* @returns a clone of the element
* @throws MalformedElementException if an element exists in the
* subtree to be cloned whose namespace can't be
* resolved
*/
public static Element extractElement(Document document, Element element) throws MalformedElementException {
Assert.isNotNull(document);
Assert.isTrue(document.getDocumentElement() == null);
Assert.isNotNull(element);
return (Element) extractNode(document, element);
}
/**
* Clones the given node, and its subtrees, and sets it as the root of
* the given parent node. Returns the cloned node. The parent node and
* the node to be cloned must not be <code>null</code>.
*
* @param parent the node that will become the parent of the cloned
* node
* @param node the node to be cloned
* @returns a clone of the element
* @throws MalformedElementException if an element exists in the
* subtree to be cloned whose namespace can't be resolved
*/
public static Node extractNode(Node parent, Node node) throws MalformedElementException {
// Get a handle to the root of the parent node tree.
Document document;
if (parent.getNodeType() == Node.DOCUMENT_NODE)
document = (Document) parent;
else
document = parent.getOwnerDocument();
// Create a clone of the node owned by the document, and add to the parent.
Node nodeClone = cloneNode(document, node);
parent.appendChild(nodeClone);
// If the node is an Element
if (node.getNodeType() == Node.ELEMENT_NODE) {
// Figure out its namespace information.
String nsPrefix = getNSPrefix((Element) node);
String nsName = getNSName((Element) node);
// Is this namespace already defined on the clone? If not declare it.
String nsNameClone = resolve(nsPrefix, (Element) nodeClone);
if (nsName != nsNameClone && (nsName == null || !nsName.equals(nsNameClone)))
declareNS((Element) nodeClone, nsPrefix, nsName);
// Do the same namespace fix-up for each of the node's attributes.
NamedNodeMap nodeMap = nodeClone.getAttributes();
for (int i = 0; i < nodeMap.getLength(); ++i) {
Attr attr = (Attr) nodeMap.item(i);
nsPrefix = getNSPrefix(attr.getName());
if (nsPrefix != null && !nsPrefix.equals(XML_PREFIX)) {
nsName = resolve(nsPrefix, (Element) node);
nsNameClone = resolve(nsPrefix, (Element) nodeClone);
if (nsName != nsNameClone && (nsName == null || !nsName.equals(nsNameClone)))
declareNS((Element) nodeClone, nsPrefix, nsName);
}
}
}
// Recursively clone each of the node's children.
Node child = node.getFirstChild();
while (child != null) {
extractNode(nodeClone, child);
child = child.getNextSibling();
}
// Finished cloning this node.
return nodeClone;
}
/**
* Returns the first child of the given parent that is a WebDAV element
* with one of the given names, or <code>null</code> if no such child
* exists.
* <p>
* If firstToLast is true, the search for the child starts at the parent's
* first child, otherwise, the search starts at the parent's last child.
* The parent must not be <code>null</code> and must be a WebDAV element.
* The names of children to search for must not be <code>null</code>.</p>
*
* @param parent the parent of the child to search.
* @param names all possible names of the child to search for.
* @param firstToLast a boolean specifying the direction to search for
* the child among the parent's children.
* @return the specified child of the parent, or
* <code>null<code> if no such child exists.
*/
private static Element getChild(Element parent, String[] names, boolean firstToLast) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(names);
// Get the first candidate.
Node child = null;
if (firstToLast)
child = parent.getFirstChild();
else
child = parent.getLastChild();
// While there are children left to consider.
while (child != null) {
// See if the child name matches any being sought.
for (int i = 0; i < names.length; ++i)
if (isDAVElement(child, names[i]))
return (Element) child;
// Try the next child.
if (firstToLast)
child = child.getNextSibling();
else
child = child.getPreviousSibling();
}
// A matching child was not found.
return null;
}
/**
* <p>Returns the child WebDAV element of the given parent that is
* nearest in position to a WebDAV element with the given name, or
* <code>null</code> if no such child exists.
* <p>Children are expected to be in the order specified by the given
* names. If firstToLast is true, the search for the child starts at the
* parent's first child, otherwise, the search starts at the parent's
* last child.
* <p>The parent must not be <code>null</code> and must be a WebDAV
* element. The name of the child to search for must not be
* <code>null</code>. The parent's valid child names must not be
* <code>null</code>, and must contain the name of the child being
* searched for.
* <p>The returned child is as follows:
* <ul>
* <li>Searching first to last</li>
* <ul>
* <li>returns <code>null</code> if an element with the given name
* should appear as the last child</li>
* <li>returns the first occurring child if an element with the given
* name is a child</li>
* <li>returns a child if an element with the given name would appear
* before it</li>
* </ul>
* <li>Searching last to first</li>
* <ul>
* <li>returns <code>null</code> if an element with the given name
* would appear as the first child</li>
* <li>returns the last occurring child if an element with the given
* name is a child</li>
* <li>returns a child if an element with the given name would appear
* after it</li>
* </ul>
* </ul>
* @param parent the parent of the child being searched for
* @param name the name of the child being searched for
* @param names the ordered collection of valid child names for
* the parent
* @param firstToLast a boolean specifying the direction to search for
* the specified child among the parent's children
* @return a child of the parent, or <code>null</code>
*/
public static Element getChild(Element parent, String name, String[] names, boolean firstToLast) {
Assert.isNotNull(parent);
Assert.isNotNull(name);
Assert.isNotNull(names);
boolean found = false;
for (int i = 0; !found && i < names.length; ++i) {
found = names[i].equals(name);
}
Assert.isTrue(found);
int i;
Node child = null;
if (firstToLast) {
i = 0;
child = parent.getFirstChild();
} else {
i = names.length - 1;
child = parent.getLastChild();
}
while (child != null && !names[i].equals(name)) {
int mark = i;
while (!isDAVElement(child, names[i]) && !names[i].equals(name)) {
if (firstToLast) {
++i;
} else {
--i;
}
}
if (!names[i].equals(name)) {
if (firstToLast) {
child = child.getNextSibling();
} else {
child = child.getPreviousSibling();
}
} else if (!isDAVElement(child, names[i])) {
int pos = i;
found = false;
while (!found && (pos >= 0 && pos < names.length)) {
found = isDAVElement(child, names[pos]);
if (firstToLast) {
++pos;
} else {
--pos;
}
}
if (!found) {
i = mark;
if (firstToLast) {
child = child.getNextSibling();
} else {
child = child.getPreviousSibling();
}
}
}
}
return (Element) child;
}
/**
* Returns the first child element of the given parent element, or
* <code>null</code> if no such child exists. If firstToLast is true,
* the search for the child starts at the parent's first child,
* otherwise, the search starts at the parent's last child. The parent
* must not be <code>null</code>.
*
* @param parent the parent of the child element to search for
* @param firstToLast a boolean specifying the direction to search for
* the child element among the parent's children
* @return the child element, or <code>null</code> if no such
* child exists
*/
public static Element getChildElement(Element parent, boolean firstToLast) {
Assert.isNotNull(parent);
Node child = null;
if (firstToLast)
child = parent.getFirstChild();
else
child = parent.getLastChild();
while (child != null && !isElement(child)) {
if (firstToLast)
child = child.getNextSibling();
else
child = child.getPreviousSibling();
}
return (Element) child;
}
/**
* Returns the data of the first child text node of the first WebDAV
* child with the given name of the given parent, or the empty
* <code>String</code> if no such child text node exists, or
* <code>null</code> if no such child exists. If firstToLast is true, the
* search for the child starts at the parent's first child, otherwise,
* the search starts at the parent's last child. The parent must not be
* <code>null</code> and must be a WebDAV element. The name of the child
* must not be <code>null</code>.
*
* @param parent the parent of the child with the child text node
* to search for
* @param name the name of the child with the child text node to
* search for
* @param firstToLast a boolean specifying the direction to search for
* the specified child among the parent's children
* @return the data of the child's child text node, or the
* empty <code>String</code> if no such child exists,
* or <code>null</code> if no such child exists
*/
public static String getChildText(Element parent, String name, boolean firstToLast) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(name);
Element child;
if (firstToLast)
child = getFirstChild(parent, name);
else
child = getLastChild(parent, name);
if (child != null)
return getText(child, firstToLast);
return null;
}
/**
* Returns the first WebDAV child of the given parent, or
* <code>null</code> if no such child exists. The parent must not be
* <code>null</code> and must be a WebDAV element.
*
* @param parent the parent of the child to search for
* @return the first WebDAV child of the parent, or null
*/
public static Element getDAVChild(Element parent) {
Assert.isTrue(isDAVElement(parent));
Node child = parent.getFirstChild();
while (child != null && !isDAVElement(child))
child = child.getNextSibling();
return (Element) child;
}
/**
* Returns the root element upon which this editor is based. It cannot
* be assumed that this element necessarily conforms to the appropriate
* specification.
*
* @return the root element upon which this editor is based
*/
public Element getElement() {
return root;
}
/**
* Returns the first child of the given parent that is a WebDAV element
* with one of the given names, or <code>null</code> if no such child
* exists.
* <p>
* The search for the child starts at the parent's first child.
* The parent must not be <code>null</code> and must be a WebDAV element.
* The names of children to search for must not be <code>null</code>.</p>
*
* @param parent the parent of the child to search.
* @param names all possible names of the child to search for.
* @return the specified child of the parent, or
* <code>null<code> if no such child exists.
*/
public static Element getFirstChild(Element parent, String[] names) {
return getChild(parent, names, true);
}
/**
* Returns the first child of the given parent that is a WebDAV element
* with the given name, or <code>null</code> if no such child exists.
* <p>
* The search for the child starts at the parent's first child.
* The parent must not be <code>null</code> and must be a DAV: namespace
* element. The name of the child to search for must not be
* <code>null</code>.
*
* @param parent the parent of the child to search.
* @param name the name of the child to search for.
* @return the specified child of the parent, or <code>null
* </code> if no such child exists.
*/
public static Element getFirstChild(Element parent, String name) {
Assert.isNotNull(name);
return getChild(parent, new String[] {name}, true);
}
/**
* Returns the data of the given parent's first text node, or the empty
* <code>String</code> if no such text node exists. The search for the
* text node starts at the parent's first child. The parent must not be
* <code>null</code>.
*
* @param parent the parent of the text node to search for
* @return the data of the text node being searched for, or
* the empty <code>String</code> if no such text node
* exists
*/
public static String getFirstText(Element parent) {
Assert.isNotNull(parent);
Node child = parent.getFirstChild();
while (child != null && !isText(child))
child = child.getNextSibling();
if (child == null)
return ""; //$NON-NLS-1$
return ((Text) child).getData();
}
/**
* Returns the last child of the given parent that is a WebDAV element
* with the given name, or <code>null</code> if no such child exists.
* <p>
* The search starts at the parent's last child.
* The parent must not be <code>null</code> and must be a DAV: namespace
* element. The name of the child to search for must not be
* <code>null</code>.
*
* @param parent the parent of the child to search.
* @param name the name of the child to search for.
* @return the specified child of the parent, or <code>null
* </code> if no such child exists.
*/
public static Element getLastChild(Element parent, String name) {
Assert.isNotNull(name);
return getChild(parent, new String[] {name}, false);
}
/**
* Returns the given element's namespace declarations. The element must
* not be <code>null</code>.
*
* @param element the element whose namespace declarations are returned
* @return the element's namespace declarations
*/
public static Namespaces getNamespaces(Element element) {
Assert.isNotNull(element);
Node parent = element.getParentNode();
while (parent != null && !isElement(parent))
parent = parent.getParentNode();
Namespaces namespaces = null;
if (parent != null)
namespaces = getNamespaces((Element) parent);
return getNamespaces(element, namespaces, false);
}
/**
* Returns the given element's namespace declarations. The given
* namespace declarations should be the element's parent's, or
* <code>null</code> if the element has no parent. If
* removeDuplicateNSDeclarations is <code>true</code>, duplicate
* namespace declarations are removed from the element's attributes. The
* element must not be <code>null</code>.
*
* @param element the element whose namespace declaration is returned
* @param namespaces the namespace declarations for the element's
* parent's or <code>null</code> if it has no parent
* @param removeDuplicateNSDeclarations a boolean indicating whether
* duplicate namespace declarations should be removed
* from the element's attributes
* @return the given element's namespace declarations
*/
protected static Namespaces getNamespaces(Element element, Namespaces namespaces, boolean removeDuplicateNSDeclarations) {
// Create a container to hold the new namespace definitions.
Namespaces newNamespaces = null;
if (namespaces == null)
newNamespaces = new Namespaces();
else
newNamespaces = new Namespaces(namespaces);
Vector oldAttributes = new Vector();
// For each attribute on the given element.
NamedNodeMap nodeMap = element.getAttributes();
for (int i = 0; i < nodeMap.getLength(); ++i) {
Attr attr = (Attr) nodeMap.item(i);
// Is it a name space declaration?
String name = attr.getName();
if (name.startsWith(XML_PREFIX)) {
String nsName = attr.getValue();
// Is it setting or clearing the default namespace?
// (i.e. has no prefix part)
if (name.length() == XML_PREFIX.length()) {
if (nsName.equals("")) //$NON-NLS-1$
newNamespaces.setDefaultNSName(null);
else
newNamespaces.setDefaultNSName(nsName);
} else if (name.charAt(XML_PREFIX.length()) == ':') {
// It is a namespace declaration.
String nsPrefix = name.substring(XML_PREFIX.length() + 1);
if (nsPrefix.length() > 0 && nsName.length() > 0) {
// Ensure it is in the new namespaces list.
newNamespaces.putNSName(nsPrefix, nsName);
boolean prefixExists = newNamespaces.getNSPrefix(nsName) != null;
if (!prefixExists)
newNamespaces.putNSPrefix(nsName, nsPrefix);
// If it is due for removal, rememebr it in the oldAttributes list.
if (removeDuplicateNSDeclarations && (prefixExists || nsName.equals(newNamespaces.getDefaultNSName())))
oldAttributes.addElement(attr);
}
}
}
}
// Remove all the duplicates on the given element.
Enumeration e = oldAttributes.elements();
while (e.hasMoreElements())
element.removeAttributeNode((Attr) e.nextElement());
// Answer the new list of namespaces for this element.
return newNamespaces;
}
/**
* Returns the next sibling element of the given element, or
* <code>null</code> if no such sibling exists. Only the sibling's
* next children are searched. The element must not be <code>null</code>.
*
* @param element the element whose sibling element is being
* searched for
* @return the sibling element, or <code>null</code>
*/
public static Element getNextSibling(Element element) {
Assert.isNotNull(element);
Node sibling = element;
do {
sibling = sibling.getNextSibling();
} while (sibling != null && !isElement(sibling));
return (Element) sibling;
}
/**
* Returns the first WebDAV sibling of the given element that has one of
* the given names, or <code>null</code> if no such sibling exists. Only
* the sibling's next children (not the previous children) are searched.
* The element must not be <code>null</code> and must be a WebDAV element.
* The possible names of the sibling to search for must not be
* <code>null</code>.
*
* @param element the element whose sibling is being searched for
* @param names the possible names of the sibling being search for
* @return the sibling with the specified name, or
* <code>null</code>
*/
public static Element getNextSibling(Element element, String[] names) {
Assert.isTrue(isDAVElement(element));
Assert.isNotNull(names);
Node sibling = element.getNextSibling();
while (sibling != null) {
for (int i = 0; i < names.length; ++i)
if (isDAVElement(sibling, names[i]))
return (Element) sibling;
sibling = sibling.getNextSibling();
}
return null;
}
/**
* Returns the next WebDAV sibling of the given element that has the
* given name, or <code>null</code> if no such sibling exists. Only
* the sibling's next children are searched. The element must not be
* <code>null</code> and must be a WebDAV element. The name of the
* sibling to search for must not be <code>null</code>.
*
* @param element the element whose sibling is being searched for
* @param name the name of the sibling being search for
* @return the sibling with the given name, or
* <code>null</code>
*/
public static Element getNextSibling(Element element, String name) {
return getNextSibling(element, new String[] {name});
}
/**
* Returns the local part of the given name, or <code>null</code> if its
* name has no local part. The name must not be <code>null</code>.
* <table>
* <caption>Example</caption>
* <tr>
* <th>name</th>
* <th>local name</th>
* <tr>
* <td>D:foo</td>
* <td>foo</td>
* <tr>
* <td>foo</td>
* <td>foo</td>
* <tr>
* <td>D:</td>
* <td>null</td>
* <tr>
* <td>:foo</td>
* <td>foo</td>
* <tr>
* <td>:</td>
* <td>null</td>
* </table>
*
* @param name the name whose local part is returned
* @return the name's local part, or <code>null</code>
*/
public static String getNSLocalName(String name) {
Assert.isNotNull(name);
int p = name.lastIndexOf(':');
if (p == -1)
return name;
if (p == name.length() - 1)
return null;
return name.substring(p + 1);
}
/**
* Returns the local part of the name of the given element, or
* <code>null</code> if its name has no local part. The element must not
* be <code>null</code>.
* <table>
* <caption>Example</caption>
* <tr>
* <th>tag name</th>
* <th>local name</th>
* <tr>
* <td>D:foo</td>
* <td>foo</td>
* <tr>
* <td>foo</td>
* <td>foo</td>
* <tr>
* <td>D:</td>
* <td>null</td>
* <tr>
* <td>:foo</td>
* <td>foo</td>
* <tr>
* <td>:</td>
* <td>null</td>
* </table>
*
* @param element the element whose local name is returned
* @return the element's local name, or <code>null</code>
*/
public static String getNSLocalName(Element element) {
Assert.isNotNull(element);
return getNSLocalName(element.getTagName());
}
/**
* Returns the URL of the given element's namespace, or
* <code>null</code> if it has no namespace. The element must not be
* <code>null</code>.
*
* @param element the element whose namespace is returned
* @return the namespace of the element, or <code>null</code>
* @throws MalformedElementException if the name of the given
* element has a namespace prefix which could not be
* resolved
*/
public static String getNSName(Element element) throws MalformedElementException {
Assert.isNotNull(element);
String nsPrefix = getNSPrefix(element);
String nsName = resolve(nsPrefix, element);
if (nsPrefix != null && nsName == null)
throw new MalformedElementException(Policy.bind("exception.namespacePrefixNotResolved", nsPrefix)); //$NON-NLS-1$
return nsName;
}
/**
* Returns the namespace prefix part of the given name, or
* <code>null</code> if its name has no prefix. The name must not be
* <code>null</code>.
* <table>
* <caption>Example</caption>
* <tr>
* <th>name</th>
* <th>namespace prefix</th>
* <tr>
* <td>D:foo</td>
* <td>D</td>
* <tr>
* <td>foo</td>
* <td>null</td>
* <tr>
* <td>D:</td>
* <td>D</td>
* <tr>
* <td>:foo</td>
* <td>null</td>
* <tr>
* <td>:</td>
* <td>null</td>
* </table>
*
* @param name the name whose namespace prefix is returned
* @return the name's namespace prefix, or <code>null</code>
*/
public static String getNSPrefix(String name) {
Assert.isNotNull(name);
int p = name.lastIndexOf(':');
if (p <= 0)
return null;
return name.substring(0, p);
}
/**
* Returns the namespace prefix part of the name of the given element,
* or <code>null</code> if its name has no prefix. The element must not
* be <code>null</code>.
* <table>
* <caption>Example</caption>
* <tr>
* <th>tag name</th>
* <th>namespace prefix</th>
* <tr>
* <td>D:foo</td>
* <td>D</td>
* <tr>
* <td>foo</td>
* <td>null</td>
* <tr>
* <td>D:</td>
* <td>D</td>
* <tr>
* <td>:foo</td>
* <td>null</td>
* <tr>
* <td>:</td>
* <td>null</td>
* </table>
*
* @param element the element whose namespace prefix is returned
* @return the element's namespace prefix, or <code>null</code>
*/
public static String getNSPrefix(Element element) {
Assert.isNotNull(element);
return getNSPrefix(element.getTagName());
}
/**
* Returns a qualified name that is formed from the given element's
* namespace name and namespace local name. The qualified name's
* qualifier is the element's namespace name and the qualified name's
* local name is the element's local name. The element must not be
* <code>null</code>.
*
* @param element the element whose qualified name is returned
* @return the qualified name that is formed from the given
* element's namespace name and namespace local name
* @exception MalformedElementException if the name of the element has a
* namespace prefix which could not be resolved, or the
* element does not have a local name
*/
public static QualifiedName getQualifiedName(Element element) throws MalformedElementException {
Assert.isNotNull(element);
String nsName = getNSName(element);
String nsLocalName = getNSLocalName(element);
if (nsLocalName == null)
throw new MalformedElementException(Policy.bind("exception.noLocalNameForElmt")); //$NON-NLS-1$
return new QualifiedNameImpl(nsName, nsLocalName);
}
/**
* Returns the first WebDAV sibling of the given element that has the
* given name, or <code>null</code> if no such sibling exists. If
* firstToLast is true, only the sibling's next children are searched,
* otherwise, only the siblings previous children are searched. The
* element must not be <code>null</code> and must be a WebDAV element.
* The name of the sibling to search for must not be <code>null</code>.
*
* @param element the element whose sibling is being searched for
* @param name the name of the sibling being search for
* @param firstToLast a boolean specifying the direction to search for
* the sibling among the element's siblings
* @return the sibling with the given name, or
* <code>null</code>
* @deprecated
*/
public static Element getSibling(Element element, String name, boolean firstToLast) {
Assert.isTrue(isDAVElement(element));
Assert.isNotNull(name);
Node sibling = element;
do {
if (firstToLast)
sibling = sibling.getNextSibling();
else
sibling = sibling.getPreviousSibling();
} while (sibling != null && !isDAVElement(sibling, name));
return (Element) sibling;
}
/**
* Returns the data of the given parent's first text node, or the empty
* <code>String</code> if no such text node exists. If firstToLast is
* true, the search for the text node starts at the parent's first
* child, otherwise, the search starts at the parent's last child. The
* parent must not be <code>null</code>.
*
* @param parent the parent of the text node to search for
* @param firstToLast a boolean specifying the direction to search for
* the text node among the parent's children
* @return the data of the text node being searched for, or
* the empty <code>String</code> if no such text node
* exists
* @deprecated
*/
public static String getText(Element parent, boolean firstToLast) {
Assert.isNotNull(parent);
Node child = null;
if (firstToLast)
child = parent.getFirstChild();
else
child = parent.getLastChild();
while (child != null && !isText(child)) {
if (firstToLast)
child = child.getNextSibling();
else
child = child.getPreviousSibling();
}
if (child != null)
return ((Text) child).getData();
return ""; //$NON-NLS-1$
}
/**
* Returns the first WebDAV sibling of the given element that has the
* same name as the element, or <code>null</code> if no such sibling
* exists. If firstToLast is true, only the sibling's next children are
* searched, otherwise, only the siblings previous children are
* searched. The element must not be <code>null</code> and must be a
* WebDAV element.
*
* @param element the element whose sibling is being searched for
* @param firstToLast a boolean specifying the direction to search for
* the sibling among the element's siblings
* @return the sibling with the same name as the given
* element, or <code>null</code> if no such element
* exists
*/
public static Element getTwin(Element element, boolean firstToLast) {
Assert.isTrue(isDAVElement(element));
String name = getNSLocalName(element);
return getSibling(element, name, firstToLast);
}
public static boolean hasChild(Element parent, QualifiedName childName) throws MalformedElementException {
// Get the first candidate.
Node child = parent.getFirstChild();
// While there are children left to consider.
while (child != null) {
if (child instanceof Element) {
QualifiedName name = getQualifiedName((Element) child);
if (name.equals(childName))
return true;
}
// Try the next child.
child = child.getNextSibling();
}
// A matching child was not found.
return false;
}
/**
* Creates a WebDAV element with the given name and inserts it before
* the given sibling. Returns the new sibling. The sibling must not be
* <code>null</code> and must be a WebDAV element. The name of the
* new sibling must not be <code>null</code>.
*
* @param sibling the existing sibling element
* @param name the name of the new sibling element that is created
* and inserted before the existing sibling element
* @return the new sibling element that is created
*/
public static Element insertBefore(Element sibling, String name) {
Assert.isTrue(isDAVElement(sibling));
Assert.isNotNull(name);
String nsPrefix = getNSPrefix(sibling);
String tagName = nsPrefix == null ? name : nsPrefix + ":" + name; //$NON-NLS-1$
Element newSibling = sibling.getOwnerDocument().createElement(tagName);
sibling.getParentNode().insertBefore(newSibling, sibling);
return newSibling;
}
/**
* Creates a WebDAV element with the given name and inserts it before
* the given sibling. In addition, a text node created from the given
* data and appended to the new sibling. Returns the new sibling. The
* sibling must not be <code>null</code> and must be a WebDAV element.
* The name of the new sibling must not be <code>null</code>. The data
* must not be <code>null</code>.
*
* @param sibling the existing sibling element
* @param name the name of the sibling element that is created and
* inserted before the existing sibling element
* @param data the data of the text node which is created and
* appended to the new sibling
* @return the new sibling element that is created
*/
public static Element insertBefore(Element sibling, String name, String data) {
Assert.isTrue(isDAVElement(sibling));
Assert.isNotNull(name);
Assert.isNotNull(data);
Element child = insertBefore(sibling, name);
child.appendChild(child.getOwnerDocument().createTextNode(data));
return child;
}
/**
* Returns a boolean indicating whether or not the given node is a
* WebDAV element. The node may be <code>null</code> in which case
* <code>false</code> is returned.
*
* @param node a node, or <code>null</code>
* @return a boolean indicating whether or not the given node is a
* WebDAV element
*/
public static boolean isDAVElement(Node node) {
if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
return false;
try {
if (!DAV_NS.equals(getNSName((Element) node)))
return false;
} catch (MalformedElementException e) {
return false;
}
return true;
}
/**
* Returns a boolean indicating whether or not the given node is a
* WebDAV element with the given name. The node may be <code>null</code>
* in which case <code>false</code> is returned. The name must not be
* <code>null</code>.
*
* @param node a node, or <code>null</code>
* @return a boolean indicating whether or not the given node is a
* WebDAV element with the given name
*/
public static boolean isDAVElement(Node node, String name) {
Assert.isNotNull(name);
if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
return false;
try {
Element element = (Element) node;
if (!name.equals(getNSLocalName(element)) || !DAV_NS.equals(getNSName(element))) {
return false;
}
} catch (MalformedElementException e) {
return false;
}
return true;
}
/**
* Returns a boolean indicating whether or not the given node is an
* element. The node may be <code>null</code> in which case
* <code>false</code> is returned.
*
* @param node a node, or <code>null</code>
* @return a boolean indicating whether or not the given node is an
* element
*/
public static boolean isElement(Node node) {
if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
return false;
return true;
}
/**
* Returns a boolean indicating whether or not the given node is a text
* node. The node may be <code>null</code> in which case
* <code>false</code> is returned.
*
* @param node a node, or <code>null</code>
* @return a boolean indicating whether or not the given node is a
* text node
*/
public static boolean isText(Node node) {
return (node != null) && (node.getNodeType() == Node.TEXT_NODE);
}
public static void makeNSStandalone(Element element) {
Assert.isTrue(false, Policy.bind("assert.notImplemented")); //$NON-NLS-1$
}
/**
* Removes redundant namespace declarations from this element and all
* its children to maximum depth. The element must not be
* <code>null</code>.
*
* @param element the element whose namespace declarations are to be
* reduced
* @return the element, or a new element with an equivalent
* qualified name but whose tag name has changed
* @throws MalformedElementException if the name of the given
* element, or one of its attributes, has a namespace
* prefix which could not be resolved
*/
public static Element reduceNS(Element element) throws MalformedElementException {
return (Element) reduceNS(element, null);
}
/**
* Removes redundant namespace declarations from the given node and all
* its children to maximum depth. The node must not be
* <code>null</code>.
*
* @param node the node whose namespace declarations are to be reduced
* @return the node, or a new node (element) with an equivalent
* qualified name but whose tag name has changed
* @throws MalformedElementException if the name of the given node
* (element), or one of its attributes, has a namespace
* prefix which could not be resolved
*/
public static Node reduceNS(Node node, Namespaces parentNamespaces) throws MalformedElementException {
Namespaces namespaces = parentNamespaces;
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
namespaces = getNamespaces(element, parentNamespaces, false);
String nsPrefix = getNSPrefix(element);
String nsLocalName = getNSLocalName(element);
if (nsPrefix != null) {
String nsName = namespaces.getNSName(nsPrefix);
ensureNotNull(Policy.bind("ensure.missingNamespaceForPrefix", nsPrefix), nsName); //$NON-NLS-1$
String tagName = null;
if (nsName.equals(namespaces.getDefaultNSName())) {
tagName = nsLocalName;
} else {
tagName = namespaces.getNSPrefix(nsName) + ":" + nsLocalName; //$NON-NLS-1$
}
if (!tagName.equals(element.getTagName())) {
Document document = element.getOwnerDocument();
Element newElement = document.createElement(tagName);
NamedNodeMap nodeMap = element.getAttributes();
for (int i = 0; i < nodeMap.getLength(); ++i) {
Attr attr = (Attr) nodeMap.item(i);
newElement.setAttribute(attr.getName(), attr.getValue());
}
Node child = element.getFirstChild();
while (child != null) {
element.removeChild(child);
newElement.appendChild(child);
child = element.getFirstChild();
}
element.getParentNode().replaceChild(newElement, element);
element = newElement;
}
}
Vector oldAttributes = new Vector();
Vector newAttributes = new Vector();
NamedNodeMap nodeMap = element.getAttributes();
for (int i = 0; i < nodeMap.getLength(); ++i) {
Attr attr = (Attr) nodeMap.item(i);
String name = attr.getName();
String value = attr.getValue();
String newName = name;
nsPrefix = getNSPrefix(name);
nsLocalName = getNSLocalName(name);
if (nsPrefix != null && !nsPrefix.equals(XML_PREFIX)) {
String nsName = namespaces.getNSName(nsPrefix);
ensureNotNull(Policy.bind("ensure.missingNamespaceForPrefix", nsPrefix), nsName); //$NON-NLS-1$
String newNSPrefix = namespaces.getNSPrefix(nsName);
if (!newNSPrefix.equals(nsPrefix)) {
newName = newNSPrefix + ":" + nsLocalName; //$NON-NLS-1$
}
}
boolean newAttribute = true;
if (parentNamespaces != null) {
if (nsPrefix == null && XML_PREFIX.equals(nsLocalName)) {
if (value.equals(parentNamespaces.getDefaultNSName())) {
newAttribute = false;
}
}
if (nsPrefix != null && XML_PREFIX.equals(nsPrefix)) {
if (parentNamespaces.getNSPrefix(value) != null) {
newAttribute = false;
}
}
}
oldAttributes.addElement(attr);
if (newAttribute) {
newAttributes.addElement(new String[] {newName, value});
}
}
Enumeration oldAttrs = oldAttributes.elements();
while (oldAttrs.hasMoreElements()) {
element.removeAttributeNode((Attr) oldAttrs.nextElement());
}
Enumeration newAttrs = newAttributes.elements();
while (newAttrs.hasMoreElements()) {
String[] newAttr = (String[]) newAttrs.nextElement();
element.setAttribute(newAttr[0], newAttr[1]);
}
node = element;
}
Node child = node.getFirstChild();
while (child != null) {
child = reduceNS(child, namespaces);
child = child.getNextSibling();
}
return node;
}
/**
* Resolves the given namespace prefix in the namespace of the given
* element. If the given prefix is <code>null</code>, the default
* namespace is resolved. Returns the URL of the namespace, or
* <code>null</code> if the prefix could not be resolved.
*
* @param prefix the namespace prefix to be resolved, or
* <code>null<code> for the default namespace
* @param element the element supplying the namespace
* @return the URL of the namespace, or <code>null</code> if the
* prefix could not be resolved
*/
public static String resolve(String prefix, Element element) {
Assert.isNotNull(element);
/* The prefix xml is by definition bound to the namespace name
* <code>XML_NS_NAME</code>.
*/
if (XML_NS_PREFIX.equals(prefix)) {
return XML_NS_NAME;
}
/* Search from given element up parent chain to root (document)
* looking for a XML namespace declaration (represented as
* an element attribute with a name beginning in
* XML_PREFIX ("xmlns")).
*/
Node current = element;
do {
NamedNodeMap attrs = current.getAttributes();
int n = attrs.getLength();
for (int i = 0; i < n; i++) {
Attr attr = (Attr) attrs.item(i);
String name = attr.getName();
if (name.startsWith(XML_PREFIX)) {
if (name.length() == XML_PREFIX.length()) {
// no prefix e.g., xmlns="foo:"
if (prefix == null) {
String nsName = attr.getValue();
if (nsName.equals("")) { //$NON-NLS-1$
return null;
}
return nsName;
}
} else {
if (prefix != null && name.equals(XML_PREFIX + ":" + prefix)) { //$NON-NLS-1$
return attr.getValue();
}
}
}
}
do {
current = current.getParentNode();
} while (current != null && current.getNodeType() != Node.ELEMENT_NODE);
} while (current != null);
return null;
}
/**
* <p>Creates a WebDAV element with the given name and sets it as a
* child of the given parent. Returns the child element.
* <p>Children are positioned in the order specified by the given names.
* If a child with the same name as the child already exist, the child
* is replaced. If firstToLast is true, the search for the child's
* position starts at the parent's first child, otherwise, the search
* starts at the parent's last child.
* <p>The parent must not be <code>null</code> and must be a WebDAV
* element. The child's name must not be <code>null</code>. The
* parent's valid child names must not be <code>null</code>, and must
* contain the name of the child.
*
* @param parent the parent to which the child is added
* @param name the name of the child which is created and added
* to the parent
* @param names the ordered collection of valid child names for
* the parent
* @param firstToLast a boolean specifying the direction to search for
* the child's position among the parent's children
* @return the child element that is created
*/
public static Element setChild(Element parent, String name, String[] names, boolean firstToLast) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(name);
Assert.isNotNull(names);
String nsPrefix = getNSPrefix(parent);
String tagName = nsPrefix == null ? name : nsPrefix + ":" + name; //$NON-NLS-1$
Element child = parent.getOwnerDocument().createElement(tagName);
setChild(parent, child, names, firstToLast);
return child;
}
/**
* <p>Creates a WebDAV element with the given name and sets it as a
* child of the given parent. In addition, a text node created from the
* given data is created and appended to the child. Returns the child
* element.
* <p>Children are positioned in the order specified by the given names.
* If a child with the same name already exists, it is replaced. If
* firstToLast is true, the search for the child's position starts at the
* parent's first child, otherwise, the search starts at the parent's
* last child.
* <p>The parent must not be <code>null</code> and must be a WebDAV
* element. The child's name and data must not be <code>null</code>.
* The parent's valid child names must not be <code>null</code>, and
* must contain the name of the child.
*
* @param parent the parent to which the child is added
* @param name the name of the child which is created and added
* to the parent
* @param data the data of the text node which is created and
* added to the child
* @param names the ordered collection of valid child names for
* the parent
* @param firstToLast a boolean specifying the direction to search for
* the child's position among the parent's children
* @return the child element that is created
*/
public static Element setChild(Element parent, String name, String data, String[] names, boolean firstToLast) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(name);
Assert.isNotNull(data);
Assert.isNotNull(names);
Element child = setChild(parent, name, names, firstToLast);
child.appendChild(parent.getOwnerDocument().createTextNode(data));
return child;
}
/**
* <p>Sets the given child element as a child of the given parent.
* <p>Children are positioned in the order specified by the given names.
* If a child with the same name already exists, it is replaced. If
* firstToLast is true, the search for the child's position starts at
* the parent's first child, otherwise, the search starts at the
* parent's last child.
* <p>The parent must not be <code>null</code> and must be a WebDAV
* element. The child must not be null and its namespace prefix must
* resolve to the WebDAV namespace URL in the parent. The parent's valid
* child names must not be <code>null</code>, and must contain the name
* of the child.
*
* @param parent the parent to which the child is added
* @param child the child which is added to the parent
* @param names the ordered collection of valid child names for
* the parent
* @param firstToLast a boolean specifying the direction to search for
* the child's position among the parent's children
*/
public static void setChild(Element parent, Element child, String[] names, boolean firstToLast) {
Assert.isTrue(isDAVElement(parent));
Assert.isNotNull(child);
Assert.isTrue(DAV_NS.equals(resolve(getNSPrefix(child), parent)));
Assert.isNotNull(names);
boolean found = false;
String name = getNSLocalName(child);
for (int i = 0; !found && i < names.length; ++i) {
found = names[i].equals(name);
}
Assert.isTrue(found);
Node sibling = getChild(parent, name, names, firstToLast);
if (isDAVElement(sibling, name)) {
parent.replaceChild(child, sibling);
} else if (firstToLast) {
if (sibling == null) {
parent.appendChild(child);
} else {
parent.insertBefore(child, sibling);
}
} else {
Node refChild = null;
if (sibling == null) {
refChild = parent.getFirstChild();
} else {
refChild = sibling.getNextSibling();
}
if (refChild == null) {
parent.appendChild(child);
} else {
parent.insertBefore(child, refChild);
}
}
}
}