518 lines
12 KiB
C#
Raw Normal View History

2024-10-23 17:55:55 +08:00
/******************************************************************************
* Spine Runtimes License Agreement
2024-11-21 09:35:48 +08:00
* Last updated July 28, 2023. Replaces all prior versions.
2024-10-23 17:55:55 +08:00
*
2024-11-21 09:35:48 +08:00
* Copyright (c) 2013-2023, Esoteric Software LLC
2024-10-23 17:55:55 +08:00
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
2024-11-21 09:35:48 +08:00
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
2024-10-23 17:55:55 +08:00
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2024-11-21 09:35:48 +08:00
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2024-10-23 17:55:55 +08:00
*****************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
2024-11-21 09:35:48 +08:00
using System.Globalization;
using System.IO;
using System.Text;
2024-10-23 17:55:55 +08:00
namespace Spine {
public static class Json {
public static object Deserialize (TextReader text) {
2024-11-21 09:35:48 +08:00
SharpJson.JsonDecoder parser = new SharpJson.JsonDecoder();
2024-10-23 17:55:55 +08:00
parser.parseNumbersAsFloat = true;
return parser.Decode(text.ReadToEnd());
}
}
}
/**
* Copyright (c) 2016 Adriano Tinoco d'Oliveira Rezende
*
* Based on the JSON parser by Patrick van Bergen
* http://techblog.procurios.nl/k/news/view/14605/14863/how-do-i-write-my-own-parser-(for-json).html
*
* Changes made:
*
* - Optimized parser speed (deserialize roughly near 3x faster than original)
* - Added support to handle lexer/parser error messages with line numbers
* - Added more fine grained control over type conversions during the parsing
* - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
2024-11-21 09:35:48 +08:00
namespace SharpJson {
class Lexer {
2024-10-23 17:55:55 +08:00
public enum Token {
None,
Null,
True,
False,
Colon,
Comma,
String,
Number,
CurlyOpen,
CurlyClose,
SquaredOpen,
SquaredClose,
};
public bool hasError {
get {
return !success;
}
}
public int lineNumber {
get;
private set;
}
public bool parseNumbersAsFloat {
get;
set;
}
char[] json;
int index = 0;
bool success = true;
char[] stringBuffer = new char[4096];
2024-11-21 09:35:48 +08:00
public Lexer (string text) {
2024-10-23 17:55:55 +08:00
Reset();
json = text.ToCharArray();
parseNumbersAsFloat = false;
}
2024-11-21 09:35:48 +08:00
public void Reset () {
2024-10-23 17:55:55 +08:00
index = 0;
lineNumber = 1;
success = true;
}
2024-11-21 09:35:48 +08:00
public string ParseString () {
2024-10-23 17:55:55 +08:00
int idx = 0;
StringBuilder builder = null;
SkipWhiteSpaces();
// "
char c = json[index++];
bool failed = false;
bool complete = false;
while (!complete && !failed) {
if (index == json.Length)
break;
c = json[index++];
if (c == '"') {
complete = true;
break;
} else if (c == '\\') {
if (index == json.Length)
break;
c = json[index++];
switch (c) {
case '"':
stringBuffer[idx++] = '"';
break;
case '\\':
stringBuffer[idx++] = '\\';
break;
case '/':
stringBuffer[idx++] = '/';
break;
case 'b':
stringBuffer[idx++] = '\b';
break;
2024-11-21 09:35:48 +08:00
case 'f':
stringBuffer[idx++] = '\f';
2024-10-23 17:55:55 +08:00
break;
case 'n':
stringBuffer[idx++] = '\n';
break;
case 'r':
stringBuffer[idx++] = '\r';
break;
case 't':
stringBuffer[idx++] = '\t';
break;
case 'u':
int remainingLength = json.Length - index;
if (remainingLength >= 4) {
2024-11-21 09:35:48 +08:00
string hex = new string(json, index, 4);
2024-10-23 17:55:55 +08:00
// XXX: handle UTF
2024-11-21 09:35:48 +08:00
stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16);
2024-10-23 17:55:55 +08:00
// skip 4 chars
index += 4;
} else {
failed = true;
}
break;
}
} else {
stringBuffer[idx++] = c;
}
if (idx >= stringBuffer.Length) {
if (builder == null)
builder = new StringBuilder();
builder.Append(stringBuffer, 0, idx);
idx = 0;
}
}
if (!complete) {
success = false;
return null;
}
if (builder != null)
2024-11-21 09:35:48 +08:00
return builder.ToString();
2024-10-23 17:55:55 +08:00
else
2024-11-21 09:35:48 +08:00
return new string(stringBuffer, 0, idx);
2024-10-23 17:55:55 +08:00
}
2024-11-21 09:35:48 +08:00
string GetNumberString () {
2024-10-23 17:55:55 +08:00
SkipWhiteSpaces();
int lastIndex = GetLastIndexOfNumber(index);
int charLength = (lastIndex - index) + 1;
2024-11-21 09:35:48 +08:00
string result = new string(json, index, charLength);
2024-10-23 17:55:55 +08:00
index = lastIndex + 1;
return result;
}
2024-11-21 09:35:48 +08:00
public float ParseFloatNumber () {
2024-10-23 17:55:55 +08:00
float number;
2024-11-21 09:35:48 +08:00
string str = GetNumberString();
2024-10-23 17:55:55 +08:00
2024-11-21 09:35:48 +08:00
if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
2024-10-23 17:55:55 +08:00
return 0;
return number;
}
2024-11-21 09:35:48 +08:00
public double ParseDoubleNumber () {
2024-10-23 17:55:55 +08:00
double number;
2024-11-21 09:35:48 +08:00
string str = GetNumberString();
2024-10-23 17:55:55 +08:00
if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
return 0;
return number;
}
2024-11-21 09:35:48 +08:00
int GetLastIndexOfNumber (int index) {
2024-10-23 17:55:55 +08:00
int lastIndex;
for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
char ch = json[lastIndex];
if ((ch < '0' || ch > '9') && ch != '+' && ch != '-'
2024-11-21 09:35:48 +08:00
&& ch != '.' && ch != 'e' && ch != 'E')
2024-10-23 17:55:55 +08:00
break;
}
return lastIndex - 1;
}
2024-11-21 09:35:48 +08:00
void SkipWhiteSpaces () {
2024-10-23 17:55:55 +08:00
for (; index < json.Length; index++) {
char ch = json[index];
if (ch == '\n')
lineNumber++;
if (!char.IsWhiteSpace(json[index]))
break;
}
}
2024-11-21 09:35:48 +08:00
public Token LookAhead () {
2024-10-23 17:55:55 +08:00
SkipWhiteSpaces();
int savedIndex = index;
return NextToken(json, ref savedIndex);
}
2024-11-21 09:35:48 +08:00
public Token NextToken () {
2024-10-23 17:55:55 +08:00
SkipWhiteSpaces();
return NextToken(json, ref index);
}
2024-11-21 09:35:48 +08:00
static Token NextToken (char[] json, ref int index) {
2024-10-23 17:55:55 +08:00
if (index == json.Length)
return Token.None;
char c = json[index++];
switch (c) {
case '{':
return Token.CurlyOpen;
case '}':
return Token.CurlyClose;
case '[':
return Token.SquaredOpen;
case ']':
return Token.SquaredClose;
case ',':
return Token.Comma;
case '"':
return Token.String;
2024-11-21 09:35:48 +08:00
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
2024-10-23 17:55:55 +08:00
case '-':
return Token.Number;
case ':':
return Token.Colon;
}
index--;
int remainingLength = json.Length - index;
// false
if (remainingLength >= 5) {
if (json[index] == 'f' &&
2024-11-21 09:35:48 +08:00
json[index + 1] == 'a' &&
json[index + 2] == 'l' &&
json[index + 3] == 's' &&
json[index + 4] == 'e') {
2024-10-23 17:55:55 +08:00
index += 5;
return Token.False;
}
}
// true
if (remainingLength >= 4) {
if (json[index] == 't' &&
2024-11-21 09:35:48 +08:00
json[index + 1] == 'r' &&
json[index + 2] == 'u' &&
json[index + 3] == 'e') {
2024-10-23 17:55:55 +08:00
index += 4;
return Token.True;
}
}
// null
if (remainingLength >= 4) {
if (json[index] == 'n' &&
2024-11-21 09:35:48 +08:00
json[index + 1] == 'u' &&
json[index + 2] == 'l' &&
json[index + 3] == 'l') {
2024-10-23 17:55:55 +08:00
index += 4;
return Token.Null;
}
}
return Token.None;
}
}
2024-11-21 09:35:48 +08:00
public class JsonDecoder {
2024-10-23 17:55:55 +08:00
public string errorMessage {
get;
private set;
}
public bool parseNumbersAsFloat {
get;
set;
}
Lexer lexer;
2024-11-21 09:35:48 +08:00
public JsonDecoder () {
2024-10-23 17:55:55 +08:00
errorMessage = null;
parseNumbersAsFloat = false;
}
2024-11-21 09:35:48 +08:00
public object Decode (string text) {
2024-10-23 17:55:55 +08:00
errorMessage = null;
lexer = new Lexer(text);
lexer.parseNumbersAsFloat = parseNumbersAsFloat;
return ParseValue();
}
2024-11-21 09:35:48 +08:00
public static object DecodeText (string text) {
JsonDecoder builder = new JsonDecoder();
2024-10-23 17:55:55 +08:00
return builder.Decode(text);
}
2024-11-21 09:35:48 +08:00
IDictionary<string, object> ParseObject () {
Dictionary<string, object> table = new Dictionary<string, object>();
2024-10-23 17:55:55 +08:00
// {
lexer.NextToken();
while (true) {
2024-11-21 09:35:48 +08:00
Lexer.Token token = lexer.LookAhead();
2024-10-23 17:55:55 +08:00
switch (token) {
case Lexer.Token.None:
TriggerError("Invalid token");
return null;
case Lexer.Token.Comma:
lexer.NextToken();
break;
case Lexer.Token.CurlyClose:
lexer.NextToken();
return table;
default:
// name
string name = EvalLexer(lexer.ParseString());
if (errorMessage != null)
return null;
// :
token = lexer.NextToken();
if (token != Lexer.Token.Colon) {
TriggerError("Invalid token; expected ':'");
return null;
}
// value
object value = ParseValue();
if (errorMessage != null)
return null;
table[name] = value;
break;
}
}
//return null; // Unreachable code
}
2024-11-21 09:35:48 +08:00
IList<object> ParseArray () {
List<object> array = new List<object>();
2024-10-23 17:55:55 +08:00
// [
lexer.NextToken();
while (true) {
2024-11-21 09:35:48 +08:00
Lexer.Token token = lexer.LookAhead();
2024-10-23 17:55:55 +08:00
switch (token) {
case Lexer.Token.None:
TriggerError("Invalid token");
return null;
case Lexer.Token.Comma:
lexer.NextToken();
break;
case Lexer.Token.SquaredClose:
lexer.NextToken();
return array;
default:
object value = ParseValue();
if (errorMessage != null)
return null;
array.Add(value);
break;
}
}
//return null; // Unreachable code
}
2024-11-21 09:35:48 +08:00
object ParseValue () {
2024-10-23 17:55:55 +08:00
switch (lexer.LookAhead()) {
case Lexer.Token.String:
return EvalLexer(lexer.ParseString());
case Lexer.Token.Number:
if (parseNumbersAsFloat)
return EvalLexer(lexer.ParseFloatNumber());
else
return EvalLexer(lexer.ParseDoubleNumber());
case Lexer.Token.CurlyOpen:
return ParseObject();
case Lexer.Token.SquaredOpen:
return ParseArray();
case Lexer.Token.True:
lexer.NextToken();
return true;
case Lexer.Token.False:
lexer.NextToken();
return false;
case Lexer.Token.Null:
lexer.NextToken();
return null;
case Lexer.Token.None:
break;
}
TriggerError("Unable to parse value");
return null;
}
2024-11-21 09:35:48 +08:00
void TriggerError (string message) {
2024-10-23 17:55:55 +08:00
errorMessage = string.Format("Error: '{0}' at line {1}",
2024-11-21 09:35:48 +08:00
message, lexer.lineNumber);
2024-10-23 17:55:55 +08:00
}
2024-11-21 09:35:48 +08:00
T EvalLexer<T> (T value) {
2024-10-23 17:55:55 +08:00
if (lexer.hasError)
TriggerError("Lexical error ocurred");
return value;
}
}
}