Ruby 1.8.2 には String.encode メソッドがない!?(パート2)

Ruby 1.8 系と Ruby 1.9 系の大きな違いに M17N という文字コードの扱いがある。

Ruby 1.9 では文字オブジェクト一つ一つが自分自身のエンコード情報を内部に保持している。

エンコード情報とは、どのような形式で符号化されているのかという情報のことで UTF-8 だったり Shift_JIS だったりする。

ファイル入力やネットワーク入力など、外部からの様々な入力を扱う際にはそれぞれの文字列がどのエンコードによって表されているものなのかを気にする必要がある。

でないと、ファイルからの入力は UTF-8 で、ネットワークからの入力は Shift_JIS で、二つの文字列を結合したらちぐはぐな文字列が...なんてことになりかねない。

そこで Ruby 1.9 では複数の文字列を扱う場合は、String オブジェクトが持つ encode メソッドを使って文字コードの変換が簡単にできるようになっている。

例えば以下のような感じだ。

buffer.encode('UTF-8')

上記の記述は buffer が持っている文字列を UTF-8 にエンコードしなおしてくれという命令である。

エンコードを変える場合、元のエンコードがなんなのかを知る必要があるが、Ruby 1.9 の文字列は全て現在のエンコードを持っているため指定が不要だ。

最も指定しようと思えば指定が可能である。

buffer.encode('UTF-8', 'Shift_JIS')

net/http や net/pop などを使って外部から情報を取得した場合、これらのモジュールは文字コードの特定まではしてくれないためエンコードは ASCII としてしか認識されないため、このような指定を必要とする場合がある。

ちなみに上記の記述は、Shift_JIS と想定される文字列を UTF-8 にエンコードしなおしてくれという命令である。

さて...。実に使い勝手が良い String.encode メソッドなのだが...。Ruby 1.8 にはこれがない!

代わりに nkf モジュールを使うのだが、これだと Ruby 1.8 と Ruby 1.9 のソースの互換性を保つことができないのだ。

そこで考えた!

Ruby 1.8 と Ruby 1.9 の互換性を保つための緩衝材として Ruby 1.8 の場合には String.encode を拡張してしまう処理を入れれば良いのだ。

以下にそのコードを示す。

if RUBY_VERSION < '1.9.0' then
  require 'kconv'

  # String クラスに擬似的な 1.9.0 互換の encoding および encode メソッドを追加します。
  # ただし、完全な互換性は持ちません。
  class String
    @encoding = nil

    # エンコーディングを取得します。
    def encoding
      if @encoding != nil then
        return @encoding
      else
        case Kconv.guess(self)
        when Kconv::JIS
          return "ISO-2022-JP"
        when Kconv::SJIS
          return "Shift_JIS"
        when Kconv::EUC
          return "EUC-JP"
        when Kconv::ASCII
          return "ASCII"
        when Kconv::UTF8
          return "UTF-8"
        when Kconv::UTF16
          return "UTF-16BE"
        when Kconv::UNKNOWN
          return nil
        when Kconv::BINARY
          return nil
        else
          return nil
        end
      end
    end

    # エンコードを変更します(options 未対応)。
    def encode(to_encoding, from_encoding = nil, options = nil)
      if (from_encoding == nil)
        if @encoding == nil then
          f_encoding = Kconv::AUTO
        else
          f_encoding = @encoding
        end
      else
        f_encoding = get_kconv_encoding(from_encoding)
      end

      result = Kconv::kconv(self, get_kconv_encoding(to_encoding), f_encoding)
      result.set_encoding(to_encoding)
      return result
    end

    def get_kconv_encoding(encoding)
      if encoding != nil then
        case encoding.upcase
        when "ISO-2022-JP"
          return Kconv::JIS
        when "SHIFT_JIS"
         return Kconv::SJIS
        when "EUC-JP"
          return Kconv::EUC
        when "ASCII"
          return Kconv::ASCII
        when "UTF-8"
          return Kconv::UTF8
        when "UTF-16BE"
          return Kconv::UTF16
        else
          return Kconv::UNKNOWN
        end
      end
    end
    private :get_kconv_encoding

    def set_encoding(encoding)
      @encoding = encoding
    end
  end
end

完全互換ではないですが、結構使えます!