CSV操作
CSVファイル/CSVデータの読み書き等操作を扱います。
ここでは、標準で備えているstd.csvモジュールを用いたCSVファイルの読み込みと、
CSVファイルへの書き出しについて説明します。
Examples:
CSVのパース
import std.csv; import std.algorithm, std.array, std.string; // サンプルのCSV enum string csv1 = ` a,b,c,d,e 1,2,3,4,5 6,7,8,9,10 `.outdent.strip; // 文字列からをCSVとしてパースして、文字列の2次元配列にします // 以下はワンライナーで取れるようにしていますが、 // csvReader(csv1)した各レコード(1行ごとに各列(Range)が格納されたRange)を、 // mapとarrayで配列に変換しているだけです。 auto mat = csvReader(csv1).map!array.array; assert(mat[0][0] == "a"); assert(mat[0][1] == "b"); assert(mat[1][0] == "1");
Examples:
もう少し複雑な場合
import std.csv; import std.algorithm, std.array, std.string, std.exception; // サンプルのCSV // 今回はヘッダ付きです。 enum string csv1 = ` Name,Height,BloodType Cocoa,154,B Chino,144,AB Rize,160,A `.outdent.strip; // 配列に直すのはこうしておくと楽 alias toMat = r => r.map!array.array; // 文字列からをCSVとしてパースして、文字列の2次元配列にします // 何もしないとヘッダも読み込まれてしまいます auto mat = toMat(csv1.csvReader); assert(mat[0][0] == "Name"); assert(mat[0][1] == "Height"); assert(mat[1][0] == "Cocoa"); assert(mat[1][2] == "B"); // 今回のCSVにはヘッダが付与されていますが、 // csvReaderの2つ目の引数にnullを指定するとその分を無視することができます // あるいは、取得するデータをピックアップすることもできます。 // ※右列のデータを左列に持ってくるような並べ替えはできません auto mat2 = toMat(csv1.csvReader(null)); assert(mat2[0][0] == "Cocoa"); assert(mat2[0][1] == "154"); assert(mat2[1][0] == "Chino"); assert(mat2[1][2] == "AB"); auto mat3 = toMat(csv1.csvReader(["Name", "BloodType"])); assert(mat3[0][0] == "Cocoa"); assert(mat3[0][1] == "B"); assert(mat3[1][0] == "Chino"); assert(mat3[1][1] == "AB"); // こんな感じの並べ替えはHeaderMismatchExceptionでNG assertThrown!HeaderMismatchException( toMat(csv1.csvReader(["Name", "BloodType", "Height"])));
Examples:
構造体でデータ構造をレイアウトする場合
import std.csv; import std.array, std.string; // サンプルのCSV // さっきと同じ enum string csv1 = ` Name,Height,BloodType Cocoa,154,B Chino,144,AB Rize,160,A `.outdent.strip; // 次は各行のレコードをこの構造体のデータとして一括読み込みします struct CharactorData { enum BloodType { A, B, AB, O } string name; BloodType bloodType; int height; } // csvReaderの1つ目のテンプレート引数に構造体を渡すと、 // そのメンバー変数のレイアウトでCSVを解釈します。 // 構造体のメンバー変数はそれぞれstd.conv.toによって文字列と相互に変換可能 // でなければなりません。 // また、CSVの先頭行にヘッダ情報があるので、構造体の場合並び替えが可能です。 // csvRaderの2つ目の引数に文字列の配列を渡してやることで、CSVの1行目を // ヘッダとして解釈し、並び替えを行って読み込むことができます。 auto order = ["Name", "BloodType", "Height"]; auto charactors = csv1.csvReader!CharactorData(order).array; assert(charactors[0].name == "Cocoa"); assert(charactors[1].height == 144); assert(charactors[2].bloodType == CharactorData.BloodType.A);
Examples:
CSVの書き出し
残念ながら、CSVを書き出す機能はありません。自分で作ります。 以下の例では汎用性を高めるため、",\nを含むものを変換することを前提とします。 これらが含まれると、各セルをエスケープする必要が出るためです。 数値だけということがあらかじめわかっているときなど、 エスケープする必要がない場合はformat!"%-(%-(%-s,%)\n%)"(mat)とするだけでOK。// CSVは、カンマ区切りで列を、改行で行を表すテキストですが、 // 細かく言うと、`"`で囲まれた範囲を文字列として処理します。 // 文字列の中では、改行やカンマが使用できます。ダブルクォーテーションを // 文字列内で表現する場合は、2つ連続させます。`""`こんな感じに。 // つまり、`",\r\n`(ダブルクォーテーションとカンマと改行)が含まれるセルは // `"`で囲んで出力し、さらにその中の`"`は`""`に置換してやります。 string escapeCSV(string txt) { import std.string, std.array; if (txt.indexOfAny(",\"\r\n") != -1) return `"` ~ txt.replace("\"", "\"\"") ~ `"`; return txt; } // 文字列の2次元配列(行列)の各セルをエスケープし、 // 各列をカンマ区切り・各行を改行区切りの文字列にします。 // ここで、formatが利用できます。`%(%)`という書式で、 // Rangeをうまいこと展開できます。 string toCSV(string[][] mat) { import std.algorithm, std.format; return format!"%-(%-(%-s,%)\n%)"( mat.map!(row => row.map!escapeCSV)); } // 準備 import std.algorithm, std.array, std.csv, std.string; alias toMat = r => r.map!array.array; // 文字列の行列を… string[][] mat = [ ["aaa", "bb,b", "c\nc"], ["123", "x\"y", "\"xxx\""]]; // CSVに変換! auto csv = toCSV(mat); // 中身はこう assert(csv == ` aaa,"bb,b","c c" 123,"x""y","""xxx""" `.outdent.strip); // もう一度文字列の行列に戻して比較しても一致! assert(equal(toMat(csv.csvReader), mat));