ここでは、テキストCSV/TSV形式などの、より高度なファイル入出力を扱います。
前回扱った標準ファイル入出力は、高速ですが、非常に単純な挙動のものです。
例えばCSV/TSV ファイルは数値データを想定したもので、単純に数値をカンマなどで区切って書き込み、 同様に区切って読み込むだけです。 しかし、数値に限らずより汎用的な内容、つまりテキストをCSV/TSV で書き込むには、複雑な処理が必要です。
まずは、基本的な項目について説明します。
CSVならカンマ記号、TSVならタブ記号などのように、 値を区切る文字の事をデリミタと呼びます。 デリミタには、他にも半角スペースなどがしばしば使われます。
例えばCSV 形式で、書き込む値がカンマ記号を含む場合、そのまま書き込んだのでは、 それが「 区切り文字(デリミタ) 」としてのカンマ記号なのか、 それとも 「 値の一部 」 としてのカンマ記号なのか、区別のつかない曖昧なファイルになってしまいます:
上の例では、「 あいう,えお 」 という値を書き込んだとしても、それは区切りのカンマと区別できません。 そのため、テキストを扱うCSV 形式では、そのような場合に値をダブルクォーテーションなどで区切ります。 このように囲む記号をエンクロージャと呼びます。
こうすると、最初の値は 「 あいう,えお 」 という意味なのだと区別できます。改行を含む場合も 同様に:
こうすると最初の値は 「 あいう(改行)えお 」 という意味であると区別できます。 なお、値がエンクロージャと同じ文字が含む場合、エンクロージャを2 つ並べるなどして区別します。
コメント行など、特定の文字で始まる行は無視して読み込ませたい場合もあります。
コメント行の行頭文字は、そのファイルを扱うソフトによって多種多様です。
上で述べたような項目への対応は、テキスト主体の汎用的なファイル入出力を行うためには避けられません。
しかし、前章で述べたような標準ファイル入出力関数は、 もともと数値ファイルを想定した単純なものであったため、 これらの項目に対応する機能がありません。 そのためVCSSL 3.1 以前では、load 関数などでファイル内容をまるごと読み込み、文字列解析を行って、 こうした機能の処理を自分で記述する必要がありました。
しかしVCSSL3.2 からは、これらの項目に対応した、 汎用ファイル入出力機能(ライブラリ)が標準サポートで用意されました。 これにより、標準ファイル入出力関数とほぼ同様の簡単な操作で、こういった項目に対応する高度な入出力を、 自動で行えるようになりました。
例として、CSV 形式でテキストファイルを書き込みます。
import file.TextFile; // 汎用入出力を行うために必要
// ファイルをCSV 書き込みモードで開く
TextFile file = openTextFile("text.csv", "wcsv");
// コメント行の行頭文字を # に設定
setCommentLineCode(file, "#");
// ファイルに内容を書き込んで改行
writeln(file, "あい,うえお", "かきくけこ", "さしすせそ");
// コメント行を書き込んで改行
commentln(file, "この行はコメントなので無視する");
// ファイルに内容を書き込んで改行
writeln(file, "ABCDE", "FGHIJ", "KL,MNO");
// ファイルを閉じる
close(file);
WriteFileTextCSV.vcssl
実行すると、以下の内容のファイルが書き出されます。
カンマ記号を含む値が、正しくエンクロージャで囲まれています。 また、コメント行は設定した「#」を付けて書き出されています。
プログラムの最初の行「 import file.TextFile; 」は、汎用入出力を行うために必要です。 具体的には、汎用ファイル入出力機能を提供するプログラム(ライブラリ)を読み込んでいますが、 これについては後の 「 モジュールとライブラリ 」 の章で説明します。
内容については、全体的に標準ファイル入出力とかなり似ているので、 特に違和感無く使用できると思われますが、以下の1 行は形が異なります。
// ファイルをCSV 書き込みモードで開く
TextFile file = openTextFile("text.csv", "wcsv");
OpenTextFile.vcssl
この部分は、標準ファイル入出力の場合なら以下のようになるでしょう:
// ファイルをCSV 書き込みモードで開く
int file = open("text.csv", "wcsv");
Open.vcssl
このように、標準ファイル入出力(下)ならばファイル番号を int 型の変数 file で受け取るわけですが、 汎用ファイル入出力(上)では TextFile という見たことの無い型の変数で受け取っています。
これはすぐ後の章で説明しますが、構造体というもので、いわば新しい型です。ここではとりあえず int 型の代わりに TextFile 型を用いるだけなので、あまり深く考える必要はありません。
なお、標準ファイル入出力と同様、モードは以下のように定数で指定する事もできます。定数名も標準ファイル入出力と全く同じです。タイプ数は増えますが、タイプミスが構文エラーで分かるので防げます。
// モードを定数で指定
TextFile file = openTextFile("text.csv", WRITE_CSV);
OpenTextFileConst.vcssl
続いて、先ほど作成したCSV 形式でテキストファイルを読み込みます。
import file.TextFile; // 汎用入出力を行うために必要
// ファイルをCSV 読み込みモードで開く
TextFile file = openTextFile( "text.csv", "rcsv" );
// コメント行の行頭文字を # に設定
setCommentLineCode( file, "#" );
// ファイル行数をカウント(コメント行は読み飛される)
int n = countln( file );
// 各行の値を格納する配列
string line[3];
for (int i=0; i<n; i++) {
// ファイルから一行を読み込む
line = readln(file);
// 読み込んだ内容を出力
println(i + "行目: " + line[0] + "/" + line[1] + "/" + line[2]);
}
// ファイルを閉じる
close(file);
ReadFileTextCSV.vcssl
実行すると、コンソールに以下の内容が出力されます。
このように、エンクロージャを認識した上で、「 あい,うえお 」を1 つの値として読み込めました。 また、コメント行は無視されています。
ファイル行数をカウントするcountln 関数も、コメント行は完全に無視して数えるてくれるめ、 ファイル行末を超過して読み込んでしまう事もありません。 なお、エンクロージャで囲まれた値が改行コードを含む場合についても、countln 関数はそれを1 行にはカウントせず、 あくまで値に含まれた改行コードであると識別してくれます。 そのため、標準入出力と同様の感覚で、まずcountln 関数で行数を取得し、その回数だけreadln 関数で読み進めるだけで、 簡単にテキストCSV/TSV ファイルの入出力を行う事ができます。
汎用入出力では、細かな設定が可能です。詳細は下記URL の公式仕様をご参照ください。
上記ページでは、ここで用いた汎用ファイル入出力機能を提供するライブラリの、詳細な仕様を確認できます。