/**
* Copyright(c) Live2D Inc. All rights reserved.
*
* Use of this source code is governed by the Live2D Open Software license
* that can be found at http: //live2d.com/eula/live2d-open-software-license-agreement_en.html.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace Live2D.Cubism.Framework.Json
{
///
/// Cubism json parser for loading the configuration file etc.
///
/// Minimal lightweight JSON parser that only supports Ascii characters.
/// Specification is a subset of JSON.
///
/// Unsupported item.
/// - Non-ASCII characters such as Japanese.
/// - Exponential representation by e.
///
public class CubismJsonParser
{
#region variable
///
/// Array of buffer.
///
private char[] buffer;
///
/// Length of buffer.
///
private int length;
///
/// For error message.
///
private int line_count = 0;
///
/// Root node.
///
private Value root;
#endregion
///
/// Constructor.
///
/// Byte data.
public CubismJsonParser(char[] jsonBytes)
{
this.buffer = jsonBytes;
this.length = jsonBytes.Length;
}
#region Parse Functionn
///
/// Parse JSON.
///
/// Value of parsed from JSON.
public Value Parse()
// throws Exception.
{
try
{
var ret = new int[1];
root = ParseValue(buffer, length, 0, ret);
return root;
}
catch (Exception e)
{
throw new Exception("json error " + "@line:" + line_count + " / " + e.Message, e);
}
}
///
/// Parse JSON from byte data.
///
/// Byte data.
/// Value of parsed from JSON.
public static Value ParseFromBytes(char[] jsonBytes)
// throws Exception.
{
var jp = new CubismJsonParser(jsonBytes);
var ret = jp.Parse();
return ret;
}
///
/// Parse JSON from string data.
///
/// String data.
/// Value of parsed from JSON.
public static Value ParseFromString(String jsonString)
// throws Exception.
{
var buffer = jsonString.ToCharArray();
var jp = new CubismJsonParser(buffer);
var ret = jp.Parse();
return ret;
}
///
/// Parse till next.
///
/// json data buffer.
/// json data buffer length.
/// Parse position.
/// End position.
/// String of parsed from JSON.
private static String ParseString(char[] str, int length, int pos, int[] endPos)
// throws Exception.
{
char c, c2;
StringBuilder stringBuffer = null;
var startPos = pos; // start pos of the word which is not in sbuf
for (var i = pos; i < length; i++)
{
c = (char)(str[i] & 0xFF);
switch (c)
{
case '\"': // end " , escape char never comes here.
endPos[0] = i + 1; // next word of "
if (stringBuffer != null)
{
if (i - 1 > startPos) stringBuffer.Append(new string(str, startPos, i - 1 - startPos)); // regist till prev char
return stringBuffer.ToString();
}
else
{
return new string(str, pos, i - pos);
}
case '\\': // escape
if (stringBuffer == null)
{
stringBuffer = new StringBuilder();
}
if (i > startPos) stringBuffer.Append(new string(str, startPos, i - startPos)); // regist till prev char
i++; // 2 chars
if (i < length)
{
c2 = (char)(str[i] & 0xFF);
switch (c2)
{
case '\\': stringBuffer.Append('\\'); break;
case '\"': stringBuffer.Append('\"'); break;
case '/': stringBuffer.Append('/'); break;
case 'b': stringBuffer.Append('\b'); break;
case 'f': stringBuffer.Append('\f'); break;
case 'n': stringBuffer.Append('\n'); break;
case 'r': stringBuffer.Append('\r'); break;
case 't': stringBuffer.Append('\t'); break;
case 'u':
throw new Exception("parse string/unicode escape not supported");
}
}
else
{
throw new Exception("parse string/escape error");
}
startPos = i + 1; // after next to escape char (2chars)
break;
}
}
throw new Exception("parse string/illegal end");
}
///
/// Parse object, not include { at pos.
///
/// json data buffer.
/// json data buffer length.
/// Parse position.
/// End position.
/// Value of parsed from JSON.
private Value ParseObject(char[] buffer, int length, int pos, int[] endPos)
// throws Exception.
{
var ret = new Dictionary();
// key : value ,
String key = null;
char c;
var i = pos;
var ret_endPos = new int[1];
var ok = false;
// loop till , is lasting
for (; i < length; i++)
{
// FOR_LOOP1:
for (; i < length; i++)
{
c = (char)(buffer[i] & 0xFF);
switch (c)
{
case '\"':
key = ParseString(buffer, length, i + 1, ret_endPos);
i = ret_endPos[0];
ok = true;
goto EXIT_FOR_LOOP1;
case '}': endPos[0] = i + 1; return new Value(ret); // empty
case ':': throw new Exception("illegal ':' position");
default: break; // skip char
}
}
EXIT_FOR_LOOP1:
if (!ok)
{
throw new Exception("key not found");
}
ok = false;
// check :
// FOR_LOOP2:
for (; i < length; i++)
{
c = (char)(buffer[i] & 0xFF);
switch (c)
{
case ':': ok = true; i++; goto EXIT_FOR_LOOP2;
case '}': throw new Exception("illegal '}' position");
case '\n': line_count++; break;
default: break; // skip char
}
}
EXIT_FOR_LOOP2:
if (!ok)
{
throw new Exception("':' not found");
}
// check :
Value value = ParseValue(buffer, length, i, ret_endPos);
i = ret_endPos[0];
ret.Add(key, value);
// FOR_LOOP3:
for (; i < length; i++)
{
c = (char)(buffer[i] & 0xFF);
switch (c)
{
case ',': goto EXIT_FOR_LOOP3; // next key, value
case '}': endPos[0] = i + 1; return new Value(ret); //finished
case '\n': line_count++; break;
default: break; // skip
}
}
EXIT_FOR_LOOP3: ;
}
throw new Exception("illegal end of ParseObject");
}
///
/// Parse Array, not include first[ at pos.
///
/// json data buffer.
/// json data buffer length.
/// Parse position.
/// End position.
/// Value of parsed from JSON.
private Value ParseArray(char[] buffer, int length, int pos, int[] endPos)
// throws Exception.
{
var ret = new List();
var i = pos;
char c;
var ret_endPos = new int[1];
// loop till, is lasting
for (; i < length; i++)
{
// check :
var value = ParseValue(buffer, length, i, ret_endPos);
i = ret_endPos[0];
if (value != null)
{
ret.Add(value);
}
// FOR_LOOP3:
for (; i < length; i++)
{
c = (char)(buffer[i] & 0xFF);
switch (c)
{
case ',': goto EXIT_FOR_LOOP3; // next key value
case ']': endPos[0] = i + 1; return new Value(ret); // finish
case '\n': line_count++; break;
default: break; // skip
}
}
EXIT_FOR_LOOP3: ;
}
throw new Exception("illegal end of ParseObject");
}
///
/// Parse double.
///
/// json data buffer.
/// json data buffer length.
/// Parse position.
/// End position.
/// Double of parsed from JSON.
public static double strToDouble(char[] str, int length, int pos, int[] endPos)
{
// int length = str.length ;
var i = pos;
var minus = false; // minus flag
var period = false;
var v1 = 0.0;
// check minus
var c = (char)(str[i] & 0xFF);
if(c == '-')
{
minus = true;
i++;
}
// check integer part
// FOR_LOOP:
for (; i < length; i++)
{
c = (char)(str[i] & 0xFF);
switch (c)
{
case '0': v1 = v1 * 10; break;
case '1': v1 = v1 * 10 + 1; break;
case '2': v1 = v1 * 10 + 2; break;
case '3': v1 = v1 * 10 + 3; break;
case '4': v1 = v1 * 10 + 4; break;
case '5': v1 = v1 * 10 + 5; break;
case '6': v1 = v1 * 10 + 6; break;
case '7': v1 = v1 * 10 + 7; break;
case '8': v1 = v1 * 10 + 8; break;
case '9': v1 = v1 * 10 + 9; break;
case '.':
period = true;
i++;
goto EXIT_FOR_LOOP;
default: // new line code , and delim
goto EXIT_FOR_LOOP;
}
}
EXIT_FOR_LOOP:
// check floating point part
if (period)
{
var mul = 0.1;
// FOR_LOOP2:
for (; i < length; i++)
{
c = (char)(str[i] & 0xFF);
switch (c)
{
case '0': break;
case '1': v1 += mul * 1; break;
case '2': v1 += mul * 2; break;
case '3': v1 += mul * 3; break;
case '4': v1 += mul * 4; break;
case '5': v1 += mul * 5; break;
case '6': v1 += mul * 6; break;
case '7': v1 += mul * 7; break;
case '8': v1 += mul * 8; break;
case '9': v1 += mul * 9; break;
default: // new line code, and delim
goto EXIT_FOR_LOOP2;
}
mul *= 0.1;
}
EXIT_FOR_LOOP2:;
}
if (minus)
{
v1 = -v1;
}
endPos[0] = i;
return v1;
}
///
/// Parse one Value(float, String, Object, Array, null, true, false).
///
/// json data buffer.
/// json data buffer length.
/// Parse position.
/// End position.
/// Value of parsed from JSON.
private Value ParseValue(char[] buffer, int length, int pos, int[] endPos)
// throws Exception.
{
Value obj;
var i = pos;
for (; i < length; i++)
{
var c = (char)(buffer[i] & 0xFF);
switch (c)
{
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
var f = strToDouble(buffer, length, i, endPos);
return new Value(f);
case '\"':
obj = new Value(ParseString(buffer, length, i + 1, endPos)); // next to \"
return obj;
case '[':
obj = ParseArray(buffer, length, i + 1, endPos);
return obj;
case ']': // It is illegal } but skip it. There seems to be unnecessary at the end of the array
// obj = null;
endPos[0] = i; // Reprocess the same letters
return null;
case '{':
obj = ParseObject(buffer, length, i + 1, endPos);
return obj;
case 'n': // null
if (i + 3 < length) obj = null;
else throw new Exception("parse null");
return obj;
case 't': // true
if (i + 3 < length) obj = new Value(true);
else throw new Exception("parse true");
return obj;
case 'f': // false
if (i + 4 < length) obj = new Value(false);
else throw new Exception("parse false");
return obj;
case ',': // Array separator
throw new Exception("illegal ',' position");
case '\n': line_count++;
break;
case ' ':
case '\t':
case '\r':
default: // skip
break;
}
}
// throw new Exception("illegal end of value");
return null;
}
#endregion
}
///
/// Json value.
///
public class Value
{
private Object _object;
///
/// Get value.
///
/// The JSON value.
public Value(Object obj)
{
this._object = obj;
}
#region toString
///
/// Value to string.
///
/// Value of string type.
public string toString()
{
return toString("");
}
///
/// Value to string.
///
/// Value of string type.
public string toString(string indent)
{
if (_object is string)
{
return (string)_object;
}
//------------ List ------------
else if (_object is List)
{
string ret = indent + "[\n";
foreach (Value v in ((List)_object))
{
ret += indent + " " + v.toString(indent + " ") + "\n";
}
ret += indent + "]\n";
return ret;
}
//------------ Dictionary ------------
else if (_object is Dictionary)
{
string ret = indent + "{\n";
Dictionary vmap = (Dictionary)_object;
foreach (KeyValuePair pair in vmap)
{
Value v = pair.Value;
ret += indent + " " + pair.Key + " : " + v.toString(indent + " ") + "\n";
}
ret += indent + "}\n";
return ret;
}
else
{
return "" + _object;
}
}
#endregion
#region toInt
///
/// Value to int.
///
/// Value of int type.
public int toInt()
{
return toInt(0);
}
///
/// Value to int.
///
/// Default value.
/// Value of int type.
public int toInt(int defaultValue)
{
return (_object is Double) ? (int)((Double)_object) : defaultValue;
}
#endregion
#region ToFloat
///
/// Value to float.
///
/// Value of float type.
public float ToFloat()
{
return ToFloat(0);
}
///
/// Value to float.
///
/// Default value.
/// Value of float type.
public float ToFloat(float defaultValue)
{
return (_object is Double) ? (float)((Double)_object) : defaultValue;
}
#endregion
#region ToDouble
///
/// Value to double.
///
/// Value of double type.
public double ToDouble()
{
return ToDouble(0);
}
///
/// Value to double.
///
/// Default value.
/// Value of double type.
public double ToDouble(double defaultValue)
{
return (_object is Double) ? ((Double)_object) : defaultValue;
}
#endregion
#region toArray
///
/// Get list.
///
/// Default value.
/// Value list.
public List GetVector(List defalutV)
{
return (_object is List) ? (List)_object : defalutV;
}
///
/// Get from list.
///
/// Value index in list.
/// Value from list.
public Value Get(int index)
{
return (_object is List) ? (Value)((List)_object)[index] : null;
}
#endregion
#region toDictionary
///
/// Get Value of dictionary type.
///
/// Default value.
/// Value of dictionary type.
public Dictionary GetMap(Dictionary defalutV)
{
return (_object is Dictionary) ? (Dictionary)_object : defalutV;
}
///
/// Get data from dictionary.
///
/// key.
/// Key value from dictionary.
public Value Get(string key)
{
if(_object is Dictionary)
{
if (((Dictionary)_object).ContainsKey(key)) return (Value)((Dictionary)_object)[key];
}
return null;
}
///
/// Get key list from dictionary.
///
/// Key list.
public List KeySet()
{
return (_object is Dictionary) ? new List(((Dictionary)_object).Keys) : null;
}
///
/// Get dictionary.
///
/// Value of dictionary type.
public Dictionary ToMap()
{
return (_object is Dictionary) ? (Dictionary)_object: null;
}
#endregion
#region check type
///
/// Confirm the type.
///
public bool isNull() { return _object == null; }
public bool isBoolean() { return _object is Boolean; }
public bool isDouble() { return _object is Double; }
public bool isString() { return _object is string; }
public bool isArray() { return _object is List; }
public bool isMap() { return _object is Dictionary; }
#endregion
}
}