/** * 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 } }