UTF8の文字列長の取り扱い方

総閲覧回数:4,031,238回 / ブログ拍手:2,623
作品DB等各サービスの機能追加情報や、技術系・面白系記事を中心に提供。
記事の投稿は基本Twitterでも告知させて頂いています。
連絡は作品DBの論客の方なら私書、DB外ユーザの方ならメールTwitterで可能です。
アクセス記録[推移 / PV内訳(過去1日 / 過去1週間) / 外部アクセス元 (昨日 / 過去1週間) / ログイン論客足跡]
プロフィール私書(メール)
   /   /送済
評価(一覧   /)
投票   /共:   /
ファン登録
作品/情報/
DB構築()
ブログ
[書く]
攻略記事リンク集
My Play List
<=次の記事 Yahoo::検索の関連語を操作するSEO会社達と関連語のシステムの脆弱性について
=>前の記事 個人ページのカスタマイズ機能を追加(上部、下部のカスタマイズ機能)

1.
2009/11/04 同日2番目 Perl > 国際化 > UTF8の文字列長の取り扱い方」
[この書込みのみ表示(記事URL紹介用) / 編集 / 削除 / トラバ送信 / 共有分類に追加(タグ付け)]

1. 前書き
2. favstarの西欧圏発のソフトとしての問題
3. UTF8を正規表現で表す
4. UTF8の文字を指定バイトで綺麗に切るプログラム(perl)
5. おまけ: 指定長でEUC-JPの文字を切った場合に文字列の末尾を消す(perl)
6. おまけ2: 追加バグレポート
7. おまけ3: favotterとfavstarを一括検索できるようにしました
8. おまけ4: favstar作者の方からの原因の説明

1. 前書き

http://ja.favstar.fm/というTwitterのお気に入りに入れられた発言をリストしてくれるサービスを提供してくれている作者の方にTwitter経由でバグ報告したついでの記事です。
favotterというサービスの方が何故後から始まったfavstarの方が英語圏ではより使われるようになったのか、ということを作者の方が記事にされていたので、それでその存在を知りました。

確かに後発ながら一気に抜き去っていますね
英語圏発のトラフィックランキングサービスのALEXAはとりわけ異言語の国間のサービスの比較では正確さが落ちると思いますが、少なくとも英語圏においては確かにもうfavstarの方が使われているのだろうと思います。

なお、favstarは最速一括検索の方には既に加えてあります(今日favotterの方が停電とのことでサービス停止していたので、代替サービスとして加えることと、日本人にとって使えるサービスにして頂く為バグレポートをさせて頂くことを決心した)。

この記事は、その時作者の方向けに書いた記事を活用して、UTF8という文字に関する理解を深めて頂く為の記事です。
ソフトウェアの国際化の為にはUTF8という文字セットに関する理解が不可欠ですが、この記事がその理解の一助となることを願って。
2. favstarの西欧圏発のソフトとしての問題

自分の英語版の方の日記で説明して、
http://twitter.com/timhaines/statuses/5416644223
@hikarine3 Ahh - thank you for taking the time to explain and bring it to my attention. I will fix it.
と、作者の方に直してくれると言って貰ったのですが(この問題が直ればfavstarは十分日本人の方が使うに値するチョイスになると思う)、favstarでは日本語の文字列が途中で切れてしまうという問題がありました。

http://ja.favstar.fm/users/hikarine3/recent

これは、英語圏の方が日本語をUTF8で格納する時に何バイト必要なのかを把握していない為の問題だと思われました。
英語ですと1文字=1byteで済みますが、日本語の場合には1文字=3bytes消費します。
Twitterの140文字を格納する為には、日本語対応するには3bytes x 140 characters = 420 bytesの領域が必要です。
恐らく例えば240bytesなどで切ってしまっているので日本語の文章は途中で切れてしまい、またそこが日本語の文字の途中の場合には、最後の文字が壊れてしまっているのでしょう。

なので、favstarがこの問題を解決するには、
1. 3bytes x 140文字 = 420 bytesまで格納できるようにする (もしくは保存の時だけUCS2を使うというのもありだけど / asciiにも2bytes使うので英語のポストには割高だけれども2bytes x 140 = 280bytesで済む)
または
2. 格納可能bytesを拡張しないままでも、日本語末尾の文字をUTF8の規格に合う文字にする
ということが必要です。

1. は設定変えるだけで出来るのなら簡単に出来ますね。容量の問題がないのならば、直すのも恐らく簡単なので(既に格納してしまったデータを再度取り直すとしたらそこは時間がかかるでしょうが)、日本語対応という意味ではこちらをお願いしたいところです。

2. の解決方法を採る場合には、何れにせよ文の途中で切れてしまうという問題は残りますが、最後の破損した文字が現れる問題は直ります。これをするにはUTF8の規格を把握した処理が必要になります。
3. UTF8を正規表現で表す

UTF8の1文字は正規表現で表すと以下のようになります。
1 byte文字Ascii[\x00-\x7F]
2 bytes文字 [\xC0-\xDF][\x80-\xBF]
3 bytes文字日本語など[\xE0-\xEF][\x80-\xBF][\x80-\xBF]
4 bytes文字 [\xF0-\xF7][\x80-\xBF][\x80-\xBF][\x80-\xBF]
5 bytes文字 [\xF8-\xFB][\x80-\xBF][\x80-\xBF][\x80-\xBF][\x80-\xBF]
6 bytes文字 [\xFC-\xFD][\x80-\xBF][\x80-\xBF][\x80-\xBF][\x80-\xBF][\x80-\xBF]
国際化の為に1-6bytesの可変長となっています。
普通googleやyahooなどの検索エンジンは国際化の為にUTF8を使います。
但し、日本語圏しかサービスを提供しない場合には、EUC-JPとかだと2bytesで日本語の文字を表す事が出来るのに、無駄が処理の面でも容量的にも生じてしまうので、EUC-JPとかの方が良いということもあります(自分の過去の関連参考記事:「日本語を対象の検索エンジンに最適な内部文字コードとは(UTF8 or EUC-JP or ?)」)。
4. UTF8の文字を指定バイトで綺麗に切るプログラム(perl)

favstarの問題の解決には容量を増やすことが一番ですが、破損文字についての理解を深める為にプログラムを紹介。
例えば280bytesで日本語の文字列を切り、また末尾の文字を破損させない為には、perlだと以下のような処理を行うことで実現する事が出来ます。

# UTF8の1文字の正規表現
my $utf8_char_regex=join('|','[\x00-\x7F]',
'[\xC0-\xDF][\x80-\xBF]',
'[\xE0-\xEF][\x80-\xBF][\x80-\xBF]',
'[\xF0-\xF7][\x80-\xBF][\x80-\xBF][\x80-\xBF]',
'[\xF8-\xFB][\x80-\xBF][\x80-\xBF][\x80-\xBF][\x80-\xBF]',
'[\xFC-\xFD][\x80-\xBF][\x80-\xBF][\x80-\xBF][\x80-\xBF][\x80-\xBF]');

# 文字列の最大バイト
my $max_bytes=280;

# 入力
my $utf8_sentence='一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十';

print '最初の文字列の長さ = '.length($utf8_sentence)." bytes\n";
print '最初の文字列 = '.$utf8_sentence."\n\n";

# max_bytesで文字列を切る
$utf8_sentence=substr($utf8_sentence, 0, $max_bytes);

print '単純にバイト数で切った後の文字列の長さ = '.length($utf8_sentence)."\n";
# 末尾が破損しているのをプリントするかどうかは環境次第
print '単純にバイト数で切った後の文字列 = '.$utf8_sentence."\n\n";

# 文章の頭から見てUTF8として正しい文字が続く範囲だけ切り出す
$utf8_sentence=~ s!^((?:$utf8_char_regex)*).*!$1!s;

print 'UTF8の文字に合うように切った後の長さ = '.length($utf8_sentence)."\n";
print '最終的に生成されたUTF8として破損していない文字列 = '.$utf8_sentence."\n";

[結果]
最初の文字列の長さ = 630 bytes
最初の文字列 = 一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十

単純にバイト数で切った後の文字列の長さ = 280 bytes
単純にバイト数で切った後の文字 = 破損していて表示できないので省略

UTF8の文字に合うように切った後の長さ = 279 bytes
最終的に生成されたUTF8として破損していない文字列 = 一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三

まあ処理の効率を考えれば、文字列の後ろからチェックしていくってのもありなんでしょうが、分かり易く頭からUTF8として妥当な文字だけを引っこ抜くという形にするとこんなかんじです。
5. おまけ: 指定長でEUC-JPの文字を切った場合に文字列の末尾を消す(perl)

EUC-JPを正規表現で表すと
[\x00-\x7F] # ASCII
\x8E[\xA1-\xDF] # 半角カタカナ
[\xA1\xB0-\xCE\xD0-\xF3][\xA1-\xFE] # 1,16-46,48-83区
\xA2[\xA1-\xAE\xBA-\xC1\xCA-\xD0\xDC-\xEA\xF2-\xF9\xFE] # 2区
\xA3[\xB0-\xB9\xC1-\xDA\xE1-\xFA] # 3区
\xA4[\xA1-\xF3] # 4区
\xA5[\xA1-\xF6] # 5区
\xA6[\xA1-\xB8\xC1-\xD8] # 6区
\xA7[\xA1-\xC1\xD1-\xF1] # 7区
\xA8[\xA1-\xC0] # 8区
[\xA9-\xAF\xF5-\xFE][\xA1-\xFE] # 9-15,85-94区
\xCF[\xA1-\xD3] # 47区
\xF4[\xA1-\xA6] # 84区

$strという変数が指定長で切られたEUC-JPの文字の場合、
if ($str =~ /\x8f$/ or $str =~ tr/\x8e\xa1-\xfe// % 2) {
  chop($str);
  chop($str) if ($str =~ /\x8f$/);
}
とすると仮に末尾に指定byte長切りで破損した文字があったとした場合、その末尾の破損した文字を削除させることが出来ます。
6. おまけ2: 追加バグレポート

適切でないところをリンクさせてしまうという問題に遭遇したので
http://ja.favstar.fm/users/hikarine3/recent
http://、https://もしくはftp://でURLは必ず始まるというルールを作れば解決するのではないかと追加でバグレポートさせて頂きました。

異言語版って自分自身が使わないから中々問題に気付きづらいんですよね。
そういう意味で、色々な窓口を広くもっておくことは大切だと思います。
自分もその点英語版の運用において反省するところがあるので、これから改善しておこうと思います。
7. おまけ3: favotterとfavstarを一括検索できるようにしました

復旧したfavotterを見てみたら、そのせいかどうかは分からないけれども6時間前のお気に入り入れられが抜けている。
こうやって両方の検索を確認して抜けがないか探すのは面倒だなぁ...
ということで、Twitter一括検索をした場合、その検索語が英数字だけで構成される一語である場合、Twitter検索の下部にIDでのお気に入り入れられ検索を混ぜるようにしました。
hikarine3で検索した例
自分のIDを入れてみて、自分絡みの情報一括チェックにご活用下さい。

あとで格納の仕方の仕様を調べてみましたが、外部からのAPI呼び出し可能回数には制限があるので、ある程度は仕方ないと切り捨てている模様。
そういう意味では、Twitter自身がやるか、Twitterと契約したところがこうしたサービスをきちんとやってくれない限り、漏れが生じない結果は得られないので、併用して複数検索する価値が生まれるということになるのでしょうね。
そういう意味では、一括検索は必要で、また便利かなと思います。
8. おまけ4: favstar作者の方からの原因の説明

http://twitter.com/timhaines/status/5501208330
timhaines Protip: If storing international tweets in your db, varchar(255) is not enough. H/T to @hikarine3 for schooling me on my heinous error.
varchar(255)が使われていた為日本語のポストを保存仕切れなかった模様。
UCS-2で格納するとしても140 x 2 bytes = 280bytes, UTF8で格納するとしたら140 x 3 bytes=420bytes必要なので、255bytesでは不十分。
その後他の方からのtweetでSMSとTwitterは互換性があるんじゃないのか?という話が来ていましたが(SMSの文字の上限は140bytes)、あくまでそれは140bytesで表す事が出来る言語の場合(つまりascii文字だけで表現できる西欧圏)。
日本語の場合にはSMSのメッセージにtwitterのポストを完全に入れ込むことは出来ません。
と伝えさせて頂きました。

コメントする


[他の記事も読む]
<=次の記事 Yahoo::検索の関連語を操作するSEO会社達と関連語のシステムの脆弱性について
=>前の記事 個人ページのカスタマイズ機能を追加(上部、下部のカスタマイズ機能)


大分類が「Perl」の記事
この論客の記事全て
↑上へ