古いソフトウェアが保存しているセーブデータファイルを開いてみると、CSV ファイルだった、なんてことが 2020 年の今でもあるようです。
場合によっては CSV 形式といっても、ときどきちょっと変わったフォーマットを採用していることがあります。
厄介な CSV ファイル
普通の CSV ファイルは、最初に示される(または、省略される)ヘッダーの列定義に従って、行のデータが追加されていくため、基本的には各行のデータ数は一致します。
こんな感じが、よくある CSV ファイルのフォーマットかと思います。(マッピングできるファイル形式)
列1,列2,列3 aaa,bbb,ccc 111,222,333
ちょっと変わったフォーマットというのは、次のようにバラバラのフォーマットで、行ごとの要素数が一致せず、区切り文字だけが CSV っぽい状態で保存されている状態のものです。
SAMPLE True,False,True,False,False,True,True,False,False,, 200 0 , 1 , 1 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 1 , 0 , 1 1 ,-1 ," ", 3,"" 2 , 21 ," ", 1,"" 3 , 33 ," ", 1,"BB,BB" 4 , 44 ," ", 1,"" 5 , 55 ," ", 2,"" 6 , 66 ," ", 1,"A,A,A,A" false, " ", 0 ,"",-1 , 0
セーブデータファイルに、設定情報なんかも全部まとめて1つのファイルに付与していると、こういうことに……。
「CsvHelper」などのライブラリーの多くは、(できなくもないとしても)本来期待しているフォーマットとはいえないため、冗長なコーディングになってしまいます。
そんなわけで、CSV ファイルをシンプルに読み込むだけの機能を用意して、CSV の標準規格である「RFC 4180」のルールを守った(つもりの)CSV パーサーを書いてみました。
Qiita に「C# 用の CSV パーサーを書いた」というライセンスフリーの CSV パーサーがあったのですが、後述のようになっています。
使い方
一行だけの CSV レコードをパースするときは、次の使い方で CSV レコードをフィールドに分解して IEnumerable<string>
を返却します。
var fields = CsvParser.ParseFieldsFromText("aaa,bbb,ccc"); foreach (var field in fields) { Console.WriteLine(field); }
aaa bbb ccc
n行の CSV レコードをパースするときは、次の使い方で CSV レコードを分解します。
var fields = CsvParser.ParseFromText("aaa,bbb,ccc\r\n111,222,333"); foreach (var record in records) { foreach (var field in record) { Console.WriteLine(field); } }
aaa bbb ccc 111 222 333
フィールドのテキストがダブルクォート("
)を囲まれているときは、次のように動作します。
var records = CsvParser.ParseFromText("aaa, bbb, ccc\r\n111,, 333\r\nAAA, \"BBB\""); foreach (var record in records) { Console.WriteLine($"record has {record.Count()} fields."); foreach (var field in record) { Console.WriteLine(field); } }
record has 3 fields. aaa bbb ccc record has 3 fields. 111 333 record has 2 fields. AAA BBB
省略表記、ダブルクォートの処理は次のとおり。
var records = CsvParser.ParseFromText("aaa,bbb,ccc\r\n111,222,\r\n,\"\"\"bbb\","); foreach (var record in records) { Console.WriteLine($"record has {record.Count()} fields."); foreach (var field in record) { Console.WriteLine(field); } }
record has 3 fields. aaa bbb ccc record has 3 fields. 111 222 record has 3 fields. "BBB
ファイルから読み込むときは、こんな感じに。読み込んだあとは、ParseFromText
と同じです。
var path = "..."; // System.AppDomain.CurrentDomain.BaseDirectory; var data = CsvParser.ParseFromFile(System.IO.Path.Combine(path, "sample.csv")); foreach (var record in records) { foreach (var field in record) { Console.WriteLine("do something..."); } }
こんな感じで無事に厄介なフォーマットの CSV ファイルも読み込めました。
テストによる検証
「Wiki - Comma-Separated Values」と「RFC 4180」に CSV フォーマットのサンプルがあるのですが、このあたりのルールを守っている保証がある CSV パーサーがほしかった。
「yutokun/CSV-Parser」は、惜しい完成度で、テストコードを走らせてみると一部うまく通りませんでした。(2020年 03 月 12 日時点)
たとえば、こんな感じです。
"日本CRLF国","""東京""","127,767,944"
[0]=日本CRLF国 [1]="京" [2]=127,767,944
他にも 空データ
, B"BB
, 空データ
だとこんな感じ。
"","b""bb",""
[0]=","bb [1]="
そんなわけで、CSV パーサーを作ってみた感じです。WIKI と RFC 4180 のサンプルテキストをコピーしただけですが、自作のパーステストは、すべてパスしています。
サンプル
参考
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)
- 作者:Dustin Boswell,Trevor Foucher
- 発売日: 2012/06/23
- メディア: 単行本(ソフトカバー)