Y-Ken Studio

新しもの好きのデータエンジニアが四方山話をお届けします。

Rubyで全角英数字を半角英数字にnkfで変換する時の落とし穴

RubyUTF-8文字列の全角英数字の表記揺れを統一したいとき、 気をつけないと希に文字化けする事象を見つけたのでメモします。

f:id:yoshi-ken:20150605122403p:plain

NKFを用いて全角英数字→半角英数字に変換する方法

ググるとよく出てくる方法は次の通りです。
もちろん、問題なく普通に動きます。

$ pry
[1] pry(main)> require 'nkf'
=> true
[2] pry(main)> NKF.nkf('-m0Z1 -w', "Ruby−2.2")
=> "Ruby-2.2"

ここでWindows機種依存文字が混じるとどうなるでしょうか。

pry(main)> NKF.nkf('-m0Z1 -w', "ルビー Ⅱ")
=> "ルビー Ⅱ"
pry(main)> NKF.nkf('-m0Z1 -w', "ルⅡ")
=> "繝ォ竇。"
pry(main)> NKF.nkf('-m0Z1 -w', "Ruby Ⅱ")
=> "Ruby 竇。"

なんと、文字の組み合わせ次第では化けてしまいます。これは気づきにくいですね。
調べると、機種依存文字に関するオプションを発見しました。

--cp932 Windows機種依存文字(〜−¢£¬|など)をUnicodeに変換するときに文字化けしないようにする。
引用元: http://www003.upp.so-net.ne.jp/NAMBOKU/ruby/ruby0352.html

試しにこの引数を追加すると、期待通りの動作とは・・・なりません!!!
まあ元がUTF-8なのでcp932を指定するのも変だとはうすうす思っておりましたが。

pry(main)> NKF.nkf('-m0Z1 -w --cp932', "ルビー Ⅱ")
=> "ルビー Ⅱ"
pry(main)> NKF.nkf('-m0Z1 -w --cp932', "ルⅡ")
=> "繝ォ竇。"
pry(main)> NKF.nkf('-m0Z1 -w --cp932', "Ruby Ⅱ")
=> "Ruby 竇。"

ここで1度、引数のそれぞれの意味を見てみましょう。

引数 意味
-m0 MIME(電子メール用のフォーマット)に変換しない
(nkfはこのオプションを指定しない限り、MIMEに変換する処理を行う)。
-Z1 全角スペースを半角スペース1個に変換する。
-w 出力ファイルの文字コードUTF-8(BOMなし)とする(-W80とおなじ)。

引用元: http://www003.upp.so-net.ne.jp/NAMBOKU/ruby/ruby0352.html

ここで気づくのは、入力文字コードの指定が行われていないことです。
このため、先ほどのcp932のオプションはShift_JISでの読み込み時に使うものであったことが分かりました。
そこで、次のように-wと-Wをそれぞれ指定してみましょう。

引数 意味
-m0 MIME(電子メール用のフォーマット)に変換しない
(nkfはこのオプションを指定しない限り、MIMEに変換する処理を行う)。
-Z1 全角スペースを半角スペース1個に変換する。
-W 入力ファイルの文字コードUTF-8(BOMなし)とする(-W80とおなじ)。※追加※
-w 出力ファイルの文字コードUTF-8(BOMなし)とする(-W80とおなじ)。
pry(main)> NKF.nkf('-m0Z1 -W -w', "ルビー Ⅱ")
=> "ルビー Ⅱ"
pry(main)> NKF.nkf('-m0Z1 -W -w', "ルⅡ")
=> "ルⅡ"
pry(main)> NKF.nkf('-m0Z1 -W -w', "Ruby Ⅱ")
=> "Ruby Ⅱ"

無事に期待通りの動作となりました。

まとめ

rubynkfを利用するときは、入力文字コードを自動判定ではなくしっかりと指定しましょう!

引数 意味
-S 入力ファイルの文字コードshift_jisとする。
-E 入力ファイルの文字コードEUC-JPとする。
-J 入力ファイルの文字コードISO-2022-JP(いわゆるjisコード)とする。
-W 入力ファイルの文字コードUTF-8(BOMなし)とする(-W80とおなじ)。

あわせて読みたい

半角英数字に変換し、さらに機種依存文字も変換したいケースでは、
NKFではなく次のページで紹介されているunicode gemが万能でお勧めです。

文字列の表記揺れをUnicode正規化で簡単に解決する方法
http://qiita.com/y-ken/items/d08eb7f66c8fb2fa7d21

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)