Ruby Advent Calendar 2013 7日目担当 @sonots です。

昨日は yujinakayama さんの RSpecの最新の動向・RSpec 3へのアップグレードガイド の記事でした。

今日は ruby 2.1.0 に新しく追加される String#scrub を取り上げたいと思います。

背景

以前、私はRuby の invalid byte sequence in UTF-8 例外を encode("UTF-8", "UTF-8") で回避するのはおかしいよ、という話を書きました。簡単に要約すると、

1.Ruby 1.9 で UTF-8 文字列に対して正しくないバイト列があると、正規表現マッチや gsub といったメソッドを使っているところで ArgumentError: invalid byte sequence in UTF-8 例外が発生する

2.ぐぐると

str = str.encode("UTF-8", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?')

のようなコードで回避できる、という記述が見つかる

3.しかし本来

String#encode は src_encoding と dst_encoding に同じエンコードを指定すると変換処理をしない仕様。つまり、例外が起きなくなるのはバグなのでこのコードを使っているとまずい (2.0.0 で例外が起きるように修正された)

4.よって、正しくは

str = str.encode("UTF-16BE", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?').encode("UTF-8")

のようにするのだが、

5.そもそも不正バイトを除去する専用のメソッドが欲しい

という話でした。これに対して、ruby 2.1.0 には不正なバイト列を代替文字に置き換える String#scrub メソッドが追加されました。o(・∇・o)(o・∇・)o ヤッタ!

String#scrub の使い方

サンプルコードは以下のようになります。不正なバイト列を ? に置き換えています。

str = "\xff"
str.force_encoding('UTF-8')
# str = str.encode("UTF-16BE", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?').encode("UTF-8")
str = str.scrub('?')
p str #=> "?"

begin
  str =~ /a/
rescue => e
  p e
end

他にも str.scrub!('?') といった破壊的メソッドも用意してありました。以下、API 仕様です。

str.scrub
Unicode 系ならば U+FFFD (Replacement Character) を置換文字とし、それ以外の場合は ? を置換文字とする。
str.scrub('**')
指定した文字列を置換文字とする。
str.scrub{|x| '' }
ブロック引数として不正なバイト列を与え、引数を置換文字とする。

String#scrub を 2.0 で使う

@hsbt 氏が string-scrub という gem を用意してくださっているので、ruby 2.0 でもこの gem をインストールすれば利用できます。

gem install string-scrub

のようにインストールし

require 'string/scrub'

のように require すればOKです。

追記:  2014/02/26: string-scrub gem が 1.9 でも動くようになりました!

まとめ

ruby 2.1.0 に追加される不正なバイト列を代替文字に置き換える String#scrub メソッドの紹介をしました。ますます便利になりますね!@nalsh さんありがとうございます!

明日は aoitaku さんです。

参考