Ruby にて文字と Unicode コードポイントの相互変換を行う

Unicode のコードポイントを指定して文字を得たり、逆にある文字のコードポイントを調べたり、ということをする機会は結構多いと思います。 が、Ruby でそれをやる方法をぐぐってもあまり上位に情報が出てこないなー、と思ったので簡単にまとめておきます。

Unicode コードポイントとは

そもそも Unicode コードポイントとは何か。 Unicode というのは世界中の文字が集められた文字集合であり、Unicode に収録されている文字には順番に番号が振られています。 この番号のことをコードポイントといいます。 あるコードポイントが指す文字を表現するときに "U+" という文字の後ろに 16 進数表記のコードポイントを書いて表すことがあります。 例えば、コードポイント 0x3041 が指す文字 (ひらがなの 「あ」) を U+3041 と書いて表します。

各文字とコードポイントの関係は以下のページで確認できます。

Array#pack メソッド, String#unpack メソッド (Ruby 1.8, 1.9)

Ruby 1.8Unicode コードポイントから文字を取得したり、文字から Unicode コードポイントを取得するには, Array#pack メソッドと String#unpack メソッドを使う必要があります *1。 ここでいう 「文字」 とは String オブジェクト (さらにいうと、UTF-8 エンコードされたバイト列) であり、「Unicode コードポイント」 とは Unicode コードポイントの値を表す Fixnum オブジェクトです。

Array#pack のテンプレート文字として "U" を指定すると、配列中の Fixnum オブジェクトを Unicode コードポイントだとみなして、文字列にパックしてくれます。

[ 0x6587, 0x5B57 ].pack( "U*" ) #=> "文字"

逆に、String#unpack のテンプレート文字として "U" を指定すると、文字列を UTF-8 エンコードされたバイト列だとみなして、その Unicode コードポイントを要素とする配列を得ることができます。

"文字".unpack( "U*" ) #=> [ 25991, 23383 ]
    # 25991 == 0x6587, 23383 == 0x5B57

ここで注意しなければならないのは、Array#pack("U*") の結果得られる文字列のエンコーディングUTF-8 である、ということです。 また、String#unpack("U*") で変換元となる文字列のエンコーディングUTF-8 でなければいけません。

これらの方法は Ruby 1.8 だけでなく、1.9 でも使用できます。 (Ruby 1.9 でもエンコーディングに気をつけなければいけません。)

String#ord メソッド, Integer#chr メソッド, Unicode エスケープ (Ruby 1.9)

Ruby 1.9 では、String#ord メソッドと Integer#chr メソッドを使って文字列と Unicode コードポイントの変換ができます。 また、Unicode エスケープを使うことで、文字列リテラル中に Unicode コードポイントを指定して文字を表すことができます。

String#ord メソッドは、Unicode 系のエンコーディング (UTF-8 や UTF-16BE など) の String オブジェクトで使うと、その文字列の最初の文字の Unicode コードポイントが得られます。

str = "".encode( "UTF-8" )
str.ord #=> 25991

Unicode 系でないエンコーディングの場合、その文字に対応するバイト列を数値に変換したものが得られます。

str = "".encode( 'EUC-JP' )
str.ord #=> 51896

# 以下と同じ
str[0].force_encoding( 'ASCII-8BIT' ).unpack( 'C*' ).inject{ |i,j| ( i << 8 ) + j } #=> 51896

Integer#chr は、String#ord と逆のことを行います。 ここで注意しないといけないのは、Integer#chr の実行時にエンコーディングを指定しないといけないということです。 (自動的に UTF-8 になるわけではない。)

25991.chr( "UTF-8" ) #=> "文"

また、文字列リテラル中に Unicode エスケープを入れることで、静的に Unicode コードポイントを文字に変換できます。 Unicode エスケープにより生成される文字のエンコーディングは常に UTF-8 です。

Unicode エスケープは \uXXXX または \u{XXXX} の形式で、XXXXUnicode コードポイント (16 進数表記) です。 前者の形式の場合、コードポイントは 4 桁で書かなければいけません。 後者の形式の場合、4 桁でなくてもよく、またスペース区切りで複数のコードポイントを連続して書くことができます。

"\u6587" #=> "文"
    # 0x6587 == 25991
"\u{41 42 43}" #=> "ABC"
    # \u{XX} 形式の場合, 4 桁でなくてもよく, 複数文字を連続して書ける

Unicode エスケープによる文字のエンコーディングUTF-8 であるので、ソースエンコーディングUTF-8 ではない場合に、"あいうえ\u6587" のような文字列リテラルを書くとエラーが発生しますのでご注意ください。

また、文字列中の各文字に対して繰り返し ord メソッドを作用させてブロックに渡すイテレーションメソッドとして String#each_codepoint メソッド (別名 String#codepoints) もあります。

"あいうえお".each_codepoint { |cp| $stdout << "U+" << cp.to_s(16) << " " }
  # U+3042 U+3044 U+3046 U+3048 U+304a

参考文献

この記事で述べた各メソッドについてはそれぞれ以下のドキュメントを参照してください。

*1:自前で UTF-8 エンコードされたバイト列と Unicode コードポイントを変換するメソッドを書く、という方法もありますが。