blob: 170431d9b2f9adb478f089c51f451002587abf74 [file] [log] [blame]
/*
* Copyright 2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.apache.jackrabbit.webdav.xml;
import org.apache.log4j.Logger;
import org.apache.jackrabbit.webdav.DavConstants;
import org.w3c.dom.Attr;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import java.util.ArrayList;
import java.util.List;
/**
* <code>DomUtil</code> provides some common utility methods related to w3c-DOM.
*/
public class DomUtil {
private static Logger log = Logger.getLogger(DomUtil.class);
/**
* Returns the value of the named attribute of the current element.
*
* @param parent
* @param localName attribute local name or 'nodeName' if no namespace is
* specified.
* @param namespace or <code>null</code>
* @return attribute value, or <code>null</code> if not found
*/
public static String getAttribute(Element parent, String localName, Namespace namespace) {
if (parent == null) {
return null;
}
Attr attribute;
if (namespace == null) {
attribute = parent.getAttributeNode(localName);
} else {
attribute = parent.getAttributeNodeNS(namespace.getURI(), localName);
}
if (attribute != null) {
return attribute.getValue();
} else {
return null;
}
}
/**
* Concatenates the values of all child nodes of type 'Text' or 'CDATA'/
*
* @param element
* @return String representing the value of all Text and CDATA child nodes or
* <code>null</code> if the length of the resulting String is 0.
* @see #isText(Node)
*/
public static String getText(Element element) {
StringBuffer content = new StringBuffer();
if (element != null) {
NodeList nodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node child = nodes.item(i);
if (isText(child)) {
// cast to super class that contains Text and CData
content.append(((CharacterData) child).getData());
}
}
}
return (content.length()==0) ? null : content.toString();
}
/**
* Removes leading and trailing whitespace after calling {@link #getText(Element).
*
* @param element
* @return Trimmed text or <code>null</code>
*/
public static String getTextTrim(Element element) {
String txt = getText(element);
return (txt == null) ? txt : txt.trim();
}
/**
* Calls {@link #getText(Element)} on the first child element that matches
* the given local name and namespace.
*
* @param parent
* @param childLocalName
* @param childNamespace
* @return text contained in the first child that matches the given local name
* and namespace or <code>null</code>.
* @see #getText(Element)
*/
public static String getChildText(Element parent, String childLocalName, Namespace childNamespace) {
Element child = getChildElement(parent, childLocalName, childNamespace);
return (child == null) ? null : getText(child);
}
/**
* Calls {@link #getTextTrim(Element)} on the first child element that matches
* the given local name and namespace.
*
* @param parent
* @param childLocalName
* @param childNamespace
* @return text contained in the first child that matches the given local name
* and namespace or <code>null</code>. Note, that leading and trailing whitespace
* is removed from the text.
* @see #getTextTrim(Element)
*/
public static String getChildTextTrim(Element parent, String childLocalName, Namespace childNamespace) {
Element child = getChildElement(parent, childLocalName, childNamespace);
return (child == null) ? null : getTextTrim(child);
}
/**
* Returns true if the given parent node has a child element that matches
* the specified local name and namespace.
*
* @param parent
* @param childLocalName
* @param childNamespace
* @return returns true if a child element exists that matches the specified
* local name and namespace.
*/
public static boolean hasChildElement(Node parent, String childLocalName, Namespace childNamespace) {
return getChildElement(parent, childLocalName, childNamespace) != null;
}
/**
* Returns the first child element that matches the given local name and
* namespace. If no child element is present or no child element matches,
* <code>null</code> is returned.
*
* @param parent
* @param childLocalName
* @param childNamespace
* @return first child element matching the specified names or <code>null</code>.
*/
public static Element getChildElement(Node parent, String childLocalName, Namespace childNamespace) {
if (parent != null) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (isElement(child) && matches(child, childLocalName, childNamespace)) {
return (Element)child;
}
}
}
return null;
}
/**
* Returns a <code>ElementIterator</code> containing all child elements of
* the given parent node that match the given local name and namespace.
* If the namespace is <code>null</code> only the localName is compared.
*
* @param parent the node the children elements should be retrieved from
* @param childLocalName
* @param childNamespace
* @return an <code>ElementIterator</code> giving access to all child elements
* that match the specified localName and namespace.
*/
public static ElementIterator getChildren(Element parent, String childLocalName, Namespace childNamespace) {
return new ElementIterator(parent, childLocalName, childNamespace);
}
/**
* Return an <code>ElementIterator</code> over all child elements.
*
* @param parent
* @return
* @see #getChildren(Element, String, Namespace) for a method that only
* retrieves child elements that match a specific local name and namespace.
*/
public static ElementIterator getChildren(Element parent) {
return new ElementIterator(parent);
}
/**
* Return the first child element
*
* @return the first child element or <code>null</code> if the given node has no
* child elements.
*/
public static Element getFirstChildElement(Node parent) {
if (parent != null) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (isElement(child)) {
return (Element)child;
}
}
}
return null;
}
/**
* Return true if the given parent contains any child that is either an
* Element, Text or CDATA.
*
* @param parent
* @return
*/
public static boolean hasContent(Node parent) {
if (parent != null) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (isAcceptedNode(child)) {
return true;
}
}
}
return false;
}
/**
* Return a list of all child nodes that are either Element, Text or CDATA.
*
* @param parent
* @return
*/
public static List getContent(Node parent) {
List content = new ArrayList();
if (parent != null) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (isAcceptedNode(child)) {
content.add(child);
}
}
}
return content;
}
/**
* Build a Namespace from the prefix and uri retrieved from the given element.
*
* @return
*/
public static Namespace getNamespace(Element element) {
String uri = element.getNamespaceURI();
String prefix = element.getPrefix();
if (uri == null) {
return Namespace.EMPTY_NAMESPACE;
} else {
return Namespace.getNamespace(prefix, uri);
}
}
/**
* Returns true if the specified node matches the required names. Note, that
* that tests return true if the required name is <code>null</code>.
*
* @param node
* @param requiredLocalName
* @param requiredNamespace
* @return true if local name and namespace match the corresponding properties
* of the given DOM node.
*/
public static boolean matches(Node node, String requiredLocalName, Namespace requiredNamespace) {
if (node == null) {
return false;
}
boolean matchingNamespace = matchingNamespace(node, requiredNamespace);
return matchingNamespace && matchingLocalName(node, requiredLocalName);
}
/**
* @param node
* @param requiredNamespace
* @return true if the required namespace is <code>null</code> or matches
* the namespace of the specified node.
*/
private static boolean matchingNamespace(Node node, Namespace requiredNamespace) {
if (requiredNamespace == null) {
return true;
} else {
return requiredNamespace.isSame(node.getNamespaceURI());
}
}
/**
* @param node
* @param requiredLocalName
* @return true if the required local name is <code>null</code> or if the
* nodes local name matches.
*/
private static boolean matchingLocalName(Node node, String requiredLocalName) {
if (requiredLocalName == null) {
return true;
} else {
String localName = node.getLocalName();
return requiredLocalName.equals(localName);
}
}
/**
* @param node
* @return true if the specified node is either an element or Text or CDATA
*/
private static boolean isAcceptedNode(Node node) {
return isElement(node) || isText(node);
}
/**
* @param node
* @return true if the given node is of type element.
*/
static boolean isElement(Node node) {
return node.getNodeType() == Node.ELEMENT_NODE;
}
/**
* @param node
* @return true if the given node is of type text or CDATA.
*/
static boolean isText(Node node) {
int ntype = node.getNodeType();
return ntype == Node.TEXT_NODE || ntype == Node.CDATA_SECTION_NODE;
}
//----------------------------------------------------< factory methods >---
/**
* Create a new DOM element with the specified local name and namespace.
*
* @param factory
* @param localName
* @param namespace
* @return a new DOM element
* @see Document#createElement(String)
* @see Document#createElementNS(String, String)
*/
public static Element createElement(Document factory, String localName, Namespace namespace) {
if (namespace != null) {
return factory.createElementNS(namespace.getURI(), getPrefixedName(localName, namespace));
} else {
return factory.createElement(localName);
}
}
/**
* Create a new DOM element with the specified local name and namespace and
* add the specified text as Text node to it.
*
* @param factory
* @param localName
* @param namespace
* @param text
* @return a new DOM element
* @see Document#createElement(String)
* @see Document#createElementNS(String, String)
* @see Document#createTextNode(String)
* @see Node#appendChild(org.w3c.dom.Node)
*/
public static Element createElement(Document factory, String localName, Namespace namespace, String text) {
Element elem = createElement(factory, localName, namespace);
setText(elem, text);
return elem;
}
/**
* Add a new child element with the given local name and namespace to the
* specified parent.
*
* @param parent
* @param localName
* @param namespace
* @return the new element that was attached to the given parent.
*/
public static Element addChildElement(Element parent, String localName, Namespace namespace) {
Element elem = createElement(parent.getOwnerDocument(), localName, namespace);
parent.appendChild(elem);
return elem;
}
/**
* Add a new child element with the given local name and namespace to the
* specified parent. The specified text is added as Text node to the created
* child element.
*
* @param parent
* @param localName
* @param namespace
* @param text
* @return child element that was added to the specified parent
* @see Document#createElement(String)
* @see Document#createElementNS(String, String)
* @see Document#createTextNode(String)
* @see Node#appendChild(org.w3c.dom.Node)
*/
public static Element addChildElement(Element parent, String localName, Namespace namespace, String text) {
Element elem = createElement(parent.getOwnerDocument(), localName, namespace, text);
parent.appendChild(elem);
return elem;
}
/**
* Create a new text node and add it as child to the given element.
*
* @param element
* @param text
*/
public static void setText(Element element, String text) {
if (text == null || "".equals(text)) {
// ignore null/empty string text
return;
}
Text txt = element.getOwnerDocument().createTextNode(text);
element.appendChild(txt);
}
/**
* Add an attribute node to the given element.
*
* @param element
* @param attrLocalName
* @param attrNamespace
* @param attrValue
*/
public static void setAttribute(Element element, String attrLocalName, Namespace attrNamespace, String attrValue) {
if (attrNamespace == null) {
Attr attr = element.getOwnerDocument().createAttribute(attrLocalName);
attr.setValue(attrValue);
element.setAttributeNode(attr);
} else {
Attr attr = element.getOwnerDocument().createAttributeNS(attrNamespace.getURI(), getPrefixedName(attrLocalName, attrNamespace));
attr.setValue(attrValue);
element.setAttributeNodeNS(attr);
}
}
/**
* Converts the given timeout (long value defining the number of milli-
* second until timeout is reached) to its Xml representation as defined
* by RTF 2518.<br>
* Note, that {@link DavConstants#INFINITE_TIMEOUT} is not represented by the String
* {@link DavConstants#TIMEOUT_INFINITE 'Infinite'} defined by RFC 2518, due to a known
* issue with Microsoft Office that opens the document "read only" and
* never unlocks the resource if the timeout is missing or 'Infinite'.
*
* @param timeout number of milli-seconds until timeout is reached.
* @return 'timeout' Xml element
*/
public static Element timeoutToXml(long timeout, Document factory) {
String expString = "Second-"+ timeout/1000;;
Element exp = createElement(factory, DavConstants.XML_TIMEOUT, DavConstants.NAMESPACE, expString);
return exp;
}
/**
* Returns the Xml representation of a boolean isDeep, where false
* presents a depth value of '0', true a depth value of 'infinity'.
*
* @param isDeep
* @return Xml representation
*/
public static Element depthToXml(boolean isDeep, Document factory) {
return depthToXml(isDeep? "infinity" : "0", factory);
}
/**
* Returns the Xml representation of a depth String. Webdav defines the
* following valid values for depths: 0, 1, infinity
*
* @param depth
* @return 'deep' JDOM element
*/
public static Element depthToXml(String depth, Document factory) {
Element dElem = createElement(factory, DavConstants.XML_DEPTH, DavConstants.NAMESPACE, depth);
return dElem;
}
/**
* Builds a 'DAV:href' Xml element from the given href. Please note, that
* the path present in the given String should be properly
* {@link org.apache.jackrabbit.util.Text#escapePath(String) escaped} in order to prevent problems with
* WebDAV clients.
*
* @param href String representing the text of the 'href' Xml element
* @param factory the Document used as factory
* @return Xml representation of a 'href' according to RFC 2518.
*/
public static Element hrefToXml(String href, Document factory) {
return createElement(factory, DavConstants.XML_HREF, DavConstants.NAMESPACE, href);
}
/**
* Return a qualified name of a DOM node consisting of "{" + namespace uri + "}"
* + localName. If the specified namespace is <code>null</code> or represents
* the empty namespace, the local name is returned.
*
* @param localName
* @param namespace
* @return
*/
public static String getQualifiedName(String localName, Namespace namespace) {
if (namespace == null || namespace.equals(Namespace.EMPTY_NAMESPACE)) {
return localName;
}
StringBuffer b = new StringBuffer("{");
b.append(namespace.getURI()).append("}");
b.append(localName);
return b.toString();
}
/**
* Return the prefixed name of a DOM node consisting of
* namespace prefix + ":" + local name. If the specified namespace is <code>null</code>
* or contains an empty prefix, the local name is returned.<br>
* NOTE, that this is the value to be used for the 'qualified Name' parameter
* expected with the namespace sensitive factory methods.
*
* @param localName
* @param namespace
* @return qualified name consisting of prefix, ':' and local name.
* @see Document#createAttributeNS(String, String)
* @see Document#createElementNS(String, String)
*/
public static String getPrefixedName(String localName, Namespace namespace) {
if (namespace == null || namespace.equals(Namespace.EMPTY_NAMESPACE) || "".equals(namespace.getPrefix())) {
return localName;
}
StringBuffer buf = new StringBuffer(namespace.getPrefix());
buf.append(":");
buf.append(localName);
return buf.toString();
}
}