正規表現で作ると CSV パーサが思ったより簡単に作れた
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ 思ってたよりもかなり短く書けた
前回の CSV の記事を書いたときに作ったパーサは
形式で一文字ずつ見ていって その時の状態に応じて分岐するものでした
これでやると 今の状態がどういうもので この状態でこういう文字が来たら……とか考えるのが面倒です
分岐ですべてのパターンを考えてるので 考慮漏れによるミスは少ないですが 実装が疲れますし 50 行は超えてきます
正規表現を使う方法だともっとシンプルできそうな気がしたので こっちもやってみました
普段は正規表現だけで色々やりすぎるのは怖いので 最小限のマッチング部分だけにして 結果を JavaScript 側で色々と追加で処理することが多いのですが正規表現にできる限り頼るようにしてみました
結果は思ったよりうまくいってシンプルになりました
ただ正規表現力があんまりないので例外的な入力でうまく動かないのがありそうなのがちょっと怖いところです
とりあえずソースコードはこうなりました
使用例です
列数がバラバラだったり
クオートがあったり
クオート内にエスケープ表現があったり
クオート内に改行があったり
「,」続きだったり
「,」終わりだったり
しても大丈夫なのでとりあえずは問題なさそうに見えます
for (const char of str) {
//
}
形式で一文字ずつ見ていって その時の状態に応じて分岐するものでした
これでやると 今の状態がどういうもので この状態でこういう文字が来たら……とか考えるのが面倒です
分岐ですべてのパターンを考えてるので 考慮漏れによるミスは少ないですが 実装が疲れますし 50 行は超えてきます
正規表現を使う方法だともっとシンプルできそうな気がしたので こっちもやってみました
普段は正規表現だけで色々やりすぎるのは怖いので 最小限のマッチング部分だけにして 結果を JavaScript 側で色々と追加で処理することが多いのですが正規表現にできる限り頼るようにしてみました
結果は思ったよりうまくいってシンプルになりました
ただ正規表現力があんまりないので例外的な入力でうまく動かないのがありそうなのがちょっと怖いところです
とりあえずソースコードはこうなりました
const quoted = `"((?:[^"]|"")*)"`
const unquoted = `([^,\r\n]*)`
const cell = `(?:${quoted}|${unquoted})`
const re_row = new RegExp(`^${cell}(?:,${cell})*$`, "gm")
const re_cell = new RegExp(`(?:^|,)${cell}`, "g")
const parse = (text) => {
const rows = []
for (const row of text.trim().match(re_row)) {
if (!row) continue
const cols = []
for (const [, quoted, unquoted] of row.matchAll(re_cell)) {
cols.push(quoted?.replaceAll('""', '"') || unquoted)
}
rows.push(cols)
}
return rows
}
使用例です
const parsed = parse(`
1,2,"a""b"
3
4,"5
6"
0,,1,,
`)
console.log(JSON.stringify(parsed, null, " "))
[
[
"1",
"2",
"a\"b"
],
[
"3"
],
[
"4",
"5\n6"
],
[
"0",
"",
"1",
"",
""
]
]
列数がバラバラだったり
クオートがあったり
クオート内にエスケープ表現があったり
クオート内に改行があったり
「,」続きだったり
「,」終わりだったり
しても大丈夫なのでとりあえずは問題なさそうに見えます