C言語でのUTF-8文字列の正確な長さを取得する


UTF-8でのstrlen関数のようなものはありますか?
"こんにちは"という文字列の長さをstrlenで測ると5ではなく、15という数値が返ってきてしまいます。

[code]

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char *s = "こんにちは";
        printf("%lu\n", strlen(s)); // 15と表示される
    
        return 0;
    }
    
[/code]

どうすれば期待通りの5を得ることができるのでしょうか?




<https://stackoverflow.com/questions/5117393/utf-8-strings-length-in-linux-c>
より、  
`mbstowcs()` が使えるそうです。  
ロケールは正しく設定する必要があるようですが。

その他にもリンク先には自分で実装する場合の例もありますので、参考にされてはいかがでしょう。




苗字や地名の異体字や国旗、色違いの絵文字など、複数のコードポイントで構成される書記素クラスターを考慮する必要がある場合、ICU の
BreakIterator を使います。書記素クラスターに対応していない文字列関数を使うと、知らないうちに書記素クラスターを壊してしまうおそれがあります。

Twitter
のようにコードポイント単位で短い文字数制限をかける場合、ヒンドゥー語やチベット語など、結合文字を多用するアジア諸国の言語のユーザーにとって、使い勝手がわるくなる可能性があることも配慮する必要があります。

漢字の異体字をあらわすには基底字の次に異体字セレクターや Standardized Variant を続けます。葛飾区の「かつ」は <U+845B
U+E0101> であらわすことができます。国旗は2文字の国コードを該当するコードポイントであらわします。日本の国コードである JP は <U+1F1EF
U+1F1F5> になります。

BreakIterator のコードの例は次のようになります。

[code]

    #include <stdio.h>
    #include <unicode/ubrk.h>
    
    int len(const char *);
    
    int main(void)
    {
        const char* str = "葛\U000E0101飾区\U0001F1EF\U0001F1F5";
        printf("%s\n%d\n", str, len(str));
    
        return 0;
    }
    
    int len(const char *str)
    {
        UText *ut;
        UBreakIterator *bi;
        UErrorCode status = U_ZERO_ERROR;
        int len = 0;
    
        ut = utext_openUTF8(NULL, str, -1, &status);
        bi = ubrk_open(UBRK_CHARACTER, "ja_JP", NULL, 0, &status);
        ubrk_setUText(bi, ut, &status);
    
        while (ubrk_next(bi) != UBRK_DONE) {
            ++len;
        }
    
        utext_close(ut);
        ubrk_close(bi);
    
        return len;
    }
    
[/code]

書記素クラスターのサイズの上限はないので、バッファーオーバーフローに気をつける必要があります。 たとえば、「葛」の後ろに U+E0101
が1000個続いても1つの書記素クラスターとしてカウントされます。 もし書記素クラスターの上限値を設ける必要があれば、Unicode の [Stream-
Safe Text
Format](http://www.unicode.org/reports/tr15/#Stream_Safe_Text_Format)
をご参照ください。 基準値の例として128バイトもしくはコードポイント単位で32個前後という数値が挙げられています。

コードポイント単位で文字数を求めるには UText (utext.h) もしくは UnicodeString (ustring.h) の
u_countChar32 を使います。次のコードは UText の例です。

[code]

    #include <stdio.h>
    #include <unicode/utext.h>
    
    int len(const char*);
    
    int main(void)
    {
        // U+20BB7 U+91CE U+5BB6
        const char* str = "野家"; 
        printf("%d\n", len(str));
    
        return 0;
    }
    
    int len(const char* str)
    {
        UText *ut;
        UErrorCode status = U_ZERO_ERROR;
        UChar32 c;
        int len = 0;
    
        ut = utext_openUTF8(NULL, str, -1, &status);
    
        for (c = utext_next32From(ut, 0); c > -1; c = utext_next32(ut)) {
            ++len;
        }
    
        return len;
    }
    
[/code]

自分で書記素クラスターの境界を判定するライブラリを実装する場合、それぞれのコードポイントから Unicode プロパティを判定するために
[GraphemeBreakProperty.txt](http://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakProperty.txt)
に記載される対応表が必要になります。境界のルールは UAX#29 の [3.1.1 Grapheme Cluster Boundary
Rules](http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules)
をご参照ください。この節では、隣接する文字の Unicode プロパティの組み合わせが並べられており、国旗は GB8 (Regional_Indicator
× Regional_Indicator)、基底字と異体字セレクターの組み合わせは GB9 ( × Extend)、通常の文字が隣接し合う場合は GB10
(Any ÷ Any) に該当します。テストケースのデータは
[GraphemeBreakTest.txt](http://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.txt)
をご参照ください。

