Skip to content

Instantly share code, notes, and snippets.

@nexpr
Created June 26, 2021 08:35
Show Gist options
  • Save nexpr/7df42ea7bfd58b55e28f169cc633f876 to your computer and use it in GitHub Desktop.
Save nexpr/7df42ea7bfd58b55e28f169cc633f876 to your computer and use it in GitHub Desktop.
制限付き CSV パーサ / 列情報が必要 / 列数は固定 / 列ごとにクオートの有無と変換する型を指定

lcsvp

制限付き CSV パーサ

  • 事前に列情報が必要
  • 列ごとに
    • クオートの有無
    • 変換する型情報

使い方

lcsvp([false, true, false])(csv_str)

const parse = lcsvp([
	{ quote:true, type: String },
	{ quote: true, type: JSON },
	{ quote: false, type: Number}
], { space: true })

parse(csv_str)

返り値は配列の配列
中の要素は type に指定した型に変換された値

列情報に名前を渡してオブジェクトの配列にする機能はなし
使うときに↓のようにすればいい

const [id, name, value1, value2] = row

基本

console.log(lcsvp([false, false])
(`1,2
10,20`))
[ [ '1', '2' ], [ '10', '20' ] ]

「,」 前後のスペースはそのままスペースになる

console.log(lcsvp([false, false])
(`1  ,  2
10 , 20`))
[ [ '1  ', '  2' ], [ '10 ', ' 20' ] ]

「,」 前後のスペースも考慮するなら {space: true} をオプションに設定

console.log(lcsvp([false, false], { space: true })
(`1  ,  2
10 , 20`))
[ [ '1', '2' ], [ '10', '20' ] ]

クオートで包まれている列は true を指定する

console.log(lcsvp([true, true])
(`"1","2"
"10","20"`))
[ [ '1', '2' ], [ '10', '20' ] ]

直接 true/false を指定するのはオブジェクトにして quote プロパティを使うのと一緒

console.log(lcsvp([{ quote: true }, { quote: true }])
(`"1","2"
"10","20"`))
[ [ '1', '2' ], [ '10', '20' ] ]

オブジェクトにして type プロパティに型を指定すると変換する
未指定は文字列型

console.log(lcsvp([{ type: Number }, { type: Number }])
(`1,2
10,20`))
[ [ 1, 2 ], [ 10, 20 ] ]

クオートの中では区切り文字の 「,」 や改行を使える
クオートの中でクオートを使うには 「""」

console.log(lcsvp([false, true])
(`1,"a""b"
10,"b,c"
20,"d
e"`))
[ [ '1', 'a"b' ], [ '10', 'b,c' ], [ '20', 'd\ne' ] ]

Number/String/Boolean 以外に JSON を指定できる
JSON.parse を使ってパースする

console.log(lcsvp([{ type: Number }, { quote: true, type: JSON }])
(`1,"{""a"":1}"
2,"{""foo"":""bar""}"
3,"{""x"":{""y"":[1,2]}}"`))
[ [ 1, { a: 1 } ], [ 2, { foo: 'bar' } ], [ 3, { x: { y: [ 1, 2 ] } } ] ]
const lcsvp = (cols, opt) => {
const cols_def = cols.map((c) => (typeof c === "boolean" ? { quote: c } : c))
const cols_restr = cols_def.map((c) => (c.quote ? `"((?:[^"]|"")*?)"` : "([^,]*?)")).join(opt?.space ? " *, *" : ",")
const row_re = new RegExp(`^${cols_restr}(?:\r?\n|$)`)
const cols_formatter = cols_def.map((x) => (x.type === JSON ? JSON.parse : x.type || String))
const unescape = (str, ci) => (cols_def[ci].quote ? str.replace(/""/g, '"') : str)
return (text) => {
const rows = []
while (text) {
text = text.replace(/^(\r?\n)+/, "")
const matched = text.match(row_re)
if (!matched) throw new Error("invalid row at: " + text.split(/\r?\n/, 1)[0])
rows.push(matched.slice(1).map((val, ci) => cols_formatter[ci](unescape(val, ci))))
text = text.slice(matched[0].length)
}
return rows
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment