概要
HEX表記の文字列を、UTF8の文字列に変換する方法は、HEX を取り出して pack する。
経緯
MySQL で utf8mb4 を設定しているのに、🎶
という文字の保存がエラーになった。
Mysql2::Error: Incorrect string value: '\xF0\x9F\x8E\xB6' for column 'content' at row 1: UPDATE
topics
SETcontent
= '🎶',updated_at
= '2016-04-22 01:53:02.596188' WHEREtopics
.id
= 17
仕方ないので、Rails でキャッチして、エラーメッセージを返すことにしたが、エラーメッセージの変換に苦労したので、共有する。
コード
rescue ActiveRecord::StatementInvalid => e
if e.message.include? "Mysql2::Error: Incorrect string value:"
message = "扱えない文字が含まれています: "
m = e.message.match(%r{Incorrect string value: '(.*)' for})
if m
m1 = m[1].scan(/../).map { |b| b.to_i(16) }.reject { |x| x == 0 }.pack("C*")
message += URI.encode(m1[0..3])
end
errors[:base] << message
false
else
fail
end
end
詳細
MySQL は、エラーメッセージとして、扱えない文字を返すが、それは HEX 表記の文字列だった。
m[1] => "\\xF0\\x9F\\x8E\\xB6"
欲しいのは、UTF8 としての文字列である
"\xF0\x9F\x8E\xB6"
よって、"\x" を削除して、hex を文字列に置き換えれば、UTF-8 のバイト列が取得できる。
[178] pry(main)> a = "\\xF0\\x9F\\x8E\\xB6"
=> "\\xF0\\x9F\\x8E\\xB6"
[179] pry(main)> a.scan(/../).map{ |b| b }
=> ["\\x", "F0", "\\x", "9F", "\\x", "8E", "\\x", "B6"]
[180] pry(main)> a.scan(/../).map{ |b| b.to_i(16) }
=> [0, 240, 0, 159, 0, 142, 0, 182]
[181] pry(main)> a.scan(/../).map{ |b| b.to_i(16) }.reject{|x| x == 0}
=> [240, 159, 142, 182]
[182] pry(main)> a.scan(/../).map{ |b| b.to_i(16) }.reject{|x| x == 0}.pack("C*")
=> "\xF0\x9F\x8E\xB6"
[183] puts a.scan(/../).map{ |b| b.to_i(16) }.reject{|x| x == 0}.pack("C*")
🎶
一回で変換できるものはないのだろうか?
調査中
utf8mb4 を使っているので、①
とかは、保存される。
なぜ🎶
が保存されないのだろうか。
参考