文字列の分割(UTF8)


テレスコ  2007-02-21 00:21:35  No: 64505

先ほど似たような質問を投稿した者ですが、
また、恐縮ですが質問させて下さい。

  文字コードがUTF8の文字列を指定された文字数で
分割したいのですが、巧い方法は無いでしょうか?

  //これを(英数字は半角)
    char str[]="3バイト文字と1byte文字が混ざってます";

  //こうしたい
  char A[18], B[18], C[18], D[18];
    A="3バイト文字
    B="と1byte
    C="文字が混ざっ"
    D="てます"

よろしく、おねがいいたします。


Blue  2007-02-21 01:12:07  No: 64506

文字数であれば、wchar_t型配列に変換して、ぶった切れば簡単でしょう。
まず、

1.MultiByteToWideChar で wchar_t型配列に変換
2.指定した文字数で切り出し(wcsncpyとか)
3.切り出した文字列をWideCharToMultiByteでUTF8に再変換

という流れ。
正直コードを書くのはめんどくさいので調べてみて、
わからないところがあれば再度具体的に質問してください。


テレスコ  2007-02-21 02:34:58  No: 64507

Blue様、いつもありがとうございます。

  初心者なので聞いてばかりでお恥ずかしいのですが教えて下さい。

>1.MultiByteToWideChar で wchar_t型配列に変換
>2.指定した文字数で切り出し(wcsncpyとか)
>3.切り出した文字列をWideCharToMultiByteでUTF8に再変換

以下のように考えてみたのですがうまく行きません。どこが悪いのでしょうか?

  CString str=L"3バイト文字と1byte文字が混ざってます";
  wchar_t     wbuff[52];
  wchar_t     buff[18];
  char *wp = new char[str.GetLength()+1];
  strcpy( wp, str );  
  MultiByteToWideChar(CP_UTF8, 0, wp, sizeof(wp), wbuff, sizeof(wbuff));

  char out[18];
  char idx=0;
  CStringArray list;

  while(idx < 52){
    memcpy(buff,wbuff+idx,18);
    int siz  = WideCharToMultiByte(CP_UTF8,0,buff,-1,NULL,0,NULL,NULL);
    WideCharToMultiByte(CP_UTF8,0,buff,-1,out,siz,NULL,NULL);
    idx +=18;
    list.Add(put);
  }

  printf("%d",(LPCSTR)list.GetAt(0));
  printf("%d",(LPCSTR)list.GetAt(1));
  printf("%d",(LPCSTR)list.GetAt(2));
  printf("%d",(LPCSTR)list.GetAt(3));

よろしく、お願いします。


Blue  2007-02-21 02:41:01  No: 64508

とりあえず、UTF8をCStringに入れるべきではないし、
printfでうまいこと表示されるものではありません。
(printfで表示できるのはShift_JISです。)

いろいろと問題があるので、サンプルを載せます。
UTF8のchar型配列の調達は、utf8.txtというUTF8コードでかいたテキストファイルから、
確認は utf8_ret.txtというUTF8コードのテキストファイルに出力してします。

#include <afx.h>
#include <afxtempl.h>

char* ReadUTF8Data(LPCTSTR filepath)
{
    char* utf8data = NULL;

    TRY
    {
        CFile f(filepath, CFile::modeRead);
        
        const DWORD length = f.GetLength();
        utf8data = new char[length + 1];
        f.Read(utf8data, length);
        utf8data[length] = 0x00;

        f.Close();
    }
    CATCH (CFileException, e)
    {
        if (utf8data) delete[] utf8data;
        return NULL;
    }
    END_CATCH
    return utf8data;
}

wchar_t* UTF8ToUTF16(const char* data)
{
    wchar_t* utf16 = NULL;

    if (!data || !data[0]) return utf16;

    const int wlen = ::MultiByteToWideChar(CP_UTF8, 0, data, -1, NULL, 0);
    if (!wlen) return utf16;

    utf16 = new wchar_t[wlen + 1];
    if (::MultiByteToWideChar(CP_UTF8, 0, data, -1, utf16, wlen))
        utf16[wlen] = L'\0';

    return utf16;
}

char* UTF16ToUTF8(const wchar_t* data)
{
    char* utf8 = NULL;

    if (!data || !data[0]) return utf8;

    const int len = ::WideCharToMultiByte(CP_UTF8, 0, data, -1, NULL, 0, NULL, NULL);
    if (!len) return utf8;

    utf8 = new char[len + 1];
    if (::WideCharToMultiByte(CP_UTF8, 0, data, -1, utf8, len, NULL, NULL))
        utf8[len] = '\0';

    return utf8;
}

BOOL WriteUTF8Data(LPCTSTR filepath, CTypedPtrArray<CPtrArray, char*>& utf8list)
{
    TRY
    {
        CFile f(filepath, CFile::modeWrite | CFile::modeCreate);
        
        for (int i = 0; i < utf8list.GetSize(); ++i)
        {
            char* elem = utf8list.GetAt(i);
            f.Write(elem, strlen(elem) + 1);
        }

        f.Close();
    }
    CATCH (CFileException, e)
    {
        return FALSE;
    }
    END_CATCH

    return TRUE;
}

int main()
{
    // ファイルから読み込む(BOMなしUTF8コードのテキストファイル)
    char* utf8data = ReadUTF8Data(_T("utf8.txt"));
    if (utf8data)
    {
        // UTF16のコードの変換
        wchar_t* utf16 = UTF8ToUTF16(utf8data);
        if (utf16)
        {
            CTypedPtrArray<CPtrArray, char*> utf8list;
            wchar_t buff[7];

            for (const wchar_t* p = utf16, * last = p + wcslen(utf16); p < last; p += 6)
            {
                wcsncpy(buff, p, 6);
                buff[6] = L'\0';

                // UTF16からUTF8に変換
                char* utf8elem = UTF16ToUTF8(buff);
                if (utf8elem)
                    utf8list.Add(utf8elem); // リストに追加
                else
                    break;
            }

            // 確認のためファイルに書き出す(0x00区切りのテキスト?ファイル)
            WriteUTF8Data(_T("utf8_ret.txt"), utf8list);

            // リストの削除
            for (int i = 0; i < utf8list.GetSize(); ++i)
                delete utf8list.GetAt(i);
            utf8list.RemoveAll();

            // UTF16文字列の削除
            delete[] utf16;
        }
        // UTF8文字列の削除
        delete[] utf8data;
    }

    return 0;
}


Blue  2007-02-21 02:47:50  No: 64509

>UTF8をCStringに入れるべきではないし、
ちなみに、領域管理のためだけにCStringを使うのであれば
一応ありかもしれません。(GetLength程度ならOKかも)

文字列の操作(LeftとかFind)とかを考えているのであれば、すべて使えませんので。
(そのつど、wchar_tに変換させる。→最初からwchar_t*で持ったほうが賢そう)


yoh2  2007-02-21 07:10:22  No: 64510

Blueさんの提案とは別のアプローチで。

UTF-8文字列は、任意の1オクテットを取り出した場合、その取りだしたオクテットが、
- 先頭オクテットか、後続オクテットか
- 先頭オクテットだった場合、何オクテットで1文字を構成するか
が簡単に分かる仕様になっていますので、その判定を行う関数を自作してしまうのもひとつの手です。

具体的な分類は、オクテットを2進数で表わすと以下のようになります。
なお、一次情報としてはUnicodeフォーラムの仕様書か、RFC3629を参照するとよいでしょう。

0*******: 1オクテット文字
10******: 複数オクテット文字の後続部分
110*****: 2オクテット文字の先頭
1110****: 3オクテット文字の先頭
11110***: 4オクテット文字の先頭
111110**: 5オクテット文字の先頭
1111110*: 6オクテット文字の先頭


Blue  2007-02-22 19:37:10  No: 64511

yoh2さんのやり方のほうが効率がよいですね。

時間があったので作ってみた。

size_t utf8scbyte(const char* string)
{
    const unsigned char c = *string;

    if ((c & 0x80) == 0x00) // 0*** ****
        return 1;
    if ((c & 0xE0) == 0xC0) // 110* ****
        return 2;
    if ((c & 0xF0) == 0xE0) // 1110 ****
        return 3;
    if ((c & 0xF8) == 0xF0) // 1111 0***
        return 4;
    if ((c & 0xFC) == 0xF8) // 1111 10**
        return 5;
    if ((c & 0xFE) == 0xFC) // 1111 110*
        return 6;

    // Error
    return 1;
}

char* utf8dup(const char* utf8src, const size_t size)
{
    char* utf8des = new char[size + 1];
    memcpy(utf8des, utf8src, size + 1);

    return utf8des;
}

int main()
{
    // ファイルから読み込む(BOMなしUTF8コード(=UTF-8N)のテキストファイル)
    char* utf8data = ReadUTF8Data(_T("utf8.txt"));
    if (utf8data)
    {
        CTypedPtrArray<CPtrArray, char*> utf8list;

        static const size_t N = 6;
        char buff[N * 6 + 1];
        size_t count = 0, bytes, index = 0;

        for (const char* p = utf8data; *p;)
        {
            bytes = utf8scbyte(p);
            memcpy(&buff[index], p, bytes);
            p += bytes;
            index += bytes;

            if (++count >= N)
            {
                buff[index] = 0x00;
                utf8list.Add(utf8dup(buff, index));

                index = 0;
                count = 0;
            }
        }
        if (count > 0)
        {
            buff[index] = 0x00;
            utf8list.Add(utf8dup(buff, index));
        }

        // 確認のためファイルに書き出す(0x00区切りのテキスト?ファイル)
        WriteUTF8Data(_T("utf8_ret.txt"), utf8list);

        // リストの削除
        for (int i = 0; i < utf8list.GetSize(); ++i)
            delete utf8list.GetAt(i);
        utf8list.RemoveAll();

        // UTF8文字列の削除
        delete[] utf8data;
    }

    return 0;
}


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加