001/*
002 * Copyright 2002-2018 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.lang.Nullable;
033import org.springframework.util.Assert;
034
035/**
036 * Convenience methods for working with the DOM API,
037 * in particular for working with DOM Nodes and DOM Elements.
038 *
039 * @author Juergen Hoeller
040 * @author Rob Harrop
041 * @author Costin Leau
042 * @author Arjen Poutsma
043 * @author Luke Taylor
044 * @since 1.2
045 * @see org.w3c.dom.Node
046 * @see org.w3c.dom.Element
047 */
048public abstract class DomUtils {
049
050        /**
051         * Retrieves all child elements of the given DOM element that match any of the given element names.
052         * Only looks at the direct child level of the given element; do not go into further depth
053         * (in contrast to the DOM API's {@code getElementsByTagName} method).
054         * @param ele the DOM element to analyze
055         * @param childEleNames the child element names to look for
056         * @return a List of child {@code org.w3c.dom.Element} instances
057         * @see org.w3c.dom.Element
058         * @see org.w3c.dom.Element#getElementsByTagName
059         */
060        public static List<Element> getChildElementsByTagName(Element ele, String... childEleNames) {
061                Assert.notNull(ele, "Element must not be null");
062                Assert.notNull(childEleNames, "Element names collection must not be null");
063                List<String> childEleNameList = Arrays.asList(childEleNames);
064                NodeList nl = ele.getChildNodes();
065                List<Element> childEles = new ArrayList<>();
066                for (int i = 0; i < nl.getLength(); i++) {
067                        Node node = nl.item(i);
068                        if (node instanceof Element && nodeNameMatch(node, childEleNameList)) {
069                                childEles.add((Element) node);
070                        }
071                }
072                return childEles;
073        }
074
075        /**
076         * Retrieves all child elements of the given DOM element that match the given element name.
077         * Only look at the direct child level of the given element; do not go into further depth
078         * (in contrast to the DOM API's {@code getElementsByTagName} method).
079         * @param ele the DOM element to analyze
080         * @param childEleName the child element name to look for
081         * @return a List of child {@code org.w3c.dom.Element} instances
082         * @see org.w3c.dom.Element
083         * @see org.w3c.dom.Element#getElementsByTagName
084         */
085        public static List<Element> getChildElementsByTagName(Element ele, String childEleName) {
086                return getChildElementsByTagName(ele, new String[] {childEleName});
087        }
088
089        /**
090         * Utility method that returns the first child element identified by its name.
091         * @param ele the DOM element to analyze
092         * @param childEleName the child element name to look for
093         * @return the {@code org.w3c.dom.Element} instance, or {@code null} if none found
094         */
095        @Nullable
096        public static Element getChildElementByTagName(Element ele, String childEleName) {
097                Assert.notNull(ele, "Element must not be null");
098                Assert.notNull(childEleName, "Element name must not be null");
099                NodeList nl = ele.getChildNodes();
100                for (int i = 0; i < nl.getLength(); i++) {
101                        Node node = nl.item(i);
102                        if (node instanceof Element && nodeNameMatch(node, childEleName)) {
103                                return (Element) node;
104                        }
105                }
106                return null;
107        }
108
109        /**
110         * Utility method that returns the first child element value identified by its name.
111         * @param ele the DOM element to analyze
112         * @param childEleName the child element name to look for
113         * @return the extracted text value, or {@code null} if no child element found
114         */
115        @Nullable
116        public static String getChildElementValueByTagName(Element ele, String childEleName) {
117                Element child = getChildElementByTagName(ele, childEleName);
118                return (child != null ? getTextValue(child) : null);
119        }
120
121        /**
122         * Retrieves all child elements of the given DOM element.
123         * @param ele the DOM element to analyze
124         * @return a List of child {@code org.w3c.dom.Element} instances
125         */
126        public static List<Element> getChildElements(Element ele) {
127                Assert.notNull(ele, "Element must not be null");
128                NodeList nl = ele.getChildNodes();
129                List<Element> childEles = new ArrayList<>();
130                for (int i = 0; i < nl.getLength(); i++) {
131                        Node node = nl.item(i);
132                        if (node instanceof Element) {
133                                childEles.add((Element) node);
134                        }
135                }
136                return childEles;
137        }
138
139        /**
140         * Extracts the text value from the given DOM element, ignoring XML comments.
141         * <p>Appends all CharacterData nodes and EntityReference nodes into a single
142         * String value, excluding Comment nodes. Only exposes actual user-specified
143         * text, no default values of any kind.
144         * @see CharacterData
145         * @see EntityReference
146         * @see Comment
147         */
148        public static String getTextValue(Element valueEle) {
149                Assert.notNull(valueEle, "Element must not be null");
150                StringBuilder sb = new StringBuilder();
151                NodeList nl = valueEle.getChildNodes();
152                for (int i = 0; i < nl.getLength(); i++) {
153                        Node item = nl.item(i);
154                        if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
155                                sb.append(item.getNodeValue());
156                        }
157                }
158                return sb.toString();
159        }
160
161        /**
162         * Namespace-aware equals comparison. Returns {@code true} if either
163         * {@link Node#getLocalName} or {@link Node#getNodeName} equals
164         * {@code desiredName}, otherwise returns {@code false}.
165         */
166        public static boolean nodeNameEquals(Node node, String desiredName) {
167                Assert.notNull(node, "Node must not be null");
168                Assert.notNull(desiredName, "Desired name must not be null");
169                return nodeNameMatch(node, desiredName);
170        }
171
172        /**
173         * Returns a SAX {@code ContentHandler} that transforms callback calls to DOM {@code Node}s.
174         * @param node the node to publish events to
175         * @return the content handler
176         */
177        public static ContentHandler createContentHandler(Node node) {
178                return new DomContentHandler(node);
179        }
180
181        /**
182         * Matches the given node's name and local name against the given desired name.
183         */
184        private static boolean nodeNameMatch(Node node, String desiredName) {
185                return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()));
186        }
187
188        /**
189         * Matches the given node's name and local name against the given desired names.
190         */
191        private static boolean nodeNameMatch(Node node, Collection<?> desiredNames) {
192                return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName()));
193        }
194
195}