【Ruby】Stringクラスのencodeメソッドについて

スポンサーリンク

String の encode 周りのメソッドについてまとめました。

文字コード関連メソッド

String クラスには以下の文字コード関連メソッドがあります。

メソッド 機能
String#encode 指定したエンコーディングに変換した文字列を返す
String#encode! 指定したエンコーディングに変換して自身を置き換える
String#encoding 文字列のエンコーディング情報を表現した Encoding オブジェクトを返す
String#force_encoding 文字列の持つエンコーディング情報を指定した Encoding に変える
String#valid_encoding? 文字列が持っているエンコーディング情報と内容が妥当であるか判定

encodeとencode!メソッド

encodeencode!メソッドは以下のように復数の引数を受け取ります。

String#encode(dst_encoding [, options])
String#encode(dst_encoding, src_encoding [, options])
String#encode([options])

引数の 1 つ目は変換後の文字コード、2 つ目は変換元の文字コードを意味します。文字コードは文字列、又は Encoding オブジェクトを指定します。

sjis_str = "\x82\xa0"  # Shift_JISで"あ"になるバイト列
#=> "\x82\xA0"
sjis_str.encoding  #=> #<Encoding:UTF-8>  # 第2引数にはselfの UTF-8 が使われるため例外が発生
# Encoding::InvalidByteSequenceError: "\x82" on UTF-8
sjis_str.encode(Encoding::UTF_16BE, Encoding::Shift_JIS)  # 変換元の文字コードを指定すると成功
#=> "\u3042"

2 つ目の引数を省略した場合はselfのエンコーディング情報が使われます。

utf8_str = "\xe3\x81\x82"  # UTF-8で"あ"になるバイト列
#=> "あ"
utf8_str.encoding  #=> #<Encoding:UTF-8>
utf8_str.encode(Encoding::Shift_JIS)  # 第2引数にはselfの UTF-8 が使われる
#=> "\x{82A0}"

引数を 2 つとも省略した場合は Encoding.default_internal が変換後の文字コードとなり、nilの場合は変換は行われません。 また、オプションでinvalid: :replaceundef: :replaceが指定されたものと見なされ、このオプションは変更できません。

Encoding.default_internal = Encoding::UTF_8
sjis_str = "\x82\xa0"  # Shift_JISで"あ”になるバイト列
sjis_str.encoding  #=> #<Encoding:UTF-8>
sjis_str.encode  # この場合は encode("UTF-8", "UTF-8") を実行するのと同じ
#=> "\x82\xA0"
sjis_str.force_encoding(Encoding::Shift_JIS)  # 正しいエンコーディング情報を指定
sjis_str.encode  # この場合は encode("UTF-8", "Shift_JIS") になる
#=> "あ"

utf8_str = "\xe3\x81\x82".force_encoding(Encoding::Shift_JIS)  # UTF-8のバイト列にShift_JISのエンコーディング情報を指定
#=> "\x{E381}\x82"
utf8_str.encode(replace: 'hoge')  # encode("UTF-8", "Shift_JIS") になって変換元が違うので変換できない
#=> "縺hoge"  # InvalidByteSequenceが発生したバイト文字が置換される

同じエンコーディングを指定した場合

encodeで変換後と変換元の文字コードに同じエンコーディングを指定した場合どのような挙動になるのか分からなかったのですが、実際に試してみたところforce_encodingと同じようにエンコーディング情報を書き換えているだけで実際の変換はしてないと推測してます。

sjis_str.encoding
#=> #<Encoding:Shift_JIS>
new_str = sjis_str.encode(Encoding::UTF_8, Encoding::UTF_8)
#=> "\x82\xA0"  # 本来なら変換元にUTF-8を指定してるのでInvalidByteSequenceが発生するはず
new_str.encoding
#=> #<Encoding:UTF-8>  # 文字コード情報のみ書き換わっている

sjis_str.force_encoding(Encoding::UTF_8)
=> "\x82\xA0"
sjis_str.encoding
=> #<Encoding:UTF-8>  # 上記と同じ挙動

発生する例外

変換元のエンコーディングにおいて不正なバイトがあった場合に、Encoding::InvalidByteSequenceErrorが発生する。

sjis_str = "\x82\xa0"
sjis_str.encode(Encoding::Shift_JIS, Encoding::UTF_8)
# Encoding::InvalidByteSequenceError: "\x82" on UTF-8
# "\x82"は UTF-8 で解釈できない

変換先のエンコーディングにおいて文字が定義されていない場合に、Encoding::UndefinedConversionErrorが発生する。

str = "テスト"
str.encode(Encoding::UTF_8, Encoding::Shift_JIS)
# Encoding::UndefinedConversionError: "\x86\xE3" from Shift_JIS to UTF-8
# Shift_JISの"\x86\xE3"はUTF-8で定義されてない

変換オプション

encodeでの変換時には以下のオプションが使用できます。

オプション デフォルト 機能
:invalid nil :replaceを指定すると変換元の不正なバイト文字を置換する
:undef nil :replaceを指定すると変換先で定義されていない文字を置換する
:replace U+FFFD | ? 置換文字を指定する
:fallback - 未定義の文字に対する置換文字をHash Proc Methodで渡す
:xml - 文字列を XML の CharData として適するように処理
:universal_newline - CR 改行および CRLF 改行を LF 改行に置き換える
:cr_newline - LF 改行を CR 改行に置き換える
:crlf_newline - LF 改行を CRLF 改行に置き換える

文字コードチェック

与えられた文字列の文字コードがUTF-8なのかShift_JISなのかの確認は次のようにできます。force_encodingでチェックしたい文字コード情報に書き変えてvalid_encoding?を実行しています。dupを使用しているのはforce_encodingが self を書き換えるためです。

utf8_str = "\xe3\x81\x82"
utf8_str.dup.force_encoding(Encoding::UTF_8).valid_encoding?
#=> true
utf8_str.dup.force_encoding(Encoding::Shift_JIS).valid_encoding?
=> false

エンコーディングについては 多言語化 (Ruby 2.4.0) も参考になります。