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}