LoginSignup
15
7

More than 5 years have passed since last update.

ruby 2.4.0 CSV の liberal_parsing オプションについて調査してみた

Last updated at Posted at 2017-09-23

概要

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 オプションに関わらず、今までと同様の挙動

正しくエスケープされたダブルクォートの場合

01.csv
a,"bb""b",c

RFC4180に合わせてダブルクォートが処理される

CSV.parse(csv, liberal_parsing: true) #=> [["a", "bb\"b", "c"]]

文中にエスケープされていないダブルクォートがある場合

01.csv
a,"bb"b",c

例外が発生する

CSV.parse(csv, liberal_parsing: true) #=> Unclosed quoted field on line 1.

ダブルクォートに囲まれていないフィールドの場合

途中にダブルクォートがある場合

01.csv
a,bb"b,c

恐らく、こういうパターンを救いたいが為の liberal_parsing オプション
ダブルクォートに特別な意味を持たせずにパースされる

CSV.parse(csv, liberal_parsing: true) #=> [["a", "bb\"b", "c"]]

途中にエスケープされたダブルクォートがある場合

01.csv
a,bb""b,c

ダブルクォートに特別な意味を持たせないのでエスケープは無視される

CSV.parse(csv, liberal_parsing: true) #=> [["a", "bb\"\"b", "c"]]

先頭にのみダブルクォートがある場合

01.csv
a,"bbb,c

RFC4180に準拠したフィールドであるかどうか確認している途中で末尾に到着して、MalformedCSVError が発生
これを救いたければ、CSVライブラリは使わずに、split(',') した方が良さそう

CSV.parse(csv, liberal_parsing: true) #=> Unclosed quoted field on line 1.

先頭と途中にダブルクォートがあって、ダブルクォートの合計が偶数の場合

01.csv
a,"bb"b,c

この場合は救われる
実装を斜め読みする限り、既存のダブルクォートをパースする処理との共存のためにそうなっている

CSV.parse(csv, liberal_parsing: true) #=> [["a", "\"bb\"b", "c"]]

先頭と途中にダブルクォートがあって、ダブルクォートの合計が奇数の場合

01.csv
a,"b"b"b,c

奇数の場合は例外になる
この辺を救いたければ(ry

CSV.parse(csv, liberal_parsing: true) #=> Unclosed quoted field on line 1.

末尾と途中にダブルクォートがある場合

01.csv
a,bb"b",c

この場合は、フィールド内のダブルクォートが奇数だろうが、偶数だろうが例外は発生しない

CSV.parse(csv, liberal_parsing: true) #=> [["a", "bb\"b\"", "c"]]
15
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
7