Kinx ライブラリ - CSV Parser

# CSV

## はじめに

**「見た目は JavaScript、頭脳(中身)は Ruby、（安定感は AC/DC）」** でお届けしているスクリプト言語 [Kinx](https://github.com/Kray-G/kinx)。言語はライブラリが命。ということでライブラリの使い方編。 

今回は CSV パーサーです。TSV もあります。

* 参考
    * 最初の動機 ... [スクリプト言語 KINX（ご紹介）](https://qiita.com/Kray-G/items/ca08b6fb40d15dd0ec76)
        * 個別記事へのリンクは全てここに集約してあります。
    * リポジトリ ... [https://github.com/Kray-G/kinx](https://github.com/Kray-G/kinx)
        * Pull Request 等お待ちしております。 

## 使い方

### using CSV

CSV ライブラリは標準組み込みではないため、using ディレクティブを使用して明示的に読み込む。

```javascript
using CSV;
```

### 全てを一度にパース

ファイルを読み込んで全体をパースするには、`CSV.parse()` を使う。

```javascript
using CSV;
var r = CSV.parse("filename.csv");
```

データの形式は以下の通り。全ての行オブジェクトの配列として返す。行オブジェクトには `data` フィールドがあり、そこに配列として各要素の文字列が設定される。ただし、コメント行（先頭が `#`）の場合、`data` フィールドは空となり `comment` フィールドにコメント文字列が入る（`#` も含む）。

```json
[{
    "data": ["aaa", "bbb", "ccc"]
}, ..., {
    "data": {},
    "comment": "# comment"
}]
```

### 1行ずつコールバック

行数が多い場合、全体を一気にパースすると大量にメモリを消費する可能性がある。そこで、以下のように 1 行ずつコールバックさせることもできる。コールバックの引数は 1 行分の行オブジェクトとなる。

```javascript
using CSV;
CSV.parse("filename.csv", &(row) => {
    System.println(row);  // like `{ data: ["aaa", "bbb", "ccc"] }`
});
```

尚、この場合 `CSV.parse()` は値を返さない（null を返す）。

### 文字列をパース

文字列をパースする場合、`CSV.parseString()` を使う。コールバック版も同様。

```javascript
using CSV;
var r = CSV.parseString(csvString);
CSV.parseString(csvString, &(row) => {
    ...
});
```

## CSV 仕様

解釈する 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
```

結果はこうなる。

```json
[{
    "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` にするだけで使える。

```javascript
using CSV;
var r = TSV.parse(tsvFilename);
TSV.parseString(tsvString, &(row) => {
    ...
});
```

## パーサー

パーサー自体を [Kinx](https://qiita.com/Kray-G/items/ca08b6fb40d15dd0ec76) で書いています。

パーサーの形態は1文字先読みの再帰下降パーサーですが、そもそも CSV の仕様に再帰的な要素がないので再帰しません。言うなればただの下降パーサーです。

コードは [GitHub](https://github.com/Kray-G/kinx/blob/master/lib/std/CSV.kx) にあります。全体で100行ちょっとしかないのですぐ理解できるかと思います。

### 内部ライブラリ用の特殊キーワードについて

ちなみに、`_class` と `_function` というキーワードが使われていますが、一般では使いません。意味は `class`、`function` と同様です。

何が違うかというと、`_` 付きのほうは例外発生時にスタックトレースに情報を残さないといった動作をします（また、`-d` オプションで VM コードを表示しません）。これは内部ライブラリの場合、スタックトレースに情報を残すと混乱する可能性があるためです。例えば、例外の起点が内部ライブラリの行番号を示していると、そこに問題があると勘違いする可能性があるためで、例外の起点はあくまでユーザー・コードを示すようにしています。VM コード出力でもユーザー・コードの邪魔になると考えられるので表示しないといった配慮がなされる形です。

このストラテジーに従うと逆にライブラリ内部にバグがあった場合は調査困難になりますが、通常ケースでパラメーター異常による例外、といったケースのほうが一般的と考えられるので、この仕組みを入れてあります。尚、内部ライブラリ自体のデバッグをしたい場合は `_class` を `class` に、`_function` を `function` に変更すればできます。

## おわりに

CSV パーサーは何か既存のライブラリを使おうかと思って探したんですが、イマイチ統合するのが手間取りそうだったのと、実は簡単なんじゃないかと思ってスクラッチで書いてみました。何か問題があればお知らせください。

ではまた、次回。