/*
 *	clipyaml.h
 *
 *	LibYAMLヘルパー関数
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2016 Naoyuki Sawa
 *
 *	* Sun Apr 24 21:05:43 JST 2016 Naoyuki Sawa
 *	- 1st リリース。
 *	  LibYAMLには、SAX形式のAPI(yaml_parser_scan(),yaml_parser_parse())と、DOM形式ののAPI(yaml_parser_load())が有ります。
 *	  アプリケーションの都合上は、DOM形式で処理する方が楽なのですが、実際の所、LibYAMLのDOM対応はあまり使い易くないです。
 *	  yaml_document_t構造体にドキュメント全体を読み込むだけで、データをトラバースする関数は一切用意されていないからです。
 *	  そこで、LibYAMLに不足している、データをトラバースするための関数を、当モジュールで実装する事にしました。
 *	  関数仕様は、使い易いJSONパーサー'parson'の関数仕様を参考にしました。
 *	* Mon Apr 25 22:45:55 JST 2016 Naoyuki Sawa
 *	- clipyaml.cに「使用例」を追記しました。
 *	* Fri Apr 29 21:34:57 JST 2016 Naoyuki Sawa
 *	- yaml_document_parse()を追加しました。
 *	- 「パス文字列による読み出し」の関数を追加しました。
 *	  パス文字列の書式は、既存のclipprp.cモジュールのPrpTbl_get_p()等とほぼ同じです。
 *	- 「使用例」を、「パス文字列による読み出し」の関数を使うように変更しました。
 *	* Sun May 01 23:11:51 JST 2016 Naoyuki Sawa
 *	- シフトJISに対応しました。
 *	  'USE_SJIS'シンボルを定義して当モジュールをビルドすると、入力ファイル,入力文字列,関数引数,関数戻り値が全てシフトJISで扱えます。
 *	  アプリケーションがUTF-8を意識する必要が一切無くなりました。
 *	  詳細は、clipyaml.c内の'#define USE_SJIS'を定義している箇所のコメントを参照して下さい。
 *	* Mon May 02 21:51:14 JST 2016 Naoyuki Sawa
 *	- clipunic.cに「文字列変換(シフトJIS⇔UTF-8)」を追加した事に伴い、これまではyaml_sjis_read_handler()の中で、
 *	  ShiftJisToUnicodeStr()とUcs2ToUtf8Str()を組み合わせて行っていた変換を、ShiftJisToUtf8Str()で一度に変換するように変更しました。
 *	* Sat May 07 21:48:49 JST 2016 Naoyuki Sawa
 *	- yaml_document_new(),yaml_document_dump()を追加しました。
 *	  「パス文字列による書き込み」の関数を追加しました。
 *	- yaml_sjis_read_handler()に、'\'の特例処理を追加しました。
 *	  詳細は、yaml_sjis_read_handler()内のコメントを参照して下さい。
 *	- 「書き込み関数の使用例」を追記しました。
 *	* Sun May 08 23:30:59 JST 2016 Naoyuki Sawa
 *	- バックスラッシュの変換問題を、clipunic.cモジュール内で吸収した事により、個別の処理が不要になりました。
 *	  この変更に伴い、yaml_sjis_read_handler()内の特例処理を削除しました。
 *	- 「パス文字列による読み出し」の関数の、node引数にNULLを指定出来るようにしました。
 *	  nodeにNULLが指定されたら、ルートノードからの相対パス(=ドキュメントの絶対パス)と見なします。
 *	  つまりnodeにNULLを指定すれば、「パス文字列による書き込み」と同様の使い方が出来ます。
 *	- yaml_document_dump()のシフトJIS対応を改良しました。
 *	  これまでは、日本語文字が'\uXXXX'形式で出力されてしまっており、シフトJISではあるもののYAMLファイルが読み辛い問題が有りました。
 *	  今回の改良により、日本語文字がそのままシフトJISコードで出力されるようになりました。
 *	* Tue May 10 22:06:16 JST 2016 Naoyuki Sawa
 *	- USE_YAML_SEHシンボルを定義すると、clipyaml.c内のDIE()を全て、SEH_throw(YamlException)に置き換えるようにしました。
 *	  当モジュールの関数は、呼び出し側のバグ(引数不正など)で失敗する事が多いので、即エラー停止でなく、呼び出し側で捕捉出来る方が便利だと思ったからです。
 *	  例えば、以下のように処理して下さい。
 *	  │double number;
 *	  │SEH_try {
 *	  │  number = yaml_path_get_number_f(document, node, "foo.bar[%d]", index);
 *	  │} SEH_catch(YamlException) {
 *	  │  number = 0.0; //デフォルト値を0.0とする。
 *	  │} SEH_end
 *	- USE_YAML_SEHシンボルを定義しなければ、これまで通りclipyaml.c内のDIE()はそのままで、即エラー停止します。
 *	  特に、コードサイズを低減する事が要求される実行環境の場合は、USE_YAML_SEHシンボルを定義しないで下さい。
 *	* Fri May 13 21:43:38 JST 2016 Naoyuki Sawa
 *	- これまでstrndupa()を使っていた箇所を、strndupa()を使わないように変更しました。
 *	  結果は変わりません。
 *	- strndupa()を使わないように変更した理由は、P/ECEではallocaを使用した関数から例外投入で関数を抜けると、allocaのチェインが破損する危険が有るからです。
 *	  (詳細はclipalca.cの実装を参照して下さい。)
 *	  上記は当モジュールに限った事ではないのですが、当モジュールは特に通常の使用で例外を投入する事が多いので、strndupa()を使わないようにする事にしました。
 *	  '通常の使用で例外を投入する事が多い'とは、存在しないキーを読みだそうとしたり、既に存在するキーを書き換えようとしたエラーを、例外で通知するからです。
 *	  具体例は、'Tue May 10 22:06:16 JST 2016'のコメントを参照して下さい。
 *	  これまでは、このような例外を補足して続行すると、P/ECE環境ではallocaのチェインが破損してしまっていました。
 *	  今後は、同様の処理を行っても、allocaのチェインが破損する事は有りません。
 *	* Sat May 14 21:51:15 JST 2016 Naoyuki Sawa
 *	- 既存のyaml_document_dump()をyaml_document_save()に変更し、新規にyaml_document_dump()を作成しました。
 *	  yaml_document_save()がファイル出力用、yaml_document_dump()が文字列出力用となります。
 *	- 上記の変更に伴い、「書き込み関数の使用例」を変更しました。
 *	* Wed May 25 10:22:02 JST 2016 Naoyuki Sawa
 *	- yaml_path_get_node(),yaml_path_set_string()の、キー文字列に'.','[',']'を含められるようになりました。
 *	  キー文字列に'.','[',']'を含めるには、直前に'\'を置いてエスケープして下さい。
 *	  │<例1> "foo.bar"   「foo」キーの「bar」キーと解釈します。
 *	  │      "foo\\.bar" 「foo.bar」キーと解釈します。
 *	  │<例2> "foo[0]"    「foo」キーの0番目の要素と解釈します。
 *	  │      "foo\\[0\\] 「foo[0]」キーと解釈します。
 *	  │      "foo\\[0]   これも「foo[0]」キーと解釈します。厳密には、']'はエスケープしてもしなくても同じです。しかし、']'も明示的にエスケープする方が判り易いと思います。
 *	  │<例3> "表現.十回.予備"     「表現」キーの「十回」キーの「予備」キーと解釈します。 ┬2バイト目に'\'を含む全角文字も正しく扱える事を確認するための例です。
 *	  │      "表現\\.十回\\.予備" 「表現.十回.予備」キーと解釈します。                   ┘
 *	- 今回は変更量がかなり多かったので、変更前のコードはコメントアウトして残していません。
 *	  変更前のコードを参照する場合は、前回のアーカイブを参照して下さい。
 *	* Thu May 26 23:31:18 JST 2016 Naoyuki Sawa
 *	- clipyaml.cに「パス文字列のキーに特殊文字が含まれている場合にエスケープする例」を追記しました。
 *	* Tue Jun 21 21:15:30 JST 2016 Naoyuki Sawa
 *	- yaml_parser_load()がエラーを返した場合、エラー停止,又は,例外を投入する前に、エラー内容を表示するようにしました。
 *	  これまでは、当モジュール内でエラー停止,又は,例外を投入した行しか判らず、読み込もうとしたYAMLテキストのどこにエラーが有るのかを、見付けづらかったからです。
 *	  今後は、表示されたエラー内容を元に、読み込もうとしたYAMLのどこにエラーが有るのかを、見付け易くなりました。
 *	- yaml_emitter_dump()がエラーを返した場合、エラー停止,又は,例外を投入する前に、エラー内容を表示するようにしました。
 *	  保存する時にエラーが出る事はまず無いので、こちらはこれまでのままでも特に不便は無かったのですが、上記の変更に合わせて一応こちらも変更しておく事にしました。
 *	* Wed Jun 22 22:49:18 JST 2016 Naoyuki Sawa
 *	- タブ文字展開の独自拡張を追加しました。
 *	  詳細は、USE_TABを定義している箇所のコメントを参照して下さい。
 *	* Sun Aug 14 21:50:35 JST 2016 Naoyuki Sawa
 *	- yaml_path_get_node()が例外を投入した時に、一時的な文字列が解放されずに、メモリリークが発生していた問題を修正しました。
 *	- yaml_path_set_string()が例外を投入した時に、一時的な文字列が解放されずに、メモリリークが発生していた問題を修正しました。
 *	- yaml_path_set_number()が例外を投入した時に、一時的な文字列が解放されずに、メモリリークが発生していた問題を修正しました。
 *	* Sun Sep 04 21:41:17 JST 2016 Naoyuki Sawa
 *	- 64ビット版では、例外コードをアドレスでなく、一意なFourCC識別子で表すようにしました。
 *	  setjmp()/longjmp()のパラメータは(int)であり、(void*)や(intptr_t)を使えないからです。
 *	  尚、32ビット版も64ビット版と共通に変更しても良いのですが、万一既存のコードに影響が生じる事を防ぐために、32ビット版はこれまで通りとしました。
 */
#ifndef __CLIP_YAML_H__
#define __CLIP_YAML_H__
#ifdef  __cplusplus
extern "C" {
#endif//__cplusplus
#define USE_YAML_SEH	//このシンボルを定義すると、clipyaml.c内のDIE()を全て、SEH_throw(YamlException)に置き換えます。
#ifdef  USE_YAML_SEH
//{{2016/09/04変更:64ビット版では、例外コードをアドレスでなく、一意なFourCC識別子で表すようにしました。
//extern const char YamlException[1/*dummy*/];
//↓2016/09/04変更:64ビット版では、例外コードをアドレスでなく、一意なFourCC識別子で表すようにしました。
#ifndef _WIN64
extern const char YamlException[1/*dummy*/];		//32ビット版
#else //_WIN64
#define YamlException MAKEFOURCC('Y','a','m','l')	//64ビット版
#endif//_WIN64
//}}2016/09/04変更:64ビット版では、例外コードをアドレスでなく、一意なFourCC識別子で表すようにしました。
#endif//USE_YAML_SEH
/****************************************************************************
 *	構造体
 ****************************************************************************/
typedef struct yaml_sequence_s yaml_sequence_t;	//opaque datatype
typedef struct yaml_mapping_s yaml_mapping_t;	//opaque datatype
/****************************************************************************
 *	document
 ****************************************************************************/
yaml_document_t* yaml_document_new();						//┐
yaml_document_t* yaml_document_load(const char* filename);			//├(yaml_document_t*)を作成する関数
yaml_document_t* yaml_document_parse(const char* string);			//┘
void yaml_document_free(yaml_document_t* document);				//┐
void yaml_document_save(yaml_document_t* document, const char* filename);	//├(yaml_document_t*)を開放する関数
char* yaml_document_dump(yaml_document_t* document);				//┘
/*--------------------------------------------------------------------------*/
//yaml_node_t* yaml_document_get_root_node(yaml_document_t* document);	⇒yaml.hで定義されています。
yaml_node_type_t yaml_document_get_root_type(yaml_document_t* document);
yaml_mapping_t* yaml_document_get_root_mapping(yaml_document_t* document);
yaml_sequence_t* yaml_document_get_root_sequence(yaml_document_t* document);
const char* yaml_document_get_root_string(yaml_document_t* document);
double yaml_document_get_root_number(yaml_document_t* document);
int yaml_document_get_root_boolean(yaml_document_t* document);
/****************************************************************************
 *	node
 ****************************************************************************/
yaml_node_type_t yaml_node_get_type(yaml_node_t* node);
yaml_mapping_t* yaml_node_get_mapping(yaml_node_t* node);
yaml_sequence_t* yaml_node_get_sequence(yaml_node_t* node);
const char* yaml_node_get_string(yaml_node_t* node);
double yaml_node_get_number(yaml_node_t* node);
int yaml_node_get_boolean(yaml_node_t* node);
/****************************************************************************
 *	mapping
 ****************************************************************************/
int yaml_mapping_get_count(yaml_document_t* document, yaml_mapping_t* mapping);	//当関数は引数'document'を参照しませんが、他の関数と引数を揃える方が判り易いので指定する事にしました。
const char* yaml_mapping_get_key(yaml_document_t* document, yaml_mapping_t* mapping, int index);
/*--------------------------------------------------------------------------*/
yaml_node_t* yaml_mapping_get_node(yaml_document_t* document, yaml_mapping_t* mapping, const char* key);
yaml_node_type_t yaml_mapping_get_type(yaml_document_t* document, yaml_mapping_t* mapping, const char* key);
yaml_mapping_t* yaml_mapping_get_mapping(yaml_document_t* document, yaml_mapping_t* mapping, const char* key);
yaml_sequence_t* yaml_mapping_get_sequence(yaml_document_t* document, yaml_mapping_t* mapping, const char* key);
const char* yaml_mapping_get_string(yaml_document_t* document, yaml_mapping_t* mapping, const char* key);
double yaml_mapping_get_number(yaml_document_t* document, yaml_mapping_t* mapping, const char* key);
int yaml_mapping_get_boolean(yaml_document_t* document, yaml_mapping_t* mapping, const char* key);
/****************************************************************************
 *	sequence
 ****************************************************************************/
int yaml_sequence_get_count(yaml_document_t* document, yaml_sequence_t* sequence);	//当関数は引数'document'を参照しませんが、他の関数と引数を揃える方が判り易いので指定する事にしました。
/*--------------------------------------------------------------------------*/
yaml_node_t* yaml_sequence_get_node(yaml_document_t* document, yaml_sequence_t* sequence, int index);
yaml_node_type_t yaml_sequence_get_type(yaml_document_t* document, yaml_sequence_t* sequence, int index);
yaml_mapping_t* yaml_sequence_get_mapping(yaml_document_t* document, yaml_sequence_t* sequence, int index);
yaml_sequence_t* yaml_sequence_get_sequence(yaml_document_t* document, yaml_sequence_t* sequence, int index);
const char* yaml_sequence_get_string(yaml_document_t* document, yaml_sequence_t* sequence, int index);
double yaml_sequence_get_number(yaml_document_t* document, yaml_sequence_t* sequence, int index);
int yaml_sequence_get_boolean(yaml_document_t* document, yaml_sequence_t* sequence, int index);
/****************************************************************************
 *	パス文字列による読み出し	pathはnodeからの相対パスです。		nodeにNULLが指定されたら、ルートノードからの相対パス(=ドキュメントの絶対パス)と見なします。つまりnodeにNULLを指定すれば、「パス文字列による書き込み」と同様の使い方が出来ます。
 ****************************************************************************/
yaml_node_t* yaml_path_get_node_f(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, ...);
yaml_node_t* yaml_path_get_node_g(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, va_list ap);
yaml_node_t* yaml_path_get_node(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* path);
/*--------------------------------------------------------------------------*/
yaml_node_type_t yaml_path_get_type_f(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, ...);
yaml_node_type_t yaml_path_get_type_g(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, va_list ap);
yaml_node_type_t yaml_path_get_type(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* path);
/*--------------------------------------------------------------------------*/
yaml_mapping_t* yaml_path_get_mapping_f(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, ...);
yaml_mapping_t* yaml_path_get_mapping_g(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, va_list ap);
yaml_mapping_t* yaml_path_get_mapping(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* path);
/*--------------------------------------------------------------------------*/
yaml_sequence_t* yaml_path_get_sequence_f(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, ...);
yaml_sequence_t* yaml_path_get_sequence_g(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, va_list ap);
yaml_sequence_t* yaml_path_get_sequence(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* path);
/*--------------------------------------------------------------------------*/
const char* yaml_path_get_string_f(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, ...);
const char* yaml_path_get_string_g(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, va_list ap);
const char* yaml_path_get_string(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* path);
/*--------------------------------------------------------------------------*/
double yaml_path_get_number_f(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, ...);
double yaml_path_get_number_g(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, va_list ap);
double yaml_path_get_number(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* path);
/*--------------------------------------------------------------------------*/
int yaml_path_get_boolean_f(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, ...);
int yaml_path_get_boolean_g(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* fmt, va_list ap);
int yaml_path_get_boolean(yaml_document_t* document, yaml_node_t* node/*NULL可*/, const char* path);
/****************************************************************************
 *	パス文字列による書き込み	pathはdocumentからの絶対パスです。	「パス文字列による書き込み」の関数を呼び出すとLibYAML内部でメモリの再割り当てが行われる可能性が有るので、それまでに取得したノードのポインタは無効になる事に注意せよ。「パス文字列による書き込み」の関数の呼び出しを跨いでは、document以外のポインタを保持しない事が望ましい。従って「パス文字列による書き込み」は常にドキュメントからの絶対パスで指定する仕様とした。「パス文字列による読み出し」のようにノード相対パス指定は出来ない仕様とした。
 ****************************************************************************/
void yaml_path_set_string_f(yaml_document_t* document, const char* value, const char* fmt, ...);
void yaml_path_set_string_g(yaml_document_t* document, const char* value, const char* fmt, va_list ap);
void yaml_path_set_string(yaml_document_t* document, const char* value, const char* path);
/*--------------------------------------------------------------------------*/
void yaml_path_set_number_f(yaml_document_t* document, double value, const char* fmt, ...);
void yaml_path_set_number_g(yaml_document_t* document, double value, const char* fmt, va_list ap);
void yaml_path_set_number(yaml_document_t* document, double value, const char* path);
/*--------------------------------------------------------------------------*/
void yaml_path_set_boolean_f(yaml_document_t* document, int value, const char* fmt, ...);
void yaml_path_set_boolean_g(yaml_document_t* document, int value, const char* fmt, va_list ap);
void yaml_path_set_boolean(yaml_document_t* document, int value, const char* path);
#ifdef  __cplusplus
}//extern "C"
#endif//__cplusplus
#endif//__CLIP_YAML_H__
