CSVは「1行をカンマで分ければ読めるファイル」と思われがちですが、それだけでは正しく扱えません。
理由は、値そのものにカンマ、ダブルクォート、改行を含められるからです。RFC 4180で文書化されている一般的なCSV形式では、これらをダブルクォートで囲むことで1つのフィールドとして扱います。
CSVを壊さず扱うには、画面上の1行=1レコードとは限らないことを理解するのが最初です。
CSVの基本形
最も単純なCSVは次のような形です。
id,name,amount
1,田中,1000
2,佐藤,2000
カンマが列の境界です。この例なら、各レコードは3フィールドあります。
RFC 4180では、ヘッダーがある場合も通常のレコードと同じ形式で、残りのレコードとフィールド数をそろえる形が文書化されています。
値の中にカンマがあるとき
次のデータを考えます。
id,address
1,東京都,千代田区
このままでは3列として解釈されます。
東京都,千代田区 を1つの値として持たせたいなら、一般的なCSV形式ではダブルクォートで囲みます。
id,address
1,"東京都,千代田区"
このレコードは2列です。
値の中にダブルクォートがあるとき
ダブルクォートで囲まれたフィールドの中に、さらにダブルクォートを入れたい場合は、2個に重ねます。
id,note
1,"he said ""OK"""
値としては次の内容です。
he said "OK"
\" のような別形式のエスケープを想定しているプログラムもありますが、RFC 4180で文書化されているCSV形式はダブルクォートを2個に重ねる形です。
セル内改行も1つの値にできる
CSVで特に事故が起きやすいのが改行です。
id,note
1,"1行目
2行目"
2,完了
画面上では4行に見えます。しかしデータ上のレコードは、ヘッダーを含めて3件です。
"1行目 で始まったフィールドは、次の行にある 2行目" まで続いています。
そのためCSVを処理するときは、単純な改行数ではなく、引用符の状態を考慮した論理レコードとして読む必要があります。
「物理行」と「論理レコード」を分けて考える
ここでは区別のため、ファイル上の改行で区切られた見た目の行を「物理行」、CSVとして1件にまとまるデータを「論理レコード」と呼びます。
物理行1: id,note
物理行2: 1,"1行目
物理行3: 2行目"
物理行4: 2,完了
CSVとしては、
論理レコード1: id,note
論理レコード2: 1,"1行目\n2行目"
論理レコード3: 2,完了
です。
この違いを無視すると、CSVの分割、件数カウント、差分比較などで誤判定が起きます。
列数を数えるときも引用符を無視しない
次のレコードは、カンマが3個あります。
1,"東京,大阪",1000
しかし列は3つです。
1東京,大阪1000
単純に文字としてカンマを数えるだけでは列数を判定できません。
RFC 4180は「唯一の絶対仕様」ではない
RFC 4180自身も、CSVにはさまざまな仕様・実装が存在してきたことを前提に、広く使われる形式を文書化しています。
そのため実務では、
- 区切りがTABやセミコロン
- 改行コードがLF
- 引用符の扱いが独自
といったファイルもあります。
RFC 4180は基準線として有用ですが、最終的には作成元と取り込み先の仕様も確認します。
CSVを安全に扱うための最低ルール
CSVを生成・加工するときは、少なくとも次を守ると事故を減らせます。
- カンマを含む値は引用符を考慮する
- 改行を含む値は引用符を考慮する
- フィールド内の
"はダブルクォートの重ね方を確認する - 行数ではなく論理レコード数を見る
- 引用符を無視して単純分割しない
- 変換後にレコード件数と列数を再確認する
まとめ
CSVのダブルクォートは、見た目を整えるためではありません。値の中にカンマ・改行・ダブルクォートを安全に持たせるための構造です。
「1行ずつsplitする」「カンマでsplitする」だけでは、正常なCSVまで壊すことがあります。
CSVを扱うときは、物理行ではなく論理レコード、文字としてのカンマではなくフィールド境界を見るのが基本です。
参考一次資料
- RFC Editor: RFC 4180 — Common Format and MIME Type for CSV Files
https://www.rfc-editor.org/info/rfc4180/
