【问题标题】:How to easily detect utf8 encoding in the string?如何轻松检测字符串中的utf8编码?
【发布时间】:2015-03-31 23:03:09
【问题描述】:

我有一个由其他程序的数据填充的字符串,这些数据可以是 UTF8 编码,也可以不是。因此,如果不能,我可以编码为 UTF8,但在 C++ 中检测 UTF8 的最佳方法是什么?我看到了这个变体https://stackoverflow.com/questions/...,但是有 cmets 说这个解决方案不能提供 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, '\0');

        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, '\0');
        WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(),
            utf16_str.length(), &utf8_str[0], utf8_size, 0, 0);

        text = utf8_str;
     }

或者上面的代码没有正确完成?我也在 Windows 7 中这样做。那么 Ubuntu 怎么样?这个变体在那里有效吗?

【问题讨论】:

    标签: c++ windows string encoding utf-8


    【解决方案1】:

    比较整个字节值不是检测 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;
    }
    

    【讨论】:

    • 如何 (*bytes & 0xE0) == 0xC0 给出从 0x80 到 0x7ff 的范围......???它应该给出从 0xc0 到 0xdf 的范围
    • @ahmedallam 不,我写的是正确的。查看 Wikipedia 上针对 UTF-8 描述的 bit pattern table。 Unicode 代码点 U+0080 到 U+07FF(不是字节 0xC0 到 0xDF)使用位模式110xxxxx 10xxxxxx 编码为 2 个字节。 0xE0 是位 11100000,0xC0 是位 11000000。所以,if ((*bytes &amp; 0xE0) == 0xC0)(*bytes &amp; 0x1F) 抓取低 5 位之前检查第一个字节的高 3 位是否为110。然后,((*bytes &amp; 0xC0) != 0x80)(*bytes &amp; 0x3F) 抓取低 6 位之前检查第 2 个字节的高 2 位是否为 10
    • @ahmedallam 似乎您需要重新了解位、位掩码和按位运算符的工作原理。
    • @RemyLebeau 这个异常/线程安全吗? (菜鸟问题)
    • @NorbertBoros 只要 string 参数指向一个有效的 C 风格的以空字符结尾的字符串,并且在函数运行时该内存没有被另一个线程修改或释放,那么是的,函数是安全的。否则,它的行为是不确定的。
    【解决方案2】:

    您可能不了解 UTF-8 及其替代方法。一个字节只有 256 个可能的值。考虑到字符数,这不是很多。因此,许多字节序列既是有效的 UTF-8 字符串,也是其他编码的有效字符串。

    事实上,每个 ASCII 字符串都是有意为有效的 UTF-8 字符串,其含义基本相同。您的代码将为ìs_utf8("Hello") 返回true

    甚至许多其他非 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 是在两种编码之间进行转换的常用方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-02
      • 2021-09-22
      • 1970-01-01
      • 1970-01-01
      • 2013-04-01
      • 1970-01-01
      • 2012-08-20
      • 1970-01-01
      相关资源
      最近更新 更多