mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
fix code
This commit is contained in:
parent
75f9a66998
commit
11ba7ba36c
@ -110,7 +110,7 @@ public class JSONParser {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
tokener.back();
|
tokener.back();
|
||||||
key = nextValue(true).toString();
|
key = tokener.nextString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The key is followed by ':'.
|
// The key is followed by ':'.
|
||||||
@ -120,7 +120,7 @@ public class JSONParser {
|
|||||||
throw tokener.syntaxError("Expected a ':' after a key");
|
throw tokener.syntaxError("Expected a ':' after a key");
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonObject.set(key, nextValue(false), predicate);
|
jsonObject.set(key, nextValue(), predicate);
|
||||||
|
|
||||||
// Pairs are separated by ','.
|
// Pairs are separated by ','.
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ public class JSONParser {
|
|||||||
jsonArray.addRaw(null, predicate);
|
jsonArray.addRaw(null, predicate);
|
||||||
} else {
|
} else {
|
||||||
x.back();
|
x.back();
|
||||||
jsonArray.addRaw(nextValue(false), predicate);
|
jsonArray.addRaw(nextValue(), predicate);
|
||||||
}
|
}
|
||||||
switch (x.nextClean()) {
|
switch (x.nextClean()) {
|
||||||
case CharUtil.COMMA:
|
case CharUtil.COMMA:
|
||||||
@ -184,20 +184,19 @@ public class JSONParser {
|
|||||||
/**
|
/**
|
||||||
* 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
* 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||||
*
|
*
|
||||||
* @param getOnlyStringValue 是否只获取String值
|
|
||||||
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||||
* @throws JSONException 语法错误
|
* @throws JSONException 语法错误
|
||||||
*/
|
*/
|
||||||
public Object nextValue(final boolean getOnlyStringValue) throws JSONException {
|
public Object nextValue() throws JSONException {
|
||||||
return nextValue(getOnlyStringValue, (token, tokener, config) -> {
|
return nextValue((token, tokener, config) -> {
|
||||||
switch (token) {
|
switch (token) {
|
||||||
case '{':
|
case CharUtil.DELIM_START:
|
||||||
try {
|
try {
|
||||||
return new JSONObject(this, config);
|
return new JSONObject(this, config);
|
||||||
} catch (final StackOverflowError e) {
|
} catch (final StackOverflowError e) {
|
||||||
throw new JSONException("JSONObject depth too large to process.", e);
|
throw new JSONException("JSONObject depth too large to process.", e);
|
||||||
}
|
}
|
||||||
case '[':
|
case CharUtil.BRACKET_START:
|
||||||
try {
|
try {
|
||||||
return new JSONArray(this, config);
|
return new JSONArray(this, config);
|
||||||
} catch (final StackOverflowError e) {
|
} catch (final StackOverflowError e) {
|
||||||
@ -211,45 +210,29 @@ public class JSONParser {
|
|||||||
/**
|
/**
|
||||||
* 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
* 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||||
*
|
*
|
||||||
* @param getOnlyStringValue 是否只获取String值
|
|
||||||
* @param objectBuilder JSON对象构建器
|
* @param objectBuilder JSON对象构建器
|
||||||
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
|
||||||
* @throws JSONException 语法错误
|
* @throws JSONException 语法错误
|
||||||
*/
|
*/
|
||||||
public Object nextValue(final boolean getOnlyStringValue, final ObjectBuilder objectBuilder) throws JSONException {
|
public Object nextValue(final ObjectBuilder objectBuilder) throws JSONException {
|
||||||
final JSONTokener tokener = this.tokener;
|
final JSONTokener tokener = this.tokener;
|
||||||
char c = tokener.nextClean();
|
final char c = tokener.nextClean();
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '"':
|
case CharUtil.DOUBLE_QUOTES:
|
||||||
case '\'':
|
case CharUtil.SINGLE_QUOTE:
|
||||||
return tokener.nextString(c);
|
return tokener.nextString(c);
|
||||||
case '{':
|
case CharUtil.DELIM_START:
|
||||||
case '[':
|
case CharUtil.BRACKET_START:
|
||||||
if (getOnlyStringValue) {
|
|
||||||
throw tokener.syntaxError("String value must not begin with '{'");
|
|
||||||
}
|
|
||||||
tokener.back();
|
tokener.back();
|
||||||
return objectBuilder.build(c, tokener, this.config);
|
return objectBuilder.build(c, tokener, this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle unquoted text. This could be the values true, false, or null, or it can be a number.
|
* 处理无引号包装的字符串,如: true, false, 或 null, 或 number.
|
||||||
* An implementation (such as this one) is allowed to also accept non-standard forms. Accumulate
|
* 同样兼容非标准的字符串,如key无引号包装。
|
||||||
* characters until we reach the end of the text or a formatting character.
|
* 此方法会不断读取并积累字符直到遇到token符
|
||||||
*/
|
*/
|
||||||
|
return InternalJSONUtil.parseValueFromString(tokener.nextUnwrapString(c));
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
|
|
||||||
sb.append(c);
|
|
||||||
c = tokener.next();
|
|
||||||
}
|
|
||||||
tokener.back();
|
|
||||||
|
|
||||||
final String valueString = sb.toString().trim();
|
|
||||||
if (valueString.isEmpty()) {
|
|
||||||
throw tokener.syntaxError("Missing value");
|
|
||||||
}
|
|
||||||
return getOnlyStringValue ? valueString : InternalJSONUtil.parseValueFromString(valueString);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,8 @@ import org.dromara.hutool.core.io.IoUtil;
|
|||||||
import org.dromara.hutool.core.io.ReaderWrapper;
|
import org.dromara.hutool.core.io.ReaderWrapper;
|
||||||
import org.dromara.hutool.core.lang.Assert;
|
import org.dromara.hutool.core.lang.Assert;
|
||||||
import org.dromara.hutool.core.math.NumberUtil;
|
import org.dromara.hutool.core.math.NumberUtil;
|
||||||
|
import org.dromara.hutool.core.text.CharUtil;
|
||||||
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -23,12 +25,18 @@ import java.io.Reader;
|
|||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON解析器,用于将JSON字符串解析为JSONObject或者JSONArray
|
* JSON解析器<br>
|
||||||
|
* 用于解析JSON字符串,支持流式解析,即逐个字符解析,而不是一次性解析整个字符串。
|
||||||
*
|
*
|
||||||
* @author from JSON.org
|
* @author from JSON.org
|
||||||
*/
|
*/
|
||||||
public class JSONTokener extends ReaderWrapper {
|
public class JSONTokener extends ReaderWrapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON的分界符
|
||||||
|
*/
|
||||||
|
private static final String TOKENS = ",:]}/\\\"[{;=#";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义结束(End of stream)为:0
|
* 定义结束(End of stream)为:0
|
||||||
*/
|
*/
|
||||||
@ -207,6 +215,20 @@ public class JSONTokener extends ReaderWrapper {
|
|||||||
return chars;
|
return chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取下一个token字符
|
||||||
|
*
|
||||||
|
* @return token字符
|
||||||
|
* @throws JSONException 非Token字符
|
||||||
|
*/
|
||||||
|
public char nextTokenChar() throws JSONException {
|
||||||
|
final char c = this.nextClean();
|
||||||
|
if (isNotTokenChar(c)) {
|
||||||
|
throw this.syntaxError("Invalid token char: " + c);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得下一个字符,跳过空白符
|
* 获得下一个字符,跳过空白符
|
||||||
*
|
*
|
||||||
@ -223,6 +245,59 @@ public class JSONTokener extends ReaderWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取一个字符串,包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>使用引号包裹的字符串,自动反转义。</li>
|
||||||
|
* <li>无包装的字符串,不转义</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return 截止到引号前的字符串
|
||||||
|
* @throws JSONException 出现无结束的字符串时抛出此异常
|
||||||
|
*/
|
||||||
|
public String nextString() throws JSONException {
|
||||||
|
final char c = nextClean();
|
||||||
|
switch (c) {
|
||||||
|
case CharUtil.DOUBLE_QUOTES:
|
||||||
|
case CharUtil.SINGLE_QUOTE:
|
||||||
|
return nextString(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容不严格的JSON,如key不被双引号包围的情况
|
||||||
|
return nextUnwrapString(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得下一个字符串,此字符串不以引号包围,不会处理转义符,主要解析:
|
||||||
|
* <ul>
|
||||||
|
* <li>非严格的key(无引号包围的key)</li>
|
||||||
|
* <li>boolean值的字符串表示</li>
|
||||||
|
* <li>Number值的字符串表示</li>
|
||||||
|
* <li>null的字符串表示</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param c 首个字符
|
||||||
|
* @return 字符串
|
||||||
|
* @throws JSONException 读取空串时抛出此异常
|
||||||
|
*/
|
||||||
|
public String nextUnwrapString(char c) throws JSONException {
|
||||||
|
// 兼容不严格的JSON,如key不被双引号包围的情况
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
while (isNotTokenChar(c)) {
|
||||||
|
sb.append(c);
|
||||||
|
c = next();
|
||||||
|
}
|
||||||
|
if (c != EOF) {
|
||||||
|
back();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String valueString = StrUtil.trim(sb);
|
||||||
|
if (valueString.isEmpty()) {
|
||||||
|
throw syntaxError("Missing value, maybe a token");
|
||||||
|
}
|
||||||
|
return valueString;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回当前位置到指定引号前的所有字符,反斜杠的转义符也会被处理。<br>
|
* 返回当前位置到指定引号前的所有字符,反斜杠的转义符也会被处理。<br>
|
||||||
* 标准的JSON是不允许使用单引号包含字符串的,但是此实现允许。
|
* 标准的JSON是不允许使用单引号包含字符串的,但是此实现允许。
|
||||||
@ -237,47 +312,21 @@ public class JSONTokener extends ReaderWrapper {
|
|||||||
while (true) {
|
while (true) {
|
||||||
c = this.next();
|
c = this.next();
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 0:
|
case EOF:
|
||||||
throw this.syntaxError("Unterminated string");
|
throw this.syntaxError("Unterminated string");
|
||||||
case '\n':
|
case CharUtil.LF:
|
||||||
case '\r':
|
case CharUtil.CR:
|
||||||
//throw this.syntaxError("Unterminated string");
|
//throw this.syntaxError("Unterminated string");
|
||||||
// https://gitee.com/dromara/hutool/issues/I76CSU
|
// https://gitee.com/dromara/hutool/issues/I76CSU
|
||||||
// 兼容非转义符
|
// 兼容非转义符
|
||||||
sb.append(c);
|
sb.append(c);
|
||||||
break;
|
break;
|
||||||
case '\\':// 转义符
|
case CharUtil.BACKSLASH:// 转义符
|
||||||
c = this.next();
|
c = this.next();
|
||||||
switch (c) {
|
sb.append(getUnescapeChar(c));
|
||||||
case 'b':
|
|
||||||
sb.append('\b');
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
sb.append('\t');
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
sb.append('\n');
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
sb.append('\f');
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
sb.append('\r');
|
|
||||||
break;
|
|
||||||
case 'u':// Unicode符
|
|
||||||
sb.append(nextUnicode());
|
|
||||||
break;
|
|
||||||
case '"':
|
|
||||||
case '\'':
|
|
||||||
case '\\':
|
|
||||||
case '/':
|
|
||||||
sb.append(c);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw this.syntaxError("Illegal escape.");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// 字符串结束
|
||||||
if (c == quote) {
|
if (c == quote) {
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@ -286,80 +335,6 @@ public class JSONTokener extends ReaderWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得从当前位置直到分隔符(不包括分隔符)或行尾的的所有字符。
|
|
||||||
*
|
|
||||||
* @param delimiter 分隔符
|
|
||||||
* @return 字符串
|
|
||||||
* @throws JSONException JSON异常,包装IO异常
|
|
||||||
*/
|
|
||||||
public String nextTo(final char delimiter) throws JSONException {
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
for (; ; ) {
|
|
||||||
final char c = this.next();
|
|
||||||
if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
|
|
||||||
if (c != 0) {
|
|
||||||
this.back();
|
|
||||||
}
|
|
||||||
return sb.toString().trim();
|
|
||||||
}
|
|
||||||
sb.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the text up but not including one of the specified delimiter characters or the end of line, whichever comes first.
|
|
||||||
*
|
|
||||||
* @param delimiters A set of delimiter characters.
|
|
||||||
* @return A string, trimmed.
|
|
||||||
* @throws JSONException JSON异常,包装IO异常
|
|
||||||
*/
|
|
||||||
public String nextTo(final String delimiters) throws JSONException {
|
|
||||||
char c;
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
for (; ; ) {
|
|
||||||
c = this.next();
|
|
||||||
if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') {
|
|
||||||
if (c != 0) {
|
|
||||||
this.back();
|
|
||||||
}
|
|
||||||
return sb.toString().trim();
|
|
||||||
}
|
|
||||||
sb.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip characters until the next character is the requested character. If the requested character is not found, no characters are skipped. 在遇到指定字符前,跳过其它字符。如果字符未找到,则不跳过任何字符。
|
|
||||||
*
|
|
||||||
* @param to 需要定位的字符
|
|
||||||
* @return 定位的字符,如果字符未找到返回0
|
|
||||||
* @throws JSONException IO异常
|
|
||||||
*/
|
|
||||||
public char skipTo(final char to) throws JSONException {
|
|
||||||
char c;
|
|
||||||
try {
|
|
||||||
final long startIndex = this.index;
|
|
||||||
final long startCharacter = this.character;
|
|
||||||
final long startLine = this.line;
|
|
||||||
mark(1000000);
|
|
||||||
do {
|
|
||||||
c = this.next();
|
|
||||||
if (c == 0) {
|
|
||||||
reset();
|
|
||||||
this.index = startIndex;
|
|
||||||
this.character = startCharacter;
|
|
||||||
this.line = startLine;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
} while (c != to);
|
|
||||||
} catch (final IOException e) {
|
|
||||||
throw new JSONException(e);
|
|
||||||
}
|
|
||||||
this.back();
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a JSONException to signal a syntax error. <br>
|
* Make a JSONException to signal a syntax error. <br>
|
||||||
* 构建 JSONException 用于表示语法错误
|
* 构建 JSONException 用于表示语法错误
|
||||||
@ -380,4 +355,44 @@ public class JSONTokener extends ReaderWrapper {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return " at " + this.index + " [character " + this.character + " line " + this.line + "]";
|
return " at " + this.index + " [character " + this.character + " line " + this.line + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取反转义的字符
|
||||||
|
*
|
||||||
|
* @param c 转义的字符,即`\`后的字符
|
||||||
|
* @return 反转义字符
|
||||||
|
*/
|
||||||
|
private char getUnescapeChar(final char c) {
|
||||||
|
switch (c) {
|
||||||
|
case 'b':
|
||||||
|
return '\b';
|
||||||
|
case 't':
|
||||||
|
return '\t';
|
||||||
|
case 'n':
|
||||||
|
return '\n';
|
||||||
|
case 'f':
|
||||||
|
return '\f';
|
||||||
|
case 'r':
|
||||||
|
return '\r';
|
||||||
|
case 'u':// Unicode符
|
||||||
|
return nextUnicode();
|
||||||
|
case CharUtil.DOUBLE_QUOTES:
|
||||||
|
case CharUtil.SINGLE_QUOTE:
|
||||||
|
case CharUtil.BACKSLASH:
|
||||||
|
case CharUtil.SLASH:
|
||||||
|
return c;
|
||||||
|
default:
|
||||||
|
throw this.syntaxError("Illegal escape.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为可见的非Token字符,这些字符存在于JSON的非字符串value中。
|
||||||
|
*
|
||||||
|
* @param c char
|
||||||
|
* @return 是否为可见的非Token字符
|
||||||
|
*/
|
||||||
|
private static boolean isNotTokenChar(final char c) {
|
||||||
|
return c >= ' ' && TOKENS.indexOf(c) < 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ public class JSONConverter implements Converter, Serializable {
|
|||||||
final char firstC = jsonStr.charAt(0);
|
final char firstC = jsonStr.charAt(0);
|
||||||
// RFC8259,JSON字符串值、number, boolean, or null
|
// RFC8259,JSON字符串值、number, boolean, or null
|
||||||
final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr), config);
|
final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr), config);
|
||||||
final Object value = jsonParser.nextValue(false);
|
final Object value = jsonParser.nextValue();
|
||||||
if(jsonParser.getTokener().nextClean() != JSONTokener.EOF){
|
if(jsonParser.getTokener().nextClean() != JSONTokener.EOF){
|
||||||
// 对于用户提供的未转义字符串导致解析未结束,报错
|
// 对于用户提供的未转义字符串导致解析未结束,报错
|
||||||
throw new JSONException("JSON format error: {}", jsonStr);
|
throw new JSONException("JSON format error: {}", jsonStr);
|
||||||
|
@ -12,14 +12,48 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.json;
|
package org.dromara.hutool.json;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.io.IoUtil;
|
||||||
import org.dromara.hutool.core.io.resource.ResourceUtil;
|
import org.dromara.hutool.core.io.resource.ResourceUtil;
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
public class JSONTokenerTest {
|
public class JSONTokenerTest {
|
||||||
@Test
|
@Test
|
||||||
public void parseTest() {
|
void parseTest() {
|
||||||
final JSONObject jsonObject = JSONUtil.parseObj(ResourceUtil.getUtf8Reader("issue1200.json"));
|
final JSONObject jsonObject = JSONUtil.parseObj(ResourceUtil.getUtf8Reader("issue1200.json"));
|
||||||
Assertions.assertNotNull(jsonObject);
|
assertNotNull(jsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nextTest() {
|
||||||
|
final JSONTokener jsonTokener = new JSONTokener("{\"ab\": \"abc\"}");
|
||||||
|
final char c = jsonTokener.nextTokenChar();
|
||||||
|
assertEquals('{', c);
|
||||||
|
assertEquals("ab", jsonTokener.nextString());
|
||||||
|
final char c2 = jsonTokener.nextTokenChar();
|
||||||
|
assertEquals(':', c2);
|
||||||
|
assertEquals("abc", jsonTokener.nextString());
|
||||||
|
|
||||||
|
|
||||||
|
IoUtil.closeQuietly(jsonTokener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兼容非包装符包装的value和key
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void nextWithoutWrapperTest() {
|
||||||
|
final JSONTokener jsonTokener = new JSONTokener("{ab: abc}");
|
||||||
|
final char c = jsonTokener.nextTokenChar();
|
||||||
|
assertEquals('{', c);
|
||||||
|
assertEquals("ab", jsonTokener.nextString());
|
||||||
|
final char c2 = jsonTokener.nextTokenChar();
|
||||||
|
assertEquals(':', c2);
|
||||||
|
assertEquals("abc", jsonTokener.nextString());
|
||||||
|
|
||||||
|
|
||||||
|
IoUtil.closeQuietly(jsonTokener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user