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を使って書きました。
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を追加。
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内の類似処理が気になったので関数化してまとめました。
処理自体は上記と同じ。人によっては少し見難いかもしれない。
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
の結果を元に再構成するので、改行だけしたい場合には使えない。
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))]);
};
やっつけで作ったので微妙。