001/*
002 * Copyright 2002-2013 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.util.xml;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.List;
023
024import org.w3c.dom.CharacterData;
025import org.w3c.dom.Comment;
026import org.w3c.dom.Element;
027import org.w3c.dom.EntityReference;
028import org.w3c.dom.Node;
029import org.w3c.dom.NodeList;
030import org.xml.sax.ContentHandler;
031
032import org.springframework.util.Assert;
033
034/**
035 * Convenience methods for working with the DOM API,
036 * in particular for working with DOM Nodes and DOM Elements.
037 *
038 * @author Juergen Hoeller
039 * @author Rob Harrop
040 * @author Costin Leau
041 * @author Arjen Poutsma
042 * @author Luke Taylor
043 * @since 1.2
044 * @see org.w3c.dom.Node
045 * @see org.w3c.dom.Element
046 */
047public abstract class DomUtils {
048
049        /**
050         * Retrieves all child elements of the given DOM element that match any of the given element names.
051         * Only looks at the direct child level of the given element; do not go into further depth
052         * (in contrast to the DOM API's {@code getElementsByTagName} method).
053         * @param ele the DOM element to analyze
054         * @param childEleNames the child element names to look for
055         * @return a List of child {@code org.w3c.dom.Element} instances
056         * @see org.w3c.dom.Element
057         * @see org.w3c.dom.Element#getElementsByTagName
058         */
059        public static List<Element> getChildElementsByTagName(Element ele, String... childEleNames) {
060                Assert.notNull(ele, "Element must not be null");
061                Assert.notNull(childEleNames, "Element names collection must not be null");
062                List<String> childEleNameList = Arrays.asList(childEleNames);
063                NodeList nl = ele.getChildNodes();
064                List<Element> childEles = new ArrayList<Element>();
065                for (int i = 0; i < nl.getLength(); i++) {
066                        Node node = nl.item(i);
067                        if (node instanceof Element && nodeNameMatch(node, childEleNameList)) {
068                                childEles.add((Element) node);
069                        }
070                }
071                return childEles;
072        }
073
074        /**
075         * Retrieves all child elements of the given DOM element that match the given element name.
076         * Only look at the direct child level of the given element; do not go into further depth
077         * (in contrast to the DOM API's {@code getElementsByTagName} method).
078         * @param ele the DOM element to analyze
079         * @param childEleName the child element name to look for
080         * @return a List of child {@code org.w3c.dom.Element} instances
081         * @see org.w3c.dom.Element
082         * @see org.w3c.dom.Element#getElementsByTagName
083         */
084        public static List<Element> getChildElementsByTagName(Element ele, String childEleName) {
085                return getChildElementsByTagName(ele, new String[] {childEleName});
086        }
087
088        /**
089         * Utility method that returns the first child element identified by its name.
090         * @param ele the DOM element to analyze
091         * @param childEleName the child element name to look for
092         * @return the {@code org.w3c.dom.Element} instance, or {@code null} if none found
093         */
094        public static Element getChildElementByTagName(Element ele, String childEleName) {
095                Assert.notNull(ele, "Element must not be null");
096                Assert.notNull(childEleName, "Element name must not be null");
097                NodeList nl = ele.getChildNodes();
098                for (int i = 0; i < nl.getLength(); i++) {
099                        Node node = nl.item(i);
100                        if (node instanceof Element && nodeNameMatch(node, childEleName)) {
101                                return (Element) node;
102                        }
103                }
104                return null;
105        }
106
107        /**
108         * Utility method that returns the first child element value identified by its name.
109         * @param ele the DOM element to analyze
110         * @param childEleName the child element name to look for
111         * @return the extracted text value, or {@code null} if no child element found
112         */
113        public static String getChildElementValueByTagName(Element ele, String childEleName) {
114                Element child = getChildElementByTagName(ele, childEleName);
115                return (child != null ? getTextValue(child) : null);
116        }
117
118        /**
119         * Retrieves all child elements of the given DOM element
120         * @param ele the DOM element to analyze
121         * @return a List of child {@code org.w3c.dom.Element} instances
122         */
123        public static List<Element> getChildElements(Element ele) {
124                Assert.notNull(ele, "Element must not be null");
125                NodeList nl = ele.getChildNodes();
126                List<Element> childEles = new ArrayList<Element>();
127                for (int i = 0; i < nl.getLength(); i++) {
128                        Node node = nl.item(i);
129                        if (node instanceof Element) {
130                                childEles.add((Element) node);
131                        }
132                }
133                return childEles;
134        }
135
136        /**
137         * Extracts the text value from the given DOM element, ignoring XML comments.
138         * <p>Appends all CharacterData nodes and EntityReference nodes into a single
139         * String value, excluding Comment nodes. Only exposes actual user-specified
140         * text, no default values of any kind.
141         * @see CharacterData
142         * @see EntityReference
143         * @see Comment
144         */
145        public static String getTextValue(Element valueEle) {
146                Assert.notNull(valueEle, "Element must not be null");
147                StringBuilder sb = new StringBuilder();
148                NodeList nl = valueEle.getChildNodes();
149                for (int i = 0; i < nl.getLength(); i++) {
150                        Node item = nl.item(i);
151                        if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
152                                sb.append(item.getNodeValue());
153                        }
154                }
155                return sb.toString();
156        }
157
158        /**
159         * Namespace-aware equals comparison. Returns {@code true} if either
160         * {@link Node#getLocalName} or {@link Node#getNodeName} equals
161         * {@code desiredName}, otherwise returns {@code false}.
162         */
163        public static boolean nodeNameEquals(Node node, String desiredName) {
164                Assert.notNull(node, "Node must not be null");
165                Assert.notNull(desiredName, "Desired name must not be null");
166                return nodeNameMatch(node, desiredName);
167        }
168
169        /**
170         * Returns a SAX {@code ContentHandler} that transforms callback calls to DOM {@code Node}s.
171         * @param node the node to publish events to
172         * @return the content handler
173         */
174        public static ContentHandler createContentHandler(Node node) {
175                return new DomContentHandler(node);
176        }
177
178        /**
179         * Matches the given node's name and local name against the given desired name.
180         */
181        private static boolean nodeNameMatch(Node node, String desiredName) {
182                return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()));
183        }
184
185        /**
186         * Matches the given node's name and local name against the given desired names.
187         */
188        private static boolean nodeNameMatch(Node node, Collection<?> desiredNames) {
189                return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName()));
190        }
191
192}