如何轻松检测字符串中的utf8编码?
How to easily detect utf8 encoding in the string?
我有一个由其他程序的数据填充的字符串,这个数据可以是 UTF8 编码,也可以不是。因此,如果不是,我可以编码为 UTF8,但在 C++ 中检测 UTF8 的最佳方法是什么?我看到了这个变体 但有评论说这个解决方案没有提供 100% 的检测。因此,如果我对已经包含 UTF8 数据的 UTF8 字符串进行编码,那么我会将错误的文本写入数据库。
那么我可以只使用这个 UTF8 检测吗:
bool is_utf8(const char * string)
{
if(!string)
return 0;
const unsigned char * bytes = (const unsigned char *)string;
while(*bytes)
{
if( (// ASCII
// use bytes[0] <= 0x7F to allow ASCII control characters
bytes[0] == 0x09 ||
bytes[0] == 0x0A ||
bytes[0] == 0x0D ||
(0x20 <= bytes[0] && bytes[0] <= 0x7E)
)
) {
bytes += 1;
continue;
}
if( (// non-overlong 2-byte
(0xC2 <= bytes[0] && bytes[0] <= 0xDF) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF)
)
) {
bytes += 2;
continue;
}
if( (// excluding overlongs
bytes[0] == 0xE0 &&
(0xA0 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
) ||
(// straight 3-byte
((0xE1 <= bytes[0] && bytes[0] <= 0xEC) ||
bytes[0] == 0xEE ||
bytes[0] == 0xEF) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
) ||
(// excluding surrogates
bytes[0] == 0xED &&
(0x80 <= bytes[1] && bytes[1] <= 0x9F) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
)
) {
bytes += 3;
continue;
}
if( (// planes 1-3
bytes[0] == 0xF0 &&
(0x90 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
) ||
(// planes 4-15
(0xF1 <= bytes[0] && bytes[0] <= 0xF3) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
) ||
(// plane 16
bytes[0] == 0xF4 &&
(0x80 <= bytes[1] && bytes[1] <= 0x8F) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
)
) {
bytes += 4;
continue;
}
return 0;
}
return 1;
}
如果检测不正确,此编码为 UTF8 的代码:
string text;
if(!is_utf8(EscReason.c_str()))
{
int size = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, text.c_str(),
text.length(), 0, 0);
std::wstring utf16_str(size, '[=11=]');
MultiByteToWideChar(CP_ACP, MB_COMPOSITE, text.c_str(),
text.length(), &utf16_str[0], size);
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(),
utf16_str.length(), 0, 0, 0, 0);
std::string utf8_str(utf8_size, '[=11=]');
WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(),
utf16_str.length(), &utf8_str[0], utf8_size, 0, 0);
text = utf8_str;
}
或者上面的代码没有正确完成?我也在 Windows 7 中这样做。 Ubuntu 怎么样?这个变体在那里工作吗?
您可能不了解 UTF-8 及其替代方案。一个字节只有 256 个可能的值。考虑到字符数,这并不多。因此,许多字节序列都是有效的 UTF-8 字符串和其他编码中的有效字符串。
事实上,每个 ASCII 字符串都是有意为一个有效的 UTF-8 字符串,具有基本相同的含义。您的代码将 return true
for ìs_utf8("Hello")
.
甚至许多其他非 UTF8、非 ASCII 字符串都与有效的 UTF-8 字符串共享一个字节序列。如果不确切知道 是什么 类型的非 UTF-8 编码,就无法将非 UTF-8 字符串转换为 UTF-8。甚至 Latin-1 和 Latin-2 也已经完全不同了。 CP_ACP
甚至比 Latin-1 更糟糕,CP_ACP
甚至到处都不一样。
您的文本必须以 UTF-8 格式输入数据库。因此,如果它还不是 UTF-8,则必须对其进行转换,并且您必须知道确切的源编码。没有神奇的逃脱。
在 Linux 上,iconv
是在 2 种编码之间转换的常用方法。
比较整个字节值不是检测 UTF-8 的正确方法。您必须分析每个字节的实际位模式。 UTF-8 使用一种非常独特的位模式,没有其他编码使用。尝试更像这样的东西:
bool is_utf8(const char * string)
{
if (!string)
return true;
const unsigned char * bytes = (const unsigned char *)string;
int num;
while (*bytes != 0x00)
{
if ((*bytes & 0x80) == 0x00)
{
// U+0000 to U+007F
num = 1;
}
else if ((*bytes & 0xE0) == 0xC0)
{
// U+0080 to U+07FF
num = 2;
}
else if ((*bytes & 0xF0) == 0xE0)
{
// U+0800 to U+FFFF
num = 3;
}
else if ((*bytes & 0xF8) == 0xF0)
{
// U+10000 to U+10FFFF
num = 4;
}
else
return false;
bytes += 1;
for (int i = 1; i < num; ++i)
{
if ((*bytes & 0xC0) != 0x80)
return false;
bytes += 1;
}
}
return true;
}
现在,这不考虑非法的 UTF-8 序列,例如超长编码、UTF-16 代理项和高于 U+10FFFF 的代码点。如果你想确保 UTF-8 既有效又正确,你需要更像这样的东西:
bool is_valid_utf8(const char * string)
{
if (!string)
return true;
const unsigned char * bytes = (const unsigned char *)string;
unsigned int cp;
int num;
while (*bytes != 0x00)
{
if ((*bytes & 0x80) == 0x00)
{
// U+0000 to U+007F
cp = (*bytes & 0x7F);
num = 1;
}
else if ((*bytes & 0xE0) == 0xC0)
{
// U+0080 to U+07FF
cp = (*bytes & 0x1F);
num = 2;
}
else if ((*bytes & 0xF0) == 0xE0)
{
// U+0800 to U+FFFF
cp = (*bytes & 0x0F);
num = 3;
}
else if ((*bytes & 0xF8) == 0xF0)
{
// U+10000 to U+10FFFF
cp = (*bytes & 0x07);
num = 4;
}
else
return false;
bytes += 1;
for (int i = 1; i < num; ++i)
{
if ((*bytes & 0xC0) != 0x80)
return false;
cp = (cp << 6) | (*bytes & 0x3F);
bytes += 1;
}
if ((cp > 0x10FFFF) ||
((cp >= 0xD800) && (cp <= 0xDFFF)) ||
((cp <= 0x007F) && (num != 1)) ||
((cp >= 0x0080) && (cp <= 0x07FF) && (num != 2)) ||
((cp >= 0x0800) && (cp <= 0xFFFF) && (num != 3)) ||
((cp >= 0x10000) && (cp <= 0x1FFFFF) && (num != 4)))
return false;
}
return true;
}
我有一个由其他程序的数据填充的字符串,这个数据可以是 UTF8 编码,也可以不是。因此,如果不是,我可以编码为 UTF8,但在 C++ 中检测 UTF8 的最佳方法是什么?我看到了这个变体 但有评论说这个解决方案没有提供 100% 的检测。因此,如果我对已经包含 UTF8 数据的 UTF8 字符串进行编码,那么我会将错误的文本写入数据库。
那么我可以只使用这个 UTF8 检测吗:
bool is_utf8(const char * string)
{
if(!string)
return 0;
const unsigned char * bytes = (const unsigned char *)string;
while(*bytes)
{
if( (// ASCII
// use bytes[0] <= 0x7F to allow ASCII control characters
bytes[0] == 0x09 ||
bytes[0] == 0x0A ||
bytes[0] == 0x0D ||
(0x20 <= bytes[0] && bytes[0] <= 0x7E)
)
) {
bytes += 1;
continue;
}
if( (// non-overlong 2-byte
(0xC2 <= bytes[0] && bytes[0] <= 0xDF) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF)
)
) {
bytes += 2;
continue;
}
if( (// excluding overlongs
bytes[0] == 0xE0 &&
(0xA0 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
) ||
(// straight 3-byte
((0xE1 <= bytes[0] && bytes[0] <= 0xEC) ||
bytes[0] == 0xEE ||
bytes[0] == 0xEF) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
) ||
(// excluding surrogates
bytes[0] == 0xED &&
(0x80 <= bytes[1] && bytes[1] <= 0x9F) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
)
) {
bytes += 3;
continue;
}
if( (// planes 1-3
bytes[0] == 0xF0 &&
(0x90 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
) ||
(// planes 4-15
(0xF1 <= bytes[0] && bytes[0] <= 0xF3) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
) ||
(// plane 16
bytes[0] == 0xF4 &&
(0x80 <= bytes[1] && bytes[1] <= 0x8F) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
)
) {
bytes += 4;
continue;
}
return 0;
}
return 1;
}
如果检测不正确,此编码为 UTF8 的代码:
string text;
if(!is_utf8(EscReason.c_str()))
{
int size = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, text.c_str(),
text.length(), 0, 0);
std::wstring utf16_str(size, '[=11=]');
MultiByteToWideChar(CP_ACP, MB_COMPOSITE, text.c_str(),
text.length(), &utf16_str[0], size);
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(),
utf16_str.length(), 0, 0, 0, 0);
std::string utf8_str(utf8_size, '[=11=]');
WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(),
utf16_str.length(), &utf8_str[0], utf8_size, 0, 0);
text = utf8_str;
}
或者上面的代码没有正确完成?我也在 Windows 7 中这样做。 Ubuntu 怎么样?这个变体在那里工作吗?
您可能不了解 UTF-8 及其替代方案。一个字节只有 256 个可能的值。考虑到字符数,这并不多。因此,许多字节序列都是有效的 UTF-8 字符串和其他编码中的有效字符串。
事实上,每个 ASCII 字符串都是有意为一个有效的 UTF-8 字符串,具有基本相同的含义。您的代码将 return true
for ìs_utf8("Hello")
.
甚至许多其他非 UTF8、非 ASCII 字符串都与有效的 UTF-8 字符串共享一个字节序列。如果不确切知道 是什么 类型的非 UTF-8 编码,就无法将非 UTF-8 字符串转换为 UTF-8。甚至 Latin-1 和 Latin-2 也已经完全不同了。 CP_ACP
甚至比 Latin-1 更糟糕,CP_ACP
甚至到处都不一样。
您的文本必须以 UTF-8 格式输入数据库。因此,如果它还不是 UTF-8,则必须对其进行转换,并且您必须知道确切的源编码。没有神奇的逃脱。
在 Linux 上,iconv
是在 2 种编码之间转换的常用方法。
比较整个字节值不是检测 UTF-8 的正确方法。您必须分析每个字节的实际位模式。 UTF-8 使用一种非常独特的位模式,没有其他编码使用。尝试更像这样的东西:
bool is_utf8(const char * string)
{
if (!string)
return true;
const unsigned char * bytes = (const unsigned char *)string;
int num;
while (*bytes != 0x00)
{
if ((*bytes & 0x80) == 0x00)
{
// U+0000 to U+007F
num = 1;
}
else if ((*bytes & 0xE0) == 0xC0)
{
// U+0080 to U+07FF
num = 2;
}
else if ((*bytes & 0xF0) == 0xE0)
{
// U+0800 to U+FFFF
num = 3;
}
else if ((*bytes & 0xF8) == 0xF0)
{
// U+10000 to U+10FFFF
num = 4;
}
else
return false;
bytes += 1;
for (int i = 1; i < num; ++i)
{
if ((*bytes & 0xC0) != 0x80)
return false;
bytes += 1;
}
}
return true;
}
现在,这不考虑非法的 UTF-8 序列,例如超长编码、UTF-16 代理项和高于 U+10FFFF 的代码点。如果你想确保 UTF-8 既有效又正确,你需要更像这样的东西:
bool is_valid_utf8(const char * string)
{
if (!string)
return true;
const unsigned char * bytes = (const unsigned char *)string;
unsigned int cp;
int num;
while (*bytes != 0x00)
{
if ((*bytes & 0x80) == 0x00)
{
// U+0000 to U+007F
cp = (*bytes & 0x7F);
num = 1;
}
else if ((*bytes & 0xE0) == 0xC0)
{
// U+0080 to U+07FF
cp = (*bytes & 0x1F);
num = 2;
}
else if ((*bytes & 0xF0) == 0xE0)
{
// U+0800 to U+FFFF
cp = (*bytes & 0x0F);
num = 3;
}
else if ((*bytes & 0xF8) == 0xF0)
{
// U+10000 to U+10FFFF
cp = (*bytes & 0x07);
num = 4;
}
else
return false;
bytes += 1;
for (int i = 1; i < num; ++i)
{
if ((*bytes & 0xC0) != 0x80)
return false;
cp = (cp << 6) | (*bytes & 0x3F);
bytes += 1;
}
if ((cp > 0x10FFFF) ||
((cp >= 0xD800) && (cp <= 0xDFFF)) ||
((cp <= 0x007F) && (num != 1)) ||
((cp >= 0x0080) && (cp <= 0x07FF) && (num != 2)) ||
((cp >= 0x0800) && (cp <= 0xFFFF) && (num != 3)) ||
((cp >= 0x10000) && (cp <= 0x1FFFFF) && (num != 4)))
return false;
}
return true;
}