前面提到,我们设计的Txt程序只能读取UTF-16-LE编码的文本,因为设定的就是按照这个格式来读取的,所以如果用这个程序去打开其他字符编码的Txt,那显然会出现乱码,这是不能被容忍的,哪能管得了用户呢,我们只能做兼容。。。。
这里只需要对函数bool CTxt0721View::ReadFileContents()进行修改就可,所有文本读取都是通过这个函数实现的,只需要在这里判断出不同的编码格式,然后采取不同方式读取即可,最后还是把这些文本转化成UTF-16-LE来处理,(主要是为了使用CString,这也是为了配合VS都使用宽字节)
先放代码:
1 // 新版本,支持多种Txt文本的读取(UTF-8BOM、ANSI、Unicode16-LE、UTF-8) 2 // // 以什么格式打开,就以什么格式写回去(没实现这个功能) 3 bool CTxt0721View::ReadFileContents() 4 { 5 CFile read_file; 6 DWORD file_length; // 文件字节长度 7 char * file_contents; // 初步读取的文件内容--char 8 9 try 10 { 11 // 只读模式打开文档 12 read_file.Open(TxtFileLoad, CFile::modeRead | CFile::typeBinary); 13 // 获取整个文件的字节长度--逻辑长度 14 file_length = read_file.GetLength(); 15 // 创建字符串存储内容 16 file_contents = new char[file_length + 2]; 17 memset(file_contents, 0, sizeof(char)*(file_length + 2)); 18 // 一次性读取整个文件 19 read_file.Read(file_contents, sizeof(char)*file_length); 20 // 文件末尾可能不会有\0(就是没有),所以加上结束符 21 file_contents[file_length] = '\0'; 22 file_contents[file_length + 1] = '\0'; 23 // 关闭文件 24 read_file.Close(); 25 } 26 catch (CFileException *e) 27 { 28 CString read_errror_str; 29 read_errror_str.Format(L"保存数据失败的原因是:%d", e->m_cause); 30 MessageBox(read_errror_str); 31 read_file.Abort(); 32 e->Delete(); 33 // 读取文件失败 34 return false; 35 } 36 37 // 获取Txt文件编码格式(UTF-8BOM、ANSI、Unicode16-LE) 38 WORD wFlag = 0; 39 memcpy(&wFlag, file_contents, 2); 40 41 //CString str; 42 //str.Format(L"%X%X", LOBYTE(wFlag),HIBYTE(wFlag)); 43 //MessageBox(str); 44 45 wchar_t* m_readTxt; // 存储转换后的Unicode16LE字符 46 47 if (wFlag == 0xFEFF) // Unicode16-LE 48 { // 摊牌--不干了 49 // MessageBox(L"Unicode16-LE"); 50 //wchar_t* m_readTxt = (LPTSTR)file_contents; 51 //m_readTxt[file_length / 2] = '\0'; // 不开空间,直接用原来的空间 52 //TxtFileString = (CString)m_readTxt; 53 m_readTxt = new wchar_t[file_length / 2 + 1]; 54 _tcscpy_s(m_readTxt, file_length / 2, (LPTSTR)file_contents + 1); // 这个标志无法转,必须干掉 55 m_readTxt[file_length / 2] = '\0'; 56 TxtFileString = (CString)m_readTxt; 57 } 58 else if(wFlag == 0xBBEF) // UTF-8BOM ? UTF-8它不带标志。。需要根据其编码特征判断(有点麻烦 59 { // (wFlag == 0xBBEF || IsUTF8(file_contents + 3, file_length - 3)) 60 // MessageBox(L"UTF-8BOM");(LPCSTR) 61 // int MultiByteToWideChar(UINT CodePage,DWORD dwFlags,LPCSTR lpMultiByteStr, \+ 62 // int cchMultiByte,LPWSTR lpWideCharStr,int cchWideChar); 63 // 如果函数运行成功,并且cchMultiByte为0,返回值是待转换字符串的缓冲区所需求的宽字符数大小。 64 // (此种情况用来获取转换所需的wchar_t的个数) 65 int len = MultiByteToWideChar(CP_UTF8, NULL, file_contents + 3, file_length - 3, NULL, 0); 66 //分配空间要给'\0'留个空间,MultiByteToWideChar不会给'\0'空间 67 m_readTxt = new wchar_t[len + 1]; 68 //转换 ------- file_contents + 3, file_length - 3 主要是去掉BOM的三个字节的标志 69 MultiByteToWideChar(CP_UTF8, NULL, file_contents + 3, file_length - 3, m_readTxt, len); 70 //最后加上'\0' 71 m_readTxt[len] = '\0'; 72 //unicode版的MessageBox API 73 TxtFileString = (CString)m_readTxt; 74 } 75 else if (IsUTF8(file_contents + 3, file_length - 3)) 76 { 77 int len = MultiByteToWideChar(CP_UTF8, NULL, file_contents, file_length, NULL, 0); 78 m_readTxt = new wchar_t[len + 1]; 79 MultiByteToWideChar(CP_UTF8, NULL, file_contents, file_length, m_readTxt, len); 80 m_readTxt[len] = '\0'; 81 TxtFileString = (CString)m_readTxt; 82 } 83 else // ANSI 问题解决 84 { 85 // MessageBox(L"ANSI"); 86 int len = MultiByteToWideChar(CP_ACP, NULL, file_contents, file_length, NULL, 0); 87 //分配空间要给'\0'留个空间,MultiByteToWideChar不会给'\0'空间 88 m_readTxt = new wchar_t[len + 1]; 89 //转换 90 MultiByteToWideChar(CP_ACP, NULL, file_contents, file_length, m_readTxt, len); 91 //最后加上'\0' 92 m_readTxt[len] = '\0'; 93 TxtFileString = (CString)m_readTxt; 94 } 95 delete[] file_contents; 96 delete[] m_readTxt; 97 return true; 98 }
不用,因为此处我们是使用char存下来的,不管你什么格式,在电脑中都是一个一个字节存下来的,现在的读取无非就是把这块内存拿了出来,
然后就是怎么判断属于哪一种编码格式?
有的编码格式会在头部加入标志,比如UTF-16-LE,UTF-8-BOM,但是Windows下的UTF-8和ANSI是没有标志的,暂且不管这两个家伙;
utf-8-BOM 前面有三个标志字节efbbbf ,,, unicode 标志头 fffe 当然这里还要看大端小端存储方式
这两个判断就方便多了,直接对比开头的字节,就知道是哪一个编码格式了,剩下的UTF-8和ANSI都没有标志,只能通过其字符编码特性来判断了,(这里百度查资料)(如何判断一个文本文件内容的编码格式 UTF-8 ? ANSI(GBK))
1 // 判断是否是UTF-8编码的Txt文本 --https://blog.csdn.net/jiangqin115/article/details/42684017 2 bool CTxt0721View::IsUTF8(const void* pBuffer, long size) 3 { 4 bool IsUTF8 = true; 5 unsigned char* start = (unsigned char*)pBuffer; 6 unsigned char* end = (unsigned char*)pBuffer + size; 7 while (start < end) 8 { 9 if (*start < 0x80) // (10000000): 值小于0x80的为ASCII字符 10 { 11 start++; 12 } 13 else if (*start < (0xC0)) // (11000000): 值介于0x80与0xC0之间的为无效UTF-8字符 14 { 15 IsUTF8 = false; 16 break; 17 } 18 else if (*start < (0xE0)) // (11100000): 此范围内为2字节UTF-8字符 19 { 20 if (start >= end - 1) 21 { 22 break; 23 } 24 if ((start[1] & (0xC0)) != 0x80) 25 { 26 IsUTF8 = false; 27 break; 28 } 29 start += 2; 30 } 31 else if (*start < (0xF0)) // (11110000): 此范围内为3字节UTF-8字符 32 { 33 if (start >= end - 2) 34 { 35 break; 36 } 37 if ((start[1] & (0xC0)) != 0x80 || (start[2] & (0xC0)) != 0x80) 38 { 39 IsUTF8 = false; 40 break; 41 } 42 start += 3; 43 } 44 else 45 { 46 IsUTF8 = false; 47 break; 48 } 49 } 50 return IsUTF8; 51 }
解决一部分问题,能够分清楚当前Txt是哪一种编码格式后,还需要对读取的数据进行转码,统一转化为Unicode(UTF-16)编码来处理。
1、如果本来就是UTF-16:那存在char中的数据实际上就是宽字节的格式存储的,可以直接让一个wchar_t指针指向这块空间,它完全符合这种编码格式,在代码中还是重新开辟了空间来存储,同时也为了加上'\0',因为真的会因为这个出现问题(记得去掉头部标志哦,还有加上最后的'\0')
2、如果本来就是UTF-8-BOM:那这里就要进行转码了,因为该编码格式中英文占一个字节,中文三字节,和UTF-16中的统一两字节不符,这里主要是利用MultiByteToWideChar函数了,网上资料很多,就不说了(其实说不出来,哈哈哈哈)(记得去掉头部标志哦,还有加上最后的'\0')
3、UTF-8:转码同UTF-8-BOM,只是它没有三个字节的标志,不需要去掉头部标志(加上最后的'\0')
4、ANSI:没人认领的就是它了,还是MultiByteToWideChar函数,其实用的还是挺多的,有机会会去详细了解的,现在就算了
问题解决了,就这样吧,累了累了。。。
其实MultiByteToWideChar说简单也简单,说难也难,主要在于你是否搞清楚了字节长度、字符长度、宽字节这些问题,主要是空间开辟的够不够,会不会爆
2022-08-17(0810.。。)