001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs.optional.junit;
019:
020: import java.util.Vector;
021: import org.w3c.dom.Attr;
022: import org.w3c.dom.CDATASection;
023: import org.w3c.dom.Comment;
024: import org.w3c.dom.DOMException;
025: import org.w3c.dom.Document;
026: import org.w3c.dom.Element;
027: import org.w3c.dom.NamedNodeMap;
028: import org.w3c.dom.Node;
029: import org.w3c.dom.NodeList;
030: import org.w3c.dom.ProcessingInstruction;
031: import org.w3c.dom.Text;
032:
033: /**
034: * Some utilities that might be useful when manipulating DOM trees.
035: *
036: */
037: public final class DOMUtil {
038:
039: /** unused constructor */
040: private DOMUtil() {
041: }
042:
043: /**
044: * Filter interface to be applied when iterating over a DOM tree.
045: * Just think of it like a <tt>FileFilter</tt> clone.
046: */
047: public interface NodeFilter {
048: /**
049: * @param node the node to check for acceptance.
050: * @return <tt>true</tt> if the node is accepted by this filter,
051: * otherwise <tt>false</tt>
052: */
053: boolean accept(Node node);
054: }
055:
056: /**
057: * list a set of node that match a specific filter. The list can be made
058: * recursively or not.
059: * @param parent the parent node to search from
060: * @param filter the filter that children should match.
061: * @param recurse <tt>true</tt> if you want the list to be made recursively
062: * otherwise <tt>false</tt>.
063: * @return the node list that matches the filter.
064: */
065: public static NodeList listChildNodes(Node parent,
066: NodeFilter filter, boolean recurse) {
067: NodeListImpl matches = new NodeListImpl();
068: NodeList children = parent.getChildNodes();
069: if (children != null) {
070: final int len = children.getLength();
071: for (int i = 0; i < len; i++) {
072: Node child = children.item(i);
073: if (filter.accept(child)) {
074: matches.addElement(child);
075: }
076: if (recurse) {
077: NodeList recmatches = listChildNodes(child, filter,
078: recurse);
079: final int reclength = recmatches.getLength();
080: for (int j = 0; j < reclength; j++) {
081: matches.addElement(recmatches.item(i));
082: }
083: }
084: }
085: }
086: return matches;
087: }
088:
089: /** custom implementation of a nodelist */
090: public static class NodeListImpl extends Vector implements NodeList {
091: /**
092: * Get the number of nodes in the list.
093: * @return the length of the list.
094: */
095: public int getLength() {
096: return size();
097: }
098:
099: /**
100: * Get a particular node.
101: * @param i the index of the node to get.
102: * @return the node if the index is in bounds, null otherwise.
103: */
104: public Node item(int i) {
105: try {
106: return (Node) elementAt(i);
107: } catch (ArrayIndexOutOfBoundsException e) {
108: return null; // conforming to NodeList interface
109: }
110: }
111: }
112:
113: /**
114: * return the attribute value of an element.
115: * @param node the node to get the attribute from.
116: * @param name the name of the attribute we are looking for the value.
117: * @return the value of the requested attribute or <tt>null</tt> if the
118: * attribute was not found or if <tt>node</tt> is not an <tt>Element</tt>.
119: */
120: public static String getNodeAttribute(Node node, String name) {
121: if (node instanceof Element) {
122: Element element = (Element) node;
123: return element.getAttribute(name);
124: }
125: return null;
126: }
127:
128: /**
129: * Iterate over the children of a given node and return the first node
130: * that has a specific name.
131: * @param parent the node to search child from. Can be <tt>null</tt>.
132: * @param tagname the child name we are looking for. Cannot be <tt>null</tt>.
133: * @return the first child that matches the given name or <tt>null</tt> if
134: * the parent is <tt>null</tt> or if a child does not match the
135: * given name.
136: */
137: public static Element getChildByTagName(Node parent, String tagname) {
138: if (parent == null) {
139: return null;
140: }
141: NodeList childList = parent.getChildNodes();
142: final int len = childList.getLength();
143: for (int i = 0; i < len; i++) {
144: Node child = childList.item(i);
145: if (child != null
146: && child.getNodeType() == Node.ELEMENT_NODE
147: && child.getNodeName().equals(tagname)) {
148: return (Element) child;
149: }
150: }
151: return null;
152: }
153:
154: /**
155: * Simple tree walker that will clone recursively a node. This is to
156: * avoid using parser-specific API such as Sun's <tt>changeNodeOwner</tt>
157: * when we are dealing with DOM L1 implementations since <tt>cloneNode(boolean)</tt>
158: * will not change the owner document.
159: * <tt>changeNodeOwner</tt> is much faster and avoid the costly cloning process.
160: * <tt>importNode</tt> is in the DOM L2 interface.
161: * @param parent the node parent to which we should do the import to.
162: * @param child the node to clone recursively. Its clone will be
163: * appended to <tt>parent</tt>.
164: * @return the cloned node that is appended to <tt>parent</tt>
165: */
166: public static Node importNode(Node parent, Node child) {
167: Node copy = null;
168: final Document doc = parent.getOwnerDocument();
169:
170: switch (child.getNodeType()) {
171: case Node.CDATA_SECTION_NODE:
172: copy = doc.createCDATASection(((CDATASection) child)
173: .getData());
174: break;
175: case Node.COMMENT_NODE:
176: copy = doc.createComment(((Comment) child).getData());
177: break;
178: case Node.DOCUMENT_FRAGMENT_NODE:
179: copy = doc.createDocumentFragment();
180: break;
181: case Node.ELEMENT_NODE:
182: final Element elem = doc.createElement(((Element) child)
183: .getTagName());
184: copy = elem;
185: final NamedNodeMap attributes = child.getAttributes();
186: if (attributes != null) {
187: final int size = attributes.getLength();
188: for (int i = 0; i < size; i++) {
189: final Attr attr = (Attr) attributes.item(i);
190: elem.setAttribute(attr.getName(), attr.getValue());
191: }
192: }
193: break;
194: case Node.ENTITY_REFERENCE_NODE:
195: copy = doc.createEntityReference(child.getNodeName());
196: break;
197: case Node.PROCESSING_INSTRUCTION_NODE:
198: final ProcessingInstruction pi = (ProcessingInstruction) child;
199: copy = doc.createProcessingInstruction(pi.getTarget(), pi
200: .getData());
201: break;
202: case Node.TEXT_NODE:
203: copy = doc.createTextNode(((Text) child).getData());
204: break;
205: default:
206: // this should never happen
207: throw new IllegalStateException("Invalid node type: "
208: + child.getNodeType());
209: }
210:
211: // okay we have a copy of the child, now the child becomes the parent
212: // and we are iterating recursively over its children.
213: try {
214: final NodeList children = child.getChildNodes();
215: if (children != null) {
216: final int size = children.getLength();
217: for (int i = 0; i < size; i++) {
218: final Node newChild = children.item(i);
219: if (newChild != null) {
220: importNode(copy, newChild);
221: }
222: }
223: }
224: } catch (DOMException ignored) {
225: // Ignore
226: }
227:
228: // bingo append it. (this should normally not be done here)
229: parent.appendChild(copy);
230: return copy;
231: }
232: }
|