萩萩日記

世界に5人くらい存在するかもしれない僕のファンとドッペルゲンガーに送る日記

絵文字の除去

携帯の絵文字を除去するプログラムなんてのはわりと古典的(という言い方は違うか)なのでいまさらハマることもないと思っていたのだけれど、なんとまあ、ハマってしまいましたよ。

というのもですね。

今回、ちょいと「メールで送ったものがDBに蓄積されてそれをHTMLとして表示する」的なことをやってまして、そこで、絵文字が残っちゃったのですな。

いままでよく考えたらこういう仕組みのものを作ったことなくて、絵文字の除去っつってもフォームで入力されたものを除去するだけだったので、Perl的に言うと、

my $SJIS_CHARS = '(?:[\x00-\x7F\xA1-\xDF]|(?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]))';

# iモード
$char =~ s/\G(${SJIS_CHARS}*?)(?:\xF8[\x9F-\xFC]|\xF9[\x40-\x49\x50-\x52\x55-\x57\x5B-\x5E\x72-\x7E\x80-\xB0])|(?:\xF9[\xB1-\xFC])/$1/g;

# SoftBank
$char =~ s/\G(${SJIS_CHARS}*?)\x1B\x24[E-G].*?\x0F/$1/g;

とすれば良かったのね(参考URL:Perlメモ)。ちなみにEmoji.pmなんてのもあるけど、あれってiモードの拡張絵文字に対応してないから、いまはもう使えないんだよね。

2007.09.04追記。

上記正規表現、間違ってるみたいです。詳しくは9月4日の日記をどうぞ。

2007.11.06追記。

auはメールでしか絵文字が書けない的なこと書いてますが勘違いでした。詳しくは11月5日の日記をどうぞ。

追記終わり。

で、auの場合は、

<IMG ICON="85">

みたいにIMGタグになってるんで、まあ処理もラクなわけなんですが。

それがですよ、あなた。メールの場合、iモードSoftBankはあらかじめメールゲートウェイで絵文字が〓とかに変換されたりするみたいだけど(インターネットメールの場合ね)、auは、そのまま送っちゃうのね。しかもメールの場合IMGタグじゃないみたいで、なんだか、良い具合に化けちゃう。

そこで調べてみたんだけど、あまりこれについての情報がない。知りたいのは、どういう文字コードで、具体的にどういう範囲のデータが絵文字として使われてるのかだったのだけど。ようやく見つけたのが、

http://www.au.kddi.com/ezfactory/tec/spec/pdf/typeD.pdf

で、まあオフィシャルな文書なんだから「ようやく」じゃなくて最初から見ろって話もあるんだけど、そこはそれ、ここまで辿りつくのに結構時間がかかった。

しかもこれ、見てもらうとわかるんだけど、コードは載ってるものの微妙にバラバラだし、かつ、具体的にどの文字コード(ちゃんと言うとcharsetですか?)が使われるのかよくわからん。

ので、まずは実際に一番左上にある絵文字(△に入った!マーク)入りのメールを送ってみて、そいつのコードを表と付き合わせ。今回使ってる言語はPHPだったので、とり急ぎ、

echo bin2hex($string);

みたいな感じ。すると、

eb59

って表示されたんで、とりあえず使われているのは、表の一番右側にある「(参考)Eメール送出用JISコードに対応したShift-JISコード」だということが判明(Shift-JISじゃなくてShift_JISだと思うけどね)。

で、あとはここに書かれているデータの範囲がわかれば良いんだけど、羅列してあるだけで範囲が載ってない。仕方ないからあなた、調べましたよ、pdfから全部コピペして。

そしたら、とりあえず、

EB40-EB7E
EB80-EBFC
EC40-EC7E
EC80-ECFC
ED40-ED7E
ED80-EDFC
EE40-EE7E
EE8D-EE80

の範囲のコードだってことが判明。よく目を凝らして見ると、

EB40-EB7E
EC40-EC7E
ED40-ED7E
EE40-EE7E

EB80-EBFC
EC80-ECFC
ED80-EDFC

EE80-EE8D

てな具合に分けられることもわかった。えーと、でも、こういうときってどういう風に正規表現書けば良いんだっけ。たぶん、

      EB      EE

40    **********
      **********
      **********
7E    **********

の*の部分にマッチするものを書けば良いんだけど。きっと、

[\xEB-\xEE][\x40-\x7E]

で良いと思うものの、いまいち自信なし。なのでマジメな僕はテストスクリプト書いてみました。まとめると、

(?:[\xEB-\xEE][\x40-\x7E]|[\xEB-\xED][\x80-\xFC]|\xEE[\x80-\x8D])

となるはずなので、

use strict;                                                                                                                                                     my $SJIS_CHARS = '(?:[\x00-\x7F\xA1-\xDF]|(?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]))';

# 範囲内の文字を削除するかチェック
for my $x (hex('EB') .. hex('EE')) {
    for my $y (hex('40') .. hex('7E')) {
        my $char = pack('H4', sprintf("%02x%02x", $x, $y));                     
        $char =~ s/\G(${SJIS_CHARS}*?)(?:[\xEB-\xEE][\x40-\x7E]|[\xEB-\xED][\x80-\xFC]|\xEE[\x80-\x8D])/$1/g;

        die if $char;
    }
}

# 範囲外の文字を削除しないかチェック
for my $x (hex('E0') .. hex('EA')) {
    for my $y (hex('40') .. hex('7E')) {
        my $char = pack('H4', sprintf("%02x%02x", $x, $y));                     
        $char =~ s/\G(${SJIS_CHARS}*?)(?:[\xEB-\xEE][\x40-\x7E]|[\xEB-\xED][\x80-\xFC]|\xEE[\x80-\x8D])/$1/g;

        die unless $char;
    }
}

# その2
for my $x (hex('EB') .. hex('EE')) {
    for my $y (hex('20') .. hex('3F')) {
        my $char = pack('H4', sprintf("%02x%02x", $x, $y));                     
        $char =~ s/\G(${SJIS_CHARS}*?)(?:[\xEB-\xEE][\x40-\x7E]|[\xEB-\xED][\x80-\xFC]|\xEE[\x80-\x8D])/$1/g;

        die unless $char;
    }
}

って感じ。まあテストコードだからやたら重複とかあるのは見逃してね。あとPerlの方が慣れてるから、ついこういうのはPerlで書いちゃうのね。「Test::More使えよ」とかいうのもナシでお願いね。

で、これでうまくいったので、PHPに組み込み。PHPPerl互換な正規表現ライブラリがあるんで、ここでの実験がほぼそのまま使えて、

<?php                                                                           
define(REMOVE_EMOJI_SJIS_CHARS, '(?:[\x00-\x7F\xA1-\xDF]|(?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]))');

function smarty_modifier_remove_emoji($string)
{                                                                                       
    // iモード
    $string = preg_replace('/\G(' . REMOVE_EMOJI_SJIS_CHARS . '*?)(?:\xF8[\x9F-\xFC]|\xF9[\x40-\x49\x50-\x52\x55-\x57\x5B-\x5E\x72-\x7E\x80-\xB0])|(?:\xF9[\xB1-\xFC])/', '$1', $string);

    // SoftBank                                                   
    $string = preg_replace('/\G(' . REMOVE_EMOJI_SJIS_CHARS . '*?)\x1B\x24[E-G].*?\x0F/', '$1', $string);
    
    // au(メールで送信されたもの)    
    $string = preg_replace('/\G(' . REMOVE_EMOJI_SJIS_CHARS . '*?)(?:[\xEB-\xEE][\x40-\x7E]|[\xEB-\xED][\x80-\xFC]|\xEE[\x80-\x8D])/', '$1', $string);

    return $string;
}

// vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
?>

と書けば良いと。なんとなく「せっかくだし」と思ってSmartyプラグイン(修飾子)にしてみたので、smarty_modifier_remove_emojiなんて名前になってるわけだども。

で、ここまでが技術的なお話。技術的なお話に興味ない人が、ここまで読んでるとも思わないが、せっかくなので今回ちょっと思ったこと。

まず「18禁」って絵文字があんのね。なんだそれ。どこで使うんだ。かわいく使うのか?(笑)

あと、「うっしっし」とか「泣き笑い」とか「ほえー」とか、なんかこう、空気感演出系みたいな絵文字があるんだけど、なぜかそれぞれ猫版が用意されてるのね。何故に猫?ま、猫ちゃん可愛いからいいけどな!

そして一番ツッコミ入れたくなったのが「白人」、「中国人」、「インド人」っていう絵文字。なんだその大雑把な世界観は。「白人」があって「黒人」がないのは何か、キング牧師にケンカ売ってんのか?あ?こんにゃろ。

僕はキング牧師の"I have a dream"な一節を聞くと何故か涙が出てくるので、こんなところで今日は終わりにしたいと思います。

ではみなさん、ご期限よう。

あ、漢字まちがえた。