diff --git a/CHANGELOG.md b/CHANGELOG.md index e72855ad0..c71630bd6 100644 --- a/CHANGELOG.md +++ b/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重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a278e5521..38779d1da 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -15,6 +15,31 @@ public class MapBuilder implements Serializable{ private Map map; + /** + * 创建Builder,默认HashMap实现 + * + * @param Key类型 + * @param Value类型 + * @return MapBuilder + * @since 5.3.0 + */ + public static MapBuilder create() { + return create(false); + } + + /** + * 创建Builder + * + * @param Key类型 + * @param Value类型 + * @param isLinked true创建LinkedHashMap,false创建HashMap + * @return MapBuilder + * @since 5.3.0 + */ + public static MapBuilder create(boolean isLinked) { + return create(MapUtil.newHashMap(isLinked)); + } + /** * 创建Builder * diff --git a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java index 078879ad5..256dda90f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java +++ b/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工具类
@@ -519,6 +531,16 @@ public class XmlUtil { return (null == doc) ? null : doc.getDocumentElement(); } + /** + * 获取节点所在的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文本中的无效字符 * @@ -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,59 +1046,106 @@ 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,避免漏洞攻击
* see: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J diff --git a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java index 6ec91be1d..6ecc6282a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java +++ b/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 = ""// @@ -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(""// - + ""// - + "张三"// - + "12"// - + ""// - + "<昵称>Looly"// - + "14"// - + ""// - + "", // + + ""// + + "张三"// + + "12"// + + ""// + + "<昵称>Looly"// + + "14"// + + ""// + + "", // XmlUtil.toStr(doc, false)); } - + + @Test + public void mapToXmlTest2() { + // 测试List + Map map = MapBuilder.create(new LinkedHashMap()) + .put("Town", CollUtil.newArrayList("town1", "town2")) + .build(); + + Document doc = XmlUtil.mapToXml(map, "City"); + Assert.assertEquals("" + + "" + + "town1" + + "town2" + + "", + XmlUtil.toStr(doc)); + } + @Test public void readTest() { Document doc = XmlUtil.readXML("test.xml"); @@ -127,9 +142,9 @@ public class XmlUtilTest { public void mapToXmlTestWithOmitXmlDeclaration() { Map map = MapBuilder.create(new LinkedHashMap()) - .put("name", "ddatsh") - .build(); - String xml=XmlUtil.mapToXmlStr(map,true); - Assert.assertEquals(xml,"ddatsh"); - } + .put("name", "ddatsh") + .build(); + String xml = XmlUtil.mapToXmlStr(map, true); + Assert.assertEquals("ddatsh", xml); + } }