mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
add XML content custom support
This commit is contained in:
parent
838a3ecfa4
commit
166d46d137
@ -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)
|
||||
|
||||
|
@ -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. <br>
|
||||
* The method is required to produce a strictly conforming text. <br>
|
||||
* If the object does not contain a toJSONString method (which is the most common case), then a text will be produced by other means. <br>
|
||||
* If the value is an array or Collection, then a JSONArray will be made from it and its toJSONString method will be called. <br>
|
||||
* If the value is a MAP, then a JSONObject will be made from it and its toJSONString method will be called. <br>
|
||||
* Otherwise, the value's toString method will be called, and the result will be quoted.<br>
|
||||
* 值转为String,用于JSON中。规则为:
|
||||
* <ul>
|
||||
* <li>对象如果实现了{@link JSONString}接口,调用{@link JSONString#toJSONString()}方法</li>
|
||||
* <li>对象如果实现了{@link JSONString}接口,调用{@link JSONString#toJSONString()}方法</li>
|
||||
* <li>对象如果是数组或Collection,包装为{@link JSONArray}</li>
|
||||
* <li>对象如果是Map,包装为{@link JSONObject}</li>
|
||||
* <li>对象如果是数字,使用{@link NumberUtil#toStr(Number)}转换为字符串</li>
|
||||
* <li>其他情况调用toString并使用双引号包装</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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,7 +179,7 @@ final class InternalJSONUtil {
|
||||
* @return 是否有序
|
||||
* @since 5.7.0
|
||||
*/
|
||||
protected static boolean isOrder(Object value){
|
||||
static boolean isOrder(Object value) {
|
||||
if (value instanceof LinkedHashMap || value instanceof SortedMap) {
|
||||
return true;
|
||||
} else if (value instanceof JSONGetter) {
|
||||
|
@ -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();
|
||||
|
||||
// <!
|
||||
|
||||
if (token == 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 '<!'.");
|
||||
} else if (token == LT) {
|
||||
i += 1;
|
||||
} else if (token == GT) {
|
||||
i -= 1;
|
||||
}
|
||||
} while (i > 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;
|
||||
return toXml(object, tagName, "content");
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
JSONArray ja;
|
||||
JSONObject jo;
|
||||
String key;
|
||||
Iterator<String> keys;
|
||||
Object value;
|
||||
|
||||
if (object instanceof JSONObject) {
|
||||
|
||||
// Emit <tagName>
|
||||
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));
|
||||
/**
|
||||
* 转换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);
|
||||
}
|
||||
}
|
||||
} else if ("".equals(value)) {
|
||||
sb.append('<');
|
||||
sb.append(key);
|
||||
sb.append("/>");
|
||||
|
||||
// Emit a new tag <k>
|
||||
|
||||
} else {
|
||||
sb.append(toXml(value, key));
|
||||
}
|
||||
}
|
||||
if (tagName != null) {
|
||||
|
||||
// Emit the </tagname> 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
|
||||
// <array> 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 + ">";
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
182
hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLParser.java
Normal file
182
hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLParser.java
Normal file
@ -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 '<!'.");
|
||||
} else if (token == XML.LT) {
|
||||
i += 1;
|
||||
} else if (token == XML.GT) {
|
||||
i -= 1;
|
||||
}
|
||||
} while (i > 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <tagName>
|
||||
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 </tagname> 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
|
||||
// <array> 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 + ">";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* JSON与XML相互转换封装,基于json.org官方库改造
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package cn.hutool.json.xml;
|
@ -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("<content>123456</content>", xml);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user