概要
ruby 2.4.0 から、CSV に liberal_parsing
オプションが追加された
これによって、RFC4180を無視してフィールド内に置かれた、エスケープされていないダブルクォートを読み込むことができるようになった
実用に足りるか細かい挙動を調査したので、それをメモ
調査した感想
- RFC4180に準拠しているフィールドと、そうでないフィールドが混在しているときに使うのを想定している様子
- たとえば
"a""a""a",b"bb
みたいな
- たとえば
- そういう使い方ならばオススメ。 ただし、ダブルクォートのとじ忘れは、今までと同様にパースに失敗する場合がある
- 文中に改行も無く、ダブルクォートに囲まれたフィールドを期待していないのであれば、
String#split(',')
を使ったほうが速いし、上記のような問題も発生しない
liberal_parsing
オプションの挙動をざっくり説明
ruby 2.4.2 の挙動
- ダブルクォートに囲まれたフィールドの場合、
liberal_parsing
があってもなくても今までと同じ挙動 - ダブルクォートに囲まれていないフィールドの場合、
- フィールド内にあるダブルクォートに特別な意味を持たせずにパースする
-
a,bb"b,c
ならば["a", "bb\"b", "c"]
-
-
フィールドの先頭にダブルクォートがあって末尾にない場合、文中のダブルクォートの数が奇数だとパースに失敗する
- 例
a,"b"b"b,c
- 既存のダブルクォートに囲まれた文字列のパースの実装とバッティングしている? バグと言ってよいのかな...?
- フィールドの先頭にダブルクォートが無くて末尾にある場合は、ダブルクォートの偶数奇数に関わらずパースされる
- 例
a,b"b"b,c
- 例
- 例
- フィールド内にあるダブルクォートに特別な意味を持たせずにパースする
参考
色々なCSVを liberal_parsing
オプションをつけてパースした結果
##ダブルクォートに囲まれたフィールドの場合
liberal_parsing
オプションに関わらず、今までと同様の挙動
正しくエスケープされたダブルクォートの場合
a,"bb""b",c
RFC4180に合わせてダブルクォートが処理される
CSV.parse(csv, liberal_parsing: true) #=> [["a", "bb\"b", "c"]]
文中にエスケープされていないダブルクォートがある場合
a,"bb"b",c
例外が発生する
CSV.parse(csv, liberal_parsing: true) #=> Unclosed quoted field on line 1.
##ダブルクォートに囲まれていないフィールドの場合
途中にダブルクォートがある場合
a,bb"b,c
恐らく、こういうパターンを救いたいが為の liberal_parsing
オプション
ダブルクォートに特別な意味を持たせずにパースされる
CSV.parse(csv, liberal_parsing: true) #=> [["a", "bb\"b", "c"]]
途中にエスケープされたダブルクォートがある場合
a,bb""b,c
ダブルクォートに特別な意味を持たせないのでエスケープは無視される
CSV.parse(csv, liberal_parsing: true) #=> [["a", "bb\"\"b", "c"]]
先頭にのみダブルクォートがある場合
a,"bbb,c
RFC4180に準拠したフィールドであるかどうか確認している途中で末尾に到着して、MalformedCSVError
が発生
これを救いたければ、CSVライブラリは使わずに、split(',')
した方が良さそう
CSV.parse(csv, liberal_parsing: true) #=> Unclosed quoted field on line 1.
先頭と途中にダブルクォートがあって、ダブルクォートの合計が偶数の場合
a,"bb"b,c
この場合は救われる
実装を斜め読みする限り、既存のダブルクォートをパースする処理との共存のためにそうなっている
CSV.parse(csv, liberal_parsing: true) #=> [["a", "\"bb\"b", "c"]]
先頭と途中にダブルクォートがあって、ダブルクォートの合計が奇数の場合
a,"b"b"b,c
奇数の場合は例外になる
この辺を救いたければ(ry
CSV.parse(csv, liberal_parsing: true) #=> Unclosed quoted field on line 1.
末尾と途中にダブルクォートがある場合
a,bb"b",c
この場合は、フィールド内のダブルクォートが奇数だろうが、偶数だろうが例外は発生しない
CSV.parse(csv, liberal_parsing: true) #=> [["a", "bb\"b\"", "c"]]