QA@IT

Rubyでinvalid byte sequence in UTF-8 というエラーの対処方法は?

25693 PV

外部のHTMLを読み込んで正規表現でマッチするコードで、次のようなエラーが出ました。

invalid byte sequence in UTF-8

文字通り、UTF-8的に正しくないバイト列があるということかと思いますが、どう対処するのが良いのでしょうか?

回答

実際にinvalidなものが含まれてしまっている場合には、そこの文字列をピリオドやはてなマークなど、別の文字に置き換えることで対処できます。

具体的には、String#encode を使います。
http://www.ruby-doc.org/core-1.9.3/String.html#method-i-encode

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

とすれば、invalidな部分だけ . に置換されます。

編集 履歴 (1)

String#encode メソッドは dst_encoding と src_encoding に同じエンコードを指定した場合、変換処理を行いません。replace 処理もされません。
しかし、なぜか例外が発生しなくなるという挙動になるのですが、それはバグ https://bugs.ruby-lang.org/issues/6190 なので、その挙動を利用するのは避けておいたほうが良いと思います。

なので、正しくは

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

のようになると思います。ブログも書いたのでよければそちらも参照してください => リンク

編集 履歴 (0)
  • ありがとうございます。バッドノウハウが広がらないよう、上の回答もUTF-16BE→UTF-8とするように修正しておきました。でも、不正文字列の除去のために、エンコーディングをぐるっと回ってくるのが、そもそもノウハウというのは、どうなのかなという気もしますよね(そうでもないのか) -
  • そこは同感です。invalid byte 除去のためのメソッドが欲しいですよね。 -

Ruby 2.1.0 で String#encode はエンコーディングを1つだけ指定すればすむようになり、String#scrub が追加されました。

str.encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?')
str.scrub
str.scrub("*")
str.scrub{ |b| "*" }

導入の経緯については次の URL から確認できます。

Feature #6752 Replacing ill-formed subsequencce
http://bugs.ruby-lang.org/issues/6752

ほかには chars メソッドもしくはエイリアスの each_char メソッドを使って Enumerator オブジェクトに変換する方法もあります。

str.chars.collect { |c| (c.valid_encoding?) ? c : "\uFFFD" }.join
str.each_char.map { |c| (c.valid_encoding?) ? c : "\uFFFD" }.join

動作検証のために、Mac OSX で rbenv を使って Ruby 2.1.0 の開発版をインストールする際に homebrew を使ってインストールした autoconf のパスを追加する必要がありました。Sam Ruby のブログにくわしい解説があります。

export PATH=/usr/local/opt/autoconf/bin:$PATH
編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。