#include <algorithm>
class CNotePadDlg : public CDialogEx
{
private:
void AddMainMenu();
void MoveEditContrl(int cx, int cy);
CString GetDragFileName(HDROP hDrop);
BOOL IsTextUnchanged();
CString GetTitle() const;
void ReadText(const CString& strFileName);
private: // 转码函数
void BEToLE(char* pStr, int nLength);
int GetCodePage(unsigned char* pStr);
BOOL CheckUtf8WithoutBom(unsigned char* pStr);
BOOL CheckUnicode(unsigned char* pStr, int nBytes);
LPWSTR ToUnicode(const char* pStr, DWORD nCode);
private:
enum class CodeType { ANSI = 0x01, UTF_BE, UTF_LE, UTF8, UTF8_BOM };
static TCHAR BASED_CODE m_szFilter[];
CString m_sFile = _T("");
DWORD m_nCode = 0;
};
TCHAR BASED_CODE CNotePadDlg::m_szFilter[] =
_T("Char Files (*.txt; *.ini; *.inf)\0*.txt; *.ini; *.inf\0")
_T("All Files (*.*)\0*.*\0\0");
//////////////////////////////////////
// 编码转化相关函数族
void CNotePadDlg::BEToLE(char* pStr, int nLength)
{
for (char* p = pStr + nLength - 2; p >= pStr; p -= 2)
std::swap(*p, *(p + 1));
}
LPWSTR CNotePadDlg::ToUnicode(const char* pStr, DWORD nCode)
{
int nBytes = MultiByteToWideChar(nCode, 0, pStr, -1, NULL, 0);
auto p = new WCHAR[nBytes + 1];
MultiByteToWideChar(nCode, 0, pStr, -1, p, nBytes);
p[nBytes] = '\0';
return p;
}
BOOL CNotePadDlg::CheckUnicode(unsigned char* pStr, int nBytes)
{
if (pStr[0] == 0xFE && pStr[1] == 0xFF)
{
BEToLE((char*)pStr, nBytes);
m_nCode = (DWORD)CodeType::UTF_BE;
return TRUE;
}
else if (pStr[0] == 0xFF && pStr[1] == 0xFE)
{
m_nCode = (DWORD)CodeType::UTF_LE;
return TRUE;
}
return FALSE;
}
int CNotePadDlg::GetCodePage(unsigned char* pStr)
{
if ((pStr[0] == 0xEF && pStr[1] == 0xBB && pStr[2] == 0xBF) || CheckUtf8WithoutBom(pStr))
{
(pStr[0] == 0xEF && pStr[1] == 0xBB && pStr[2] == 0xBF)
? m_nCode = (DWORD)CodeType::UTF8_BOM
: m_nCode = (DWORD)CodeType::UTF8;
return CP_UTF8;
}
m_nCode = (DWORD)CodeType::ANSI;
return CP_ACP;
}
/*
unicode符号范围 16进制 | TUF8编码方式 2进制
----------------------------------------------------------------------------------------
1 | 0000 0000 0000 007F | 0XXXXXXX
2 | 0000 0000 0000 07FF | 110XXXXX 10XXXXXX
3 | 0000 0000 0000 FFFF | 1110XXXX 10XXXXXX 10XXXXXX
4 | 0000 0000 0010 FFFF | 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
5 | 0000 0000 03FF FFFF | 111110XX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX
6 | 0000 0000 7FFF FFFF | 1111110X 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX
按字节顺序遍历字符串 (使用循环的原因: 要按字节为单位检测完所有字节才能确定该字符串是否为TUF编码的字符串)
1) 单字节的UTF8字符, 字节的最高位为0, 继续检测相邻的下一个字节的最高位
2) 获取一个UTF8字符的长度, 单位为字节 (使用循环的原因: 不清楚当前TUF8下的字符占用一个字节还是多个字节)
3) UTF8字符是单个字节, 错误的UTF8编码
4) 检测一个UTF8字符的非首字节,最高两位(截取的掩码是0xC0),不等于0x80, 错误的UTF8编码 (使用循环的原因: 不清楚当前TUF8下的字符出来首字节外还占用了几个字节)
循环结束, 正确的UTF8编码
*/
BOOL CNotePadDlg::CheckUtf8WithoutBom(unsigned char* pStr)
{
auto p = pStr;
int nCount = 0; // 字符的个数
int nLength = strlen((LPCSTR)pStr); // 原始字符串的字节长度
// 按字节顺序遍历字符串
while (*p) // '\0'是字符串的结束标记
{
auto c = *p++; // 获取当前字节, 指针指向相邻的下一个字节
// 1) 单字节的UTF8字符, 字节的最高位为0, 继续检测相邻的下一个字节的最高位, 默认单字节的字符为UTF8编码的字符
if ((c & 0x80) == 0)
if (++nCount == nLength) // 区分ANSI与UTF8编码, ANSI的字符总个数等于字节总数, UTF8的字符总数不等于字节总数
return FALSE;
else continue;
// 2) 获取一个UTF8字符的长度, 单位为字节
int nBytes = 0; // 一个UTF8字符的长度, 单位为字节
do nBytes++; while ((c = c << 1) & 0x80); // 当前字节左移一位后,最高位为0, 停止循环
if (nBytes == 1) return FALSE; // 单字节的UTF8在前面处理过. 这里出现就是缺损的编码字符,错误的UTF8编码
// 4) 检测一个UTF8字符的非首字节,最高两位(截取的掩码是0xC0),不等于二进制的10(0x80), 就是错误的UTF8编码
for (int i = 1; i < nBytes; i++) // 检测字符串中某一个UTF8字符的非首字节
if ((*p++ & 0xC0) != 0x80)
return FALSE;
}
// 循环结束, 正确的UTF8编码
return TRUE;
}
void CNotePadDlg::ReadText(const CString& strFileName)
{
CFile file;
int nReadMask = CFile::modeRead | OF_SHARE_DENY_WRITE;
if (!file.Open(strFileName, nReadMask, NULL))
{
MessageBox(_T("打开文件失败"));
return;
}
int nBytes = 0;
if ((nBytes = (int)file.GetLength()) <= 0)
{
MessageBox(_T("打开空文件"));
SetDlgItemText(IDC_TEXT, _T(""));
return;
}
unsigned char* p = new unsigned char[nBytes + 2];
nBytes = file.Read(p, nBytes);
p[nBytes] = p[nBytes + 1] = 0;
if (CheckUnicode(p, nBytes)) // 检查是否为UTF-16 BE 或者是UTF-16 LE
SetDlgItemText(IDC_TEXT, (LPTSTR)p);
else
{
int nCodePage = GetCodePage(p); // 在UTF8与ANSI中做区别
if (nCodePage != CP_ACP && nCodePage != CP_UTF8) return; // 不是UTF8或ANSI
LPWSTR pText = ToUnicode((const char*)p, nCodePage);
if (!pText) return;
SetDlgItemText(IDC_TEXT, (LPCTSTR)pText);
}
if (!p) delete[]p;
}