不正なバイトを置換文字で置き換えるCSV.openオプション

CSV 3.1.6 がリリースされました。

github.com

CSV 3.1.6 には、CSV.open に不正なバイトを置換文字で置き換えるオプションを追加するのに送ったパッチが取り込まれているのでその紹介です。

ユースケースとして、MS Excel 向けのエンコーディング (有名な CP932) に変換して CSV 出力するに際して、例えば Rails アプリケーションのバリデーション不足などで含まれてしまった不正なバイトをハンドリングしたいケースに活用できるオプションです。

CSV.open(..., invalid: :replace)

指定しているエンコーディング (ここでは CP932) へ変換できないバイトが含まれている場合、不正なバイトを置換文字で置き換えるという:invalid => :replace オプションが File.open にあります。CSV 3.1.5 までは CSV.open では受け付けないオプションだったため、例えば以下のように指定する必要がありました。

File.open(filename, 'w', encoding: Encoding::CP932, invalid: :replace, undef: :replace) do |file|
  csv = CSV.new(file, encoding: Encoding::CP932)

  csv << ...

  csv.close
end

CSV 3.1.6 から CSV.open:invalud オプションを受け付けるようになったため、以下のように書くことができます。

CSV.open(filename, 'w', encoding: Encoding::CP932, invalid: :replace) do |csv|
  csv << ...
end

対応したパッチは以下です。

github.com

また、Encoding::InvalidByteSequenceError が起きるようなエンコーディングに対して invalid: :replace オプション指定した場合、期待に反して ArgumentError が起きる以下のようなコードの問題についても解消されています。

require 'csv'

filename = 'foo.csv'

File.open(filename, 'w', encoding: Encoding::CP932, invalid: :replace) do |file|
  CSV.open(filename, encoding: Encoding::CP932) do |rows|
    rows << ["\x82\xa0"]
  end
end

これは上記のようなコードを書いていたとしても CSV 3.1.5 までは以下のエラーになっていたものです。

% ruby /tmp/csv.rb
Traceback (most recent call last):
        10: from /tmp/csv.rb:5:in `<main>'
         9: from /tmp/csv.rb:5:in `open'
         8: from /tmp/csv.rb:6:in `block in <main>'
         7: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv.rb:658:in `open'
         6: from /tmp/csv.rb:7:in `block (2 levels) in <main>'
         5: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv.rb:1230:in `<<'
         4: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:46:in `<<'
         3: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:46:in `collect'
         2: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:47:in `block in <<'
         1: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:159:in `quote'
/Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:159:in `match?': invalid byte sequence in UTF-8 (ArgumentError)

対応したパッチは以下です。

github.com

CSV.open(..., undef: :replace)

こちらも CSV 3.1.6 で以下のように File.openundef: :replace オプションを CSV.open に指定することができるようになったので、簡潔に対応できるようになりました。

CSV.open(filename, 'w', encoding: Encoding::CP932, undef: :replace) do |csv|
  csv << ...
end

対応したパッチは以下です。

github.com

CSV gem は Gemify されているため、Ruby のアップデートと独立して bundle update できます。詳しくは以下のエントリを参照してください。

koic.hatenablog.com

ruby/csv にパッチを送った際にスピーディーなレビューをしていただいた須藤さんありがとうございました。