先ほど似たような質問を投稿した者ですが、
また、恐縮ですが質問させて下さい。
文字コードがUTF8の文字列を指定された文字数で
分割したいのですが、巧い方法は無いでしょうか?
//これを(英数字は半角)
char str[]="3バイト文字と1byte文字が混ざってます";
//こうしたい
char A[18], B[18], C[18], D[18];
A="3バイト文字
B="と1byte
C="文字が混ざっ"
D="てます"
よろしく、おねがいいたします。
文字数であれば、wchar_t型配列に変換して、ぶった切れば簡単でしょう。
まず、
1.MultiByteToWideChar で wchar_t型配列に変換
2.指定した文字数で切り出し(wcsncpyとか)
3.切り出した文字列をWideCharToMultiByteでUTF8に再変換
という流れ。
正直コードを書くのはめんどくさいので調べてみて、
わからないところがあれば再度具体的に質問してください。
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));
よろしく、お願いします。
とりあえず、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;
}
>UTF8をCStringに入れるべきではないし、
ちなみに、領域管理のためだけにCStringを使うのであれば
一応ありかもしれません。(GetLength程度ならOKかも)
文字列の操作(LeftとかFind)とかを考えているのであれば、すべて使えませんので。
(そのつど、wchar_tに変換させる。→最初からwchar_t*で持ったほうが賢そう)
Blueさんの提案とは別のアプローチで。
UTF-8文字列は、任意の1オクテットを取り出した場合、その取りだしたオクテットが、
- 先頭オクテットか、後続オクテットか
- 先頭オクテットだった場合、何オクテットで1文字を構成するか
が簡単に分かる仕様になっていますので、その判定を行う関数を自作してしまうのもひとつの手です。
具体的な分類は、オクテットを2進数で表わすと以下のようになります。
なお、一次情報としてはUnicodeフォーラムの仕様書か、RFC3629を参照するとよいでしょう。
0*******: 1オクテット文字
10******: 複数オクテット文字の後続部分
110*****: 2オクテット文字の先頭
1110****: 3オクテット文字の先頭
11110***: 4オクテット文字の先頭
111110**: 5オクテット文字の先頭
1111110*: 6オクテット文字の先頭
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;
}
ツイート | ![]() |