CSV
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は CSV パーサーです。TSV もあります。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
使い方
using CSV
CSV ライブラリは標準組み込みではないため、using ディレクティブを使用して明示的に読み込む。
using CSV;
全てを一度にパース
ファイルを読み込んで全体をパースするには、CSV.parse()
を使う。
using CSV;
var r = CSV.parse("filename.csv");
データの形式は以下の通り。全ての行オブジェクトの配列として返す。行オブジェクトには data
フィールドがあり、そこに配列として各要素の文字列が設定される。ただし、コメント行(先頭が #
)の場合、data
フィールドは空となり comment
フィールドにコメント文字列が入る(#
も含む)。
[{
"data": ["aaa", "bbb", "ccc"]
}, ..., {
"data": {},
"comment": "# comment"
}]
1行ずつコールバック
行数が多い場合、全体を一気にパースすると大量にメモリを消費する可能性がある。そこで、以下のように 1 行ずつコールバックさせることもできる。コールバックの引数は 1 行分の行オブジェクトとなる。
using CSV;
CSV.parse("filename.csv", &(row) => {
System.println(row); // like `{ data: ["aaa", "bbb", "ccc"] }`
});
尚、この場合 CSV.parse()
は値を返さない(null を返す)。
文字列をパース
文字列をパースする場合、CSV.parseString()
を使う。コールバック版も同様。
using CSV;
var r = CSV.parseString(csvString);
CSV.parseString(csvString, &(row) => {
...
});
CSV 仕様
解釈する CSV の仕様は以下の通り。
- 先頭文字が
#
の場合、コメント行と認識。 - ダブルクォートは無くても良い。
-
,
や"
、改行を含めたい場合はダブルクォートが必要。 - ダブルクォート内では以下の仕様。
-
,
は直接記載可能。 -
"
は""
と重ねて記載。 - 改行はそのまま改行することで表現する。
-
- 最終行の末尾に改行コードは無くても良い。
サンプル
サンプルとして以下をパース。
# comment in CSV.
abcde,efgh,non-quoted string
"abcde","efgh","quoted string"
"abcde",efgh,"quoted & non-quoted string in the same row"
"abcde",efgh,"can use , or "" in csv"
"abcde",efgh,"can use newlines
in the row"
# another comment in CSV.
abcde,efgh,not necessary \n the end of csv
結果はこうなる。
[{
"data": {},
"comment": "# comment in CSV."
}, {
"data": ["abcde", "efgh", "non-quoted string"]
}, {
"data": ["abcde", "efgh", "quoted string"]
}, {
"data": ["abcde", "efgh", "quoted & non-quoted string in the same row"]
}, {
"data": ["abcde", "efgh", "can use , or \" in csv"]
}, {
"data": ["abcde", "efgh", "can use newlines\n\nin the row"]
}, {
"data": {},
"comment": "# another comment in CSV."
}, {
"data": ["abcde", "efgh", "not necessary \\n the end of csv"]
}]
TSV
TSV とは CSV のカンマ区切りがタブ区切りになったもの。
using CSV
は一緒。使うときに CSV
を TSV
にするだけで使える。
using CSV;
var r = TSV.parse(tsvFilename);
TSV.parseString(tsvString, &(row) => {
...
});
パーサー
パーサー自体を Kinx で書いています。
パーサーの形態は1文字先読みの再帰下降パーサーですが、そもそも CSV の仕様に再帰的な要素がないので再帰しません。言うなればただの下降パーサーです。
コードは GitHub にあります。全体で100行ちょっとしかないのですぐ理解できるかと思います。
内部ライブラリ用の特殊キーワードについて
ちなみに、_class
と _function
というキーワードが使われていますが、一般では使いません。意味は class
、function
と同様です。
何が違うかというと、_
付きのほうは例外発生時にスタックトレースに情報を残さないといった動作をします(また、-d
オプションで VM コードを表示しません)。これは内部ライブラリの場合、スタックトレースに情報を残すと混乱する可能性があるためです。例えば、例外の起点が内部ライブラリの行番号を示していると、そこに問題があると勘違いする可能性があるためで、例外の起点はあくまでユーザー・コードを示すようにしています。VM コード出力でもユーザー・コードの邪魔になると考えられるので表示しないといった配慮がなされる形です。
このストラテジーに従うと逆にライブラリ内部にバグがあった場合は調査困難になりますが、通常ケースでパラメーター異常による例外、といったケースのほうが一般的と考えられるので、この仕組みを入れてあります。尚、内部ライブラリ自体のデバッグをしたい場合は _class
を class
に、_function
を function
に変更すればできます。
おわりに
CSV パーサーは何か既存のライブラリを使おうかと思って探したんですが、イマイチ統合するのが手間取りそうだったのと、実は簡単なんじゃないかと思ってスクラッチで書いてみました。何か問題があればお知らせください。
ではまた、次回。