ネットの海の片隅で

技術ネタの放流、あるいは不法投棄。

String#encodeが変換できそうで変換できない文字

問題

RubyUTF-8からWindows-31JShift_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で書かれたCSVUTF-8として開いてごにょごにょした後に再度Shift_JISCSVとして保存するときとかによく起こります。

では、なぜCSVShift_JISなんかで書かれているのかというと、世の中のExcel on Windowsのためですね。つらい。

*1:U+2015も変換不可でしたが、Vista以降では修正されているようです。そして、Windows XP is dead!