All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dahuatech.hutool.core.util.XmlUtil Maven / Gradle / Ivy

There is a newer version: 1.0.13.7
Show newest version
package com.dahuatech.hutool.core.util;

import com.dahuatech.hutool.core.collection.CollUtil;
import com.dahuatech.hutool.core.exceptions.UtilException;
import com.dahuatech.hutool.core.io.FileUtil;
import com.dahuatech.hutool.core.io.IoUtil;
import com.dahuatech.hutool.core.lang.Assert;
import com.dahuatech.hutool.core.map.MapUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

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.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * XML工具类
* 此工具使用w3c dom工具,不需要依赖第三方包。
* 工具类封装了XML文档的创建、读取、写出和部分XML操作 * * @author xiaoleilu */ public class XmlUtil { /** 在XML中无效的字符 正则 */ public static final String INVALID_REGEX = "[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]"; /** XML格式化输出默认缩进量 */ public static final int INDENT_DEFAULT = 2; /** 默认的DocumentBuilderFactory实现 */ private static String defaultDocumentBuilderFactory = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"; /** 禁用默认的DocumentBuilderFactory,禁用后如果有第三方的实现(如oracle的xdb包中的xmlparse),将会自动加载实现。 */ public static synchronized void disableDefaultDocumentBuilderFactory() { defaultDocumentBuilderFactory = null; } // -------------------------------------------------------------------------------------- Read /** * 读取解析XML文件 * * @param file XML文件 * @return XML文档对象 */ public static Document readXML(File file) { Assert.notNull(file, "Xml file is null !"); if (false == file.exists()) { throw new UtilException("File [{}] not a exist!", file.getAbsolutePath()); } if (false == file.isFile()) { throw new UtilException("[{}] not a file!", file.getAbsolutePath()); } try { file = file.getCanonicalFile(); } catch (IOException e) { // ignore } BufferedInputStream in = null; try { in = FileUtil.getInputStream(file); return readXML(in); } finally { IoUtil.close(in); } } /** * 读取解析XML文件
* 如果给定内容以“<”开头,表示这是一个XML内容,直接读取,否则按照路径处理
* 路径可以为相对路径,也可以是绝对路径,相对路径相对于ClassPath * * @param pathOrContent 内容或路径 * @return XML文档对象 * @since 3.0.9 */ public static Document readXML(String pathOrContent) { if (StrUtil.startWith(pathOrContent, '<')) { return parseXml(pathOrContent); } return readXML(FileUtil.file(pathOrContent)); } /** * 读取解析XML文件
* 编码在XML中定义 * * @param inputStream XML流 * @return XML文档对象 * @throws UtilException IO异常或转换异常 * @since 3.0.9 */ public static Document readXML(InputStream inputStream) throws UtilException { return readXML(new InputSource(inputStream)); } /** * 读取解析XML文件 * * @param reader XML流 * @return XML文档对象 * @throws UtilException IO异常或转换异常 * @since 3.0.9 */ public static Document readXML(Reader reader) throws UtilException { return readXML(new InputSource(reader)); } /** * 读取解析XML文件
* 编码在XML中定义 * * @param source {@link InputSource} * @return XML文档对象 * @since 3.0.9 */ public static Document readXML(InputSource source) { final DocumentBuilder builder = createDocumentBuilder(); try { return builder.parse(source); } catch (Exception e) { throw new UtilException(e, "Parse XML from stream error!"); } } /** * 将String类型的XML转换为XML文档 * * @param xmlStr XML字符串 * @return XML文档 */ public static Document parseXml(String xmlStr) { if (StrUtil.isBlank(xmlStr)) { throw new IllegalArgumentException("XML content string is empty !"); } xmlStr = cleanInvalid(xmlStr); return readXML(new InputSource(StrUtil.getReader(xmlStr))); } /** * 从XML中读取对象 Reads serialized object from the XML file. * * @param 对象类型 * @param source XML文件 * @return 对象 */ public static T readObjectFromXml(File source) { return readObjectFromXml(new InputSource(FileUtil.getInputStream(source))); } /** * 从XML中读取对象 Reads serialized object from the XML file. * * @param 对象类型 * @param xmlStr XML内容 * @return 对象 * @since 3.2.0 */ public static T readObjectFromXml(String xmlStr) { return readObjectFromXml(new InputSource(StrUtil.getReader(xmlStr))); } /** * 从XML中读取对象 Reads serialized object from the XML file. * * @param 对象类型 * @param source {@link InputSource} * @return 对象 * @since 3.2.0 */ @SuppressWarnings("unchecked") public static T readObjectFromXml(InputSource source) { Object result; XMLDecoder xmldec = null; try { xmldec = new XMLDecoder(source); result = xmldec.readObject(); } finally { IoUtil.close(xmldec); } return (T) result; } // -------------------------------------------------------------------------------------- Write /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8
* 默认非格式化输出,若想格式化请使用{@link #format(Document)} * * @param doc XML文档 * @return XML字符串 */ public static String toStr(Document doc) { return toStr(doc, false); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8 * * @param doc XML文档 * @param isPretty 是否格式化输出 * @return XML字符串 * @since 3.0.9 */ public static String toStr(Document doc, boolean isPretty) { return toStr(doc, CharsetUtil.UTF_8, isPretty); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8 * * @param doc XML文档 * @param charset 编码 * @param isPretty 是否格式化输出 * @return XML字符串 * @since 3.0.9 */ public static String toStr(Document doc, String charset, boolean isPretty) { return toStr(doc, charset, isPretty, false); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8 * * @param doc XML文档 * @param charset 编码 * @param isPretty 是否格式化输出 * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML字符串 * @since 5.1.2 */ public static String toStr( Document doc, String charset, boolean isPretty, boolean omitXmlDeclaration) { final StringWriter writer = StrUtil.getWriter(); try { write(doc, writer, charset, isPretty ? INDENT_DEFAULT : 0, omitXmlDeclaration); } catch (Exception e) { throw new UtilException(e, "Trans xml document to string error!"); } return writer.toString(); } /** * 格式化XML输出 * * @param doc {@link Document} XML文档 * @return 格式化后的XML字符串 * @since 4.4.5 */ public static String format(Document doc) { return toStr(doc, true); } /** * 格式化XML输出 * * @param xmlStr XML字符串 * @return 格式化后的XML字符串 * @since 4.4.5 */ public static String format(String xmlStr) { return format(parseXml(xmlStr)); } /** * 将XML文档写入到文件
* 使用Document中的编码 * * @param doc XML文档 * @param absolutePath 文件绝对路径,不存在会自动创建 */ public static void toFile(Document doc, String absolutePath) { toFile(doc, absolutePath, null); } /** * 将XML文档写入到文件
* * @param doc XML文档 * @param path 文件路径绝对路径或相对ClassPath路径,不存在会自动创建 * @param charset 自定义XML文件的编码,如果为{@code null} 读取XML文档中的编码,否则默认UTF-8 */ public static void toFile(Document doc, String path, String charset) { if (StrUtil.isBlank(charset)) { charset = doc.getXmlEncoding(); } if (StrUtil.isBlank(charset)) { charset = CharsetUtil.UTF_8; } BufferedWriter writer = null; try { writer = FileUtil.getWriter(path, charset, false); write(doc, writer, charset, INDENT_DEFAULT); } finally { IoUtil.close(writer); } } /** * 将XML文档写出 * * @param node {@link Node} XML文档节点或文档本身 * @param writer 写出的Writer,Writer决定了输出XML的编码 * @param charset 编码 * @param indent 格式化输出中缩进量,小于1表示不格式化输出 * @since 3.0.9 */ public static void write(Node node, Writer writer, String charset, int indent) { transform(new DOMSource(node), new StreamResult(writer), charset, indent); } /** * 将XML文档写出 * * @param node {@link Node} XML文档节点或文档本身 * @param writer 写出的Writer,Writer决定了输出XML的编码 * @param charset 编码 * @param indent 格式化输出中缩进量,小于1表示不格式化输出 * @param omitXmlDeclaration 是否输出 xml Declaration * @since 5.1.2 */ public static void write( Node node, Writer writer, String charset, int indent, boolean omitXmlDeclaration) { transform(new DOMSource(node), new StreamResult(writer), charset, indent, omitXmlDeclaration); } /** * 将XML文档写出 * * @param node {@link Node} XML文档节点或文档本身 * @param out 写出的Writer,Writer决定了输出XML的编码 * @param charset 编码 * @param indent 格式化输出中缩进量,小于1表示不格式化输出 * @since 4.0.8 */ public static void write(Node node, OutputStream out, String charset, int indent) { transform(new DOMSource(node), new StreamResult(out), charset, indent); } /** * 将XML文档写出 * * @param node {@link Node} XML文档节点或文档本身 * @param out 写出的Writer,Writer决定了输出XML的编码 * @param charset 编码 * @param indent 格式化输出中缩进量,小于1表示不格式化输出 * @param omitXmlDeclaration 是否输出 xml Declaration * @since 5.1.2 */ public static void write( Node node, OutputStream out, String charset, int indent, boolean omitXmlDeclaration) { transform(new DOMSource(node), new StreamResult(out), charset, indent, omitXmlDeclaration); } /** * 将XML文档写出
* 格式化输出逻辑参考:https://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java * * @param source 源 * @param result 目标 * @param charset 编码 * @param indent 格式化输出中缩进量,小于1表示不格式化输出 * @since 4.0.9 */ public static void transform(Source source, Result result, String charset, int indent) { transform(source, result, charset, indent, false); } /** * 将XML文档写出
* 格式化输出逻辑参考:https://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java * * @param source 源 * @param result 目标 * @param charset 编码 * @param indent 格式化输出中缩进量,小于1表示不格式化输出 * @param omitXmlDeclaration 是否输出 xml Declaration * @since 5.1.2 */ public static void transform( Source source, Result result, String charset, int indent, boolean omitXmlDeclaration) { final TransformerFactory factory = TransformerFactory.newInstance(); try { final Transformer xformer = factory.newTransformer(); if (indent > 0) { xformer.setOutputProperty(OutputKeys.INDENT, "yes"); xformer.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", String.valueOf(indent)); } if (StrUtil.isNotBlank(charset)) { xformer.setOutputProperty(OutputKeys.ENCODING, charset); } if (omitXmlDeclaration) { xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); } xformer.transform(source, result); } catch (Exception e) { throw new UtilException(e, "Trans xml document to string error!"); } } // -------------------------------------------------------------------------------------- Create /** * 创建XML文档
* 创建的XML默认是utf8编码,修改编码的过程是在toStr和toFile方法里,即XML在转为文本的时候才定义编码 * * @return XML文档 * @since 4.0.8 */ public static Document createXml() { return createDocumentBuilder().newDocument(); } /** * 创建 DocumentBuilder * * @return DocumentBuilder * @since 4.1.2 */ public static DocumentBuilder createDocumentBuilder() { DocumentBuilder builder; try { builder = createDocumentBuilderFactory().newDocumentBuilder(); } catch (Exception e) { throw new UtilException(e, "Create xml document error!"); } return builder; } /** * 创建{@link DocumentBuilderFactory} * *

默认使用"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"
* 如果使用第三方实现,请调用{@link #disableDefaultDocumentBuilderFactory()} * * @return {@link DocumentBuilderFactory} */ public static DocumentBuilderFactory createDocumentBuilderFactory() { final DocumentBuilderFactory factory; if (StrUtil.isNotEmpty(defaultDocumentBuilderFactory)) { factory = DocumentBuilderFactory.newInstance(defaultDocumentBuilderFactory, null); } else { factory = DocumentBuilderFactory.newInstance(); } return disableXXE(factory); } /** * 创建XML文档
* 创建的XML默认是utf8编码,修改编码的过程是在toStr和toFile方法里,即XML在转为文本的时候才定义编码 * * @param rootElementName 根节点名称 * @return XML文档 */ public static Document createXml(String rootElementName) { return createXml(rootElementName, null); } /** * 创建XML文档
* 创建的XML默认是utf8编码,修改编码的过程是在toStr和toFile方法里,即XML在转为文本的时候才定义编码 * * @param rootElementName 根节点名称 * @param namespace 命名空间,无则传null * @return XML文档 * @since 5.0.4 */ public static Document createXml(String rootElementName, String namespace) { final Document doc = createXml(); doc.appendChild( null == namespace ? doc.createElement(rootElementName) : doc.createElementNS(rootElementName, namespace)); return doc; } // -------------------------------------------------------------------------------------- Function /** * 获得XML文档根节点 * * @param doc {@link Document} * @return 根节点 * @see Document#getDocumentElement() * @since 3.0.8 */ public static Element getRootElement(Document doc) { return (null == doc) ? null : doc.getDocumentElement(); } /** * 去除XML文本中的无效字符 * * @param xmlContent XML文本 * @return 当传入为null时返回null */ public static String cleanInvalid(String xmlContent) { if (xmlContent == null) { return null; } return xmlContent.replaceAll(INVALID_REGEX, ""); } /** * 根据节点名获得子节点列表 * * @param element 节点 * @param tagName 节点名,如果节点名为空(null或blank),返回所有子节点 * @return 节点列表 */ public static List getElements(Element element, String tagName) { final NodeList nodeList = StrUtil.isBlank(tagName) ? element.getChildNodes() : element.getElementsByTagName(tagName); return transElements(element, nodeList); } /** * 根据节点名获得第一个子节点 * * @param element 节点 * @param tagName 节点名 * @return 节点 */ public static Element getElement(Element element, String tagName) { final NodeList nodeList = element.getElementsByTagName(tagName); if (nodeList == null || nodeList.getLength() < 1) { return null; } int length = nodeList.getLength(); for (int i = 0; i < length; i++) { Element childEle = (Element) nodeList.item(i); if (childEle == null || childEle.getParentNode() == element) { return childEle; } } return null; } /** * 根据节点名获得第一个子节点 * * @param element 节点 * @param tagName 节点名 * @return 节点中的值 */ public static String elementText(Element element, String tagName) { Element child = getElement(element, tagName); return child == null ? null : child.getTextContent(); } /** * 根据节点名获得第一个子节点 * * @param element 节点 * @param tagName 节点名 * @param defaultValue 默认值 * @return 节点中的值 */ public static String elementText(Element element, String tagName, String defaultValue) { Element child = getElement(element, tagName); return child == null ? defaultValue : child.getTextContent(); } /** * 将NodeList转换为Element列表 * * @param nodeList NodeList * @return Element列表 */ public static List transElements(NodeList nodeList) { return transElements(null, nodeList); } /** * 将NodeList转换为Element列表
* 非Element节点将被忽略 * * @param parentEle 父节点,如果指定将返回此节点的所有直接子节点,null返回所有就节点 * @param nodeList NodeList * @return Element列表 */ public static List transElements(Element parentEle, NodeList nodeList) { int length = nodeList.getLength(); final ArrayList elements = new ArrayList<>(length); Node node; Element element; for (int i = 0; i < length; i++) { node = nodeList.item(i); if (Node.ELEMENT_NODE == node.getNodeType()) { element = (Element) nodeList.item(i); if (parentEle == null || element.getParentNode() == parentEle) { elements.add(element); } } } return elements; } /** * 将可序列化的对象转换为XML写入文件,已经存在的文件将被覆盖
* Writes serializable object to a XML file. Existing file will be overwritten * * @param dest 目标文件 * @param bean 对象 */ public static void writeObjectAsXml(File dest, Object bean) { XMLEncoder xmlenc = null; try { xmlenc = new XMLEncoder(FileUtil.getOutputStream(dest)); xmlenc.writeObject(bean); } finally { // 关闭XMLEncoder会相应关闭OutputStream IoUtil.close(xmlenc); } } /** * 创建XPath
* Xpath相关文章:https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html * * @return {@link XPath} * @since 3.2.0 */ public static XPath createXPath() { return XPathFactory.newInstance().newXPath(); } /** * 通过XPath方式读取XML节点等信息
* Xpath相关文章:https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html * * @param expression XPath表达式 * @param source 资源,可以是Docunent、Node节点等 * @return 匹配返回类型的值 * @since 4.0.9 */ public static Element getElementByXPath(String expression, Object source) { return (Element) getNodeByXPath(expression, source); } /** * 通过XPath方式读取XML的NodeList
* Xpath相关文章:https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html * * @param expression XPath表达式 * @param source 资源,可以是Docunent、Node节点等 * @return NodeList * @since 4.0.9 */ public static NodeList getNodeListByXPath(String expression, Object source) { return (NodeList) getByXPath(expression, source, XPathConstants.NODESET); } /** * 通过XPath方式读取XML节点等信息
* Xpath相关文章:https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html * * @param expression XPath表达式 * @param source 资源,可以是Docunent、Node节点等 * @return 匹配返回类型的值 * @since 4.0.9 */ public static Node getNodeByXPath(String expression, Object source) { return (Node) getByXPath(expression, source, XPathConstants.NODE); } /** * 通过XPath方式读取XML节点等信息
* Xpath相关文章:https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html * * @param expression XPath表达式 * @param source 资源,可以是Docunent、Node节点等 * @param returnType 返回类型,{@link javax.xml.xpath.XPathConstants} * @return 匹配返回类型的值 * @since 3.2.0 */ public static Object getByXPath(String expression, Object source, QName returnType) { final XPath xPath = createXPath(); try { if (source instanceof InputSource) { return xPath.evaluate(expression, (InputSource) source, returnType); } else { return xPath.evaluate(expression, source, returnType); } } catch (XPathExpressionException e) { throw new UtilException(e); } } /** * 转义XML特殊字符: * *

   * & (ampersand) 替换为 &amp;
   * < (小于) 替换为 &lt;
   * > (大于) 替换为 &gt;
   * " (双引号) 替换为 &quot;
   * 
* * @param string 被替换的字符串 * @return 替换后的字符串 * @since 4.0.8 */ public static String escape(String string) { return EscapeUtil.escape(string); } /** * 反转义XML特殊字符: * * @param string 被替换的字符串 * @return 替换后的字符串 * @see EscapeUtil#unescape(String) * @since 5.0.6 */ public static String unescape(String string) { return EscapeUtil.unescape(string); } /** * XML格式字符串转换为Map * * @param xmlStr XML字符串 * @return XML数据转换后的Map * @since 4.0.8 */ public static Map xmlToMap(String xmlStr) { return xmlToMap(xmlStr, new HashMap()); } /** * XML格式字符串转换为Map * * @param node XML节点 * @return XML数据转换后的Map * @since 4.0.8 */ public static Map xmlToMap(Node node) { return xmlToMap(node, new HashMap()); } /** * XML格式字符串转换为Map
* 只支持第一级别的XML,不支持多级XML * * @param xmlStr XML字符串 * @param result 结果Map类型 * @return XML数据转换后的Map * @since 4.0.8 */ public static Map xmlToMap(String xmlStr, Map result) { final Document doc = parseXml(xmlStr); final Element root = getRootElement(doc); root.normalize(); return xmlToMap(root, result); } /** * XML节点转换为Map * * @param node XML节点 * @param result 结果Map类型 * @return XML数据转换后的Map * @since 4.0.8 */ @SuppressWarnings("unchecked") public static Map xmlToMap(Node node, Map result) { if (null == result) { result = new HashMap<>(); } final NodeList nodeList = node.getChildNodes(); final int length = nodeList.getLength(); Node childNode; Element childEle; for (int i = 0; i < length; ++i) { childNode = nodeList.item(i); if (false == isElement(childNode)) { continue; } childEle = (Element) childNode; final Object value = result.get(childEle.getNodeName()); Object newValue; if (childEle.hasChildNodes()) { // 子节点继续递归遍历 final Map map = xmlToMap(childEle); if (MapUtil.isNotEmpty(map)) { newValue = map; } else { newValue = childEle.getTextContent(); } } else { newValue = childEle.getTextContent(); } if (null != newValue) { if (null != value) { if (value instanceof List) { ((List) value).add(newValue); } else { result.put(childEle.getNodeName(), CollUtil.newArrayList(value, newValue)); } } else { result.put(childEle.getNodeName(), newValue); } } } return result; } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @return XML格式的字符串 * @since 5.1.2 */ public static String mapToXmlStr(Map data) { return toStr(mapToXml(data, "xml")); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML格式的字符串 * @since 5.1.2 */ public static String mapToXmlStr(Map data, boolean omitXmlDeclaration) { return toStr(mapToXml(data, "xml"), CharsetUtil.UTF_8, false, omitXmlDeclaration); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @return XML格式的字符串 * @since 4.0.8 */ public static String mapToXmlStr(Map data, String rootName) { return toStr(mapToXml(data, rootName)); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @return XML格式的字符串 * @since 5.0.4 */ public static String mapToXmlStr(Map data, String rootName, String namespace) { return toStr(mapToXml(data, rootName, namespace)); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML格式的字符串 * @since 5.1.2 */ public static String mapToXmlStr( Map data, String rootName, String namespace, boolean omitXmlDeclaration) { return toStr(mapToXml(data, rootName, namespace), CharsetUtil.UTF_8, false, omitXmlDeclaration); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @param isPretty 是否格式化输出 * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML格式的字符串 * @since 5.1.2 */ public static String mapToXmlStr( Map data, String rootName, String namespace, boolean isPretty, boolean omitXmlDeclaration) { return toStr(mapToXml(data, rootName, namespace), CharsetUtil.UTF_8, isPretty); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @param charset 编码 * @param isPretty 是否格式化输出 * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML格式的字符串 * @since 5.1.2 */ public static String mapToXmlStr( Map data, String rootName, String namespace, String charset, boolean isPretty, boolean omitXmlDeclaration) { return toStr(mapToXml(data, rootName, namespace), charset, isPretty, omitXmlDeclaration); } /** * 将Map转换为XML * * @param data Map类型数据 * @param rootName 根节点名 * @return XML * @since 4.0.9 */ public static Document mapToXml(Map data, String rootName) { return mapToXml(data, rootName, null); } /** * 将Map转换为XML * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @return XML * @since 5.0.4 */ public static Document mapToXml(Map data, String rootName, String namespace) { final Document doc = createXml(); final Element root = appendChild(doc, rootName, namespace); mapToXml(doc, root, data); return doc; } /** * 给定节点是否为{@link Element} 类型节点 * * @param node 节点 * @return 是否为{@link Element} 类型节点 * @since 4.0.8 */ public static boolean isElement(Node node) { return (null != node) && Node.ELEMENT_NODE == node.getNodeType(); } /** * 在已有节点上创建子节点 * * @param node 节点 * @param tagName 标签名 * @return 子节点 * @since 4.0.9 */ public static Element appendChild(Node node, String tagName) { return appendChild(node, tagName, null); } /** * 在已有节点上创建子节点 * * @param node 节点 * @param tagName 标签名 * @param namespace 命名空间,无传null * @return 子节点 * @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 Element child = (null == namespace) ? doc.createElement(tagName) : doc.createElementNS(namespace, tagName); node.appendChild(child); return child; } // ---------------------------------------------------------------------------------------- // Private method start /** * 将Map转换为XML格式的字符串 * * @param doc {@link Document} * @param element 节点 * @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())); } } } else if (value instanceof Map) { // 如果值依旧为map,递归继续 mapToXml(doc, filedEle, (Map) value); } else { filedEle.appendChild(doc.createTextNode(value.toString())); } } } /** * 关闭XXE,避免漏洞攻击
* see: * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J * * @param dbf DocumentBuilderFactory * @return DocumentBuilderFactory */ private static DocumentBuilderFactory disableXXE(DocumentBuilderFactory dbf) { String feature; try { // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity // attacks are prevented // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl feature = "http://apache.org/xml/features/disallow-doctype-decl"; dbf.setFeature(feature, true); // If you can't completely disable DTDs, then at least do the following: // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities // JDK7+ - http://xml.org/sax/features/external-general-entities feature = "http://xml.org/sax/features/external-general-entities"; dbf.setFeature(feature, false); // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities // JDK7+ - http://xml.org/sax/features/external-parameter-entities feature = "http://xml.org/sax/features/external-parameter-entities"; dbf.setFeature(feature, false); // Disable external DTDs as well feature = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; dbf.setFeature(feature, false); // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); } catch (ParserConfigurationException e) { // ignore } return dbf; } // ---------------------------------------------------------------------------------------- // Private method end }