ある理由により、UTF-8 な文字列を指定バイトに収まる最大長でぶったぎりたくなりました。
平たく言うと、Linux などで採用されているファイルシステムではファイル名の最大長が255バイトなので、Windows や Mac で作成されたファイルを保存できないことがあるのでなんとかしたいと思った訳です。
UTF-8は一文字あたりのバイト数が一定ではない文字コードなので、単に文字数でぶった切ると255バイトをオーバーしかねません。最大6バイトなので 255÷6で42文字にしておけば安全ですが、どうせならできるだけ長いファイル名にしたいってもんです。
Ruby/Rails 初心者の私は当然ながらそういうバイトぶった切りお手軽関数が用意されていることを期待したんですが、社内の達人に聞いたところ「ない」というにべもない答えが返ってきました。
ほんとかなと思って手を尽くして検索してみましたが、確かにないみたい・・・。
コピペプログラマとしては万事休すです。
しかしどう検索してもみつからないので、仕方なく自作しました。
あ、社内の達人もほんのちょっとだけ手伝っています。
original_file_extension = File.extname(@original_filename)
#@original_filename = File.basename(@original_filename)
@original_filename.force_encoding("ascii-8bit")
pp "ascii"
pp @original_filename
@original_filename = @original_filename[0, (configatron.uploadfilename.bytesize - original_file_extension.bytesize)]
pp "slice"
pp @original_filename
@original_filename.encode!("UTF-16BE", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '')
@original_filename.encode!("UTF-8")
pp "utf-8"
pp @original_filename
@original_filename += original_file_extension
pp "bytesize"
pp @original_filename.bytesize
configatron.uploadfilename.bytesize は255を設定しています。
ところどころデバッグライトしてますが、当然なくても動きます。
@original_filename はパスを含まないファイル名のみが渡ってくるの前提です。
+文字エンコーディングをASCII-8bitに変更してバイト単位のぶった切り
+String#encode を使用して、ぶった切りの際に発生した不正な UTF-8 文字列を更正
ってあたりが肝でしょうか。
後者については下記サイトを参考にしました。
Rubyでinvalidなバイト列を含むUTF-8文字列を扱う
Ruby の invalid byte sequence in UTF-8 例外を encode("UTF-8", "UTF-8") で回避するのはおかしいよ、という話
2バイト〜4バイトの UTF-8 文字列が混在するようなファイル名をいろいろ付けてみてテストしましたが、うまく動いてくれているようです。
最終行でぶった切り後のバイト数を表示していますが、253あたりの数字が表示されると、無駄なく使えている感が感じられて思わずにんまりしてしまいました。
「ここはこう直した方がいいんじゃない?」「その目的のコードはここに書いてあるよ!」と言った情報をお持ちの方は、ぜひ教えてくださいね。