Browse Source

XmlUtil.mapToXml support List

Looly 5 years ago
parent
commit
ced42166d3

+ 1 - 0
CHANGELOG.md

@@ -20,6 +20,7 @@
 * 【core   】     添加ValidateObjectInputStream避免对象反序列化漏洞风险
 * 【core   】     添加BiMap
 * 【all    】     cn.hutool.extra.servlet.multipart包迁移到cn.hutool.core.net下
+* 【core   】     XmlUtil.mapToXml方法支持集合解析(issue#820@Github)
 
 ### Bug修复
 * 【extra  】     修复SpringUtil使用devtools重启报错问题

+ 25 - 0
hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java

@@ -16,6 +16,31 @@ public class MapBuilder<K, V> implements Serializable{
 	private Map<K, V> map;
 
 	/**
+	 * 创建Builder,默认HashMap实现
+	 *
+	 * @param <K> Key类型
+	 * @param <V> Value类型
+	 * @return MapBuilder
+	 * @since 5.3.0
+	 */
+	public static <K, V> MapBuilder<K, V> create() {
+		return create(false);
+	}
+
+	/**
+	 * 创建Builder
+	 *
+	 * @param <K> Key类型
+	 * @param <V> Value类型
+	 * @param isLinked true创建LinkedHashMap,false创建HashMap
+	 * @return MapBuilder
+	 * @since 5.3.0
+	 */
+	public static <K, V> MapBuilder<K, V> create(boolean isLinked) {
+		return create(MapUtil.newHashMap(isLinked));
+	}
+
+	/**
 	 * 创建Builder
 	 * 
 	 * @param <K> Key类型

+ 107 - 39
hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java

@@ -17,7 +17,11 @@ import javax.xml.namespace.QName;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.*;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 import javax.xml.xpath.XPath;
@@ -26,12 +30,20 @@ import javax.xml.xpath.XPathExpressionException;
 import javax.xml.xpath.XPathFactory;
 import java.beans.XMLDecoder;
 import java.beans.XMLEncoder;
-import java.io.*;
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 /**
  * XML工具类<br>
@@ -520,6 +532,16 @@ public class XmlUtil {
 	}
 
 	/**
+	 * 获取节点所在的Document
+	 * @param node 节点
+	 * @return {@link Document}
+	 * @since 5.3.0
+	 */
+	public static Document getOwnerDocument(Node node){
+		return (node instanceof Document) ? (Document) node : node.getOwnerDocument();
+	}
+
+	/**
 	 * 去除XML文本中的无效字符
 	 *
 	 * @param xmlContent XML文本
@@ -956,7 +978,6 @@ public class XmlUtil {
 	 * @since 4.0.9
 	 */
 	public static Document mapToXml(Map<?, ?> data, String rootName) {
-
 		return mapToXml(data, rootName, null);
 	}
 
@@ -973,7 +994,7 @@ public class XmlUtil {
 		final Document doc = createXml();
 		final Element root = appendChild(doc, rootName, namespace);
 
-		mapToXml(doc, root, data);
+		appendMap(doc, root, data);
 		return doc;
 	}
 
@@ -1025,60 +1046,107 @@ public class XmlUtil {
 	 * @since 5.0.4
 	 */
 	public static Element appendChild(Node node, String tagName, String namespace) {
-		final Document doc = (node instanceof Document) ? (Document) node : node.getOwnerDocument();
+		final Document doc = getOwnerDocument(node);
 		final Element child = (null == namespace) ? doc.createElement(tagName) : doc.createElementNS(namespace, tagName);
 		node.appendChild(child);
 		return child;
 	}
 
+	/**
+	 * 创建文本子节点
+	 *
+	 * @param node 节点
+	 * @param text 文本
+	 * @return 子节点
+	 * @since 5.3.0
+	 */
+	public static Node appendText(Node node, CharSequence text){
+		return appendText(getOwnerDocument(node), node, text);
+	}
 	// ---------------------------------------------------------------------------------------- Private method start
 
 	/**
-	 * 将Map转换为XML格式的字符串
+	 * 追加数据子节点,可以是Map、集合、文本
+	 *
+	 * @param doc {@link Document}
+	 * @param node 节点
+	 * @param data 数据
+	 */
+	@SuppressWarnings("rawtypes")
+	private static void append(Document doc, Node node, Object data){
+		if (data instanceof Map) {
+			// 如果值依旧为map,递归继续
+			appendMap(doc, node, (Map) data);
+		} else if (data instanceof Iterator) {
+			// 如果值依旧为map,递归继续
+			appendIterator(doc, node, (Iterator) data);
+		}else if (data instanceof Iterable) {
+			// 如果值依旧为map,递归继续
+			appendIterator(doc, node, ((Iterable)data).iterator());
+		} else {
+			appendText(doc, node, data.toString());
+		}
+	}
+
+	/**
+	 * 追加Map数据子节点
 	 *
 	 * @param doc     {@link Document}
-	 * @param element 节点
+	 * @param node 当前节点
 	 * @param data    Map类型数据
 	 * @since 4.0.8
 	 */
-	@SuppressWarnings("rawtypes")
-	private static void mapToXml(Document doc, Element element, Map<?, ?> data) {
-		Element filedEle;
-		Object key;
-		for (Entry<?, ?> entry : data.entrySet()) {
-			key = entry.getKey();
-			if (null == key) {
-				continue;
-			}
-			// key作为标签名,无值的节点作为空节点创建
-			filedEle = doc.createElement(key.toString());
-			element.appendChild(filedEle);
-			// value作为标签内的值。
-			final Object value = entry.getValue();
-			if (null == value) {
-				continue;
-			}
-			if (value instanceof List) {
-				for (Object listEle : (List) value) {
-					if (listEle instanceof Map) {
-						// 如果值依旧为map,递归继续
-						mapToXml(doc, filedEle, (Map<?, ?>) listEle);
-					} else {
-						// 创建文本节点
-						filedEle.appendChild(doc.createTextNode(value.toString()));
-					}
+	@SuppressWarnings({"rawtypes", "unchecked"})
+	private static void appendMap(Document doc, Node node, Map data) {
+		data.forEach((key, value)->{
+			if(null != key){
+				final Element child = appendChild(node, key.toString());
+				if(null != value){
+					append(doc, child, value);
 				}
-			} else if (value instanceof Map) {
-				// 如果值依旧为map,递归继续
-				mapToXml(doc, filedEle, (Map<?, ?>) value);
-			} else {
-				filedEle.appendChild(doc.createTextNode(value.toString()));
+			}
+		});
+	}
 
+	/**
+	 * 追加集合节点
+	 *
+	 * @param doc {@link Document}
+	 * @param node 节点
+	 * @param data 数据
+	 */
+	@SuppressWarnings("rawtypes")
+	private static void appendIterator(Document doc, Node node, Iterator data){
+		final Node parentNode = node.getParentNode();
+		boolean isFirst = true;
+		Object eleData;
+		while(data.hasNext()){
+			eleData = data.next();
+			if(isFirst){
+				append(doc, node, eleData);
+				isFirst = false;
+			} else{
+				final Node cloneNode = node.cloneNode(false);
+				parentNode.appendChild(cloneNode);
+				append(doc, cloneNode, eleData);
 			}
 		}
 	}
 
 	/**
+	 * 追加文本节点
+	 *
+	 * @param doc {@link Document}
+	 * @param node 节点
+	 * @param text 文本内容
+	 * @return 增加的子节点,即Text节点
+	 * @since 5.3.0
+	 */
+	private static Node appendText(Document doc, Node node, CharSequence text){
+		return node.appendChild(doc.createTextNode(StrUtil.str(text)));
+	}
+
+	/**
 	 * 关闭XXE,避免漏洞攻击<br>
 	 * see: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J
 	 *

+ 33 - 18
hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java

@@ -14,12 +14,11 @@ import java.util.Map;
 
 /**
  * {@link XmlUtil} 工具类
- * 
- * @author Looly
  *
+ * @author Looly
  */
 public class XmlUtilTest {
-	
+
 	@Test
 	public void parseTest() {
 		String result = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"//
@@ -84,7 +83,7 @@ public class XmlUtilTest {
 		Assert.assertEquals("1490", map.get("remainpoint"));
 		Assert.assertEquals("885", map.get("taskID"));
 		Assert.assertEquals("1", map.get("successCounts"));
-		Assert.assertEquals("subText", ((Map<?, ?>)map.get("newNode")).get("sub"));
+		Assert.assertEquals("subText", ((Map<?, ?>) map.get("newNode")).get("sub"));
 	}
 
 	@Test
@@ -106,17 +105,33 @@ public class XmlUtilTest {
 		Document doc = XmlUtil.mapToXml(map, "user");
 		// Console.log(XmlUtil.toStr(doc, false));
 		Assert.assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"//
-				+ "<user>"//
-				+ "<name>张三</name>"//
-				+ "<age>12</age>"//
-				+ "<game>"//
-				+ "<昵称>Looly</昵称>"//
-				+ "<level>14</level>"//
-				+ "</game>"//
-				+ "</user>", //
+						+ "<user>"//
+						+ "<name>张三</name>"//
+						+ "<age>12</age>"//
+						+ "<game>"//
+						+ "<昵称>Looly</昵称>"//
+						+ "<level>14</level>"//
+						+ "</game>"//
+						+ "</user>", //
 				XmlUtil.toStr(doc, false));
 	}
-	
+
+	@Test
+	public void mapToXmlTest2() {
+		// 测试List
+		Map<String, Object> map = MapBuilder.create(new LinkedHashMap<String, Object>())
+				.put("Town", CollUtil.newArrayList("town1", "town2"))
+				.build();
+
+		Document doc = XmlUtil.mapToXml(map, "City");
+		Assert.assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
+						"<City>" +
+						"<Town>town1</Town>" +
+						"<Town>town2</Town>" +
+						"</City>",
+				XmlUtil.toStr(doc));
+	}
+
 	@Test
 	public void readTest() {
 		Document doc = XmlUtil.readXML("test.xml");
@@ -127,9 +142,9 @@ public class XmlUtilTest {
 	public void mapToXmlTestWithOmitXmlDeclaration() {
 
 		Map<String, Object> map = MapBuilder.create(new LinkedHashMap<String, Object>())
-		                                    .put("name", "ddatsh")
-		                                    .build();
-		String xml=XmlUtil.mapToXmlStr(map,true);
-		Assert.assertEquals(xml,"<xml><name>ddatsh</name></xml>");
- 	}
+				.put("name", "ddatsh")
+				.build();
+		String xml = XmlUtil.mapToXmlStr(map, true);
+		Assert.assertEquals("<xml><name>ddatsh</name></xml>", xml);
+	}
 }