Java tutorial
// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the repository root. package com.microsoft.tfs.util.xml; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.CDATASection; 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 com.microsoft.tfs.util.Check; /** * <p> * {@link DOMUtils} contains static helper methods for working with XML * documents using the DOM {@link Document} API. * </p> * * <p> * There are two categories of methods. One category is methods that add new * content to the document tree (<code>append*()</code>). The second category is * methods that retrieve existing content from the tree (<code>get*()</code>). * </p> * * <p> * Any XML-related exception generated by this class will be wrapped in an * unchecked {@link XMLException}. Callers do not need to handle several * different (often checked) JAXP-specific exceptions. If callers want to handle * exceptions generated by this class, they must catch {@link XMLException}. * </p> */ public class DOMUtils { private static final Log log = LogFactory.getLog(DOMUtils.class.getName()); /** * A DOM helper method to get the text contents of a {@link Node}. If the * {@link Node} is a text node ({@link Node#TEXT_NODE}) or a CDATA section * node ({@link Node#CDATA_SECTION_NODE}), the node's value is returned. If * the {@link Node} is a element node ({@link Node#ELEMENT_NODE}), the node * value of any <i>direct child</i> nodes that are text or CDATA sections * are appended together and returned. If the node is not of any of the * above types, an empty string is returned. * * @param node * the {@link Node} to get text for (must not be <code>null</code>) * @return the text (never <code>null</code> but may be an empty * {@link String}) */ public static String getText(final Node node) { Check.notNull(node, "node"); //$NON-NLS-1$ final int type = node.getNodeType(); if (Node.ELEMENT_NODE == type) { final NodeList children = node.getChildNodes(); final StringBuffer buffer = new StringBuffer(); final int length = children.getLength(); for (int i = 0; i < length; i++) { final Node child = children.item(i); final int childType = child.getNodeType(); if (Node.TEXT_NODE == childType || Node.CDATA_SECTION_NODE == childType) { buffer.append(child.getNodeValue()); } } return buffer.toString(); } if (Node.TEXT_NODE == type || Node.CDATA_SECTION_NODE == type) { return node.getNodeValue(); } return ""; //$NON-NLS-1$ } /** * Walks the parent chain from the specified element to find the root node. * * @param descendant * The starting point for the parent chain traversal. * * @return The root node of the document. */ public static Element getRootElement(final Element descendant) { Element element = descendant; while (element.getParentNode() != null && element.getParentNode() instanceof Element) { element = (Element) element.getParentNode(); } return element; } /** * Enumerates all of the child {@link Element}s of the specified * {@link Node}. * * @param node * the {@link Node} to enumerate child {@link Element}s of (must not * be <code>null</code>) * @return the child {@link Element}s of the specified {@link Node} (never * <code>null</code>) */ public static Element[] getChildElements(final Node node) { return getChildElementsInternal(node, false, null, null); } /** * Enumerates all of the child {@link Element}s of the specified * {@link Node} that have the specified name. * * @param tagName * the child element name to match on, <code>null</code> or * <code>*</code> to return child elements with any local name * @param node * the {@link Node} to enumerate child {@link Element}s of (must not * be <code>null</code>) * @return the child {@link Element}s that matched the specified criteria * (never <code>null</code>) */ public static Element[] getChildElements(final Node node, final String tagName) { return getChildElementsInternal(node, false, null, tagName); } /** * Enumerates all of the child {@link Element}s of the specified * {@link Node} that have the specified name. * * @param namespaceURI * the child element namespace to match on, <code>null</code> to * return only child elements not in a namespace, <code>*</code> to * return child elements in any namespace * @param tagName * the child element local name to match on, <code>null</code> or * <code>*</code> to return child elements with any local name * @param node * the {@link Node} to enumerate child {@link Element}s of (must not * be <code>null</code>) * @return the child {@link Element}s that matched the specified criteria * (never <code>null</code>) */ public static Element[] getChildElementsNS(final Node node, final String namespaceURI, final String localName) { return getChildElementsInternal(node, true, namespaceURI, localName); } /** * Gets the first child {@link Element} of the specified {@link Node}, if * any. * * @param node * the {@link Node} to get the first child {@link Element}s of (must * not be <code>null</code>) * @return the first child {@link Element} of the specified {@link Node}, or * <code>null</code> if there were no child elements */ public static Element getFirstChildElement(final Node node) { return getFirstChildElementInternal(node, false, null, null); } /** * Gets the first child {@link Element} of the specified {@link Node} that * has the specified name, if any. * * @param tagName * the child element name to match on, <code>null</code> or * <code>*</code> to match child elements with any local name * @param node * the {@link Node} to get the first child {@link Element} of (must * not be <code>null</code>) * @return the first child {@link Element} that matched the specified * criteria, or <code>null</code> if there were no child elements * that matched */ public static Element getFirstChildElement(final Node node, final String tagName) { return getFirstChildElementInternal(node, false, null, tagName); } /** * Gets the first child {@link Element} of the specified {@link Node} that * has the specified name, if any. * * @param namespaceURI * the child element namespace to match on, <code>null</code> to * match child elements not in a namespace, <code>*</code> to match * child elements in any namespace * @param tagName * the child element local name to match on, <code>null</code> or * <code>*</code> to match child elements with any local name * @param node * the {@link Node} to get the first child {@link Element} of (must * not be <code>null</code>) * @return the first child {@link Element} that matched the specified * criteria, or <code>null</code> if there were no child elements * that matched */ public static Element getFirstChildElementNS(final Node node, final String namespaceURI, final String localName) { return getFirstChildElementInternal(node, true, namespaceURI, localName); } /** * Adds a new {@link Element} node to a {@link Document} tree as a child of * the specified parent {@link Element}. * * @param parent * the parent {@link Element} (must not be <code>null</code>) * @param tagName * the name to give the new child {@link Element} (must not be * <code>null</code>) * @return the new child {@link Element} (never <code>null</code>) */ public static Element appendChild(final Element parent, final String tagName) { Check.notNull(parent, "parent"); //$NON-NLS-1$ final Element newChild = parent.getOwnerDocument().createElement(tagName); parent.appendChild(newChild); return newChild; } /** * Adds a new {@link Element} node to a {@link Document} tree as a child of * the specified parent {@link Element}. * * @param parent * the parent {@link Element} (must not be <code>null</code>) * @param namespaceURI * the namespace URI to put the new child in, or <code>null</code> * @param qualifiedName * the qualified name to give the new child (must not be * <code>null</code>) * @return the new child {@link Element} (never <code>null</code>) */ public static Element appendChildNS(final Element parent, final String namespaceURI, final String qualifiedName) { Check.notNull(parent, "parent"); //$NON-NLS-1$ final Element newChild = parent.getOwnerDocument().createElementNS(namespaceURI, qualifiedName); parent.appendChild(newChild); return newChild; } /** * Adds a new {@link Text} node to a {@link Document} tree as a child of the * specified parent {@link Element}. * * @param parent * the parent {@link Element} (must not be <code>null</code>) * @param data * the contents to give the new {@link Text} node * @return the new {@link Text} node (never <code>null</code>) */ public static Text appendText(final Element parent, final String data) { Check.notNull(parent, "parent"); //$NON-NLS-1$ final Text newChild = parent.getOwnerDocument().createTextNode(data); parent.appendChild(newChild); return newChild; } /** * Adds a new {@link CDATASection} node to a {@link Document} tree as a * child of the specified parent {@link Element}. * * @param parent * the parent {@link Element} (must not be <code>null</code>) * @param data * the contents to give the new {@link CDATASection} node * @return the new {@link CDATASection} node (never <code>null</code>) */ public static CDATASection appendCDATA(final Element parent, final String data) { Check.notNull(parent, "parent"); //$NON-NLS-1$ final CDATASection newChild = parent.getOwnerDocument().createCDATASection(data); parent.appendChild(newChild); return newChild; } /** * Adds a new {@link Element} node to the {@link Document} tree as a child * of the specified parent {@link Element}, and adds a new {@link Text} node * to the {@link Document} tree as a child of the new {@link Element} node. * * @param parent * the parent {@link Element} (must not be <code>null</code>) * @param tagName * the name to give the new child {@link Element} (must not be * <code>null</code>) * @param data * the contents to give the new {@link Text} node * @return the new child {@link Element} (never <code>null</code>) */ public static Element appendChildWithText(final Element parent, final String tagName, final String data) { Check.notNull(parent, "parent"); //$NON-NLS-1$ final Element newChild = appendChild(parent, tagName); appendText(newChild, data); return newChild; } /** * Adds a new {@link Element} node to the {@link Document} tree as a child * of the specified parent {@link Element}, and adds a new {@link Text} node * to the {@link Document} tree as a child of the new {@link Element} node. * * @param parent * the parent {@link Element} (must not be <code>null</code>) * @param namespaceURI * the namespace URI to put the new child in, or <code>null</code> * @param qualifiedName * the qualified name to give the new child (must not be * <code>null</code>) * @param data * the contents to give the new {@link Text} node * @return the new child {@link Element} (never <code>null</code>) */ public static Element appendChildWithTextNS(final Element parent, final String namespaceURI, final String qualifiedName, final String data) { final Element newChild = appendChildNS(parent, namespaceURI, qualifiedName); appendText(newChild, data); return newChild; } /** * Adds a new {@link Element} node to the {@link Document} tree as a child * of the specified parent {@link Element}, and adds a new * {@link CDATASection} node to the {@link Document} tree as a child of the * new {@link Element} node. * * @param parent * the parent {@link Element} (must not be <code>null</code>) * @param tagName * the name to give the new child {@link Element} (must not be * <code>null</code>) * @param data * the contents to give the new {@link CDATASection} node * @return the new child {@link Element} (never <code>null</code>) */ public static Element appendChildWithCDATA(final Element parent, final String tagName, final String data) { final Element newChild = appendChild(parent, tagName); appendCDATA(newChild, data); return newChild; } /** * Adds a new {@link Element} node to the {@link Document} tree as a child * of the specified parent {@link Element}, and adds a new * {@link CDATASection} node to the {@link Document} tree as a child of the * new {@link Element} node. * * @param parent * the parent {@link Element} (must not be <code>null</code>) * @param namespaceURI * the namespace URI to put the new child in, or <code>null</code> * @param qualifiedName * the qualified name to give the new child (must not be * <code>null</code>) * @param data * the contents to give the new {@link CDATASection} node * @return the new child {@link Element} (never <code>null</code>) */ public static Element appendChildWithCDATANS(final Element parent, final String namespaceURI, final String qualifiedName, final String data) { final Element newChild = appendChildNS(parent, namespaceURI, qualifiedName); appendCDATA(newChild, data); return newChild; } /** * Helper method to enumerate child {@link Element}s of a {@link Node} that * match specified criteria. No <code>null</code> checking is done of the * <code>node</code> argument. * * @param node * the {@link Node} to enumerate (must not be <code>null</code>) * @param useNamespaces * passed to the {@link #matches(Node, boolean, String, String)} * method * @param namespaceURI * passed to the {@link #matches(Node, boolean, String, String)} * method * @param localName * passed to the {@link #matches(Node, boolean, String, String)} * method * @return the {@link Element} children of the given {@link Node} (never * <code>null</code>) */ private static Element[] getChildElementsInternal(final Node node, final boolean useNamespaces, final String namespaceURI, final String localName) { final NodeList children = node.getChildNodes(); final List childElements = new ArrayList(); final int length = children.getLength(); for (int i = 0; i < length; i++) { final Node child = children.item(i); if (Node.ELEMENT_NODE == child.getNodeType()) { if (matches(child, useNamespaces, namespaceURI, localName)) { childElements.add(child); } } } return (Element[]) childElements.toArray(new Element[childElements.size()]); } /** * Helper method to get the first child {@link Element} of a {@link Node} * that matches specified criteria. No <code>null</code> checking is done of * the <code>node</code> argument. * * @param node * the {@link Node} to obtain the child from (must not be * <code>null</code>) * @param useNamespaces * passed to the {@link #matches(Node, boolean, String, String)} * method * @param namespaceURI * passed to the {@link #matches(Node, boolean, String, String)} * method * @param localName * passed to the {@link #matches(Node, boolean, String, String)} * method * @return the first child {@link Element} of the given {@link Node} that * matches the criteria, or <code>null</code> if none match */ private static Element getFirstChildElementInternal(final Node node, final boolean useNamespaces, final String namespaceURI, final String localName) { final NodeList children = node.getChildNodes(); final int length = children.getLength(); for (int i = 0; i < length; i++) { final Node child = children.item(i); if (Node.ELEMENT_NODE == child.getNodeType()) { if (matches(child, useNamespaces, namespaceURI, localName)) { return (Element) child; } } } return null; } /** * Tests whether the given {@link Node} matches the specified name. No * <code>null</code> checking is done of the <code>node</code> argument. * * @param node * the {@link Node} to test (must not be <code>null</code>) * @param useNamespaces * <code>true</code> for namespaces mode * @param namespaceURI * the namespace URI to match, <code>null</code> to match no * namespace, <code>*</code> to match any namespace (ignored if * <code>useNamespaces</code> is <code>false</code>) * @param localName * <code>null</code> or <code>*</code> to match any node, matches the * node name if <code>useNamespaces</code> is <code>false</code>, * matches the local name if <code>useNamespaces</code> is * <code>true</code> * @return <code>true</code> if the {@link Node} matches the criteria */ private static boolean matches(final Node node, final boolean useNamespaces, final String namespaceURI, final String localName) { if (useNamespaces) { if (namespaceURI == null && node.getNamespaceURI() != null) { return false; } if (namespaceURI != null && !namespaceURI.equals("*") && !namespaceURI.equals(node.getNamespaceURI())) //$NON-NLS-1$ { return false; } } if (localName == null || localName.equals("*")) //$NON-NLS-1$ { return true; } if (useNamespaces) { return localName.equals(node.getLocalName()); } return localName.equals(node.getNodeName()); } }