2007/11/12 (月)
■ KDDI/AUでutf-8のHTMLフォームから送られてくる絵文字コード

長くなったので先に結論。KDDIのUnicode絵文字マッピングには2種類あり、KDDI仕様書にあるマッピング(A)と端末がSJISコードから変換して送信する機械的なマッピング(B)がある(仕様書には一切記述されてない)。Unicode なDBに保存する際は、(B)を利用するといろいろと効率がよくなるかもしれない。
以下、考察。
- utf-8のページから絵文字をPOSTした場合 - tomi-ruのモバイル日記 - モバイルハッカー連絡会
- ezwebでutf8 - tokuhiromの日記 - モバイルハッカー連絡会
- 404 Not Found
tomi-ruさんのつくった表(書いてる時点では消えてるけど キャッシュ から)と KDDI/AU にある絵文字表 を見比べて、コードを書きながら比較してみた。
Unicodeの番号のほうは相変わらずいい加減だけど、SJISコードと生成されるUnicode番号に関連性があることに気づいた。
#!/usr/bin/perl
use strict;
use warnings;
use Encode::JP::Mobile;
use Encode;
while (<>) {
chomp;
my($hex, $bytes) = /^U(\w{4}) => (.{12})$/;
$bytes = eval '"' . $bytes . '"';
my $unicode = ord decode_utf8 $bytes;
my $au_unicode = hex $hex;
my $sjis_hex = sjis_hex($au_unicode);
my $sjis_num = hex $sjis_hex;
printf "U$hex (Display) %s (SJIS) U%04X (Form) Diff: %d\n", sjis_hex($au_unicode), $unicode, ($unicode-$sjis_num);
}
sub sjis_hex {
my $cp = shift;
my $str = chr $cp; # Unicode
my $bytes = encode "shift_jis-kddi", $str;
uc unpack "H*", $bytes;
}
さっきの charset.txt を食わせる。
UE469 (Display) F641 (SJIS) UEF41 (Form) Diff: -1792 UE46A (Display) F642 (SJIS) UEF42 (Form) Diff: -1792 UE46B (Display) F643 (SJIS) UEF43 (Form) Diff: -1792 UE46C (Display) F644 (SJIS) UEF44 (Form) Diff: -1792 UE471 (Display) F649 (SJIS) UEF49 (Form) Diff: -1792 UE472 (Display) F64A (SJIS) UEF4A (Form) Diff: -1792 UE473 (Display) F64B (SJIS) UEF4B (Form) Diff: -1792 ... UEB7F (Display) F484 (SJIS) UED84 (Form) Diff: -1792 UEB80 (Display) F485 (SJIS) UED85 (Form) Diff: -1792 UEB82 (Display) F487 (SJIS) UED87 (Form) Diff: -1792 UEB83 (Display) F488 (SJIS) UED88 (Form) Diff: -1792 UEB84 (Display) F489 (SJIS) UED89 (Form) Diff: -1792 UEB87 (Display) F48C (SJIS) UED8C (Form) Diff: -1792 UEB88 (Display) F48D (SJIS) UED8D (Form) Diff: -1792
キタコレ。というわけで SJIS のコード(&#NNNNN; の数字)に 1792 (0x0700) を引いて utf-8 にする、という変換でOK。
おそらく Unicode の番号自体が後付けで、端末に乗ってる絵文字はSJISの外字領域コードがベースになってて、外字からUTF-8の私的領域(PRIVATE AREA) に変換するときに簡単なビット操作で変換するようにコードが書かれてるんじゃないかと推測。
→追記: この推測は一部間違いで、「基本的には」CP932 の外字<->Unicode 私的領域の変換表にもとづいて Unicodeにマッピングしているようです。CP932 に含まれていない文字領域もかなりありますが。
そもそも表示のほうのSJIS <-> Unicode の関連付けがめちゃくちゃすぎる上に softbank の絵文字領域 とかぶってるので、KDDI はこっちだけ使うように変更しろ!
ということでこれって実際に表示はできるのか、日本で実機もってる tokuhirom と実験してみた。
たとえば台風マークの絵文字は、SJIS/Unicode では以下のような値をとる。
- SJIS コード \xF6\x41
- Unicode U+E469 (KDDI 仕様書: Unicode (A))
- UTF-8 \xEE\xBD\x81 (UTF-8なHTMLフォームから送信される値)
- Unicode U+EF41 (↑のUTF-8のコードポイント: Unicode (B))
http://blog.bulknews.net/mt/au.html のようなUTF-8のページをつくって実機で表示してもらってみた。li タグの値と表示結果は上から、
- \xEE\x91\xa9 (NG: ? と表示) ← Unicode (A) の UTF-8エンコーディング
-  (OK: 台風の絵文字) ← Unicode (A) の &#xXXXX; 表示
- \xEE\xBD\x81 (OK: 台風の絵文字) ← Unicode (B) のUTF-8エンコーディング
-  (NG: (火) の外字) ← Unicode (B) の &#xXXXX; 表示
のようになった。つまり、上記の仮説は実証され、端末上では絵文字のフォントは SJISのコードに対応しており、
- &#xXXXX; で記述した場合、KDDI仕様書にある表どおり(CP932の外字変換がベース) にいったんSJISコードに一度変換してから絵文字表示している。よって仕様書にある Unicode は &#xXXXX; で表示可能であるがそれを UTF-8 にエンコードしても表示できない
- 一方、UTF-8 な HTML form から送られる値は、内部的な SJIS <-> UTF-8 相互変換によって作られている。よってこれに対応する Unicode コードポイントを &#xXXXX; で送っても表示はされないが、これをUTF-8でエンコードした場合、UTF-8→SJISデコードでうまく絵文字にマップされるので、表示できる。
これは革命的な発見かもしれない。AU/Softbank の絵文字コンフリクトも、AUの仕様書にあるUnicode は無視して、SJISとの機械的マッピングで得られる方の私的領域をつかえばほかともコンフリクトせず、UTF-8で絵文字表示もできるようになる(ただし XXXX; 形式ではできないので HTML フィルタなどで変換されないように注意が必要)。