2013年04月27日

CSVを読み込むクラス

当ブログのプログラミングの記事では、
主にC++でのゲームプログラミングに関するものを掲載していきたいと思います。
C++の初心者のうち、C++でクラスの作り方やSTLの使い方が一通り分かった人向けに
実践的なプログラミング例を示したいと思います。
ゲームプログラミングの一助になれば幸いです。


今回はCSVの読み込みをクラス化してみましょう。
一つ作っておくとCSVをExcelや汎用マップエディタ等で
編集できるのでゲーム製作が凄く楽になります。
ゲーム本体に直接組み込むのは勿論、
CSVを元にバイナリファイル作成ツールも簡単に作れるようになります。

CSV読込クラス(CCSVReader)で必要となるメンバ変数・関数一覧
メンバ変数

分割された全てのトークン:STRING_TABLE csvtoken
ファイルパス:string path


メンバ関数

ファイル読込関数:int readfile(char *filepath)
セル指定が有効かどうか:bool is_validpos(int line,int row)
トークン文字列を返す:const char *token(int line,int row)
トークン文字列サイズを返す:int tokensize(int line,int row)
列数を返す:int num_row(int line)
行数を返す:int num_line()
読み込んだファイルのパスを返す:const char *filepath()
ファイルが読込済みかどうか:bool is_readfile()



STRING_TABLEはvector<vector<string>>としてtypedefされています。
CSVは行毎に列数が異なる場合がありますので注意が必要です。

csvtokenにちゃんと読み込まれれば、各関数の作成は難しいものじゃないでしょう。
つまり、readfile()関数をどう作るか、が肝要です。
大まかには次のような順序で処理していきます。
@ファイルオープン
ACSVを一行ごと、文字列に読み込む
Bその文字列を","(カンマ記号)で分割する
Ccsvtokenに格納していく
D全ての行を処理するまでAから繰り返す
Eファイルクローズ


Bに関しては標準関数のstrtok_s()を使用します。

書式:char *strtok_s(char *strToken, const char *strDelimit, char **context)
ヘッダ:string.h (VisualStudio2005以降、それ以外はstrtok()を使います)

strTokenには分割したい文字列を(2回目以降はNULLを)、
strDelimitには区切り文字を(デリミタ)、
contextはstrtok_sが内部使用するので宣言した変数をそのまま渡します。操作不要。
分割できなくなったらNULLを返すので、
NULLを返すまでこの関数を繰り返し実行します。

あとは標準ライブラリ等で処理できそうです。


int CCSVReader::readfile(char *filepath)
{

ifstream fin;

setlocale(LC_ALL, "");//日本語パスに対応

fin.open(filepath,ios::in);
if(!fin.is_open())
{return -1;}

string newline;
char *c_newline;
char *tk; //strtok_sが返したトークン
char *ctx; //strtok_sが内部使用する
STRING_V linetoken; //1行のトークンを一旦こちらに格納

csvtoken.clear();

while(!fin.eof())
{
linetoken.clear();

//バッファオーバーランを防ぐため、一旦stringで取得する
getline(fin,newline);
c_newline = new char[newline.size()+1];
strcpy_s(c_newline,newline.size()+1,newline.c_str());

while(1)
{
if(linetoken.empty())
{
tk=strtok_s(c_newline,",",&ctx);
if(tk==NULL)
{
linetoken.push_back(c_newline);
csvtoken.push_back(linetoken);
break;
}
}
else
{
tk=strtok_s(NULL,",",&ctx);
if(tk==NULL)
{
csvtoken.push_back(linetoken);
break;
}
}
linetoken.push_back(tk);
}

delete [] c_newline;

}

path=filepath;

return 0;
}

STRING_Vはvector<string>としてtypedefされています。

newlineをstringで取得しているのは、バッファオーバーランを防ぐためです。
例えば、char newline[256]などを渡してしまうと、
1行中に256byte以上あった場合、バッファオーバーランが発生します。
じゃぁなんで、わざわざchar c_newline[]にコピーしているのか、と言えば、
strtok_s()はデリミタで指定された文字(カンマ)を'\0'に置換するので、
stringのc_str()を渡すわけにはいきません。

linetokenができたらcsvtokenにコピーするという重そうな処理をしていますが、
readfile()関数は一秒間に何回も呼び出すものでもないので、気にしないことにします。
明示的なファイルクローズをしていませんが、
ifstreamは関数終了時に自動できちんと処理してくれるお利口さんです。


NULL文字列(カンマが連続する場合)は正しく読み込めないという課題はありますが、
とりあえずは実用に耐えられるCSV読込クラスになったんじゃないでしょうか?
読み込んだデータはもちろんそのまま文字列として使用できますし、
strtol()やstrtod()の標準関数を使えば数値として取得できます。
使い道の多いクラスではないかと思います。


今回のソースコード
csvreader.zip





posted by ぷーすけ at 12:29| Comment(0) | プログラミング
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: