/** * 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 https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. */ import { Live2DCubismFramework as csmstring } from '../type/csmstring'; import { Live2DCubismFramework as csmmap } from '../type/csmmap'; import { Live2DCubismFramework as csmvector } from '../type/csmvector'; import { CubismLogInfo } from './cubismdebug'; import { strtod } from '../live2dcubismframework'; import csmVector = csmvector.csmVector; import csmVector_iterator = csmvector.iterator; import csmMap = csmmap.csmMap; import csmMap_iterator = csmmap.iterator; import csmString = csmstring.csmString; export namespace Live2DCubismFramework { // StaticInitializeNotForClientCall()で初期化する const CSM_JSON_ERROR_TYPE_MISMATCH = 'Error: type mismatch'; const CSM_JSON_ERROR_INDEX_OF_BOUNDS = 'Error: index out of bounds'; /** * パースしたJSONエレメントの要素の基底クラス。 */ export abstract class Value { /** * コンストラクタ */ public constructor() {} /** * 要素を文字列型で返す(csmString型) */ public abstract getString(defaultValue?: string, indent?: string): string; /** * 要素を文字列型で返す(string) */ public getRawString(defaultValue?: string, indent?: string): string { return this.getString(defaultValue, indent); } /** * 要素を数値型で返す(number) */ public toInt(defaultValue = 0): number { return defaultValue; } /** * 要素を数値型で返す(number) */ public toFloat(defaultValue = 0): number { return defaultValue; } /** * 要素を真偽値で返す(boolean) */ public toBoolean(defaultValue = false): boolean { return defaultValue; } /** * サイズを返す */ public getSize(): number { return 0; } /** * 要素を配列で返す(Value[]) */ public getArray(defaultValue: Value[] = null): Value[] { return defaultValue; } /** * 要素をコンテナで返す(array) */ public getVector(defaultValue?: csmVector): csmVector { return defaultValue; } /** * 要素をマップで返す(csmMap) */ public getMap(defaultValue?: csmMap): csmMap { return defaultValue; } /** * 添字演算子[index] */ public getValueByIndex(index: number): Value { return Value.errorValue.setErrorNotForClientCall( CSM_JSON_ERROR_TYPE_MISMATCH ); } /** * 添字演算子[string | csmString] */ public getValueByString(s: string | csmString): Value { return Value.nullValue.setErrorNotForClientCall( CSM_JSON_ERROR_TYPE_MISMATCH ); } /** * マップのキー一覧をコンテナで返す * * @return マップのキーの一覧 */ public getKeys(): csmVector { return Value.s_dummyKeys; } /** * Valueの種類がエラー値ならtrue */ public isError(): boolean { return false; } /** * Valueの種類がnullならtrue */ public isNull(): boolean { return false; } /** * Valueの種類が真偽値ならtrue */ public isBool(): boolean { return false; } /** * Valueの種類が数値型ならtrue */ public isFloat(): boolean { return false; } /** * Valueの種類が文字列ならtrue */ public isString(): boolean { return false; } /** * Valueの種類が配列ならtrue */ public isArray(): boolean { return false; } /** * Valueの種類がマップ型ならtrue */ public isMap(): boolean { return false; } /** * 引数の値と等しければtrue */ public equals(value: csmString): boolean; public equals(value: string): boolean; public equals(value: number): boolean; public equals(value: boolean): boolean; public equals(value: any): boolean { return false; } /** * Valueの値が静的ならtrue、静的なら解放しない */ public isStatic(): boolean { return false; } /** * Valueにエラー値をセットする */ public setErrorNotForClientCall(errorStr: string): Value { return JsonError.errorValue; } /** * 初期化用メソッド */ public static staticInitializeNotForClientCall(): void { JsonBoolean.trueValue = new JsonBoolean(true); JsonBoolean.falseValue = new JsonBoolean(false); JsonError.errorValue = new JsonError('ERROR', true); this.nullValue = new JsonNullvalue(); Value.s_dummyKeys = new csmVector(); } /** * リリース用メソッド */ public static staticReleaseNotForClientCall(): void { JsonBoolean.trueValue = null; JsonBoolean.falseValue = null; JsonError.errorValue = null; Value.nullValue = null; Value.s_dummyKeys = null; JsonBoolean.trueValue = null; JsonBoolean.falseValue = null; JsonError.errorValue = null; Value.nullValue = null; Value.s_dummyKeys = null; } protected _stringBuffer: string; // 文字列バッファ private static s_dummyKeys: csmVector; // ダミーキー public static errorValue: Value; // 一時的な返り値として返すエラー。 CubismFramework::Disposeするまではdeleteしない public static nullValue: Value; // 一時的な返り値として返すNULL。 CubismFramework::Disposeするまではdeleteしない } /** * Ascii文字のみ対応した最小限の軽量JSONパーサ。 * 仕様はJSONのサブセットとなる。 * 設定ファイル(model3.json)などのロード用 * * [未対応項目] * ・日本語などの非ASCII文字 * ・eによる指数表現 */ export class CubismJson { /** * コンストラクタ */ public constructor(buffer?: ArrayBuffer, length?: number) { this._error = null; this._lineCount = 0; this._root = null; if (buffer != undefined) { this.parseBytes(buffer, length); } } /** * バイトデータから直接ロードしてパースする * * @param buffer バッファ * @param size バッファサイズ * @return CubismJsonクラスのインスタンス。失敗したらNULL */ public static create(buffer: ArrayBuffer, size: number) { const json = new CubismJson(); const succeeded: boolean = json.parseBytes(buffer, size); if (!succeeded) { CubismJson.delete(json); return null; } else { return json; } } /** * パースしたJSONオブジェクトの解放処理 * * @param instance CubismJsonクラスのインスタンス */ public static delete(instance: CubismJson) { instance = null; } /** * パースしたJSONのルート要素を返す */ public getRoot(): Value { return this._root; } /** * UnicodeのバイナリをStringに変換 * * @param buffer 変換するバイナリデータ * @return 変換後の文字列 */ public arrayBufferToString(buffer: ArrayBuffer): string { const uint8Array: Uint8Array = new Uint8Array(buffer); let str = ''; for (let i = 0, len: number = uint8Array.length; i < len; ++i) { str += '%' + this.pad(uint8Array[i].toString(16)); } str = decodeURIComponent(str); return str; } /** * エンコード、パディング */ private pad(n: string): string { return n.length < 2 ? '0' + n : n; } /** * JSONのパースを実行する * @param buffer パース対象のデータバイト * @param size データバイトのサイズ * return true : 成功 * return false: 失敗 */ public parseBytes(buffer: ArrayBuffer, size: number): boolean { const endPos: number[] = new Array(1); // 参照渡しにするため配列 const decodeBuffer: string = this.arrayBufferToString(buffer); this._root = this.parseValue(decodeBuffer, size, 0, endPos); if (this._error) { let strbuf = '\0'; strbuf = 'Json parse error : @line ' + (this._lineCount + 1) + '\n'; this._root = new JsonString(strbuf); CubismLogInfo('{0}', this._root.getRawString()); return false; } else if (this._root == null) { this._root = new JsonError(new csmString(this._error), false); // rootは解放されるのでエラーオブジェクトを別途作成する return false; } return true; } /** * パース時のエラー値を返す */ public getParseError(): string { return this._error; } /** * ルート要素の次の要素がファイルの終端だったらtrueを返す */ public checkEndOfFile(): boolean { return this._root.getArray()[1].equals('EOF'); } /** * JSONエレメントからValue(float,String,Value*,Array,null,true,false)をパースする * エレメントの書式に応じて内部でParseString(), ParseObject(), ParseArray()を呼ぶ * * @param buffer JSONエレメントのバッファ * @param length パースする長さ * @param begin パースを開始する位置 * @param outEndPos パース終了時の位置 * @return パースから取得したValueオブジェクト */ protected parseValue( buffer: string, length: number, begin: number, outEndPos: number[] ) { if (this._error) return null; let o: Value = null; let i: number = begin; let f: number; for (; i < length; i++) { const c: string = buffer[i]; switch (c) { case '-': case '.': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { const afterString: string[] = new Array(1); // 参照渡しにするため f = strtod(buffer.slice(i), afterString); outEndPos[0] = buffer.indexOf(afterString[0]); return new JsonFloat(f); } case '"': return new JsonString( this.parseString(buffer, length, i + 1, outEndPos) ); // \"の次の文字から case '[': o = this.parseArray(buffer, length, i + 1, outEndPos); return o; case '{': o = this.parseObject(buffer, length, i + 1, outEndPos); return o; case 'n': // null以外にない if (i + 3 < length) { o = new JsonNullvalue(); // 解放できるようにする outEndPos[0] = i + 4; } else { this._error = 'parse null'; } return o; case 't': // true以外にない if (i + 3 < length) { o = JsonBoolean.trueValue; outEndPos[0] = i + 4; } else { this._error = 'parse true'; } return o; case 'f': // false以外にない if (i + 4 < length) { o = JsonBoolean.falseValue; outEndPos[0] = i + 5; } else { this._error = "illegal ',' position"; } return o; case ',': // Array separator this._error = "illegal ',' position"; return null; case ']': // 不正な}だがスキップする。配列の最後に不要な , があると思われる outEndPos[0] = i; // 同じ文字を再処理 return null; case '\n': this._lineCount++; case ' ': case '\t': case '\r': default: // スキップ break; } } this._error = 'illegal end of value'; return null; } /** * 次の「"」までの文字列をパースする。 * * @param string -> パース対象の文字列 * @param length -> パースする長さ * @param begin -> パースを開始する位置 * @param outEndPos -> パース終了時の位置 * @return パースした文F字列要素 */ protected parseString( string: string, length: number, begin: number, outEndPos: number[] ): string { if (this._error) return null; let i = begin; let c: string, c2: string; const ret: csmString = new csmString(''); let bufStart: number = begin; // sbufに登録されていない文字の開始位置 for (; i < length; i++) { c = string[i]; switch (c) { case '"': { // 終端の”、エスケープ文字は別に処理されるのでここに来ない outEndPos[0] = i + 1; // ”の次の文字 ret.append(string.slice(bufStart), i - bufStart); // 前の文字までを登録する return ret.s; } case '//': { // エスケープの場合 i++; // 2文字をセットで扱う if (i - 1 > bufStart) { ret.append(string.slice(bufStart), i - bufStart); // 前の文字までを登録する } bufStart = i + 1; // エスケープ(2文字)の次の文字から if (i < length) { c2 = string[i]; switch (c2) { case '\\': ret.expansion(1, '\\'); break; case '"': ret.expansion(1, '"'); break; case '/': ret.expansion(1, '/'); break; case 'b': ret.expansion(1, '\b'); break; case 'f': ret.expansion(1, '\f'); break; case 'n': ret.expansion(1, '\n'); break; case 'r': ret.expansion(1, '\r'); break; case 't': ret.expansion(1, '\t'); break; case 'u': this._error = 'parse string/unicord escape not supported'; break; default: break; } } else { this._error = 'parse string/escape error'; } } default: { break; } } } this._error = 'parse string/illegal end'; return null; } /** * JSONのオブジェクトエレメントをパースしてValueオブジェクトを返す * * @param buffer JSONエレメントのバッファ * @param length パースする長さ * @param begin パースを開始する位置 * @param outEndPos パース終了時の位置 * @return パースから取得したValueオブジェクト */ protected parseObject( buffer: string, length: number, begin: number, outEndPos: number[] ): Value { if (this._error) return null; const ret: JsonMap = new JsonMap(); // Key: Value let key = ''; let i: number = begin; let c = ''; const localRetEndPos2: number[] = Array(1); let ok = false; // , が続く限りループ for (; i < length; i++) { FOR_LOOP: for (; i < length; i++) { c = buffer[i]; switch (c) { case '"': key = this.parseString(buffer, length, i + 1, localRetEndPos2); if (this._error) { return null; } i = localRetEndPos2[0]; ok = true; break FOR_LOOP; //-- loopから出る case '}': // 閉じカッコ outEndPos[0] = i + 1; return ret; // 空 case ':': this._error = "illegal ':' position"; break; case '\n': this._lineCount++; default: break; // スキップする文字 } } if (!ok) { this._error = 'key not found'; return null; } ok = false; // : をチェック FOR_LOOP2: for (; i < length; i++) { c = buffer[i]; switch (c) { case ':': ok = true; i++; break FOR_LOOP2; case '}': this._error = "illegal '}' position"; break; case '\n': this._lineCount++; // case ' ': case '\t' : case '\r': default: break; // スキップする文字 } } if (!ok) { this._error = "':' not found"; return null; } // 値をチェック const value: Value = this.parseValue( buffer, length, i, localRetEndPos2 ); if (this._error) { return null; } i = localRetEndPos2[0]; // ret.put(key, value); ret.put(key, value); FOR_LOOP3: for (; i < length; i++) { c = buffer[i]; switch (c) { case ',': break FOR_LOOP3; case '}': outEndPos[0] = i + 1; return ret; // 正常終了 case '\n': this._lineCount++; default: break; // スキップ } } } this._error = 'illegal end of perseObject'; return null; } /** * 次の「"」までの文字列をパースする。 * @param buffer JSONエレメントのバッファ * @param length パースする長さ * @param begin パースを開始する位置 * @param outEndPos パース終了時の位置 * @return パースから取得したValueオブジェクト */ protected parseArray( buffer: string, length: number, begin: number, outEndPos: number[] ): Value { if (this._error) return null; let ret: JsonArray = new JsonArray(); // key : value let i: number = begin; let c: string; const localRetEndpos2: number[] = new Array(1); // , が続く限りループ for (; i < length; i++) { // : をチェック const value: Value = this.parseValue( buffer, length, i, localRetEndpos2 ); if (this._error) { return null; } i = localRetEndpos2[0]; if (value) { ret.add(value); } // FOR_LOOP3: // boolean breakflag = false; FOR_LOOP: for (; i < length; i++) { c = buffer[i]; switch (c) { case ',': // breakflag = true; // break; // 次のKEY, VAlUEへ break FOR_LOOP; case ']': outEndPos[0] = i + 1; return ret; // 終了 case '\n': ++this._lineCount; //case ' ': case '\t': case '\r': default: break; // スキップ } } } ret = void 0; this._error = 'illegal end of parseObject'; return null; } _error: string; // パース時のエラー _lineCount: number; // エラー報告に用いる行数カウント _root: Value; // パースされたルート要素 } /** * パースしたJSONの要素をfloat値として扱う */ export class JsonFloat extends Value { /** * コンストラクタ */ constructor(v: number) { super(); this._value = v; } /** * Valueの種類が数値型ならtrue */ public isFloat(): boolean { return true; } /** * 要素を文字列で返す(csmString型) */ public getString(defaultValue: string, indent: string): string { const strbuf = '\0'; this._value = parseFloat(strbuf); this._stringBuffer = strbuf; return this._stringBuffer; } /** * 要素を数値型で返す(number) */ public toInt(defaultValue = 0): number { return parseInt(this._value.toString()); } /** * 要素を数値型で返す(number) */ public toFloat(defaultValue = 0.0): number { return this._value; } /** * 引数の値と等しければtrue */ public equals(value: csmString): boolean; public equals(value: string): boolean; public equals(value: number): boolean; public equals(value: boolean): boolean; public equals(value: any): boolean { if ('number' === typeof value) { // int if (Math.round(value)) { return false; } // float else { return value == this._value; } } return false; } private _value: number; // JSON要素の値 } /** * パースしたJSONの要素を真偽値として扱う */ export class JsonBoolean extends Value { /** * Valueの種類が真偽値ならtrue */ public isBool(): boolean { return true; } /** * 要素を真偽値で返す(boolean) */ public toBoolean(defaultValue = false): boolean { return this._boolValue; } /** * 要素を文字列で返す(csmString型) */ public getString(defaultValue: string, indent: string): string { this._stringBuffer = this._boolValue ? 'true' : 'false'; return this._stringBuffer; } /** * 引数の値と等しければtrue */ public equals(value: csmString): boolean; public equals(value: string): boolean; public equals(value: number): boolean; public equals(value: boolean): boolean; public equals(value: any): boolean { if ('boolean' === typeof value) { return value == this._boolValue; } return false; } /** * Valueの値が静的ならtrue, 静的なら解放しない */ public isStatic(): boolean { return true; } /** * 引数付きコンストラクタ */ public constructor(v: boolean) { super(); this._boolValue = v; } static trueValue: JsonBoolean; // true static falseValue: JsonBoolean; // false private _boolValue: boolean; // JSON要素の値 } /** * パースしたJSONの要素を文字列として扱う */ export class JsonString extends Value { /** * 引数付きコンストラクタ */ public constructor(s: string); public constructor(s: csmString); public constructor(s: any) { super(); if ('string' === typeof s) { this._stringBuffer = s; } if (s instanceof csmString) { this._stringBuffer = s.s; } } /** * Valueの種類が文字列ならtrue */ public isString(): boolean { return true; } /** * 要素を文字列で返す(csmString型) */ public getString(defaultValue: string, indent: string): string { return this._stringBuffer; } /** * 引数の値と等しければtrue */ public equals(value: csmString): boolean; public equals(value: string): boolean; public equals(value: number): boolean; public equals(value: boolean): boolean; public equals(value: any): boolean { if ('string' === typeof value) { return this._stringBuffer == value; } if (value instanceof csmString) { return this._stringBuffer == value.s; } return false; } } /** * JSONパース時のエラー結果。文字列型のようにふるまう */ export class JsonError extends JsonString { /** * Valueの値が静的ならtrue、静的なら解放しない */ public isStatic(): boolean { return this._isStatic; } /** * エラー情報をセットする */ public setErrorNotForClientCall(s: string): Value { this._stringBuffer = s; return this; } /** * 引数付きコンストラクタ */ public constructor(s: csmString | string, isStatic: boolean) { if ('string' === typeof s) { super(s); } else { super(s); } this._isStatic = isStatic; } /** * Valueの種類がエラー値ならtrue */ public isError(): boolean { return true; } protected _isStatic: boolean; // 静的なValueかどうか } /** * パースしたJSONの要素をNULL値として持つ */ export class JsonNullvalue extends Value { /** * Valueの種類がNULL値ならtrue */ public isNull(): boolean { return true; } /** * 要素を文字列で返す(csmString型) */ public getString(defaultValue: string, indent: string): string { return this._stringBuffer; } /** * Valueの値が静的ならtrue, 静的なら解放しない */ public isStatic(): boolean { return true; } /** * コンストラクタ */ public constructor() { super(); this._stringBuffer = 'NullValue'; } } /** * パースしたJSONの要素を配列として持つ */ export class JsonArray extends Value { /** * コンストラクタ */ public constructor() { super(); this._array = new csmVector(); } /** * デストラクタ相当の処理 */ public release(): void { for ( let ite: csmVector_iterator = this._array.begin(); ite.notEqual(this._array.end()); ite.preIncrement() ) { let v: Value = ite.ptr(); if (v && !v.isStatic()) { v = void 0; v = null; } } } /** * Valueの種類が配列ならtrue */ public isArray(): boolean { return true; } /** * 添字演算子[index] */ public getValueByIndex(index: number): Value { if (index < 0 || this._array.getSize() <= index) { return Value.errorValue.setErrorNotForClientCall( CSM_JSON_ERROR_INDEX_OF_BOUNDS ); } const v: Value = this._array.at(index); if (v == null) { return Value.nullValue; } return v; } /** * 添字演算子[string | csmString] */ public getValueByString(s: string | csmString): Value { return Value.errorValue.setErrorNotForClientCall( CSM_JSON_ERROR_TYPE_MISMATCH ); } /** * 要素を文字列で返す(csmString型) */ public getString(defaultValue: string, indent: string): string { const stringBuffer: string = indent + '[\n'; for ( let ite: csmVector_iterator = this._array.begin(); ite.notEqual(this._array.end()); ite.increment() ) { const v: Value = ite.ptr(); this._stringBuffer += indent + '' + v.getString(indent + ' ') + '\n'; } this._stringBuffer = stringBuffer + indent + ']\n'; return this._stringBuffer; } /** * 配列要素を追加する * @param v 追加する要素 */ public add(v: Value): void { this._array.pushBack(v); } /** * 要素をコンテナで返す(csmVector) */ public getVector(defaultValue: csmVector = null): csmVector { return this._array; } /** * 要素の数を返す */ public getSize(): number { return this._array.getSize(); } private _array: csmVector; // JSON要素の値 } /** * パースしたJSONの要素をマップとして持つ */ export class JsonMap extends Value { /** * コンストラクタ */ public constructor() { super(); this._map = new csmMap(); } /** * デストラクタ相当の処理 */ public release(): void { const ite: csmMap_iterator = this._map.begin(); while (ite.notEqual(this._map.end())) { let v: Value = ite.ptr().second; if (v && !v.isStatic()) { v = void 0; v = null; } ite.preIncrement(); } } /** * Valueの値がMap型ならtrue */ public isMap(): boolean { return true; } /** * 添字演算子[string | csmString] */ public getValueByString(s: string | csmString): Value { if (s instanceof csmString) { const ret: Value = this._map.getValue(s.s); if (ret == null) { return Value.nullValue; } return ret; } for ( let iter: csmMap_iterator = this._map.begin(); iter.notEqual(this._map.end()); iter.preIncrement() ) { if (iter.ptr().first == s) { if (iter.ptr().second == null) { return Value.nullValue; } return iter.ptr().second; } } return Value.nullValue; } /** * 添字演算子[index] */ public getValueByIndex(index: number): Value { return Value.errorValue.setErrorNotForClientCall( CSM_JSON_ERROR_TYPE_MISMATCH ); } /** * 要素を文字列で返す(csmString型) */ public getString(defaultValue: string, indent: string) { this._stringBuffer = indent + '{\n'; const ite: csmMap_iterator = this._map.begin(); while (ite.notEqual(this._map.end())) { const key = ite.ptr().first; const v: Value = ite.ptr().second; this._stringBuffer += indent + ' ' + key + ' : ' + v.getString(indent + ' ') + ' \n'; ite.preIncrement(); } this._stringBuffer += indent + '}\n'; return this._stringBuffer; } /** * 要素をMap型で返す */ public getMap(defaultValue?: csmMap): csmMap { return this._map; } /** * Mapに要素を追加する */ public put(key: string, v: Value): void { this._map.setValue(key, v); } /** * Mapからキーのリストを取得する */ public getKeys(): csmVector { if (!this._keys) { this._keys = new csmVector(); const ite: csmMap_iterator = this._map.begin(); while (ite.notEqual(this._map.end())) { const key: string = ite.ptr().first; this._keys.pushBack(key); ite.preIncrement(); } } return this._keys; } /** * Mapの要素数を取得する */ public getSize(): number { return this._keys.getSize(); } private _map: csmMap; // JSON要素の値 private _keys: csmVector; // JSON要素の値 } }