C++でYMLファイルが読みたい...読めた

最近、C++のコードをちまちまいじっていたところ、設定ファイルとかの外部から渡すファイルって階層構造にしたほうが作りやすいし、読みやすいんだよな、これをそのままC++で読ませたいな...と思い立って調べました。

結論から言うと無事読めるようになったのですが、結構苦労したので備忘録を残しておきます。

今回はこちらの[jbeder/yaml-cpp](https://github.com/jbeder/yaml-cpp)を使わせていただきました。提供方法はバイナリファイルではなくソースコードなので自分の環境でビルドしてバイナリ化してから使う必要があります。本当は最新版を使いたかったのですが、ビルドがうまくいかなかったのでver0.3.0を使いました。ver0.3とver0.6では使い方が結構違うようなのでご注意を。
基本的なインストール方法は公式の通りです。日本語の解説記事はこちらが参考になります。

- [C++ - yaml-cpp で YAML をパース！ - mk-mode BLOG](https://www.mk-mode.com/octopress/2014/10/14/cpp-installation-yaml-cpp/)
- [[cpp] yaml-cppをインストールした - はわわーっ](http://yomi322.hateblo.jp/entry/2012/04/17/235443)
- [C++ から YAMLファイルを読み込む](http://99blues.dyndns.org/blog/2010/12/parse_yaml_by_cpp/)
<p>ついでに私が行った手順を大雑把に手順を書いておきます。</p>

1. [GitHubのリポジトリ](https://github.com/jbeder/yaml-cpp)からファイルをダウンロードする。今回は`ver-0.3.0`を使用する
2. [CMake](https://cmake.org/)をダウンロードし、インストールする。今回は`ver-3.0.0-rc2`を使用する。 
3. ダウンロードしてきた`cpp-yaml`の圧縮ファイルを解凍する
4. 解凍したフォルダ内に`build`という名前の空フォルダを作成する(この中にcamkeしたバイナリを保存します) 
5. cmakeでsourceに`CMakeList.txt`が入ったフォルダを指定してcmakeする。今回はGUIでcmakeした。このとき、どのコンパイラでcmakeするかによって32bitか64bitかが決まるので注意 
    - GUIを使った場合 
        1. srcにcpp-yamlのルートフォルダを出力先にbuildフォルダを指定して`Configure`ボタンをクリックする
        2. コンパイラとして何を使うか聞かれるので選択する。今回はVisualStudio2012がインストールされている環境で64bit版で使用したいので`Visual Studio 11 2012 Win64`を選択 
        3. 無事完了し、`Configuring done`が表示されればOKなので、次に`Generate`ボタンをクリックする 
        4. 無事`Generating done`が表示されればOK 
6. `build`フォルダ内にソリューションファイル`.sln`が生成されてるはずなのでVSでこれを開く 
7. プラットフォームがx64になっていることを確認し、ビルドモードを`RelWithDebInfo`に設定してビルドする。このときビルド対象に`Install`を含めると適当な位置にライブラリを配備してくれるらしい。試していないので不明。 
8. 無事ビルドが成功し、Installを指定しなかった場合は、RelWithDebInfoフォルダ内の`.lib`ファイルを取り出してC++でスタティックライブラリを読めるように設定する。わからなければ以下を参考にする 
    - [Qiita:スタティックライブラリを作って読み込んでみる](https://corgi-lab.com/windows/vs-external-library/) 
    - [VisualStudioで外部ライブラリを読み込めるようにする方法](https://qiita.com/izuki_y/items/b6f35838c17bbab2d489) 
9. include内のyaml-cppフォルダをごそっと組み込みたいソフトで読める位置に設定する。より具体的にはVSのヘッダーファイルの設定をする 
10. あとは公式にそって書いていけばよい。 

まず、このような形式のymlファイルを用意します。内容は適当で意味はありませんです。ファイル名も適当に`info.yml`とでもしておきます。 

```yml
- enemy:
    - name: enemy0
      hp: 100
      atk: 10
    - name: enemy1
      hp: 50
      atk: 20 
    - name: enemy2
      hp: 200
      atk: 30
    world: japan
    player: sashimimochi
```
次にymlの形式に合わせてC++を実装します。 基本的には参考にさせていただいたページの通りですが、複数の要素を持つ場合にループで回す部分を改良しています。 インクルードはするとして、 

```cpp

#include "yaml-cpp\yaml.h" 
```
まず、受け取る情報に合わせて構造体を実装します。ymlのデータが階層構造になっていれば階層分作成します。

```cpp
 
struct Enemy{
  string name;
  int hp;
  int atk;
} 
struct Info{ 
  vector<Enemy> enemy;
  string world;
  string player; 
}
```
これを処理するオペレーターを用意します。オペレーターも作成した構造体分作成します。 

```cpp

void operator >> (const YAML::Node& node, Enemy& enemy){ 
  node["name"] >> enemy.name;
  node["hp"] >> enemy.hp; 
  node["atk"] >> enemy.atk; 
} 
void operator >> (const YAML::Node& node, Info& info){ 
  const YAML::Node& enemies = node["enemy"];
  for(int i=0;i<enemies.size();i++){ 
    Enemy enemy;
    enemies[i] >> enemy;
    info.enemy.push_back(enemy); 
  }
  node["world"] >> info.world;
  node["player"] >> info.player;
} 
```
そしてこれを読み込む関数を実装します。 

```cpp

void loadYMLFile(string ymlpath){ 
  string name;
  int hp; 
  int atk; 
  string world; 
  string player; 
  try{ 
    ifstream fin(ymlpath);
    YAML::Parser parser(fin);
    YAML::Node doc; 
    parser.GetNextDocument(doc);
    Info info;
    for(int i=0;i<doc.size();i++){ 
      Info info; doc[i] >> info;
      for(int j=0;j<info.enemy.size();j++){
        name = info.enemy[j].name;
        hp = info.enemy[j].hp;
        atk = info.enemy[j].atk;
        cout << "name:" << name << "\n" << "HP:" << hp << "\n" << "ATK:" << atk << endl;
      } 
      world = info.world; 
      player = info.player;
      cout << "world:" << world << "\n" << "player:" << player << endl;
    }
  }catch(YAML::ParserException& e){
    cerr << e.what() << endl;
  } 
} 
```
無事ymlファイルに入力した情報がコンソールに出力されれば成功です。 まとめて書くとこうなる。 `cpp-yaml-reader.cpp`

```cpp

#include "yaml-cpp\yaml.h"
#include <string>
#include <fstream>


struct Enemy{ 
    string name;
    int hp;
    int atk;
}
struct Info{ 
    vector<Enemy> enemy;
    string world;
    string player;
}


void operator >> (const YAML::Node& node, Enemy& enemy){
    node["name"] >> enemy.name;
    node["hp"] >> enemy.hp;
    node["atk"] >> enemy.atk;
}
void operator >> (const YAML::Node& node, Info& info){
    const YAML::Node& enemies = node["enemy"];
    for(int i=0;i<enemies.size();i++){
        Enemy enemy;
        enemies[i] >> enemy;
        info.enemy.push_back(enemy);
    } 
    node["world"] >> info.world;
    node["player"] >> info.player;
} 


void loadYMLFile(string ymlpath){ 
    string name;
    int hp; 
    int atk;
    string world;
    string player;
    try{ 
        ifstream fin(ymlpath);
        YAML::Parser parser(fin);
        YAML::Node doc; 
        parser.GetNextDocument(doc);
        Info info;
        for(int i=0;i<doc.size();i++){
            Info info; doc[i] >> info;
            for(int j=0;j<info.enemy.size();j++){
                name = info.enemy[j].name;
                hp = info.enemy[j].hp;
                atk = info.enemy[j].atk;
                cout << "name:" << name << "\n" << "HP:" << hp << "\n" << "ATK:" << atk << endl;
            } 
            world = info.world;
            player = info.player;
            cout << "world:" << world << "\n" << "player:" << player << endl;
        }
    }catch(YAML::ParserException& e){ 
        cerr << e.what() << endl;
    }
} 

int main(int argc, char* argv[]){
    string ymlpath = argv[1];
    loadYMLFile(ymlpath);
    return 0;
} 
```
これをコンパイルして引数渡しで先ほどのymlファイルのパスを渡してあげると表示してくれる形にしてみました。 

```terminal
$> cpp-yaml-reader.exe info.yml 
```
これでめでたくC++でymlファイルが扱えるようになりました。設定情報をcsvファイルとかを使って頑張って階層構造を組まなくていいようになりました。マジックナンバーではなくキーで指定できるのも地味にうれしいです。