UTF8の文字数を数える手法の検討
AirOneの内部文字列のエンコーディングはUTF8です。
内部文字列のエンコーディングの候補は、事実上、次の4パターンだと思います。
- utf8
- ucs2
- (locale依存の)マルチバイト
- wchar_t
CSI(CodeSet Independent)の観点での推奨はwchar_tです。しかし、Windowsでのwchar_tの扱いに不安、と言うより、今はともかく5年前は、GNU/LinuxのGNU libcでもwchar_tは不安、という状況だったのでwchar_tの選択肢は消えました。
ucs2の選択はWindows限定であれば、ありかもしれません(WCHARを使う)。しかし、この選択は見かけほど簡単ではありません。外部ライブラリの多くはWCHARに依存したAPIは持ちません。これらのAPI呼び出しの時に、WCHARとの相互変換が必要になります。エンコーディングの変換の必要箇所が増えるほど、バグを埋め込む可能性も高くなります。
外部ライブラリを考慮すると、結局、内部文字列はchar*で持つのが妥当という判断をしました。本当の文字列処理(ソートや検索など)を外部(WebブラウザやXSLT。下記参照)に追い出しているからこそできる芸当かもしれませんが。
選択肢はUTF8か(locale依存の)マルチバイトになりました。locale依存のマルチバイトは、正しく国際化APIを使っている限り問題は無いのですが、高度な政治的判断でUTF8にしました。
最近、「UTF8文字列の文字数を数えるAPIはありますか」、と質問されました。
今までUTF8の文字数を数える必要がありませんでした。 上に書いたように、UI周りはWebブラウザ(IEコンポーネントやFirefox)に任せているので、AirOne本体が文字数を意識する場面が無かったことと、HTML生成の多くがXSLT任せ(libxslt)だったので、やはりAirOne本体が文字数を意識する必要が無かったからです。
UTF8文字列の文字数を数えるには次の方法があります。
- mblen(3)やmbrlen(3)のマルチバイト用標準APIを使う
- mbstowcs(3)でwchar_tに変換してから数える
- iconvでUCS2(or UCS4)に変換してから数える
- 自前でUCS2(or UCS4)に変換してから数える
- 自前でUTF8用に実装する
国際化プログラミングの教えは、「文字列処理にはAPIを使え」、であり、更に言えば「標準APIを使え」、です。この観点で正しい手法は、1番目か2番目の手法です。しかし、これらはlocaleに依存します。文字列のエンコーディングがUTF8と分かっている時には、一般的すぎて大げさに感じます。そもそも、localeによらず内部エンコーディングをUTF8に固定している時点でCSIに反しているので、大げさ、と批判するのはお門違いです。
選択肢は、UCS2に変換するか、UTF8のまま数えるか、です。自分の経験からの国際化プログラミングの鉄則は、「エンコーディングの変換にバグが宿る」です。入ってくる所(ファイル、ネットワーク、ユーザ入力など)と出ていく所でだけエンコーディングの変換をして、プログラムの中では内部エンコーディングを一貫すべき、と言うのが結論です。
と言うことで、UTF8のまま文字数を数えることにしました。
GLib(http://www.gtk.org)から以下のコードを拝借しました。pはUTF8文字の先頭バイトを指している必要があります。
/* Array of skip-bytes-per-initial character. */ GLIB_VAR const gchar * const g_utf8_skip; #define g_utf8_next_char(p) (char *)((p) + g_utf8_skip[*(guchar *)(p)]) static const gchar utf8_skip_data[256] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 }; const gchar * const g_utf8_skip = utf8_skip_data;
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/utf8-handling/tbping