【javascript】「,」でsplitする際、「"～,～"」の「,」での分割を回避（追記コード2個）（修正＋改行用追記）

2021.4.22
 - 追記コードの不備を修正（case 3:の処理、要素が消える）
 - 改行処理用の関数を追記
 - 元の関数名を`csvSplit`から`lienSplit`に変更


#前置き

javascriptでsplitを使ってコンマで分割すると、""で囲まれた内部のコンマまで分割されてしまう件 - Qiita
https://qiita.com/hatorijobs/items/dd0c730e6faba0c84203

こちらの記事きっかけで書いてます。
はい、また他人の記事に乗っかってます。ごめんなさい。

CSVで読み込んだ行を「,」で分割する際の問題のようです。

```
このような行を
block1,block2,"¥12,345",block4,block5

こうしたいのに
=> ["block1", "block2", ""¥12,345"", "block4", "block5"]

こうなってしまう
=> ["block1", "block2", ""¥12", "345"", "block4", "block5"]
```

#コード

最近ハマってるreduceを使って書きました。

```javascript:
const csvSplit = (str, s=',') => {
	str = str.replace(/"+/g,'"').split(s);		//replaceは不要なら外してもOK
	let flag = str[0].startsWith('"') - str[0].endsWith('"');		//"～"内の時「1」
	str = str.slice(1).reduce((p, x) => {
		//「"」が[1]先頭・[2]末尾または[3]両方に存在するか、[0]無いか分類し、処理
		switch (x.startsWith('"') + x.endsWith('"')*2) {
			case 0: return p.concat( flag===1 ? p.pop()+s+x : x );
			case 2: return p.concat( flag--===1 ? p.pop()+s+x : x );
			case 1: flag===1 ? p.push( ...p.pop().split(s) ) : flag = 1;
					return p.concat(x);		//修正
			case 3: flag--;return p.concat(x);		//修正
		}
	}, [str[0]]);
	//最後の要素が閉じられてない場合は分割
	return flag===1 ? str.concat( ...str.pop().split(s) ) : str;
};

console.log( csvSplit('block1",block2",block3","¥12,345,678,900",block4,block5"') );
// ["block1"", "block2"", "block3"", ""¥12,345,678,900"", "block4", "block5""]

console.log( csvSplit(
	'"block1,block2","block3,block4,"¥12,345,678,900",block4,"block5,block6'
	) );
// [""block1,block2"", "block3", ""¥12,345,678,900"", "block4", ""block5", "block6"]
```

#感想

なんかごちゃごちゃしてて微妙。
気を遣う所が増えてしまったので、別のアプローチで考えたほうが良かったかもしれない。


#コード（追記）

要素一つ一つを追加処理していく先のコードと違い、今回は範囲を切り出して追加していく。
切り出しの開始位置を記憶する変数startを追加。

```javascript:
const lienSplit = (str, s=',') => {
	let [flag, start] = [0, 0];
	str = str.replace(/"+/g,'"').split(s);
	return  str.reduce((p, x, i, a) => {
		switch (x.startsWith('"') + x.endsWith('"')*2) {
			case 1: 
				p.push( ...a.slice(start, i) );
				[flag, start] = [1, i];
				break;
			case 2: 
				if (!flag) break;
				p.push( a.slice(start, i+1).join(s) );
				[flag, start] = [0, i+1];
				break;
			case 3: 
				//修正
				p.push(i===start ? x : a.slice(start,i).concat(x));
				[flag, start] = [0,i+1];
				break;
		}
		return p;
	}, []).concat(str.slice(start));
};
```

↓更に追加。
switch内の類似処理が気になったので関数化してまとめました。
処理自体は上記と同じ。人によっては少し見難いかもしれない。

```javascript:
const lienSplit = (str, s=',') => {
	let [flag, start] = [0, 0];
	const proc = (p, e, f, t) => ([flag, start] = [f, t], p.concat(e));
	const reducer = (p, x, i, a) => {
		switch (x.startsWith('"') + x.endsWith('"')*2) {
			case 1: return proc(p, a.slice(start,i), 1, i);
			case 2: return flag ? proc(p, a.slice(start,i+1).join(s), 0, i+1) : p;
			//修正
			case 3: return proc(p, i===start ? x : a.slice(start,i).concat(x), 0, i+1);
			default: return p;
		}
	};
	str = str.replace(/"+/g,'"').split(s);
	return  str.reduce(reducer, []).concat(str.slice(start));
};
```

個人的に気になっているところは潰せたと思う。

#改行用コード（追記）

先述の関数`lineSplit`の結果を元に再構成するので、改行だけしたい場合には使えない。

```javascript:
const csvSplit = text => {
	let [start, rest] = [0, []];
	const reducer = (p, x, i, a) => {
		if (!(x.includes('\n') && !x.startsWith('"') && !x.endsWith('"'))) return p;
		const br = x.split('\n').map(y => [y]);
		p.push( rest.concat(a.slice(start, i), br.shift()) );
		[start, rest] = [i+1, br.pop()];
		return br.length ? p.concat(br) : p;
	};
	const arr = lineSplit(text);
	return arr.reduce(reducer, []).concat([rest.concat(arr.slice(start))]);
};
```

やっつけで作ったので微妙。