CSVファイルにカンマの含まれる項目を分割せずに読み込む（Java）

CSVファイルの中にカンマ付きの項目があった場合、通常のString.splitメソッドで分割すると、項目の途中で分割されてしまう問題を解決しようという話です。

例えば、こんなCSVのレコードがあった場合

```csv:test.csv
ABC,DEF,GHI,'1,000,000',JKL,MN
```

このレコードをカンマで分割したい時に、このシングルクォートで囲まれた文字列はカンマで区切らず、１つの文字列として分割したいとします。

そうした時に期待する結果は

```result:期待する結果
ABC
DEF
GHI
'1,000,000'
JKL
MN
```

ですが、普通にsplitメソッドを使うとこんな感じに分かれてしまいます。

```result:split(",")の残念な結果
ABC
DEF
GHI
'1
000
000'
JKL
MN
```

どうしたものかと、正規表現とか色々調べてみましたが、そもそもsplitメソッドは

「条件に一致するときに分割する」
メソッドであって、

「条件に一致するときに分割しない」
ということはできないので（今回の場合だと「引用符で囲まれているカンマ」が条件で、その条件に一致する時のみ分割しない、ということはできない）、諦めて自作メソッドを作りました。

```java:Main.java

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Main {

	public static void main(String[] args) throws IOException {

		try {
			String fileName= "C:\\temp\\test.csv"; // 読み込みたいCSVファイル

			// 入力CSVファイルの読み込み
			File file= new File(fileName);
			FileInputStream input = new FileInputStream(file);
			InputStreamReader stream= new InputStreamReader(input,"SJIS");
			BufferedReader br = new BufferedReader(stream);

			String line = br.readLine();
			// 自作メソッド呼び出し
			List<String> data = csvSplit(line);

			for (String col : data) {
				System.out.print(col + "\r\n");
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private static List<String> csvSplit(String line) {

		char c;
		StringBuilder s = new StringBuilder();
		List<String> data = new ArrayList<String>();
		boolean singleQuoteFlg = false;

		for(int i=0; i < line.length(); i++){
			c = line.charAt(i);
			if (c == ',' && !singleQuoteFlg) {
				data.add(s.toString());
				s.delete(0,s.length());
			} else if (c == ',' && singleQuoteFlg) {
				s.append(c);
			} else if (c == '\'') {
				singleQuoteFlg = !singleQuoteFlg;
				s.append(c);
			} else {
				s.append(c);
			}
		}
		return data;
	}
}
```

以下、実行結果です。

```result:実行結果
ABC
DEF
GHI
'1,000,000'
JKL
```
引用符の中のカンマは区切られず、きちんと1つの文字列としてみなされています。

やっていることは、

- CSVのレコードを1行読み込む（String line = br.readLine();）
- 読み込んだレコードから1文字ずつ取り出し、（c = line.charAt(i);）StringBuilderに連結して格納する。（s.append(c);）
- その1文字のパターンによって処理を分ける。
	- カンマで、かつカンマがシングルクォートに囲まれていないとき
	- カンマで、かつカンマがシングルクォートに囲まれているとき
	- シングルクォートのとき
	- それ以外
- シングルクォートに囲まれていないカンマは、区切り文字としてみなし、それまでに読み込んだ文字列をリストに格納する。（data.add(s.toString());）
- シングルクォートに囲まれているカンマは、文字列の一部としてみなすため、StringBuilderに連結する。（s.append(c);）
- シングルクォートのときは、自作のシングルクォートフラグのtrue,falseを切り替えます。（singleQuoteFlg = !singleQuoteFlg）シングルクォートフラグは、今シングルクォートの中を読み込んでいるかどうかを判定するフラグです。シングルクォートも出力に含めるならStringBuilderに連結します。（s.append(c);）
- それ以外の場合は、StringBuilderへの連結のみします。（s.append(c);）

ロジックの説明はだいたいこんな感じです。
ちなみに、シングルクォートを出力に含めたくないときは、StringBuilderへの連結をしなければ出力には含まれません。

```java:修正後csvSplit
	private static List<String> csvSplit(String line) {

		char c;
		StringBuilder s = new StringBuilder();
		List<String> data = new ArrayList<String>();
		boolean singleQuoteFlg = false;

		for(int i=0; i < line.length(); i++){
			c = line.charAt(i);
			if (c == ',' && !singleQuoteFlg) {
				data.add(s.toString());
				s.delete(0,s.length());
			} else if (c == ',' && singleQuoteFlg) {
				s.append(c);
			} else if (c == '\'') {
				singleQuoteFlg = !singleQuoteFlg;
//				s.append(c); // 文字連結をやめるとシングルクォートは出力されないよ！
			} else {
				s.append(c);
			}
		}

		return data;
	}
```

```result:実行結果（シングルクォートなし）
ABC
DEF
GHI
1,000,000
JKL
```

こんな感じになります。

何かほかにいい方法がありましたら、ご教示いただけますと幸いです！