String#encodeが変換できそうで変換できない文字
問題
RubyでUTF-8からWindows-31J(Shift_JIS)に文字コードを変換すると、正常に変換できそうであるにもかかわらず、実際には変換が行えず例外が投げられる文字があります。
"\u{301C}" # => "〜" "\u{301C}".encode(Encoding::Windows_31J) # => Encoding::UndefinedConversionError: U+301C from UTF-8 to Windows-31J
原因
原因については UTF-8 → cp932(Shift_JIS)変換表 - fudist あたりがわかりやすいです。
例外が投げられる文字はたとえば以下の様なものがあります。*1
文字コード(UTF-8) | 文字 | 備考 |
---|---|---|
U+00A2 | ¢ | セント記号(通貨) |
U+00A3 | £ | ポンド記号(通貨) |
U+00AC | ¬ | NOT記号 |
U+2016 | ‖ | Double vertical line |
U+2212 | − | マイナス記号 |
U+301C | 〜 | 波ダッシュ |
解決法1
String#encodeにオプションを渡します。
"\u{301C}" # => "〜" "\u{301C}".encode(Encoding::Windows_31J, undef: :replace) # => "?" "\u{301C}".encode(Encoding::Windows_31J, undef: :replace, replace: "") # => ""
例外を吐かなくなるという点では問題が解決しますが、困ることもあります。
salad = "シェフの気まぐれサラダ \u{301C}季節のフルーツを添えて\u{301C}" # => "シェフの気まぐれサラダ 〜季節のフルーツを添えて〜" salad.encode(Encoding::Windows_31J, undef: :replace) # => "\x{8356}\x{8346}\x{8374}\x{82CC}\x{8B43}\x{82DC}\x{82AE}\x{82EA}\x{8354}\x{8389}\x{835F} ?\x{8B47}\x{90DF}\x{82CC}\x{8374}\x{838B}\x{815B}\x{8363}\x{82F0}\x{9359}\x{82A6}\x{82C4}?" salad.encode(Encoding::Windows_31J, undef: :replace).encode(Encoding::UTF_8) # => "シェフの気まぐれサラダ ?季節のフルーツを添えて?"
せっかくのステキな料理名が台無しですね。
解決法2
Shift_JISに変換できるUTF-8の文字に置換します。
文字 | before | after |
---|---|---|
¢ | U+00A2 | U+FFE0 |
£ | U+00A3 | U+FFE1 |
¬ | U+00AC | U+FFE2 |
‖ | U+2016 | U+2225 |
− | U+2212 | U+FF0D |
〜 | U+301C | U+FF5E |
mappings = { "\u{00A2}" => "\u{FFE0}", "\u{00A3}" => "\u{FFE1}", "\u{00AC}" => "\u{FFE2}", "\u{2016}" => "\u{2225}", "\u{2012}" => "\u{FF0D}", "\u{301C}" => "\u{FF5E}" } # => {"¢"=>"¢", "£"=>"£", "¬"=>"¬", "‖"=>"∥", "‒"=>"-", "〜"=>"~"} salad = "シェフの気まぐれサラダ \u{301C}季節のフルーツを添えて\u{301C}" # => "シェフの気まぐれサラダ 〜季節のフルーツを添えて〜" salad.encode(Encoding::Windows_31J, undef: :replace).encode(Encoding::UTF_8) # => "シェフの気まぐれサラダ ?季節のフルーツを添えて?" # 変換できない文字を変換できる文字に置換 mappings.each{|before, after| salad = salad.gsub(before, after) } salad.encode(Encoding::Windows_31J, undef: :replace).encode(Encoding::UTF_8) # => "シェフの気まぐれサラダ ~季節のフルーツを添えて~"
これで料理名をステキなまま保つことができます。
上記解決法の問題:だるい
いちいちString#gsubで置き換えたりするのは面倒なので、この辺をあまり意識せずに扱えるようにするgemでも書こうかと思いましたが、良い実装方法が思いつきませんでした。
良い案をお待ちしております。
この問題の遠因あるいは蛇足
この変換エラーですが、個人的なケースとしては、Shift_JISで書かれたCSVをUTF-8として開いてごにょごにょした後に再度Shift_JISのCSVとして保存するときとかによく起こります。
では、なぜCSVがShift_JISなんかで書かれているのかというと、世の中のExcel on Windows様のためですね。つらい。
*1:U+2015も変換不可でしたが、Vista以降では修正されているようです。そして、Windows XP is dead!