mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add xml
This commit is contained in:
parent
f901c9419d
commit
0760e44c12
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import org.dromara.hutool.core.exception.HutoolException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
/**
|
||||
* {@link DocumentBuilder} 工具类
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class DocumentBuilderUtil {
|
||||
|
||||
/**
|
||||
* 创建 DocumentBuilder
|
||||
*
|
||||
* @param namespaceAware 是否打开命名空间支持
|
||||
* @return DocumentBuilder
|
||||
*/
|
||||
public static DocumentBuilder createDocumentBuilder(final boolean namespaceAware) {
|
||||
final DocumentBuilder builder;
|
||||
try {
|
||||
builder = createDocumentBuilderFactory(namespaceAware).newDocumentBuilder();
|
||||
} catch (final Exception e) {
|
||||
throw new HutoolException(e, "Create xml document error!");
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建{@link DocumentBuilderFactory}
|
||||
* <p>
|
||||
* 默认使用"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"<br>
|
||||
* </p>
|
||||
*
|
||||
* @param namespaceAware 是否打开命名空间支持
|
||||
* @return {@link DocumentBuilderFactory}
|
||||
*/
|
||||
public static DocumentBuilderFactory createDocumentBuilderFactory(final boolean namespaceAware) {
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
|
||||
// 默认打开NamespaceAware,getElementsByTagNameNS可以使用命名空间
|
||||
factory.setNamespaceAware(namespaceAware);
|
||||
return XXEUtil.disableXXE(factory);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
/**
|
||||
* {@link SAXParserFactory} 工具
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class SAXParserFactoryUtil {
|
||||
|
||||
/**
|
||||
* Sax读取器工厂缓存
|
||||
*/
|
||||
private static volatile SAXParserFactory factory;
|
||||
|
||||
/**
|
||||
* 获取全局{@link SAXParserFactory}<br>
|
||||
* <ul>
|
||||
* <li>默认不验证</li>
|
||||
* <li>默认打开命名空间支持</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return {@link SAXParserFactory}
|
||||
*/
|
||||
public static SAXParserFactory getFactory() {
|
||||
if (null == factory) {
|
||||
synchronized (SAXParserFactoryUtil.class) {
|
||||
if (null == factory) {
|
||||
factory = createFactory(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建{@link SAXParserFactory}
|
||||
*
|
||||
* @param validating 是否验证
|
||||
* @param namespaceAware 是否打开命名空间支持
|
||||
* @return {@link SAXParserFactory}
|
||||
*/
|
||||
public static SAXParserFactory createFactory(final boolean validating, final boolean namespaceAware) {
|
||||
final SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||
factory.setValidating(validating);
|
||||
factory.setNamespaceAware(namespaceAware);
|
||||
|
||||
return XXEUtil.disableXXE(factory);
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import org.dromara.hutool.core.map.BiMap;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* 全局命名空间上下文<br>
|
||||
* 见:https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class UniversalNamespaceCache implements NamespaceContext {
|
||||
private static final String DEFAULT_NS = "DEFAULT";
|
||||
private final BiMap<String, String> prefixUri = new BiMap<>(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(final Node node, final 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(final Node node, final boolean attributesOnly) {
|
||||
final NamedNodeMap attributes = node.getAttributes();
|
||||
if (null != attributes) {
|
||||
final int length = attributes.getLength();
|
||||
for (int i = 0; i < length; i++) {
|
||||
final Node attribute = attributes.item(i);
|
||||
storeAttribute(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
if (!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(final 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(final 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(final String namespaceURI) {
|
||||
return prefixUri.getInverse().get(namespaceURI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> getPrefixes(final String namespaceURI) {
|
||||
// Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
132
hutool-core/src/main/java/org/dromara/hutool/core/xml/XPathUtil.java
Executable file
132
hutool-core/src/main/java/org/dromara/hutool/core/xml/XPathUtil.java
Executable file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import org.dromara.hutool.core.exception.HutoolException;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
/**
|
||||
* {@link XPath}相关工具类
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class XPathUtil {
|
||||
|
||||
/**
|
||||
* 创建XPath<br>
|
||||
* Xpath相关文章:<a href="https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html">https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html</a>
|
||||
*
|
||||
* @return {@link XPath}
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static XPath createXPath() {
|
||||
return XPathFactory.newInstance().newXPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过XPath方式读取XML节点等信息<br>
|
||||
* Xpath相关文章:<a href="https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html">https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html</a>
|
||||
*
|
||||
* @param expression XPath表达式
|
||||
* @param source 资源,可以是Docunent、Node节点等
|
||||
* @return 匹配返回类型的值
|
||||
* @since 4.0.9
|
||||
*/
|
||||
public static Element getElementByXPath(final String expression, final Object source) {
|
||||
return (Element) getNodeByXPath(expression, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过XPath方式读取XML的NodeList<br>
|
||||
* Xpath相关文章:<a href="https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html">https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html</a>
|
||||
*
|
||||
* @param expression XPath表达式
|
||||
* @param source 资源,可以是Docunent、Node节点等
|
||||
* @return NodeList
|
||||
* @since 4.0.9
|
||||
*/
|
||||
public static NodeList getNodeListByXPath(final String expression, final Object source) {
|
||||
return (NodeList) getByXPath(expression, source, XPathConstants.NODESET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过XPath方式读取XML节点等信息<br>
|
||||
* Xpath相关文章:<a href="https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html">https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html</a>
|
||||
*
|
||||
* @param expression XPath表达式
|
||||
* @param source 资源,可以是Docunent、Node节点等
|
||||
* @return 匹配返回类型的值
|
||||
* @since 4.0.9
|
||||
*/
|
||||
public static Node getNodeByXPath(final String expression, final Object source) {
|
||||
return (Node) getByXPath(expression, source, XPathConstants.NODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过XPath方式读取XML节点等信息<br>
|
||||
* Xpath相关文章:<a href="https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html">https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html</a>
|
||||
*
|
||||
* @param expression XPath表达式
|
||||
* @param source 资源,可以是Docunent、Node节点等
|
||||
* @param returnType 返回类型,{@link javax.xml.xpath.XPathConstants}
|
||||
* @return 匹配返回类型的值
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static Object getByXPath(final String expression, final Object source, final QName returnType) {
|
||||
NamespaceContext nsContext = null;
|
||||
if (source instanceof Node) {
|
||||
nsContext = new UniversalNamespaceCache((Node) source, false);
|
||||
}
|
||||
return getByXPath(expression, source, returnType, nsContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过XPath方式读取XML节点等信息<br>
|
||||
* Xpath相关文章:<br>
|
||||
* <a href="https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html">https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html</a><br>
|
||||
* <a href="https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/">https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/</a>
|
||||
*
|
||||
* @param expression XPath表达式
|
||||
* @param source 资源,可以是Docunent、Node节点等
|
||||
* @param returnType 返回类型,{@link javax.xml.xpath.XPathConstants}
|
||||
* @param nsContext {@link NamespaceContext}
|
||||
* @return 匹配返回类型的值
|
||||
* @since 5.3.1
|
||||
*/
|
||||
public static Object getByXPath(final String expression, final Object source, final QName returnType, final NamespaceContext nsContext) {
|
||||
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 (final XPathExpressionException e) {
|
||||
throw new HutoolException(e);
|
||||
}
|
||||
}
|
||||
}
|
172
hutool-core/src/main/java/org/dromara/hutool/core/xml/XXEUtil.java
Executable file
172
hutool-core/src/main/java/org/dromara/hutool/core/xml/XXEUtil.java
Executable file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import org.dromara.hutool.core.exception.HutoolException;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.sax.SAXTransformerFactory;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import javax.xml.validation.Validator;
|
||||
|
||||
/**
|
||||
* XXE漏洞修复相关工具类<br>
|
||||
* 参考:https://blog.spoock.com/2018/10/23/java-xxe/
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class XXEUtil {
|
||||
|
||||
/**
|
||||
* 关闭XXE,避免漏洞攻击<br>
|
||||
* see: <a href="https://www.owasp.org/index.php/XML_External_Entity_">https://www.owasp.org/index.php/XML_External_Entity_</a>(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J
|
||||
*
|
||||
* @param factory DocumentBuilderFactory
|
||||
* @return DocumentBuilderFactory
|
||||
*/
|
||||
public static DocumentBuilderFactory disableXXE(final DocumentBuilderFactory factory) {
|
||||
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
|
||||
factory.setFeature(XmlFeatures.DISALLOW_DOCTYPE_DECL, 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
|
||||
factory.setFeature(XmlFeatures.EXTERNAL_GENERAL_ENTITIES, 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
|
||||
factory.setFeature(XmlFeatures.EXTERNAL_PARAMETER_ENTITIES, false);
|
||||
// Disable external DTDs as well
|
||||
factory.setFeature(XmlFeatures.LOAD_EXTERNAL_DTD, false);
|
||||
} catch (final ParserConfigurationException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
|
||||
factory.setXIncludeAware(false);
|
||||
factory.setExpandEntityReferences(false);
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭XEE避免漏洞攻击
|
||||
*
|
||||
* @param factory {@link SAXParserFactory}
|
||||
* @return {@link SAXParserFactory}
|
||||
*/
|
||||
public static SAXParserFactory disableXXE(final SAXParserFactory factory) {
|
||||
try {
|
||||
factory.setFeature(XmlFeatures.DISALLOW_DOCTYPE_DECL, true);
|
||||
factory.setFeature(XmlFeatures.EXTERNAL_GENERAL_ENTITIES, false);
|
||||
factory.setFeature(XmlFeatures.EXTERNAL_PARAMETER_ENTITIES, false);
|
||||
factory.setFeature(XmlFeatures.LOAD_EXTERNAL_DTD, false);
|
||||
} catch (final Exception ignore) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
factory.setXIncludeAware(false);
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭XEE避免漏洞攻击
|
||||
*
|
||||
* @param reader {@link XMLReader}
|
||||
* @return {@link XMLReader}
|
||||
*/
|
||||
public static XMLReader disableXXE(final XMLReader reader) {
|
||||
try {
|
||||
reader.setFeature(XmlFeatures.DISALLOW_DOCTYPE_DECL, true);
|
||||
reader.setFeature(XmlFeatures.EXTERNAL_GENERAL_ENTITIES, false);
|
||||
reader.setFeature(XmlFeatures.EXTERNAL_PARAMETER_ENTITIES, false);
|
||||
reader.setFeature(XmlFeatures.LOAD_EXTERNAL_DTD, false);
|
||||
} catch (final Exception ignore) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭XEE避免漏洞攻击
|
||||
*
|
||||
* @param factory {@link TransformerFactory }
|
||||
* @return {@link TransformerFactory }
|
||||
*/
|
||||
public static TransformerFactory disableXXE(final TransformerFactory factory) {
|
||||
try {
|
||||
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
} catch (final TransformerConfigurationException e) {
|
||||
throw new HutoolException(e);
|
||||
}
|
||||
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, StrUtil.EMPTY);
|
||||
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, StrUtil.EMPTY);
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭XEE避免漏洞攻击
|
||||
*
|
||||
* @param validator {@link Validator }
|
||||
* @return {@link Validator }
|
||||
*/
|
||||
public static Validator disableXXE(final Validator validator) {
|
||||
try {
|
||||
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, StrUtil.EMPTY);
|
||||
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, StrUtil.EMPTY);
|
||||
} catch (final Exception ignore) {
|
||||
// ignore
|
||||
}
|
||||
return validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭XEE避免漏洞攻击
|
||||
*
|
||||
* @param factory {@link SAXTransformerFactory}
|
||||
* @return {@link SAXTransformerFactory}
|
||||
*/
|
||||
public static SAXTransformerFactory disableXXE(final SAXTransformerFactory factory) {
|
||||
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, StrUtil.EMPTY);
|
||||
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, StrUtil.EMPTY);
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭XEE避免漏洞攻击
|
||||
*
|
||||
* @param factory {@link SchemaFactory}
|
||||
* @return {@link SchemaFactory}
|
||||
*/
|
||||
public static SchemaFactory disableXXE(final SchemaFactory factory) {
|
||||
try {
|
||||
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, StrUtil.EMPTY);
|
||||
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, StrUtil.EMPTY);
|
||||
} catch (final Exception ignore) {
|
||||
// ignore
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
}
|
66
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlConstants.java
Executable file
66
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlConstants.java
Executable file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* XML相关常量
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class XmlConstants {
|
||||
/**
|
||||
* 字符串常量: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 Pattern INVALID_PATTERN = Pattern.compile("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]");
|
||||
/**
|
||||
* 在XML中注释的内容 正则
|
||||
*/
|
||||
public static final Pattern COMMENT_PATTERN = Pattern.compile("(?s)<!--.+?-->");
|
||||
/**
|
||||
* XML格式化输出默认缩进量
|
||||
*/
|
||||
public static final int INDENT_DEFAULT = 2;
|
||||
}
|
41
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlFeatures.java
Executable file
41
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlFeatures.java
Executable file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
/**
|
||||
* XXE安全相关参数<br>
|
||||
* 见:https://blog.spoock.com/2018/10/23/java-xxe/
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class XmlFeatures {
|
||||
|
||||
/**
|
||||
* 禁用xml中的inline DOCTYPE 声明,即禁用DTD<br>
|
||||
* 不允许将外部实体包含在传入的 XML 文档中,从而防止XML实体注入(XML External Entities 攻击,利用能够在处理时动态构建文档的 XML 功能,注入外部实体)
|
||||
*/
|
||||
public static final String DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
|
||||
/**
|
||||
* 忽略外部DTD
|
||||
*/
|
||||
public static final String LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
|
||||
/**
|
||||
* 不包括外部一般实体
|
||||
*/
|
||||
public static final String EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities";
|
||||
/**
|
||||
* 不包含外部参数实体或外部DTD子集。
|
||||
*/
|
||||
public static final String EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities";
|
||||
}
|
147
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlMapper.java
Executable file
147
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlMapper.java
Executable file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import org.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.collection.CollUtil;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* XML转换器,用于转换Map或Bean等
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class XmlMapper {
|
||||
|
||||
/**
|
||||
* 创建XmlMapper
|
||||
*
|
||||
* @param node {@link Node}XML节点
|
||||
* @return XmlMapper
|
||||
*/
|
||||
public static XmlMapper of(final Node node) {
|
||||
return new XmlMapper(node);
|
||||
}
|
||||
|
||||
private final Node node;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param node {@link Node}XML节点
|
||||
*/
|
||||
public XmlMapper(final Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* XML转Java Bean<br>
|
||||
* 如果XML根节点只有一个,且节点名和Bean的名称一致,则直接转换子节点
|
||||
*
|
||||
* @param <T> bean类型
|
||||
* @param bean bean类
|
||||
* @return bean
|
||||
* @since 5.2.4
|
||||
*/
|
||||
public <T> T toBean(final Class<T> bean) {
|
||||
final Map<String, Object> map = toMap();
|
||||
if (null != map && map.size() == 1) {
|
||||
final String nodeName = CollUtil.getFirst(map.keySet());
|
||||
if (bean.getSimpleName().equalsIgnoreCase(nodeName)) {
|
||||
// 只有key和bean的名称匹配时才做单一对象转换
|
||||
return BeanUtil.toBean(CollUtil.get(map.values(), 0), bean);
|
||||
}
|
||||
}
|
||||
return BeanUtil.toBean(map, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* XML节点转Map
|
||||
*
|
||||
* @return map
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
return toMap(new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* XML节点转Map
|
||||
*
|
||||
* @param result 结果Map
|
||||
* @return map
|
||||
*/
|
||||
public Map<String, Object> toMap(final Map<String, Object> result) {
|
||||
return toMap(this.node, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* XML节点转Map
|
||||
*
|
||||
* @param result 结果Map
|
||||
* @return map
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> toMap(final Node node, Map<String, Object> 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 (!XmlUtil.isElement(childNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
childEle = (Element) childNode;
|
||||
final Object value = result.get(childEle.getNodeName());
|
||||
final Object newValue;
|
||||
if (childEle.hasChildNodes()) {
|
||||
// 子节点继续递归遍历
|
||||
final Map<String, Object> map = toMap(childEle, new LinkedHashMap<>());
|
||||
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<Object>) value).add(newValue);
|
||||
} else {
|
||||
result.put(childEle.getNodeName(), ListUtil.of(value, newValue));
|
||||
}
|
||||
} else {
|
||||
result.put(childEle.getNodeName(), newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
96
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlSaxReader.java
Executable file
96
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlSaxReader.java
Executable file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import org.dromara.hutool.core.exception.HutoolException;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
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.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* XML SAX方式读取器
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class XmlSaxReader {
|
||||
|
||||
private final SAXParserFactory factory;
|
||||
private final InputSource source;
|
||||
|
||||
/**
|
||||
* 创建XmlSaxReader,使用全局{@link SAXParserFactory}
|
||||
*
|
||||
* @param source XML源,可以是文件、流、路径等
|
||||
* @return XmlSaxReader
|
||||
*/
|
||||
public static XmlSaxReader of(final InputSource source) {
|
||||
return of(SAXParserFactoryUtil.getFactory(), source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建XmlSaxReader
|
||||
*
|
||||
* @param factory {@link SAXParserFactory}
|
||||
* @param source XML源,可以是文件、流、路径等
|
||||
* @return XmlSaxReader
|
||||
*/
|
||||
public static XmlSaxReader of(final SAXParserFactory factory, final InputSource source) {
|
||||
return new XmlSaxReader(factory, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param factory {@link SAXParserFactory}
|
||||
* @param source XML源,可以是文件、流、路径等
|
||||
*/
|
||||
public XmlSaxReader(final SAXParserFactory factory, final InputSource source) {
|
||||
this.factory = factory;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取内容
|
||||
*
|
||||
* @param contentHandler XML流处理器,用于按照Element处理xml
|
||||
*/
|
||||
public void read(final ContentHandler contentHandler) {
|
||||
final SAXParser parse;
|
||||
final XMLReader reader;
|
||||
try {
|
||||
parse = factory.newSAXParser();
|
||||
if (contentHandler instanceof DefaultHandler) {
|
||||
parse.parse(source, (DefaultHandler) contentHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
// 得到解读器
|
||||
reader = XXEUtil.disableXXE(parse.getXMLReader());
|
||||
reader.setContentHandler(contentHandler);
|
||||
reader.parse(source);
|
||||
} catch (final ParserConfigurationException | SAXException e) {
|
||||
throw new HutoolException(e);
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
173
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlWriter.java
Executable file
173
hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlWriter.java
Executable file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import org.dromara.hutool.core.exception.HutoolException;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* XML生成器
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class XmlWriter {
|
||||
|
||||
/**
|
||||
* 构建XmlWriter
|
||||
*
|
||||
* @param node {@link Node} XML文档节点或文档本身
|
||||
* @return XmlWriter
|
||||
*/
|
||||
public static XmlWriter of(final Node node) {
|
||||
return of(new DOMSource(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建XmlWriter
|
||||
*
|
||||
* @param source XML数据源
|
||||
* @return XmlWriter
|
||||
*/
|
||||
public static XmlWriter of(final Source source) {
|
||||
return new XmlWriter(source);
|
||||
}
|
||||
|
||||
private final Source source;
|
||||
|
||||
private Charset charset = CharsetUtil.UTF_8;
|
||||
private int indent;
|
||||
private boolean omitXmlDeclaration;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source XML数据源
|
||||
*/
|
||||
public XmlWriter(final Source source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置编码
|
||||
*
|
||||
* @param charset 编码,null跳过
|
||||
* @return this
|
||||
*/
|
||||
public XmlWriter setCharset(final Charset charset) {
|
||||
if (null != charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缩进
|
||||
*
|
||||
* @param indent 缩进
|
||||
* @return this
|
||||
*/
|
||||
public XmlWriter setIndent(final int indent) {
|
||||
this.indent = indent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否输出 xml Declaration
|
||||
*
|
||||
* @param omitXmlDeclaration 是否输出 xml Declaration
|
||||
* @return this
|
||||
*/
|
||||
public XmlWriter setOmitXmlDeclaration(final boolean omitXmlDeclaration) {
|
||||
this.omitXmlDeclaration = omitXmlDeclaration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得XML字符串
|
||||
*
|
||||
* @return XML字符串
|
||||
*/
|
||||
public String getStr(){
|
||||
final StringWriter writer = StrUtil.getWriter();
|
||||
write(writer);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将XML文档写出
|
||||
*
|
||||
* @param file 目标
|
||||
*/
|
||||
public void write(final File file) {
|
||||
write(new StreamResult(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将XML文档写出
|
||||
*
|
||||
* @param writer 目标
|
||||
*/
|
||||
public void write(final Writer writer) {
|
||||
write(new StreamResult(writer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将XML文档写出
|
||||
*
|
||||
* @param out 目标
|
||||
*/
|
||||
public void write(final OutputStream out) {
|
||||
write(new StreamResult(out));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将XML文档写出<br>
|
||||
* 格式化输出逻辑参考:https://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java
|
||||
*
|
||||
* @param result 目标
|
||||
*/
|
||||
public void write(final Result result) {
|
||||
final TransformerFactory factory = XXEUtil.disableXXE(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 (ObjUtil.isNotNull(this.charset)) {
|
||||
xformer.setOutputProperty(OutputKeys.ENCODING, charset.name());
|
||||
}
|
||||
if (omitXmlDeclaration) {
|
||||
xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
}
|
||||
xformer.transform(source, result);
|
||||
} catch (final Exception e) {
|
||||
throw new HutoolException(e, "Trans xml document to string error!");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package org.dromara.hutool.core.text.escape;
|
||||
|
||||
import org.dromara.hutool.core.lang.Console;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -66,4 +67,11 @@ public class EscapeUtilTest {
|
||||
final String s = EscapeUtil.unescapeHtml4(str);
|
||||
Assertions.assertEquals("'some text with single quotes'", s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void escapeXmlTest(){
|
||||
final String a = "<>";
|
||||
final String escape = EscapeUtil.escape(a);
|
||||
Console.log(escape);
|
||||
}
|
||||
}
|
||||
|
@ -10,17 +10,18 @@
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.util;
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.collection.set.SetUtil;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.io.resource.ResourceUtil;
|
||||
import org.dromara.hutool.core.lang.Console;
|
||||
import org.dromara.hutool.core.map.MapBuilder;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.core.xml.XmlUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -46,13 +47,13 @@ public class XmlUtilTest {
|
||||
@Test
|
||||
public void parseTest() {
|
||||
final String result = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"//
|
||||
+ "<returnsms>"//
|
||||
+ "<returnstatus>Success</returnstatus>"//
|
||||
+ "<message>ok</message>"//
|
||||
+ "<remainpoint>1490</remainpoint>"//
|
||||
+ "<taskID>885</taskID>"//
|
||||
+ "<successCounts>1</successCounts>"//
|
||||
+ "</returnsms>";
|
||||
+ "<returnsms>"//
|
||||
+ "<returnstatus>Success</returnstatus>"//
|
||||
+ "<message>ok</message>"//
|
||||
+ "<remainpoint>1490</remainpoint>"//
|
||||
+ "<taskID>885</taskID>"//
|
||||
+ "<successCounts>1</successCounts>"//
|
||||
+ "</returnsms>";
|
||||
final Document docResult = XmlUtil.parseXml(result);
|
||||
final String elementText = XmlUtil.elementText(docResult.getDocumentElement(), "returnstatus");
|
||||
Assertions.assertEquals("Success", elementText);
|
||||
@ -62,29 +63,29 @@ public class XmlUtilTest {
|
||||
@Disabled
|
||||
public void writeTest() {
|
||||
final String result = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"//
|
||||
+ "<returnsms>"//
|
||||
+ "<returnstatus>Success(成功)</returnstatus>"//
|
||||
+ "<message>ok</message>"//
|
||||
+ "<remainpoint>1490</remainpoint>"//
|
||||
+ "<taskID>885</taskID>"//
|
||||
+ "<successCounts>1</successCounts>"//
|
||||
+ "</returnsms>";
|
||||
+ "<returnsms>"//
|
||||
+ "<returnstatus>Success(成功)</returnstatus>"//
|
||||
+ "<message>ok</message>"//
|
||||
+ "<remainpoint>1490</remainpoint>"//
|
||||
+ "<taskID>885</taskID>"//
|
||||
+ "<successCounts>1</successCounts>"//
|
||||
+ "</returnsms>";
|
||||
final Document docResult = XmlUtil.parseXml(result);
|
||||
XmlUtil.toFile(docResult, "e:/aaa.xml", "utf-8");
|
||||
XmlUtil.write(docResult, FileUtil.file("d:/test/aaa.xml"), CharsetUtil.UTF_8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xpathTest() {
|
||||
final String result = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"//
|
||||
+ "<returnsms>"//
|
||||
+ "<returnstatus>Success(成功)</returnstatus>"//
|
||||
+ "<message>ok</message>"//
|
||||
+ "<remainpoint>1490</remainpoint>"//
|
||||
+ "<taskID>885</taskID>"//
|
||||
+ "<successCounts>1</successCounts>"//
|
||||
+ "</returnsms>";
|
||||
+ "<returnsms>"//
|
||||
+ "<returnstatus>Success(成功)</returnstatus>"//
|
||||
+ "<message>ok</message>"//
|
||||
+ "<remainpoint>1490</remainpoint>"//
|
||||
+ "<taskID>885</taskID>"//
|
||||
+ "<successCounts>1</successCounts>"//
|
||||
+ "</returnsms>";
|
||||
final Document docResult = XmlUtil.parseXml(result);
|
||||
final Object value = XmlUtil.getByXPath("//returnsms/message", docResult, XPathConstants.STRING);
|
||||
final Object value = XPathUtil.getByXPath("//returnsms/message", docResult, XPathConstants.STRING);
|
||||
Assertions.assertEquals("ok", value);
|
||||
}
|
||||
|
||||
@ -92,21 +93,21 @@ public class XmlUtilTest {
|
||||
public void xpathTest2() {
|
||||
final String result = ResourceUtil.readUtf8Str("test.xml");
|
||||
final Document docResult = XmlUtil.parseXml(result);
|
||||
final Object value = XmlUtil.getByXPath("//returnsms/message", docResult, XPathConstants.STRING);
|
||||
final Object value = XPathUtil.getByXPath("//returnsms/message", docResult, XPathConstants.STRING);
|
||||
Assertions.assertEquals("ok", value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlToMapTest() {
|
||||
final String xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"//
|
||||
+ "<returnsms>"//
|
||||
+ "<returnstatus>Success</returnstatus>"//
|
||||
+ "<message>ok</message>"//
|
||||
+ "<remainpoint>1490</remainpoint>"//
|
||||
+ "<taskID>885</taskID>"//
|
||||
+ "<successCounts>1</successCounts>"//
|
||||
+ "<newNode><sub>subText</sub></newNode>"//
|
||||
+ "</returnsms>";
|
||||
+ "<returnsms>"//
|
||||
+ "<returnstatus>Success</returnstatus>"//
|
||||
+ "<message>ok</message>"//
|
||||
+ "<remainpoint>1490</remainpoint>"//
|
||||
+ "<taskID>885</taskID>"//
|
||||
+ "<successCounts>1</successCounts>"//
|
||||
+ "<newNode><sub>subText</sub></newNode>"//
|
||||
+ "</returnsms>";
|
||||
final Map<String, Object> map = XmlUtil.xmlToMap(xml);
|
||||
|
||||
Assertions.assertEquals(6, map.size());
|
||||
@ -130,51 +131,51 @@ public class XmlUtilTest {
|
||||
@Test
|
||||
public void mapToXmlTest() {
|
||||
final Map<String, Object> map = MapBuilder.of(new LinkedHashMap<String, Object>())//
|
||||
.put("name", "张三")//
|
||||
.put("age", 12)//
|
||||
.put("game", MapUtil.builder(new LinkedHashMap<String, Object>()).put("昵称", "Looly").put("level", 14).build())//
|
||||
.build();
|
||||
.put("name", "张三")//
|
||||
.put("age", 12)//
|
||||
.put("game", MapUtil.builder(new LinkedHashMap<String, Object>()).put("昵称", "Looly").put("level", 14).build())//
|
||||
.build();
|
||||
final Document doc = XmlUtil.mapToXml(map, "user");
|
||||
// Console.log(XmlUtil.toStr(doc, false));
|
||||
Assertions.assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"//
|
||||
+ "<user>"//
|
||||
+ "<name>张三</name>"//
|
||||
+ "<age>12</age>"//
|
||||
+ "<game>"//
|
||||
+ "<昵称>Looly</昵称>"//
|
||||
+ "<level>14</level>"//
|
||||
+ "</game>"//
|
||||
+ "</user>", //
|
||||
XmlUtil.toStr(doc, false));
|
||||
+ "<user>"//
|
||||
+ "<name>张三</name>"//
|
||||
+ "<age>12</age>"//
|
||||
+ "<game>"//
|
||||
+ "<昵称>Looly</昵称>"//
|
||||
+ "<level>14</level>"//
|
||||
+ "</game>"//
|
||||
+ "</user>", //
|
||||
XmlUtil.toStr(doc, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapToXmlTest2() {
|
||||
// 测试List
|
||||
final Map<String, Object> map = MapBuilder.of(new LinkedHashMap<String, Object>())
|
||||
.put("Town", ListUtil.of("town1", "town2"))
|
||||
.build();
|
||||
.put("Town", ListUtil.of("town1", "town2"))
|
||||
.build();
|
||||
|
||||
final Document doc = XmlUtil.mapToXml(map, "City");
|
||||
Assertions.assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
|
||||
"<City>" +
|
||||
"<Town>town1</Town>" +
|
||||
"<Town>town2</Town>" +
|
||||
"</City>",
|
||||
XmlUtil.toStr(doc));
|
||||
"<City>" +
|
||||
"<Town>town1</Town>" +
|
||||
"<Town>town2</Town>" +
|
||||
"</City>",
|
||||
XmlUtil.toStr(doc));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTest() {
|
||||
final Document doc = XmlUtil.readXML("test.xml");
|
||||
final Document doc = XmlUtil.readXml("test.xml");
|
||||
Assertions.assertNotNull(doc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readBySaxTest(){
|
||||
public void readBySaxTest() {
|
||||
final Set<String> eles = SetUtil.of(
|
||||
"returnsms", "returnstatus", "message", "remainpoint", "taskID", "successCounts");
|
||||
XmlUtil.readBySax(ResourceUtil.getStream("test.xml"), new DefaultHandler(){
|
||||
"returnsms", "returnstatus", "message", "remainpoint", "taskID", "successCounts");
|
||||
XmlUtil.readBySax(ResourceUtil.getStream("test.xml"), new DefaultHandler() {
|
||||
@Override
|
||||
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) {
|
||||
Assertions.assertTrue(eles.contains(localName));
|
||||
@ -186,8 +187,8 @@ public class XmlUtilTest {
|
||||
public void mapToXmlTestWithOmitXmlDeclaration() {
|
||||
|
||||
final Map<String, Object> map = MapBuilder.of(new LinkedHashMap<String, Object>())
|
||||
.put("name", "ddatsh")
|
||||
.build();
|
||||
.put("name", "ddatsh")
|
||||
.build();
|
||||
final String xml = XmlUtil.mapToXmlStr(map, true);
|
||||
Assertions.assertEquals("<xml><name>ddatsh</name></xml>", xml);
|
||||
}
|
||||
@ -195,18 +196,18 @@ public class XmlUtilTest {
|
||||
@Test
|
||||
public void getByPathTest() {
|
||||
final String xmlStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
||||
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
|
||||
" <soap:Body>\n" +
|
||||
" <ns2:testResponse xmlns:ns2=\"http://ws.xxx.com/\">\n" +
|
||||
" <return>2020/04/15 21:01:21</return>\n" +
|
||||
" </ns2:testResponse>\n" +
|
||||
" </soap:Body>\n" +
|
||||
"</soap:Envelope>\n";
|
||||
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
|
||||
" <soap:Body>\n" +
|
||||
" <ns2:testResponse xmlns:ns2=\"http://ws.xxx.com/\">\n" +
|
||||
" <return>2020/04/15 21:01:21</return>\n" +
|
||||
" </ns2:testResponse>\n" +
|
||||
" </soap:Body>\n" +
|
||||
"</soap:Envelope>\n";
|
||||
|
||||
final Document document = XmlUtil.readXML(xmlStr);
|
||||
final Object value = XmlUtil.getByXPath(
|
||||
"//soap:Envelope/soap:Body/ns2:testResponse/return",
|
||||
document, XPathConstants.STRING);//
|
||||
final Document document = XmlUtil.readXml(xmlStr);
|
||||
final Object value = XPathUtil.getByXPath(
|
||||
"//soap:Envelope/soap:Body/ns2:testResponse/return",
|
||||
document, XPathConstants.STRING);//
|
||||
Assertions.assertEquals("2020/04/15 21:01:21", value);
|
||||
}
|
||||
|
||||
@ -267,7 +268,7 @@ public class XmlUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlToBeanTest2(){
|
||||
public void xmlToBeanTest2() {
|
||||
@Data
|
||||
class SmsRes {
|
||||
private String code;
|
||||
@ -298,7 +299,7 @@ public class XmlUtilTest {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void formatTest(){
|
||||
public void formatTest() {
|
||||
// https://github.com/looly/hutool/pull/1234
|
||||
final Document xml = XmlUtil.createXml("NODES");
|
||||
xml.setXmlStandalone(true);
|
||||
@ -325,22 +326,15 @@ public class XmlUtilTest {
|
||||
|
||||
parentNode.item(0).appendChild(parent1Node);
|
||||
|
||||
final String format = XmlUtil.toStr(xml,"GBK",true);
|
||||
final String format = XmlUtil.toStr(xml, CharsetUtil.GBK, true);
|
||||
Console.log(format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void escapeTest(){
|
||||
final String a = "<>";
|
||||
final String escape = XmlUtil.escape(a);
|
||||
Console.log(escape);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParamTest(){
|
||||
public void getParamTest() {
|
||||
final String xml = "<Config name=\"aaaa\">\n" +
|
||||
" <url>222222</url>\n" +
|
||||
"</Config>";
|
||||
" <url>222222</url>\n" +
|
||||
"</Config>";
|
||||
|
||||
final Document doc = XmlUtil.parseXml(xml);
|
||||
final String name = doc.getDocumentElement().getAttribute("name");
|
||||
@ -349,24 +343,23 @@ public class XmlUtilTest {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void issueI5DO8ETest(){
|
||||
public void issueI5DO8ETest() {
|
||||
// 增加子节点后,格式会错乱,JDK的bug
|
||||
XmlUtil.setNamespaceAware(false);
|
||||
final String xmlStr = ResourceUtil.readUtf8Str("issueI5DO8E.xml");
|
||||
final Document doc = XmlUtil.readXML(xmlStr);
|
||||
final Document doc = XmlUtil.readXml(xmlStr);
|
||||
|
||||
final Element item = doc.createElement("item");
|
||||
item.setAttribute("id", "cover-image");
|
||||
final Element manifestEl = XmlUtil.getElementByXPath("//package/manifest", doc);
|
||||
final Element manifestEl = XPathUtil.getElementByXPath("//package/manifest", doc);
|
||||
manifestEl.appendChild(item);
|
||||
|
||||
Console.log(XmlUtil.format(doc));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlStrToBeanTest(){
|
||||
public void xmlStrToBeanTest() {
|
||||
final String xml = "<userInfo><name>张三</name><age>20</age><email>zhangsan@example.com</email></userInfo>";
|
||||
final Document document = XmlUtil.readXML(xml);
|
||||
final Document document = XmlUtil.readXml(xml);
|
||||
final UserInfo userInfo = XmlUtil.xmlToBean(document, UserInfo.class);
|
||||
Assertions.assertEquals("张三", userInfo.getName());
|
||||
Assertions.assertEquals("20", userInfo.getAge());
|
@ -1,6 +1,7 @@
|
||||
package org.dromara.hutool.json;
|
||||
|
||||
import org.dromara.hutool.core.io.resource.ResourceUtil;
|
||||
import org.dromara.hutool.core.xml.XPathUtil;
|
||||
import org.dromara.hutool.core.xml.XmlUtil;
|
||||
import org.dromara.hutool.json.xml.JSONXMLSerializer;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@ -13,7 +14,7 @@ public class IssueI676ITTest {
|
||||
public void parseXMLTest() {
|
||||
final JSONObject jsonObject = JSONUtil.parseObj(ResourceUtil.readUtf8Str("issueI676IT.json"));
|
||||
final String xmlStr = JSONXMLSerializer.toXml(jsonObject, null, (String) null);
|
||||
final String content = String.valueOf(XmlUtil.getByXPath("/page/orderItems[1]/content", XmlUtil.readXML(xmlStr), XPathConstants.STRING));
|
||||
final String content = String.valueOf(XPathUtil.getByXPath("/page/orderItems[1]/content", XmlUtil.readXml(xmlStr), XPathConstants.STRING));
|
||||
Assertions.assertEquals(content, "bar1");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user