はじめに
Rubyのencodeメソッドとforce_encodingメソッドの違いをご存じでしょうか?
まずはRubyのリファレンスマニュアルを見てみましょう。
encode
self を指定したエンコーディングに変換した文字列を作成して返します。(以下、省略)
force_encoding
文字列の持つエンコーディング情報を指定された encoding に変えます。
このとき実際のエンコーディングは変換されず、検査もされません。
encode
はその名の通り文字列を変換してくれて、force_encoding
はエンコーディング情報(?)を変えるけど、文字列自体は変換されないと。エンコーディング情報が何かは分かりませんが、とりあえず動かしてみましょう。
動作確認
検証用に「テスト」という文字列をiso-2022-jp
という文字符号化方式に変換したものを用意します。メールでUTF-8を期待していたところに別の文字符号化方式の文字列が送られてきたイメージです。
puts "テスト".encode('iso-2022-jp')
出力結果
$B%F%9%H(B
以降の内容を試す場合は下記のリンク先からエンコードされた文字列をコピーしてご使用ください。
https://paiza.io/projects/jCRDa1-PFB06JTcOZTGJ-g
この文字列をUTF-8
の元の文字列に戻してみましょう。
まずはencode
を試してみます。
puts "$B%F%9%H(B".encode('utf-8')
出力結果
$B%F%9%H(B
あれ、何も変わらない・・・。
次にforce_encoding
puts "$B%F%9%H(B".force_encoding('utf-8')
出力結果
$B%F%9%H(B
こちらも変わらず・・・。
この文字列を元に戻すにはどうすれば良いのでしょうか?
以下に、前提となる知識と変換方法をご紹介します。
エンコーディング情報
force_encoding
の説明の中にもありましたが、文字列はエンコーディング情報を持ちます。
文字列のエンコーディング情報を確認するにはencodingメソッドで確認することができます。
puts "テスト".encoding
出力結果
UTF-8
encoding
の返り値として、Encodingオブジェクトを返します。Encodingクラスは文字エンコーディング(文字符号化方式)のクラスです。encode
とforce_encoding
の実行後に確認してみると違いが分かりそうですね。
文字列とバイト
たとえば「あ」という文字列はバイトで表すと3つの数字が返ってきます。
puts "#{"あ".bytes}"
出力結果
[227, 129, 130]
それぞれの数字を16進数に変換すると[e3 81 82]
となり、この数字はUTF-8
の「あ」と一致します。(参考)
このように文字はそれぞれバイト列が決まっているので、バイト列の数値が変化していれば文字が変わったと判断できそうですね。
対象文字列の確認
さて、これらを踏まえた上で改めて先程の文字列を見てみましょう。
str = "$B%F%9%H(B"
puts "文字列:#{str}"
puts "バイト列:#{str.bytes}"
puts "エンコーディング情報:#{str.encoding}"
出力結果
文字列:$B%F%9%H(B
バイト列:[36, 66, 37, 70, 37, 57, 37, 72, 40, 66]
エンコーディング情報:UTF-8
エンコーディング情報を見るとUTF-8
となっているので、encode
でUTF-8
に変換しようとしても何も変わりません。また、force_encoding
を実行しても、元々のエンコーディング情報がUTF-8
のためこちらも何も変わりません。これによって最初の動作確認で何も変化しなかった理由が分かりました。では、どうすれば変換できるのでしょうか?
変換方法
以下の2ステップで変換します。
-
force_encoding
でエンコーディング情報をiso-2022-jp
に変換する -
encode
で文字列をUTF-8
に変換する
str = "$B%F%9%H(B"
puts "オリジナル"
puts str
puts str.encoding
puts "#{str.bytes}"
puts "force_encoding"
force_encode_str = str.force_encoding('iso-2022-jp')
puts force_encode_str
puts force_encode_str.encoding
puts "#{force_encode_str.bytes}"
puts "encode"
encode_str = force_encode_str.encode('utf-8')
puts encode_str
puts encode_str.encoding
puts "#{encode_str.bytes}"
実行結果
オリジナル
$B%F%9%H(B
UTF-8
[27, 36, 66, 37, 70, 37, 57, 37, 72, 27, 40, 66]
force_encoding
$B%F%9%H(B
ISO-2022-JP
[27, 36, 66, 37, 70, 37, 57, 37, 72, 27, 40, 66]
encode
テスト
UTF-8
[227, 131, 134, 227, 130, 185, 227, 131, 136]
force_encoding
で文字列に期待するコーディング情報に変換し、その後、encode
で期待する文字符号化方式(今回であればUTF-8
)で文字列を変換しました。文字列はもちろんのこと、バイト列も変換されているのが確認できますね。
まとめ
encode
- 文字列を指定したエンコーディングに変換
- 文字列がもつエンコーディング情報についても、指定したエンコーディングに変換
- エンコーディング情報と指定したエンコーディングが同一の場合、変化しない
force_encoding
- 文字列自体は変化しない
- 文字列がもつエンコーディング情報を指定したエンコーディングに変換
- encodeの前処理として使いそう
参考
encoding - What is the difference between #encode and #force_encoding in ruby? - Stack Overflow