JScript on WSH 5.6でちょっとしたツールを書いている。CSVファイルを2つ読み込んで処理するので、CSVファイルをフィールドごとに分割した2次元配列を返す簡易パーサを書いてみた。
最初はCOM経由でExcelを使ってCSVファイルを直接読み込んでしまおうと考えていたのだけど、
- 動作が結構重かった。
- 読み込んだレコード数/フィールド数を取得する方法が分からなかった。
ということで、仕方なくパーサを自作することに。
CSVのフォーマットについては、
をベースにしつつ、
- レコードの区切りはCR/LF/CRLFに一応対応。
- 微妙なデータへの対応は殆ど考えてないけど、幾つか気になった点はExcelの挙動を参考に一応対応。
といった感じに若干変更。
ファイルサイズがそれ程大きくないので、一旦ファイル全体をStringオブジェクトとして取得して、それを解析するという構成で実装。試してないからアレだけど、使用している機能的にJScript以外のJavaScriptの処理系でも動きそうな気がする。
/// @brief CSVファイルをパースする /// /// @param[in] text CSVファイルの全内容を保持する文字列 /// @param[in] delim フィールドの区切り文字。省略時は","と見なす。 /// /// @return フィールドごとに分割した二次元配列。 /// フィールドの中身は文字列として格納している。 /// /// @note /// RFC 4180を参照(WikipediaのCSVの記事も参考になる)。 /// CSV を扱う既存の実装は色々とあるが、ごく一部にしか対応していない。 /// function parse_csv(text, delim) { if (!delim) { delim = ","; } var escaped = false; // ダブルクォートで囲まれたフィールドを処理中ならtrue var cells = new Array(); var rec = new Array(); var field = new Array(); for (var i = 0; i < text.length; ++i) { var c = text.charAt(i); switch (escaped) { case false: switch (c) { case "\r": if (i+1 < text.length && text.charAt(i+1) == "\n") { ++i; } /*FALLTHRU*/ case "\n": rec.push(field.join("")); cells.push(rec); field = new Array(); rec = new Array(); break; case "\"": if (field.length == 0) { escaped = true; } else { field.push(c); } break; case delim: rec.push(field.join("")); field = new Array(); break; default: field.push(c); break; } break; case true: switch (c) { case "\"": if (i+1 < text.length) { if (text.charAt(i+1) == "\"") { field.push(c); ++i; } else { escaped = false; } } else { rec.push(field.join("")); cells.push(rec); field = new Array(); rec = new Array(); } break; default: field.push(c); break; } break; default: break; } } // 最後のレコードの末尾に改行コードがない場合の追加処理 if (field.length > 0 || c == delim) { rec.push(field.join("")); } if (rec.length > 0) { cells.push(rec); } return cells; }
書いてみて思ったけど、発想がCプログラマ的かも知れない。例えば1文字ずつ処理している辺りとか。C++にならそれほど手間を掛けずに移植できそうだし、C言語への移植もいけそうな気がする。見た目がC言語系なので錯覚しているだけかもしれないけど。