Подтвердить что ты не робот

Как обнаружить UTF-8 в простой C?

Я ищу фрагмент кода в обычном старом 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")
}
4b9b3361

Ответ 1

Здесь (без надежды) реализация это выражение в простой 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).

Кроме того, даже после исправления этого путем внесения изменений, упомянутых в комментарии, он по-прежнему принимает нулевое завершение, что предотвращает вложение символов NUL, хотя оно должно быть технически законным.

Когда я занимался созданием собственной библиотеки строк, я пошел с измененным UTF-8 (то есть кодировал NUL в качестве последовательности двухбайтовых последовательностей), не стесняйтесь использовать этот заголовок в качестве шаблона для предоставления процедуры проверки, которая не страдает от вышеуказанных недостатков.

Ответ 2

Этот декодер Bjoern Hoermann является самым простым, что я нашел. Он также работает, подавая ему один байт, а также сохраняя состояние. Состояние очень полезно для разбора UTF8, входящего в куски по сети.

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

// Copyright (c) 2008-2009 Bjoern Hoehrmann <[email protected]>
// 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");
}

Ответ 3

Вы не можете определить, является ли данная строка (или байтовая последовательность) кодированным текстом UTF-8, например, каждая серия октетов UTF-8 также является допустимой (если не бессмысленной) серией латинского-1 (или некоторой другое кодирование) октетов. Однако не все серии действительных латинских-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   

Ответ 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 или любой другой дюжиной кодировок символов, что делает жизнь программы трудно..?

Ответ 5

Вы можете использовать детектор UTF-8, встроенный в Firefox. Он находится в универсальном детекторе кодировки и в значительной степени поддерживает библиотеку С++. Было бы очень легко найти класс, который распознает UTF-8 и возьмет только это.
То, что этот класс в основном делает, это обнаружить последовательности символов, которые уникальны для UTF-8.

  • получить последнюю версию firefox
  • перейти к \mozilla\extensions\universalchardet\
  • найти класс детектора UTF-8 (я не совсем помню, что это точное имя)

Ответ 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 и ничего не поддерживает"; так что вам не нужно делать изворотливые догадки на первом месте.

Ответ 7

3 случайных байта, по-видимому, имеют вероятность 15.8% быть действительной UTF-8 согласно моим расчетам:

128 ^ 3 возможных ASCII-последовательностей = 2097152

2 ^ 16-2 ^ 11 возможных 3-байтных символов UTF-8 (это предполагает суррогатные пары и нехарактеры) = 63488

1920 2-байтовые символы UTF-8 либо до, либо после символа ASCII = 1920 * 128 * 2 = 524288

Разделить на число 3-байтовых последовательностей = (2097152 + 63488 + 491520)/16777216.0 = 0,1580810546875

IMHO это сильно переоценивает количество неправильных совпадений, потому что файл имеет длину всего 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. Это очень сложно, что указывает на то, что это не проблема с реальными данными.

Ответ 8

В основном я проверяю, соответствует ли данный ключ (строка максимум 4 символа) формату из этой ссылки: http://www.fileformat.info/info/unicode/utf8.htm p >

/*
** 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

Ответ 9

В приведенной ниже программе читаются строки utf-8 (ascii, non ascii chars like euro и т.д.) от stdin. Каждая строка передается func_find_utf8. Поскольку символы utf-8 имеют многобайтовые символы, функция func_find_utf8 проверяет бит char, чтобы найти символ whetehr is ascii или не-ascii. Если charcter не является 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);

}