如何检测用标准C UTF-8?如何检测用标准C UTF-8?(How to detect UTF-8

2019-05-13 15:45发布

我要寻找一个在普通的旧的C代码段检测到给定的字符串是UTF-8编码。 我知道用正则表达式的解决方案,但由于种种原因,效果会更好,以避免在这种特殊情况下使用什么,但纯C。

与正则表达式的解决方案看起来像这样(警告:各种检查省略):

#define UTF8_DETECT_REGEXP  "^([\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$"

const char *error;
int         error_off;
int         rc;
int         vect[100];

utf8_re = pcre_compile(UTF8_DETECT_REGEXP, PCRE_CASELESS, &error, &error_off, NULL);
utf8_pe = pcre_study(utf8_re, 0, &error);

rc = pcre_exec(utf8_re, utf8_pe, str, len, 0, 0, vect, sizeof(vect)/sizeof(vect[0]));

if (rc > 0) {
    printf("string is in UTF8\n");
} else {
    printf("string is not in UTF8\n")
}

Answer 1:

这里有一个(希望没有bug的)实现的这种表达在纯C:

_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;
}

请注意,这是由W3C进行表单验证建议的正则表达式,确实拒绝一些有效UTF-8序列的忠实翻译(特别是那些含有ASCII控制字符)。

此外,即使通过在注释中的变化修正此之后,它仍然假设零终止,这将阻止嵌入NULL字符,但它应该在技术上是合法的。

当我创造我自己的字符串库涉足,我用UTF-8修改去(即编码NUL作为一个超长的两个字节) -随意使用这个头作为模板来提供验证例程不患上述缺点。



Answer 2:

该解码器由比约恩德国霍曼集团是我发现最简单的。 它也可以通过喂养它一个字节,以及保持状态。 国家是解析UTF8未来在通过网络块非常有用的。

http://bjoern.hoehrmann.de/utf-8/decoder/dfa/

// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.

#define UTF8_ACCEPT 0
#define UTF8_REJECT 1

static const uint8_t utf8d[] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
  8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
  0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
  0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
  0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
  1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
  1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
  1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
};

uint32_t inline
decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
  uint32_t type = utf8d[byte];

  *codep = (*state != UTF8_ACCEPT) ?
    (byte & 0x3fu) | (*codep << 6) :
    (0xff >> type) & (byte);

  *state = utf8d[256 + *state*16 + type];
  return *state;
}

一个简单的验证器/检测器并不需要的代码点,所以它可以被(初始状态被设置成这样写UTF8_ACCEPT ):

uint32_t validate_utf8(uint32_t *state, char *str, size_t len) {
   size_t i;
   uint32_t type;

    for (i = 0; i < len; i++) {
        // We don't care about the codepoint, so this is
        // a simplified version of the decode function.
        type = utf8d[(uint8_t)str[i]];
        *state = utf8d[256 + (*state) * 16 + type];

        if (*state == UTF8_REJECT)
            break;
    }

    return *state;
}

如果文字是有效的UTF8 UTF8_ACCEPT返回。 如果无效UTF8_REJECT 。 如果需要更多的数据,返回一些其他的整数。

在组块馈送数据使用实例(例如,从网络):

char buf[128];
size_t bytes_read;
uint32_t state = UTF8_ACCEPT;

// Validate the UTF8 data in chunks.
while ((bytes_read = get_new_data(buf, sizeof(buf))) {
    if (validate_utf8(&state, buf, bytes_read) == UTF8_REJECT)) {
        fprintf(stderr, "Invalid UTF8 data!\n");
        return -1;
    }
}

// If everything went well we should have proper UTF8,
// the data might instead have ended in the middle of a UTF8
// codepoint.
if (state != UTF8_ACCEPT) {
    fprintf(stderr, "Invalid UTF8, incomplete codepoint\n");
}


Answer 3:

不能检测一个给定字符串(或字节序列)是UTF-8编码的文本,例如每个系列UTF-8的八位位组也有效(如果无意义)系列的Latin-1的(或一些其它的编码)字节。 然而并不是每一个系列有效的Latin-1个字节的有效UTF-8系列。 所以,你可以排除不符合UTF-8编码模式的字符串:

U+0000-U+007F    0xxxxxxx
U+0080-U+07FF    110yyyxx    10xxxxxx
U+0800-U+FFFF    1110yyyy    10yyyyxx    10xxxxxx
U+10000-U+10FFFF 11110zzz    10zzyyyy    10yyyyxx    10xxxxxx   


Answer 4:

你不得不解析字符串为UTF-8,看http://www.rfc-editor.org/rfc/rfc3629.txt这很简单。 如果解析失败,这不是UTF-8。 有几个简单的UTF-8的周围,图书馆可以做到这一点。

这也许可以,如果你知道字符串为老式ASCII 简化它包含外ASCII字符,这是UTF-8编码。 在这种情况下,你往往不需要关心的差别,UTF-8的设计是一个能够处理ASCII的现有方案,可在大多数情况下透明地处理UTF-8。

请记住,ASCII是UTF-8编码本身,所以ASCII是有效的UTF-8。

AC字符串可以是任何东西,就是你需要解决的是你不知道的内容是ASCII问题,GB 2312,CP437,UTF-16,或任何其他十几个字符编码,使一个项目生活困难的.. ?



Answer 5:

您可以使用集成到Firefox UTF-8探测器 。 它在通用字符检测器和其几乎是一个沿着C ++库立场发现。 它应该是非常容易找到类识别UTF-8,并采取只。
这是什么类通常做的就是检测所特有的UTF-8字符序列。

  • 获得最新的Firefox干线
  • 去\ Mozilla的\分机\ universalchardet \
  • 找到UTF-8探测器级(我不太记得什么是它的确切名称)


Answer 6:

这是不可能检测到给定的字节阵列是UTF-8字符串。 您可以可靠地确定,它不能有效UTF-8(这并不意味着它不是无效的UTF-8); 你能确定,它可能是一个有效的UTF-8序列,但是这是受误报。

对于一个简单的例子,使用一个随机数生成器生成的3个随机字节的数组,并用它来测试代码。 这些都是随机字节,因此不是UTF-8,所以每次你的代码串认为是“可能是UTF-8”是假阳性。 我的猜测是,(在这些条件下),你的代码将是错误的时代结束了12%。

一旦你认识到,这是不可能的,你可以开始考虑(你的预测除)返回的置信水平。 例如,你的函数可能会返回类似“我88%肯定这是UTF-8”。

现在做这个的所有其他类型的数据。 例如,您可能有检查,如果数据是UTF-16可能返回“我95%确信,这是UTF-16”的功能,然后决定(因为95%大于88%),这是更有可能的是数据是UTF-16,而不是UTF-8。

接下来的一步是添加技巧,以增加可信度。 例如,如果字符串似乎大多含有组由空格分隔的合法音节,那么你可以有很多更自信,它实际上是UTF-8。 以同样的方式,如果数据可能是HTML,那么你可以检查东西,可能是有效的HTML标记,并用它来增加你的信心。

当然,同样适用于其他类型的数据。 例如,如果数据有一个有效的PE32或ELF头,或正确的BMP或JPG或MP3头,那么你可以有更多信心,这不是UTF-8的。

一个更好的办法是解决问题的实际原因。 例如,有可能以某种“文件类型”标识的添加到你关心的所有文件的开始,或者说“这个软件假定UTF-8,不支持任何东西”; 这样你就不需要做狡猾的猜测在首位。



Answer 7:

3个随机字节似乎有被根据我的计算有效的UTF-8 15.8%的机会:

128 ^ 3可能ASCII仅序列= 2097152

2 ^ 16-2 ^ 11个可能的3-字节UTF-8字符(这是假设代理对和noncharacters被允许)= 63488

1920 2个字节的UTF-8字符之前或ASCII字符后= 1920 * 128 * 2 = 524288

由3字节的序列数=除法(2097152 + 63488 + 491520)/16777216.0 = 0.1580810546875

恕我直言,这是大大高估不正确匹配的数量,因为文件只有3个字节。 交点去的方式向下作为字节的数量增加。 此外,在非UTF-8实际文本是不是随机的,没有与高位集,这是不合法的UTF-8大量孤独的字节数。

对于猜测故障的几率更有用的度量是与高比特组的字节序列的可能性有多大是有效的UTF-8。 我得到这些值:

1 byte = 0% # the really important number that is often ignored
2 byte = 11.7%
3 byte = 3.03% (assumes surrogate halves are valid)
4 byte = 1.76% (includes two 2-byte characters)

这也是有益的尝试找到一个实际的可读字符串(在任何语言和任何编码),这也是一个有效的UTF-8字符串。 这是非常非常困难的,这表明这不是真实的数据有问题。



Answer 8:

基本上我检查给定的密钥(最多4个字符的字符串)从该链接的格式相匹配: http://www.fileformat.info/info/unicode/utf8.htm

/*
** Checks if the given string has all bytes like: 10xxxxxx
** where x is either 0 or 1
*/

static int      chars_are_folow_uni(const unsigned char *chars)
{
    while (*chars)
    {
        if ((*chars >> 6) != 0x2)
            return (0);
        chars++;
    }
    return (1);
}

int             char_is_utf8(const unsigned char *key)
{
    int         required_len;

    if (key[0] >> 7 == 0)
        required_len = 1;
    else if (key[0] >> 5 == 0x6)
        required_len = 2;
    else if (key[0] >> 4 == 0xE)
        required_len = 3;
    else if (key[0] >> 5 == 0x1E)
        required_len = 4;
    else
        return (0);
    return (strlen(key) == required_len && chars_are_folow_uni(key + 1));
}

对我的作品罚款:

unsigned char   buf[5];

ft_to_utf8(L'歓', buf);
printf("%d\n", char_is_utf8(buf)); // => 1


Answer 9:

我知道这是一个古老的线程,但我想我会在这里发布我的解决方案,因为我认为这是在@Christoph的妙解(我upvoted)的改善。

我不是专家,所以我可能读了RFC错了,但在我看来,一个32字节的地图可以用来代替一个256字节的地图,既节省内存和时间。

这使我一个简单的宏,通过一个UTF8字符前进一个字符串指针,存储在32位有符号整数UTF8码点和存储值-1在错误的情况下。

下面是用一些意见的代码。

#include <stdint.h>
/**
 * Maps the last 5 bits in a byte (0b11111xxx) to a UTF-8 codepoint length.
 *
 * Codepoint length 0 == error.
 *
 * The first valid length can be any value between 1 to 4 (5== error).
 *
 * An intermidiate (second, third or forth) valid length must be 5.
 *
 * To map was populated using the following Ruby script:
 *
 *      map = []; 32.times { map << 0 }; (0..0b1111).each {|i| map[i] = 1} ;
 *      (0b10000..0b10111).each {|i| map[i] = 5} ;
 *      (0b11000..0b11011).each {|i| map[i] = 2} ;
 *      (0b11100..0b11101).each {|i| map[i] = 3} ;
 *      map[0b11110] = 4; map;
 */
static uint8_t fio_str_utf8_map[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                                     1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5,
                                     5, 5, 2, 2, 2, 2, 3, 3, 4, 0};

/**
 * Advances the `ptr` by one utf-8 character, placing the value of the UTF-8
 * character into the i32 variable (which must be a signed integer with 32bits
 * or more). On error, `i32` will be equal to `-1` and `ptr` will not step
 * forwards.
 *
 * The `end` value is only used for overflow protection.
 */
#define FIO_STR_UTF8_CODE_POINT(ptr, end, i32)                                 \
  switch (fio_str_utf8_map[((uint8_t *)(ptr))[0] >> 3]) {                      \
  case 1:                                                                      \
    (i32) = ((uint8_t *)(ptr))[0];                                             \
    ++(ptr);                                                                   \
    break;                                                                     \
  case 2:                                                                      \
    if (((ptr) + 2 > (end)) ||                                                 \
        fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5) {                   \
      (i32) = -1;                                                              \
      break;                                                                   \
    }                                                                          \
    (i32) =                                                                    \
        ((((uint8_t *)(ptr))[0] & 31) << 6) | (((uint8_t *)(ptr))[1] & 63);    \
    (ptr) += 2;                                                                \
    break;                                                                     \
  case 3:                                                                      \
    if (((ptr) + 3 > (end)) ||                                                 \
        fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 ||                   \
        fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5) {                   \
      (i32) = -1;                                                              \
      break;                                                                   \
    }                                                                          \
    (i32) = ((((uint8_t *)(ptr))[0] & 15) << 12) |                             \
            ((((uint8_t *)(ptr))[1] & 63) << 6) |                              \
            (((uint8_t *)(ptr))[2] & 63);                                      \
    (ptr) += 3;                                                                \
    break;                                                                     \
  case 4:                                                                      \
    if (((ptr) + 4 > (end)) ||                                                 \
        fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 ||                   \
        fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5 ||                   \
        fio_str_utf8_map[((uint8_t *)(ptr))[3] >> 3] != 5) {                   \
      (i32) = -1;                                                              \
      break;                                                                   \
    }                                                                          \
    (i32) = ((((uint8_t *)(ptr))[0] & 7) << 18) |                              \
            ((((uint8_t *)(ptr))[1] & 63) << 12) |                             \
            ((((uint8_t *)(ptr))[2] & 63) << 6) |                              \
            (((uint8_t *)(ptr))[3] & 63);                                      \
    (ptr) += 4;                                                                \
    break;                                                                     \
  default:                                                                     \
    (i32) = -1;                                                                \
    break;                                                                     \
  }

/** Returns 1 if the String is UTF-8 valid and 0 if not. */
inline static size_t fio_str_utf8_valid2(char const *str, size_t length) {
  if (!str)
    return 0;
  if (!length)
    return 1;
  const char *const end = str + length;
  int32_t c = 0;
  do {
    FIO_STR_UTF8_CODE_POINT(str, end, c);
  } while (c > 0 && str < end);
  return str == end && c >= 0;
}


Answer 10:

以下程序读取从stdin UTF-8字符串(ASCII,非ASCII字符像欧元等...)。 每行传递给func_find_utf8。 为UTF-8字符是多字节字符,func_find_utf8检查炭比特找到whetehr字符是ASCII或非ASCII的功能。 如果字符内的非ASCII,知道字节的宽度。 通过字节的宽度和定位它发现在功能print_non_ascii。

#include<stdio.h>

#include<string.h>

/* UTF-8 : BYTE_BITS*/

/* B0_BYTE : 0XXXXXXX */

/* B1_BYTE : 10XXXXXX */

/* B2_BYTE : 110XXXXX */

/* B3_BYTE : 1110XXXX */

/* B4_BYTE : 11110XXX */

/* B5_BYTE : 111110XX */

/* B6_BYTE : 1111110X */

#define B0_BYTE 0x00

#define B1_BYTE 0x80

#define B2_BYTE 0xC0

#define B3_BYTE 0xE0

#define B4_BYTE 0xF0

#define B5_BYTE 0xF8

#define B6_BYTE 0xFC

#define B7_BYTE 0xFE

/* Please tune this as per number of lines input */

#define MAX_UTF8_STR 10

/* 600 is used because 6byteX100chars */

#define MAX_UTF8_CHR 600

void func_find_utf8 (char *ptr_to_str);

void print_non_ascii (int bytes, char *pbyte);

char strbuf[MAX_UTF8_STR][MAX_UTF8_CHR];

int
main (int ac, char *av[])
{

  int i = 0;

  char no_newln_str[MAX_UTF8_CHR];

  i = 0;

  printf ("\n\nYou can enter utf-8 string or Q/q to QUIT\n\n");

  while (i < MAX_UTF8_STR)
    {

      fgets (strbuf[i], MAX_UTF8_CHR, stdin);

      if (!strlen (strbuf[i]))
    break;

      if ((strbuf[i][0] == 'Q') || (strbuf[i][0] == 'q'))
    break;

      strcpy (no_newln_str, strbuf[i]);

      no_newln_str[strlen (no_newln_str) - 1] = 0;

      func_find_utf8 (no_newln_str);

      ++i;

    }

  return 1;

}

void
func_find_utf8 (char *ptr_to_str)
{

  int found_non_ascii;

  char *pbyte;

  pbyte = ptr_to_str;

  found_non_ascii = 0;

  while (*pbyte)
    {

      if ((*pbyte & B1_BYTE) == B0_BYTE)
    {

      pbyte++;

      continue;

    }

      else
    {

      found_non_ascii = 1;

      if ((*pbyte & B7_BYTE) == B6_BYTE)
        {

          print_non_ascii (6, pbyte);

          pbyte += 6;

          continue;

        }

      if ((*pbyte & B6_BYTE) == B5_BYTE)
        {

          print_non_ascii (5, pbyte);

          pbyte += 5;

          continue;

        }

      if ((*pbyte & B5_BYTE) == B4_BYTE)
        {

          print_non_ascii (4, pbyte);

          pbyte += 4;

          continue;

        }

      if ((*pbyte & B4_BYTE) == B3_BYTE)
        {

          print_non_ascii (3, pbyte);

          pbyte += 3;

          continue;

        }

      if ((*pbyte & B3_BYTE) == B2_BYTE)
        {

          print_non_ascii (2, pbyte);

          pbyte += 2;

          continue;

        }

    }

    }

  if (found_non_ascii)
    printf (" These are Non Ascci chars\n");

}

void
print_non_ascii (int bytes, char *pbyte)
{

  char store[6];

  int i;

  memset (store, 0, 6);

  memcpy (store, pbyte, bytes);

  i = 0;

  while (i < bytes)
    printf ("%c", store[i++]);

  printf ("%c", ' ');

  fflush (stdout);

}


文章来源: How to detect UTF-8 in plain C?
标签: c utf-8