diff --git a/CHANGELOG.md b/CHANGELOG.md
index 101ebcf48..66d048ca6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,13 +3,14 @@
-------------------------------------------------------------------------------------------------------------
-# 5.7.11 (2021-08-30)
+# 5.7.11 (2021-08-31)
### 🐣新特性
* 【crypto 】 修改SymmetricCrypto初始化逻辑
* 【core 】 FileTypeUtil增加对wps编辑的docx的识别(issue#I47JGH@Gitee)
* 【core 】 Money修改构造,0表示读取所有分(issue#1796@Github)
-
+* 【json 】 增加JSONXMLParser和JSONXMLSerializer
+* 【json 】 XML支持自定义内容标签(issue#I47TV8@Gitee)
### 🐞Bug修复
* 【cron 】 **重要**修复Scheduler启动默认线程池为null的bug(issue#I47PZW@Gitee)
diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
index 13ac191ea..960b6d9ef 100644
--- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
+++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
@@ -1,6 +1,7 @@
package cn.hutool.json;
import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -17,7 +18,7 @@ import java.util.SortedMap;
*
* @author Looly
*/
-final class InternalJSONUtil {
+public final class InternalJSONUtil {
private InternalJSONUtil() {
}
@@ -28,27 +29,30 @@ final class InternalJSONUtil {
* @param obj 被检查的对象
* @throws JSONException If o is a non-finite number.
*/
- protected static void testValidity(Object obj) throws JSONException {
+ static void testValidity(Object obj) throws JSONException {
if (false == ObjectUtil.isValidIfNumber(obj)) {
throw new JSONException("JSON does not allow non-finite numbers.");
}
}
/**
- * 值转为String,用于JSON中。 If the object has an value.toJSONString() method, then that method will be used to produce the JSON text.
- * The method is required to produce a strictly conforming text.
- * If the object does not contain a toJSONString method (which is the most common case), then a text will be produced by other means.
- * If the value is an array or Collection, then a JSONArray will be made from it and its toJSONString method will be called.
- * If the value is a MAP, then a JSONObject will be made from it and its toJSONString method will be called.
- * Otherwise, the value's toString method will be called, and the result will be quoted.
+ * 值转为String,用于JSON中。规则为:
+ *
+ * - 对象如果实现了{@link JSONString}接口,调用{@link JSONString#toJSONString()}方法
+ * - 对象如果实现了{@link JSONString}接口,调用{@link JSONString#toJSONString()}方法
+ * - 对象如果是数组或Collection,包装为{@link JSONArray}
+ * - 对象如果是Map,包装为{@link JSONObject}
+ * - 对象如果是数字,使用{@link NumberUtil#toStr(Number)}转换为字符串
+ * - 其他情况调用toString并使用双引号包装
+ *
*
* @param value 需要转为字符串的对象
* @return 字符串
* @throws JSONException If the value is or contains an invalid number.
*/
- protected static String valueToString(Object value) throws JSONException {
+ static String valueToString(Object value) throws JSONException {
if (value == null || value instanceof JSONNull) {
- return "null";
+ return JSONNull.NULL.toString();
}
if (value instanceof JSONString) {
try {
@@ -66,7 +70,7 @@ final class InternalJSONUtil {
} else if (value instanceof Collection) {
Collection> coll = (Collection>) value;
return new JSONArray(coll).toString();
- } else if (value.getClass().isArray()) {
+ } else if (ArrayUtil.isArray(value)) {
return new JSONArray(value).toString();
} else {
return JSONUtil.quote(value.toString());
@@ -79,16 +83,13 @@ final class InternalJSONUtil {
* @param string A String.
* @return A simple JSON value.
*/
- protected static Object stringToValue(String string) {
+ public static Object stringToValue(String string) {
// null处理
- if (null == string || "null".equalsIgnoreCase(string)) {
+ if (StrUtil.isEmpty(string) || StrUtil.NULL.equalsIgnoreCase(string)) {
return JSONNull.NULL;
}
// boolean处理
- if (0 == string.length()) {
- return StrUtil.EMPTY;
- }
if ("true".equalsIgnoreCase(string)) {
return Boolean.TRUE;
}
@@ -130,7 +131,7 @@ final class InternalJSONUtil {
* @param value 值
* @return JSONObject
*/
- protected static JSONObject propertyPut(JSONObject jsonObject, Object key, Object value) {
+ static JSONObject propertyPut(JSONObject jsonObject, Object key, Object value) {
final String[] path = StrUtil.splitToArray(Convert.toStr(key), CharUtil.DOT);
int last = path.length - 1;
JSONObject target = jsonObject;
@@ -160,7 +161,7 @@ final class InternalJSONUtil {
* @return 是否忽略null值
* @since 4.3.1
*/
- protected static boolean defaultIgnoreNullValue(Object obj) {
+ static boolean defaultIgnoreNullValue(Object obj) {
return (false == (obj instanceof CharSequence))//
&& (false == (obj instanceof JSONTokener))//
&& (false == (obj instanceof Map));
@@ -178,12 +179,12 @@ final class InternalJSONUtil {
* @return 是否有序
* @since 5.7.0
*/
- protected static boolean isOrder(Object value){
- if(value instanceof LinkedHashMap || value instanceof SortedMap){
+ static boolean isOrder(Object value) {
+ if (value instanceof LinkedHashMap || value instanceof SortedMap) {
return true;
- } else if(value instanceof JSONGetter){
+ } else if (value instanceof JSONGetter) {
final JSONConfig config = ((JSONGetter>) value).getConfig();
- if(null != config){
+ if (null != config) {
return config.isOrder();
}
}
diff --git a/hutool-json/src/main/java/cn/hutool/json/XML.java b/hutool-json/src/main/java/cn/hutool/json/XML.java
index 7d7b9e967..570f20d92 100644
--- a/hutool-json/src/main/java/cn/hutool/json/XML.java
+++ b/hutool-json/src/main/java/cn/hutool/json/XML.java
@@ -1,16 +1,15 @@
package cn.hutool.json;
-import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
-import cn.hutool.core.util.EscapeUtil;
-import cn.hutool.core.util.StrUtil;
-
-import java.util.Iterator;
+import cn.hutool.json.xml.JSONXMLParser;
+import cn.hutool.json.xml.JSONXMLSerializer;
/**
* 提供静态方法在XML和JSONObject之间转换
*
- * @author JSON.org
+ * @author JSON.org, looly
+ * @see JSONXMLParser
+ * @see JSONXMLSerializer
*/
public class XML {
@@ -92,189 +91,23 @@ public class XML {
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
*
* @param jo JSONObject
- * @param string XML字符串
- * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings
- * @return A JSONObject containing the structured data from the XML string.
- * @throws JSONException Thrown if there is an errors while parsing the string
+ * @param xmlStr XML字符串
+ * @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean
+ * @return A JSONObject 解析后的JSON对象,与传入的jo为同一对象
+ * @throws JSONException 解析异常
* @since 5.3.1
*/
- public static JSONObject toJSONObject(JSONObject jo, String string, boolean keepStrings) throws JSONException {
- XMLTokener x = new XMLTokener(string, jo.getConfig());
- while (x.more() && x.skipPast("<")) {
- parse(x, jo, null, keepStrings);
- }
+ public static JSONObject toJSONObject(JSONObject jo, String xmlStr, boolean keepStrings) throws JSONException {
+ JSONXMLParser.parseJSONObject(jo, xmlStr, keepStrings);
return jo;
}
- /**
- * Scan the content following the named tag, attaching it to the context.
- *
- * @param x The XMLTokener containing the source string.
- * @param context The JSONObject that will include the new material.
- * @param name The tag name.
- * @return true if the close tag is processed.
- * @throws JSONException JSON异常
- */
- private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) throws JSONException {
- char c;
- int i;
- JSONObject jsonobject;
- String string;
- String tagName;
- Object token;
-
- // Test for and skip past these forms:
- //
- //
- //
- // ... ?>
- // Report errors for these forms:
- // <>
- // <=
- // <<
-
- token = x.nextToken();
-
- // ");
- return false;
- }
- x.back();
- } else if (c == '[') {
- token = x.nextToken();
- if ("CDATA".equals(token)) {
- if (x.next() == '[') {
- string = x.nextCDATA();
- if (string.length() > 0) {
- context.accumulate("content", string);
- }
- return false;
- }
- }
- throw x.syntaxError("Expected 'CDATA['");
- }
- i = 1;
- do {
- token = x.nextMeta();
- if (token == null) {
- throw x.syntaxError("Missing '>' after ' 0);
- return false;
- } else if (token == QUEST) {
-
- //
- x.skipPast("?>");
- return false;
- } else if (token == SLASH) {
-
- // Close tag
-
- token = x.nextToken();
- if (name == null) {
- throw x.syntaxError("Mismatched close tag " + token);
- }
- if (!token.equals(name)) {
- throw x.syntaxError("Mismatched " + name + " and " + token);
- }
- if (x.nextToken() != GT) {
- throw x.syntaxError("Misshaped close tag");
- }
- return true;
-
- } else if (token instanceof Character) {
- throw x.syntaxError("Misshaped tag");
-
- // Open tag <
-
- } else {
- tagName = (String) token;
- token = null;
- jsonobject = new JSONObject();
- for (; ; ) {
- if (token == null) {
- token = x.nextToken();
- }
-
- // attribute = value
- if (token instanceof String) {
- string = (String) token;
- token = x.nextToken();
- if (token == EQ) {
- token = x.nextToken();
- if (!(token instanceof String)) {
- throw x.syntaxError("Missing value");
- }
- jsonobject.accumulate(string, keepStrings ? token : InternalJSONUtil.stringToValue((String) token));
- token = null;
- } else {
- jsonobject.accumulate(string, "");
- }
-
- } else if (token == SLASH) {
- // Empty tag <.../>
- if (x.nextToken() != GT) {
- throw x.syntaxError("Misshaped tag");
- }
- if (jsonobject.size() > 0) {
- context.accumulate(tagName, jsonobject);
- } else {
- context.accumulate(tagName, "");
- }
- return false;
-
- } else if (token == GT) {
- // Content, between <...> and
- for (; ; ) {
- token = x.nextContent();
- if (token == null) {
- if (tagName != null) {
- throw x.syntaxError("Unclosed tag " + tagName);
- }
- return false;
- } else if (token instanceof String) {
- string = (String) token;
- if (string.length() > 0) {
- jsonobject.accumulate("content", keepStrings ? token : InternalJSONUtil.stringToValue(string));
- }
-
- } else if (token == LT) {
- // Nested element
- if (parse(x, jsonobject, tagName, keepStrings)) {
- if (jsonobject.size() == 0) {
- context.accumulate(tagName, "");
- } else if (jsonobject.size() == 1 && jsonobject.get("content") != null) {
- context.accumulate(tagName, jsonobject.get("content"));
- } else {
- context.accumulate(tagName, jsonobject);
- }
- return false;
- }
- }
- }
- } else {
- throw x.syntaxError("Misshaped tag");
- }
- }
- }
- }
-
/**
* 转换JSONObject为XML
- * Convert a JSONObject into a well-formed, element-normal XML string.
*
- * @param object A JSONObject.
- * @return A string.
- * @throws JSONException Thrown if there is an error parsing the string
+ * @param object JSON对象或数组
+ * @return XML字符串
+ * @throws JSONException JSON解析异常
*/
public static String toXml(Object object) throws JSONException {
return toXml(object, null);
@@ -282,122 +115,26 @@ public class XML {
/**
* 转换JSONObject为XML
- * Convert a JSONObject into a well-formed, element-normal XML string.
*
- * @param object A JSONObject.
- * @param tagName The optional name of the enclosing tag.
+ * @param object JSON对象或数组
+ * @param tagName 可选标签名称,名称为空时忽略标签
* @return A string.
- * @throws JSONException Thrown if there is an error parsing the string
+ * @throws JSONException JSON解析异常
*/
public static String toXml(Object object, String tagName) throws JSONException {
- if (null == object) {
- return null;
- }
-
- StringBuilder sb = new StringBuilder();
- JSONArray ja;
- JSONObject jo;
- String key;
- Iterator keys;
- Object value;
-
- if (object instanceof JSONObject) {
-
- // Emit
- if (tagName != null) {
- sb.append('<');
- sb.append(tagName);
- sb.append('>');
- }
-
- // Loop thru the keys.
- jo = (JSONObject) object;
- keys = jo.keySet().iterator();
- while (keys.hasNext()) {
- key = keys.next();
- value = jo.get(key);
- if (value == null) {
- value = StrUtil.EMPTY;
- } else if (ArrayUtil.isArray(value)) {
- value = new JSONArray(value);
- }
-
- // Emit content in body
- if ("content".equals(key)) {
- if (value instanceof JSONArray) {
- ja = (JSONArray) value;
- int i = 0;
- for (Object val : ja) {
- if (i > 0) {
- sb.append('\n');
- }
- sb.append(EscapeUtil.escapeXml(val.toString()));
- i++;
- }
- } else {
- sb.append(EscapeUtil.escapeXml(value.toString()));
- }
-
- // Emit an array of similar keys
-
- } else if (value instanceof JSONArray) {
- ja = (JSONArray) value;
- for (Object val : ja) {
- if (val instanceof JSONArray) {
- sb.append('<');
- sb.append(key);
- sb.append('>');
- sb.append(toXml(val));
- sb.append("");
- sb.append(key);
- sb.append('>');
- } else {
- sb.append(toXml(val, key));
- }
- }
- } else if ("".equals(value)) {
- sb.append('<');
- sb.append(key);
- sb.append("/>");
-
- // Emit a new tag
-
- } else {
- sb.append(toXml(value, key));
- }
- }
- if (tagName != null) {
-
- // Emit the close tag
- sb.append("");
- sb.append(tagName);
- sb.append('>');
- }
- return sb.toString();
-
- }
-
- if (ArrayUtil.isArray(object)) {
- object = new JSONArray(object);
- }
-
- if (object instanceof JSONArray) {
- ja = (JSONArray) object;
- for (Object val : ja) {
- // XML does not have good support for arrays. If an array
- // appears in a place where XML is lacking, synthesize an
- // element.
- sb.append(toXml(val, tagName == null ? "array" : tagName));
- }
- return sb.toString();
- }
-
- String string = EscapeUtil.escapeXml(object.toString());
- return (tagName == null) ?
- "\"" + string + "\"" : (string.length() == 0) ? "<" + tagName + "/>"
- : "<" + tagName + ">" + string + "" + tagName + ">";
-
+ return toXml(object, tagName, "content");
}
-
+ /**
+ * 转换JSONObject为XML
+ *
+ * @param object JSON对象或数组
+ * @param tagName 可选标签名称,名称为空时忽略标签
+ * @param contentKeys 标识为内容的key,遇到此key直接解析内容而不增加对应名称标签
+ * @return A string.
+ * @throws JSONException JSON解析异常
+ */
+ public static String toXml(Object object, String tagName, String... contentKeys) throws JSONException {
+ return JSONXMLSerializer.toXml(object, tagName, contentKeys);
+ }
}
diff --git a/hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLParser.java b/hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLParser.java
new file mode 100644
index 000000000..6b16ca506
--- /dev/null
+++ b/hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLParser.java
@@ -0,0 +1,182 @@
+package cn.hutool.json.xml;
+
+import cn.hutool.json.InternalJSONUtil;
+import cn.hutool.json.JSONException;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.XML;
+import cn.hutool.json.XMLTokener;
+
+/**
+ * XML解析器,将XML解析为JSON对象
+ *
+ * @author JSON.org, looly
+ * @since 5.7.11
+ */
+public class JSONXMLParser {
+
+ /**
+ * 转换XML为JSONObject
+ * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
+ *
+ * @param jo JSONObject
+ * @param xmlStr XML字符串
+ * @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean
+ * @throws JSONException 解析异常
+ */
+ public static void parseJSONObject(JSONObject jo, String xmlStr, boolean keepStrings) throws JSONException {
+ XMLTokener x = new XMLTokener(xmlStr, jo.getConfig());
+ while (x.more() && x.skipPast("<")) {
+ parse(x, jo, null, keepStrings);
+ }
+ }
+
+ /**
+ * Scan the content following the named tag, attaching it to the context.
+ *
+ * @param x The XMLTokener containing the source string.
+ * @param context The JSONObject that will include the new material.
+ * @param name The tag name.
+ * @return true if the close tag is processed.
+ * @throws JSONException JSON异常
+ */
+ private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) throws JSONException {
+ char c;
+ int i;
+ JSONObject jsonobject;
+ String string;
+ String tagName;
+ Object token;
+
+ token = x.nextToken();
+
+ if (token == XML.BANG) {
+ c = x.next();
+ if (c == '-') {
+ if (x.next() == '-') {
+ x.skipPast("-->");
+ return false;
+ }
+ x.back();
+ } else if (c == '[') {
+ token = x.nextToken();
+ if ("CDATA".equals(token)) {
+ if (x.next() == '[') {
+ string = x.nextCDATA();
+ if (string.length() > 0) {
+ context.accumulate("content", string);
+ }
+ return false;
+ }
+ }
+ throw x.syntaxError("Expected 'CDATA['");
+ }
+ i = 1;
+ do {
+ token = x.nextMeta();
+ if (token == null) {
+ throw x.syntaxError("Missing '>' after ' 0);
+ return false;
+ } else if (token == XML.QUEST) {
+
+ //
+ x.skipPast("?>");
+ return false;
+ } else if (token == XML.SLASH) {
+
+ // Close tag
+
+ token = x.nextToken();
+ if (name == null) {
+ throw x.syntaxError("Mismatched close tag " + token);
+ }
+ if (!token.equals(name)) {
+ throw x.syntaxError("Mismatched " + name + " and " + token);
+ }
+ if (x.nextToken() != XML.GT) {
+ throw x.syntaxError("Misshaped close tag");
+ }
+ return true;
+
+ } else if (token instanceof Character) {
+ throw x.syntaxError("Misshaped tag");
+
+ // Open tag <
+
+ } else {
+ tagName = (String) token;
+ token = null;
+ jsonobject = new JSONObject();
+ for (; ; ) {
+ if (token == null) {
+ token = x.nextToken();
+ }
+
+ // attribute = value
+ if (token instanceof String) {
+ string = (String) token;
+ token = x.nextToken();
+ if (token == XML.EQ) {
+ token = x.nextToken();
+ if (!(token instanceof String)) {
+ throw x.syntaxError("Missing value");
+ }
+ jsonobject.accumulate(string, keepStrings ? token : InternalJSONUtil.stringToValue((String) token));
+ token = null;
+ } else {
+ jsonobject.accumulate(string, "");
+ }
+
+ } else if (token == XML.SLASH) {
+ // Empty tag <.../>
+ if (x.nextToken() != XML.GT) {
+ throw x.syntaxError("Misshaped tag");
+ }
+ if (jsonobject.size() > 0) {
+ context.accumulate(tagName, jsonobject);
+ } else {
+ context.accumulate(tagName, "");
+ }
+ return false;
+
+ } else if (token == XML.GT) {
+ // Content, between <...> and
+ for (; ; ) {
+ token = x.nextContent();
+ if (token == null) {
+ if (tagName != null) {
+ throw x.syntaxError("Unclosed tag " + tagName);
+ }
+ return false;
+ } else if (token instanceof String) {
+ string = (String) token;
+ if (string.length() > 0) {
+ jsonobject.accumulate("content", keepStrings ? token : InternalJSONUtil.stringToValue(string));
+ }
+
+ } else if (token == XML.LT) {
+ // Nested element
+ if (parse(x, jsonobject, tagName, keepStrings)) {
+ if (jsonobject.size() == 0) {
+ context.accumulate(tagName, "");
+ } else if (jsonobject.size() == 1 && jsonobject.get("content") != null) {
+ context.accumulate(tagName, jsonobject.get("content"));
+ } else {
+ context.accumulate(tagName, jsonobject);
+ }
+ return false;
+ }
+ }
+ }
+ } else {
+ throw x.syntaxError("Misshaped tag");
+ }
+ }
+ }
+ }
+}
diff --git a/hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLSerializer.java b/hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLSerializer.java
new file mode 100644
index 000000000..fa6dd1e34
--- /dev/null
+++ b/hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLSerializer.java
@@ -0,0 +1,159 @@
+package cn.hutool.json.xml;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.EscapeUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONException;
+import cn.hutool.json.JSONObject;
+
+/**
+ * JSON转XML字符串工具
+ *
+ * @author looly
+ * @since 5.7.11
+ */
+public class JSONXMLSerializer {
+ /**
+ * 转换JSONObject为XML
+ * Convert a JSONObject into a well-formed, element-normal XML string.
+ *
+ * @param object A JSONObject.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toXml(Object object) throws JSONException {
+ return toXml(object, null);
+ }
+
+ /**
+ * 转换JSONObject为XML
+ *
+ * @param object JSON对象或数组
+ * @param tagName 可选标签名称,名称为空时忽略标签
+ * @return A string.
+ * @throws JSONException JSON解析异常
+ */
+ public static String toXml(Object object, String tagName) throws JSONException {
+ return toXml(object, tagName, "content");
+ }
+
+ /**
+ * 转换JSONObject为XML
+ *
+ * @param object JSON对象或数组
+ * @param tagName 可选标签名称,名称为空时忽略标签
+ * @param contentKeys 标识为内容的key,遇到此key直接解析内容而不增加对应名称标签
+ * @return A string.
+ * @throws JSONException JSON解析异常
+ */
+ public static String toXml(Object object, String tagName, String... contentKeys) throws JSONException {
+ if (null == object) {
+ return null;
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ if (object instanceof JSONObject) {
+
+ // Emit
+ appendTag(sb, tagName, false);
+
+ // Loop thru the keys.
+ ((JSONObject) object).forEach((key, value) -> {
+ if (ArrayUtil.isArray(value)) {
+ value = new JSONArray(value);
+ }
+
+ // Emit content in body
+ if (ArrayUtil.contains(contentKeys, key)) {
+ if (value instanceof JSONArray) {
+ int i = 0;
+ for (Object val : (JSONArray) value) {
+ if (i > 0) {
+ sb.append(CharUtil.LF);
+ }
+ sb.append(EscapeUtil.escapeXml(val.toString()));
+ i++;
+ }
+ } else {
+ sb.append(EscapeUtil.escapeXml(value.toString()));
+ }
+
+ // Emit an array of similar keys
+
+ } else if (StrUtil.isEmptyIfStr(value)) {
+ sb.append(wrapWithTag(null, key));
+ } else if (value instanceof JSONArray) {
+ for (Object val : (JSONArray) value) {
+ if (val instanceof JSONArray) {
+ sb.append(wrapWithTag(toXml(val), key));
+ } else {
+ sb.append(toXml(val, key));
+ }
+ }
+ } else {
+ sb.append(toXml(value, key));
+ }
+ });
+
+ // Emit the close tag
+ appendTag(sb, tagName, true);
+ return sb.toString();
+ }
+
+ if (ArrayUtil.isArray(object)) {
+ object = new JSONArray(object);
+ }
+
+ if (object instanceof JSONArray) {
+ for (Object val : (JSONArray) object) {
+ // XML does not have good support for arrays. If an array
+ // appears in a place where XML is lacking, synthesize an
+ // element.
+ sb.append(toXml(val, tagName == null ? "array" : tagName));
+ }
+ return sb.toString();
+ }
+
+ return wrapWithTag(EscapeUtil.escapeXml(object.toString()), tagName);
+ }
+
+ /**
+ * 追加标签
+ *
+ * @param sb XML内容
+ * @param tagName 标签名
+ * @param isEndTag 是否结束标签
+ * @since 5.7.11
+ */
+ private static void appendTag(StringBuilder sb, String tagName, boolean isEndTag) {
+ if (StrUtil.isNotBlank(tagName)) {
+ sb.append('<');
+ if (isEndTag) {
+ sb.append('/');
+ }
+ sb.append(tagName).append('>');
+ }
+ }
+
+ /**
+ * 将内容使用标签包装为XML
+ *
+ * @param tagName 标签名
+ * @param content 内容
+ * @return 包装后的XML
+ * @since 5.7.11
+ */
+ private static String wrapWithTag(String content, String tagName) {
+ if (StrUtil.isBlank(tagName)) {
+ return StrUtil.wrap(content, "\"");
+ }
+
+ if (StrUtil.isEmpty(content)) {
+ return "<" + tagName + "/>";
+ } else {
+ return "<" + tagName + ">" + content + "" + tagName + ">";
+ }
+ }
+}
diff --git a/hutool-json/src/main/java/cn/hutool/json/xml/package-info.java b/hutool-json/src/main/java/cn/hutool/json/xml/package-info.java
new file mode 100644
index 000000000..3c55d30e6
--- /dev/null
+++ b/hutool-json/src/main/java/cn/hutool/json/xml/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * JSON与XML相互转换封装,基于json.org官方库改造
+ *
+ * @author looly
+ */
+package cn.hutool.json.xml;
diff --git a/hutool-json/src/test/java/cn/hutool/json/XMLTest.java b/hutool-json/src/test/java/cn/hutool/json/xml/XMLTest.java
similarity index 58%
rename from hutool-json/src/test/java/cn/hutool/json/XMLTest.java
rename to hutool-json/src/test/java/cn/hutool/json/xml/XMLTest.java
index 927808de7..99d095fce 100644
--- a/hutool-json/src/test/java/cn/hutool/json/XMLTest.java
+++ b/hutool-json/src/test/java/cn/hutool/json/xml/XMLTest.java
@@ -1,5 +1,8 @@
-package cn.hutool.json;
+package cn.hutool.json.xml;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import cn.hutool.json.XML;
import org.junit.Assert;
import org.junit.Test;
@@ -25,4 +28,14 @@ public class XMLTest {
Assert.assertEquals(xml, xml2);
}
+ @Test
+ public void xmlContentTest(){
+ JSONObject jsonObject = JSONUtil.createObj().set("content","123456");
+
+ String xml = XML.toXml(jsonObject);
+ Assert.assertEquals("123456", xml);
+
+ xml = XML.toXml(jsonObject, null, new String[0]);
+ Assert.assertEquals("123456", xml);
+ }
}