[JavaScript] 正規表現を使ったCSVパーサ
ちょっとした理由から、CSVファイルをJavaScriptで読み込むためのパーサを書きたいと思って、先ほどからがんばってみた。
CSVはいろいろな実装があって正式な仕様がなかなかないらしいけど、考えるのが面倒なのと、Excelが吐くCSVを一応処理できるという理由から、Wikipediaで紹介されていたRFC 4180の形式を採用することに決定。
パーサとかレキサみたいなのは書いたことがないからいびつな仕上がりかも。
仕様
RFC 4180の仕様はだいたい↓のような感じ。
fileは改行(CRLF)で区切られた一つ以上のrecordで構成されるrecordはコンマ(,)で区切られた一つ以上のfieldで構成されるfieldにはescaped、または、non-escapedの2種類があるescapedは、ダブルクオート(")で括られた文字列。文字列中のダブルクオートは、二つつなげる("")ことでエスケープする。コンマ・改行も含むことができる。non-escapedは、コンマ、CR、LF、ダブルクオートを含まない文字列
ダブルクオートで括られた文字列と、括られない文字列との間に型の違いなどはない。大事なのは、ダブルクオートのエスケープが""であるということと、コンマ、CR、LF、ダブルクオートを含む文字列の場合は必ずダブルクオートで括ってエスケープしなければいけないという点っぽい。
バックスラッシュ(\)で文字列中のダブルクオートをエスケープする実装は結構あるっぽいけど、対応するのが面倒なので今回は見送った。
トークン化
まずはCSVをトークン化するレキサを正規表現で作ることにした。
たぶんトークンとして認識する必要があるのは、コンマ、改行、escaped、non-escapedの4種類だけだと思う。間違ってるかもだけど。
とりあえず、それぞれを取り出せる正規表現を書いた。
- コンマ
,- 改行
\r?\nescaped[^,"\r\n]+non-escaped"(?:[^"]|"")*"
つなげると↓
/,|\r?\n|[^,"\r\n]+|"(?:[^"]|"")*"/g
この正規表現で↓のようなCSVをトークン化してみる。
aaa,"bbb" "c""c","d,d" "e e",,""
↓Firebugで実行してトークン化
>>> 'aaa,"bbb"\r\n"c""c","d,d"\r\n"e\r\ne",,""'.match(/,|\r?\n|[^,"\r\n]+|"(?:[^"]|"")*"/g) ["aaa", ",", ""bbb"", "\r\n", ""c""c"", ",", ""d,d"", "\r\n", ""e\r\ne"", ",", ",", """"]
うまくいってるみたいだ。あとは↓みたいな感じのパーサを書いてトークンを解釈していけばOK。
csv.replace(/,|\r?\n|[^,"\r\n]+|"(?:[^"]|"")*"/g, function(token) { ... })
パーサはまた今度書く。
追記
Tab-Separatedの場合は↓のような正規表現になりそう。
/\t|\r?\n|[^"\t\r\n][^\t\r\n]*|"(?:[^"]|"")*"/g
Excelが吐くテキストを見てると、先頭以外にダブルクオートを含む場合はダブルクオートで括らない仕様らしい。なので、non-escapedの部分がやや複雑に。