アルゴリズム/CSVファイルデータの読み込み
CSVファイルのデータを扱いたい。
でも「,」を含む値も取り扱いたい。。。ということがあったのでメモです。。。
CSVの仕様によると、
とのことなので、(参考/RFC4180 http://www.ietf.org/rfc/rfc4180.txt)
ダブルクォートのエスケープが面倒くさいけど
「,」を含むデータを取り扱うにはダブルクォートで囲めばよいそうです。
今回は、ダブルクォート内に改行が含まれない1行のCSVデータを読み込み、各値を配列で返す処理を考えます。
簡単に考えるために1行の文字を先頭から見ていき、遷移する状態として次の5状態を考えます
- 初期状態
- 各データ先頭文字の状態、データ状態のいずれかへ遷移
- データ状態(非クォート)
- ダブルクォートで囲まれない場合で、コンマが現れるまでデータとして取り込んで行く
- データ状態(クォート)
- ダブルクォートで囲まれている場合、ダブルクォートが現れるまでデータとして取り込んで行く
- ダブルクォート中のダブルクォート状態
- 次の文字がダブルクォートなら再びクォート内のデータ状態へ移り、コンマならデータ末尾
- データ末尾状態
- 各データの末尾まで読み込んだ状態で、配列に詰め込む
以上の状態遷移をもとにPHPで記述してみました。
<?php function csv($dataline){ $result = array(); $len = strlen($dataline); //各状態の初期化 // start, not_quoted, quoted, end_or_escape, end $state = "start"; $tmp = ""; for($i=0; $i<$len; $i++){ $ch = $dataline[$i]; switch($state){ case 'start'://初期状態の処理 if($ch == '"') $state = 'quoted'; elseif($ch == ',') $state = 'end'; else { $state = 'not_quoted'; $tmp .= $ch; } break; case 'not_quoted'://クォーティングされてない場合 if($ch == ',') $state = 'end'; else $tmp .= $ch; break; case 'quoted'://クォーティングされている場合 if($ch == '"') $state = 'end_or_escape'; else $tmp .= $ch; break; case 'end_or_escape'://クォーティング中のクォート if($ch == '"'){ $state = 'quoted'; $tmp .= '"'; } else $state = 'end'; break; } if($state == 'end'){ $state = 'start'; $result[] = $tmp; $tmp = ""; } } //最後の要素を結果に詰め込む //行末がコンマで終わる時はデータとして含めない if($tmp != "") $result[] = $tmp; return $result; } ?>
状態遷移の仕方などよく考えるともっとコードは短くなりそうです。