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

io.polaris.core.xml.Xml Maven / Gradle / Ivy

There is a newer version: 3.2.1
Show newest version
package io.polaris.core.xml;

import io.polaris.core.collection.Iterables;
import io.polaris.core.consts.StdConsts;
import io.polaris.core.consts.SymbolConsts;
import io.polaris.core.io.IO;
import io.polaris.core.map.Maps;
import io.polaris.core.lang.bean.Beans;
import io.polaris.core.string.Escapes;
import io.polaris.core.string.Strings;
import org.w3c.dom.*;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import javax.annotation.Nonnull;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.parsers.*;
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.XMLEncoder;
import java.io.*;
import java.util.*;

/**
 * @author Qt
 */
public class Xml {
	/**
	 * 字符串常量:XML 不间断空格转义 {@code " " -> " "}
	 */
	public static final String NBSP = " ";

	/**
	 * 字符串常量:XML And 符转义 {@code "&" -> "&"}
	 */
	public static final String AMP = "&";

	/**
	 * 字符串常量:XML 双引号转义 {@code """ -> "\""}
	 */
	public static final String QUOTE = """;

	/**
	 * 字符串常量:XML 单引号转义 {@code "&apos" -> "'"}
	 */
	public static final String APOS = "'";

	/**
	 * 字符串常量:XML 小于号转义 {@code "<" -> "<"}
	 */
	public static final String LT = "<";

	/**
	 * 字符串常量:XML 大于号转义 {@code ">" -> ">"}
	 */
	public static final String GT = ">";

	/**
	 * 在XML中无效的字符 正则
	 */
	public static final String INVALID_REGEX = "[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]";
	/**
	 * 在XML中注释的内容 正则
	 */
	public static final String COMMENT_REGEX = "(?s)";
	/**
	 * XML格式化输出默认缩进量
	 */
	public static final int INDENT_DEFAULT = 2;

	/**
	 * 默认的DocumentBuilderFactory实现
	 */
	private static String defaultDocumentBuilderFactory = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl";

	/**
	 * 是否打开命名空间支持
	 */
	private static boolean namespaceAware = true;
	/**
	 * Sax读取器工厂缓存
	 */
	private static SAXParserFactory factory;

	/**
	 * 禁用默认的DocumentBuilderFactory,禁用后如果有第三方的实现(如oracle的xdb包中的xmlparse),将会自动加载实现。
	 */
	synchronized public static void disableDefaultDocumentBuilderFactory() {
		defaultDocumentBuilderFactory = null;
	}

	/**
	 * 设置是否打开命名空间支持,默认打开
	 *
	 * @param isNamespaceAware 是否命名空间支持
	 */
	synchronized public static void setNamespaceAware(boolean isNamespaceAware) {
		namespaceAware = isNamespaceAware;
	}


	/**
	 * 读取解析XML文件
	 *
	 * @param file XML文件
	 * @return XML文档对象
	 */
	public static Document readXML(@Nonnull File file) throws IOException {
		BufferedInputStream in = null;
		try {
			file = file.getCanonicalFile();
			in = IO.getInputStream(file);
			return readXML(in);
		} finally {
			IO.close(in);
		}
	}

	/**
	 * 读取解析XML文件
* 如果给定内容以“<”开头,表示这是一个XML内容,直接读取,否则按照路径处理
* 路径可以为相对路径,也可以是绝对路径,相对路径相对于ClassPath * * @param pathOrContent 内容或路径 * @return XML文档对象 */ public static Document readXML(@Nonnull String pathOrContent) throws IOException { if (pathOrContent.startsWith("<")) { return parseXml(pathOrContent); } return readXML(IO.getInputStream(pathOrContent)); } /** * 读取解析XML文件
* 编码在XML中定义 * * @param inputStream XML流 * @return XML文档对象 */ public static Document readXML(InputStream inputStream) throws IOException { return readXML(new InputSource(inputStream)); } /** * 读取解析XML文件 * * @param reader XML流 * @return XML文档对象 */ public static Document readXML(Reader reader) throws IOException { return readXML(new InputSource(reader)); } /** * 读取解析XML文件
* 编码在XML中定义 * * @param source {@link InputSource} * @return XML文档对象 */ public static Document readXML(InputSource source) throws IOException { DocumentBuilder builder = createDocumentBuilder(); try { return builder.parse(source); } catch (SAXException e) { throw new IOException(e.getMessage(), e); } } /** * 使用Sax方式读取指定的XML
* 如果用户传入的contentHandler为{@link DefaultHandler},则其接口都会被处理 * * @param file XML源文件,使用后自动关闭 * @param contentHandler XML流处理器,用于按照Element处理xml */ public static void readBySax(File file, ContentHandler contentHandler) throws IOException { InputStream in = null; try { in = IO.getInputStream(file); readBySax(new InputSource(in), contentHandler); } finally { IO.close(in); } } /** * 使用Sax方式读取指定的XML
* 如果用户传入的contentHandler为{@link DefaultHandler},则其接口都会被处理 * * @param reader XML源Reader,使用后自动关闭 * @param contentHandler XML流处理器,用于按照Element处理xml */ public static void readBySax(Reader reader, ContentHandler contentHandler) throws IOException { try { readBySax(new InputSource(reader), contentHandler); } finally { IO.close(reader); } } /** * 使用Sax方式读取指定的XML
* 如果用户传入的contentHandler为{@link DefaultHandler},则其接口都会被处理 * * @param source XML源流,使用后自动关闭 * @param contentHandler XML流处理器,用于按照Element处理xml */ public static void readBySax(InputStream source, ContentHandler contentHandler) throws IOException { try { readBySax(new InputSource(source), contentHandler); } finally { IO.close(source); } } /** * 使用Sax方式读取指定的XML
* 如果用户传入的contentHandler为{@link DefaultHandler},则其接口都会被处理 * * @param source XML源,可以是文件、流、路径等 * @param contentHandler XML流处理器,用于按照Element处理xml */ public static void readBySax(InputSource source, ContentHandler contentHandler) throws IOException { // 1.获取解析工厂 if (null == factory) { factory = SAXParserFactory.newInstance(); factory.setValidating(false); factory.setNamespaceAware(namespaceAware); } // 2.从解析工厂获取解析器 final SAXParser parse; XMLReader reader; try { parse = factory.newSAXParser(); if (contentHandler instanceof DefaultHandler) { parse.parse(source, (DefaultHandler) contentHandler); return; } // 3.得到解读器 reader = parse.getXMLReader(); reader.setContentHandler(contentHandler); reader.parse(source); } catch (ParserConfigurationException | SAXException e) { throw new IOException(e.getMessage(), e); } } /** * 将String类型的XML转换为XML文档 * * @param xmlStr XML字符串 * @return XML文档 */ public static Document parseXml(String xmlStr) throws IOException { if (Strings.isBlank(xmlStr)) { throw new IllegalArgumentException("XML content string is empty !"); } xmlStr = cleanInvalid(xmlStr); return readXML(new StringReader(xmlStr)); } // -------------------------------------------------------------------------------------- Write /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8
* 默认非格式化输出,若想格式化请使用{@link #format(Document)} * * @param doc XML文档 * @return XML字符串 */ public static String toStr(Node doc) throws IOException { return toStr(doc, false); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8
* 默认非格式化输出,若想格式化请使用{@link #format(Document)} * * @param doc XML文档 * @return XML字符串 */ public static String toStr(Document doc) throws IOException { return toStr((Node) doc); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8 * * @param doc XML文档 * @param isPretty 是否格式化输出 * @return XML字符串 */ public static String toStr(Node doc, boolean isPretty) throws IOException { return toStr(doc, StdConsts.UTF_8, isPretty); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8 * * @param doc XML文档 * @param isPretty 是否格式化输出 * @return XML字符串 */ public static String toStr(Document doc, boolean isPretty) throws IOException { return toStr((Node) doc, isPretty); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8 * * @param doc XML文档 * @param charset 编码 * @param isPretty 是否格式化输出 * @return XML字符串 */ public static String toStr(Node doc, String charset, boolean isPretty) throws IOException { return toStr(doc, charset, isPretty, false); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8 * * @param doc XML文档 * @param charset 编码 * @param isPretty 是否格式化输出 * @return XML字符串 */ public static String toStr(Document doc, String charset, boolean isPretty) throws IOException { return toStr((Node) doc, charset, isPretty); } /** * 将XML文档转换为String
* 字符编码使用XML文档中的编码,获取不到则使用UTF-8 * * @param doc XML文档 * @param charset 编码 * @param isPretty 是否格式化输出 * @param omitXmlDeclaration 是否忽略 xml Declaration * @return XML字符串 */ public static String toStr(Node doc, String charset, boolean isPretty, boolean omitXmlDeclaration) throws IOException { final StringWriter writer = new StringWriter(); write(doc, writer, charset, isPretty ? INDENT_DEFAULT : 0, omitXmlDeclaration); return writer.toString(); } /** * 格式化XML输出 * * @param doc {@link Document} XML文档 * @return 格式化后的XML字符串 */ public static String format(Document doc) throws IOException { return toStr(doc, true); } /** * 格式化XML输出 * * @param xmlStr XML字符串 * @return 格式化后的XML字符串 */ public static String format(String xmlStr) throws IOException { return format(parseXml(xmlStr)); } /** * 将XML文档写入到文件
* 使用Document中的编码 * * @param doc XML文档 * @param absolutePath 文件绝对路径,不存在会自动创建 */ public static void toFile(Document doc, String absolutePath) throws IOException { toFile(doc, absolutePath, null); } /** * 将XML文档写入到文件
* * @param doc XML文档 * @param path 文件路径 * @param charsetName 自定义XML文件的编码,如果为{@code null} 读取XML文档中的编码,否则默认UTF-8 */ public static void toFile(Document doc, String path, String charsetName) throws IOException { if (Strings.isBlank(charsetName)) { charsetName = doc.getXmlEncoding(); } if (Strings.isBlank(charsetName)) { charsetName = StdConsts.UTF_8; } BufferedWriter writer = null; try { FileOutputStream fos = new FileOutputStream(path); OutputStreamWriter osw = new OutputStreamWriter(fos, charsetName); writer = new BufferedWriter(osw); write(doc, writer, charsetName, INDENT_DEFAULT); } finally { IO.close(writer); } } /** * 将XML文档写出 * * @param node {@link Node} XML文档节点或文档本身 * @param writer 写出的Writer,Writer决定了输出XML的编码 * @param charset 编码 * @param indent 格式化输出中缩进量,小于1表示不格式化输出 */ public static void write(Node node, Writer writer, String charset, int indent) throws IOException { 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 */ public static void write(Node node, Writer writer, String charset, int indent, boolean omitXmlDeclaration) throws IOException { 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表示不格式化输出 */ public static void write(Node node, OutputStream out, String charset, int indent) throws IOException { 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 */ public static void write(Node node, OutputStream out, String charset, int indent, boolean omitXmlDeclaration) throws IOException { 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表示不格式化输出 */ public static void transform(Source source, Result result, String charset, int indent) throws IOException { 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 */ public static void transform(Source source, Result result, String charset, int indent, boolean omitXmlDeclaration) throws IOException { final TransformerFactory factory = TransformerFactory.newInstance(); try { final Transformer xformer = factory.newTransformer(); if (indent > 0) { xformer.setOutputProperty(OutputKeys.INDENT, "yes"); //fix issue#1232@Github xformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes"); xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indent)); } if (Strings.isNotBlank(charset)) { xformer.setOutputProperty(OutputKeys.ENCODING, charset); } if (omitXmlDeclaration) { xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); } xformer.transform(source, result); } catch (TransformerConfigurationException e) { throw new IOException(e.getMessage(), e); } catch (TransformerException e) { throw new IOException(e.getMessage(), e); } } // -------------------------------------------------------------------------------------- Create /** * 创建XML文档
* 创建的XML默认是utf8编码,修改编码的过程是在toStr和toFile方法里,即XML在转为文本的时候才定义编码 * * @return XML文档 */ public static Document createXml() throws IOException { return createDocumentBuilder().newDocument(); } /** * 创建 DocumentBuilder * * @return DocumentBuilder */ public static DocumentBuilder createDocumentBuilder() throws IOException { DocumentBuilder builder; try { builder = createDocumentBuilderFactory().newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new IOException(e.getMessage(), e); } 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 (Strings.isNotEmpty(defaultDocumentBuilderFactory)) { factory = DocumentBuilderFactory.newInstance(defaultDocumentBuilderFactory, null); } else { factory = DocumentBuilderFactory.newInstance(); } // 默认打开NamespaceAware,getElementsByTagNameNS可以使用命名空间 factory.setNamespaceAware(namespaceAware); return disableXXE(factory); } /** * 创建XML文档
* 创建的XML默认是utf8编码,修改编码的过程是在toStr和toFile方法里,即XML在转为文本的时候才定义编码 * * @param rootElementName 根节点名称 * @return XML文档 */ public static Document createXml(String rootElementName) throws IOException { return createXml(rootElementName, null); } /** * 创建XML文档
* 创建的XML默认是utf8编码,修改编码的过程是在toStr和toFile方法里,即XML在转为文本的时候才定义编码 * * @param rootElementName 根节点名称 * @param namespace 命名空间,无则传null * @return XML文档 */ public static Document createXml(String rootElementName, String namespace) throws IOException { final Document doc = createXml(); doc.appendChild(null == namespace ? doc.createElement(rootElementName) : doc.createElementNS(namespace, rootElementName)); return doc; } // -------------------------------------------------------------------------------------- Function /** * 获得XML文档根节点 * * @param doc {@link Document} * @return 根节点 * @see Document#getDocumentElement() */ public static Element getRootElement(Document doc) { return (null == doc) ? null : doc.getDocumentElement(); } /** * 获取节点所在的Document * * @param node 节点 * @return {@link Document} */ public static Document getOwnerDocument(Node node) { return (node instanceof Document) ? (Document) node : node.getOwnerDocument(); } /** * 去除XML文本中的无效字符 * * @param xmlContent XML文本 * @return 当传入为null时返回null */ public static String cleanInvalid(String xmlContent) { if (xmlContent == null) { return null; } return xmlContent.replaceAll(INVALID_REGEX, SymbolConsts.EMPTY); } /** * 去除XML文本中的注释内容 * * @param xmlContent XML文本 * @return 当传入为null时返回null */ public static String cleanComment(String xmlContent) { if (xmlContent == null) { return null; } return xmlContent.replaceAll(COMMENT_REGEX, SymbolConsts.EMPTY); } /** * 根据节点名获得子节点列表 * * @param element 节点 * @param tagName 节点名,如果节点名为空(null或blank),返回所有子节点 * @return 节点列表 */ public static List getElements(Element element, String tagName) { final NodeList nodeList = Strings.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); final int length = nodeList.getLength(); if (length < 1) { return null; } 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) throws IOException { XMLEncoder xmlenc = null; try { xmlenc = new XMLEncoder(IO.getOutputStream(dest)); xmlenc.writeObject(bean); } finally { IO.close(xmlenc); } } /** * 创建XPath
* Xpath相关文章:https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html * * @return {@link XPath} */ 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 匹配返回类型的值 */ public static Element getElementByXPath(String expression, Object source) throws IOException { 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 */ public static NodeList getNodeListByXPath(String expression, Object source) throws IOException { 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 匹配返回类型的值 */ public static Node getNodeByXPath(String expression, Object source) throws IOException { 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 匹配返回类型的值 */ public static Object getByXPath(String expression, Object source, QName returnType) throws IOException { NamespaceContext nsContext = null; if (source instanceof Node) { nsContext = new UniversalNamespaceCache((Node) source, false); } return getByXPath(expression, source, returnType, nsContext); } /** * 通过XPath方式读取XML节点等信息
* Xpath相关文章:
* https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
* https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/ * * @param expression XPath表达式 * @param source 资源,可以是Docunent、Node节点等 * @param returnType 返回类型,{@link javax.xml.xpath.XPathConstants} * @param nsContext {@link NamespaceContext} * @return 匹配返回类型的值 */ public static Object getByXPath(String expression, Object source, QName returnType, NamespaceContext nsContext) throws IOException { final XPath xPath = createXPath(); if (null != nsContext) { xPath.setNamespaceContext(nsContext); } try { if (source instanceof InputSource) { return xPath.evaluate(expression, (InputSource) source, returnType); } else { return xPath.evaluate(expression, source, returnType); } } catch (XPathExpressionException e) { throw new IOException(e.getMessage(), e); } } /** * 转义XML特殊字符: * *
	 * & (ampersand) 替换为 &amp;
	 * < (小于) 替换为 &lt;
	 * > (大于) 替换为 &gt;
	 * " (双引号) 替换为 &quot;
	 * 
* * @param string 被替换的字符串 * @return 替换后的字符串 */ public static String escape(String string) { return Escapes.escapeHtml4(string); } /** * 反转义XML特殊字符: * * @param string 被替换的字符串 * @return 替换后的字符串 */ public static String unescape(String string) { return Escapes.unescapeHtml4(string); } /** * XML格式字符串转换为Map * * @param xmlStr XML字符串 * @return XML数据转换后的Map */ public static Map xmlToMap(String xmlStr) throws IOException { return xmlToMap(xmlStr, new HashMap<>()); } /** * XML转Java Bean * * @param bean类型 * @param node XML节点 * @param bean bean类 * @return bean */ public static T xmlToBean(Node node, Class bean) { final Map map = xmlToMap(node); if (null != map && map.size() == 1) { final String simpleName = bean.getSimpleName(); if (map.containsKey(simpleName)) { // 只有key和bean的名称匹配时才做单一对象转换 return Beans.copyBean(map.get(simpleName), bean); } } return Beans.copyBean(map, bean); } /** * XML格式字符串转换为Map * * @param node XML节点 * @return XML数据转换后的Map */ public static Map xmlToMap(Node node) { return xmlToMap(node, new HashMap<>()); } /** * XML格式字符串转换为Map
* 只支持第一级别的XML,不支持多级XML * * @param xmlStr XML字符串 * @param result 结果Map类型 * @return XML数据转换后的Map */ public static Map xmlToMap(String xmlStr, Map result) throws IOException { 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 */ @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 (map != null && !map.isEmpty()) { 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(), Iterables.asList(value, newValue)); } } else { result.put(childEle.getNodeName(), newValue); } } } return result; } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @return XML格式的字符串 */ public static String mapToXmlStr(Map data) throws IOException { return toStr(mapToXml(data, "xml")); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML格式的字符串 */ public static String mapToXmlStr(Map data, boolean omitXmlDeclaration) throws IOException { return toStr(mapToXml(data, "xml"), StdConsts.UTF_8, false, omitXmlDeclaration); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @return XML格式的字符串 */ public static String mapToXmlStr(Map data, String rootName) throws IOException { return toStr(mapToXml(data, rootName)); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @return XML格式的字符串 */ public static String mapToXmlStr(Map data, String rootName, String namespace) throws IOException { return toStr(mapToXml(data, rootName, namespace)); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML格式的字符串 */ public static String mapToXmlStr(Map data, String rootName, String namespace, boolean omitXmlDeclaration) throws IOException { return toStr(mapToXml(data, rootName, namespace), StdConsts.UTF_8, false, omitXmlDeclaration); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @param isPretty 是否格式化输出 * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML格式的字符串 */ public static String mapToXmlStr(Map data, String rootName, String namespace, boolean isPretty, boolean omitXmlDeclaration) throws IOException { return toStr(mapToXml(data, rootName, namespace), StdConsts.UTF_8, isPretty, omitXmlDeclaration); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @param charset 编码 * @param isPretty 是否格式化输出 * @param omitXmlDeclaration 是否输出 xml Declaration * @return XML格式的字符串 */ public static String mapToXmlStr(Map data, String rootName, String namespace, String charset, boolean isPretty, boolean omitXmlDeclaration) throws IOException { return toStr(mapToXml(data, rootName, namespace), charset, isPretty, omitXmlDeclaration); } /** * 将Map转换为XML * * @param data Map类型数据 * @param rootName 根节点名 * @return XML */ public static Document mapToXml(Map data, String rootName) throws IOException { return mapToXml(data, rootName, null); } /** * 将Map转换为XML * * @param data Map类型数据 * @param rootName 根节点名 * @param namespace 命名空间,可以为null * @return XML */ public static Document mapToXml(Map data, String rootName, String namespace) throws IOException { final Document doc = createXml(); final Element root = appendChild(doc, rootName, namespace); appendMap(doc, root, data); return doc; } /** * 将Bean转换为XML * * @param bean Bean对象 * @return XML */ public static Document beanToXml(Object bean) throws IOException { return beanToXml(bean, null); } /** * 将Bean转换为XML * * @param bean Bean对象 * @param namespace 命名空间,可以为null * @return XML */ public static Document beanToXml(Object bean, String namespace) throws IOException { return beanToXml(bean, namespace, false); } /** * 将Bean转换为XML * * @param bean Bean对象 * @param namespace 命名空间,可以为null * @param ignoreNull 忽略值为{@code null}的属性 * @return XML */ public static Document beanToXml(Object bean, String namespace, boolean ignoreNull) throws IOException { if (null == bean) { return null; } return mapToXml(Beans.copyBean(bean, false, ignoreNull), bean.getClass().getSimpleName(), namespace); } /** * 给定节点是否为{@link Element} 类型节点 * * @param node 节点 * @return 是否为{@link Element} 类型节点 */ public static boolean isElement(Node node) { return (null != node) && Node.ELEMENT_NODE == node.getNodeType(); } /** * 在已有节点上创建子节点 * * @param node 节点 * @param tagName 标签名 * @return 子节点 */ public static Element appendChild(Node node, String tagName) { return appendChild(node, tagName, null); } /** * 在已有节点上创建子节点 * * @param node 节点 * @param tagName 标签名 * @param namespace 命名空间,无传null * @return 子节点 */ public static Element appendChild(Node node, String tagName, String namespace) { 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 子节点 */ public static Node appendText(Node node, CharSequence text) { return appendText(getOwnerDocument(node), node, text); } /** * 追加数据子节点,可以是Map、集合、文本 * * @param node 节点 * @param data 数据 */ public static void append(Node node, Object data) { append(getOwnerDocument(node), node, data); } // ---------------------------------------------------------------------------------------- Private method start /** * 追加数据子节点,可以是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 node 当前节点 * @param data Map类型数据 */ @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); } } }); } /** * 追加集合节点 * * @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节点 */ private static Node appendText(Document doc, Node node, CharSequence text) { return node.appendChild(doc.createTextNode(text.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; } /** * 全局命名空间上下文
* 见:https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/ */ public static class UniversalNamespaceCache implements NamespaceContext { private static final String DEFAULT_NS = "DEFAULT"; private final Map prefixUri = new HashMap<>(); /** * This constructor parses the document and stores all namespaces it can * find. If toplevelOnly is true, only namespaces in the root are used. * * @param node source Node * @param toplevelOnly restriction of the search to enhance performance */ public UniversalNamespaceCache(Node node, boolean toplevelOnly) { examineNode(node.getFirstChild(), toplevelOnly); } /** * A single node is read, the namespace attributes are extracted and stored. * * @param node to examine * @param attributesOnly, if true no recursion happens */ private void examineNode(Node node, boolean attributesOnly) { final NamedNodeMap attributes = node.getAttributes(); if (null != attributes) { final int length = attributes.getLength(); for (int i = 0; i < length; i++) { Node attribute = attributes.item(i); storeAttribute(attribute); } } if (false == attributesOnly) { final NodeList childNodes = node.getChildNodes(); //noinspection ConstantConditions if (null != childNodes) { Node item; final int childLength = childNodes.getLength(); for (int i = 0; i < childLength; i++) { item = childNodes.item(i); if (item.getNodeType() == Node.ELEMENT_NODE) { examineNode(item, false); } } } } } /** * This method looks at an attribute and stores it, if it is a namespace * attribute. * * @param attribute to examine */ private void storeAttribute(Node attribute) { if (null == attribute) { return; } // examine the attributes in namespace xmlns if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attribute.getNamespaceURI())) { // Default namespace xmlns="uri goes here" if (XMLConstants.XMLNS_ATTRIBUTE.equals(attribute.getNodeName())) { prefixUri.put(DEFAULT_NS, attribute.getNodeValue()); } else { // The defined prefixes are stored here prefixUri.put(attribute.getLocalName(), attribute.getNodeValue()); } } } /** * This method is called by XPath. It returns the default namespace, if the * prefix is null or "". * * @param prefix to search for * @return uri */ @Override public String getNamespaceURI(String prefix) { if (prefix == null || XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { return prefixUri.get(DEFAULT_NS); } else { return prefixUri.get(prefix); } } /** * This method is not needed in this context, but can be implemented in a * similar way. */ @Override public String getPrefix(String namespaceURI) { return (Maps.inverse(prefixUri)).get(namespaceURI); } @Override public Iterator getPrefixes(String namespaceURI) { // Not implemented return null; } } }