programing

평원 C에서 UTF-8을 탐지하는 방법은?

yellowcard 2023. 10. 27. 21:51
반응형

평원 C에서 UTF-8을 탐지하는 방법은?

나는 주어진 문자열이 UTF-8 인코딩임을 감지하는 평이한 오래된 C에서 코드 스니펫을 찾고 있습니다.regex로 해결하는 방법은 알고 있지만, 여러 가지 이유로 이 경우에는 일반적인 C 이외의 것은 사용하지 않는 것이 좋을 것 같습니다.

regex를 사용하는 솔루션은 다음과 같습니다(경고: 다양한 검사 생략).

#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")
}

플레인 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 <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;
}

단순한 validator/detector는 코드 포인트가 필요 없기 때문에 이렇게 쓸 수 있습니다(초기 상태는 다음과 같이 설정됨).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;
}

텍스트가 유효한 경우 utf8UTF8_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");
}

주어진 문자열(또는 바이트 시퀀스)이 예를 들어 각각의 UTF-8 옥텟과 같은 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   

문자열을 UTF-8로 구문 분석해야 합니다. http://www.rfc-editor.org/rfc/rfc3629.txt 매우 간단합니다.파싱이 실패하면 UTF-8이 아닙니다.이것을 할 수 있는 간단한 UTF-8 라이브러리가 주변에 몇 개 있습니다.

문자열이 기존의 일반 ASCII이거나 UTF-8 인코딩된 ASCII 외부의 문자를 포함하고 있다는 것을 알고 있다면 단순화할 수 있습니다.이런 경우에는 차이점을 신경 쓸 필요가 없는 경우가 많습니다. UTF-8의 설계는 ASCII를 처리할 수 있는 기존 프로그램이 대부분의 경우 투명하게 UTF-8을 처리할 수 있다는 것이었습니다.

ASCII는 UTF-8 자체로 인코딩되어 있으므로 ASCII는 유효한 UTF-8입니다.

C 문자열은 무엇이든 될 수 있습니다. 콘텐츠가 ASCII, GB 2312, CP437, UTF-16인지 아니면 프로그램을 힘들게 만드는 다른 12개의 문자 인코딩인지 알 수 없는 문제를 해결해야 합니까?

주어진 바이트 배열이 UTF-8 문자열이라는 것을 감지하는 것은 불가능합니다.유효한 UTF-8이 될 수 없다는 것을 확실하게 판단할 수 있으며(유효하지 않은 UTF-8이 아님을 의미하는 것은 아님), 유효한 UTF-8 시퀀스일 수 있지만 이는 위양성이 될 수 있다고 판단할 수 있습니다.

간단한 예를 들어 난수 생성기를 사용하여 3개의 난수 바이트 배열을 생성하고 이를 사용하여 코드를 테스트합니다.이들은 무작위 바이트이므로 UTF-8이 아니므로 코드가 "UTF-8일 가능성이 있다"고 생각하는 모든 문자열은 거짓 양성입니다.제 추측으로는 (이런 상황에서) 당신의 코드가 12% 이상 틀릴 것입니다.

그것이 불가능하다는 것을 알게 되면, (자신의 예측과 더불어) 신뢰 수준을 반환하는 것에 대해 생각해 볼 수 있습니다.예를 들어, 함수가 "이것이 UTF-8이라고 88% 확신합니다"와 같은 것을 반환할 수 있습니다.

이제 다른 모든 유형의 데이터에 대해 이 작업을 수행합니다.예를 들어, "이것이 UTF-16이라고 95% 확신합니다"라고 반환하는 데이터가 UTF-16인지 확인한 다음 (95%가 88%보다 높기 때문에) 데이터가 UTF-8이 아니라 UTF-16이었을 가능성이 더 높다고 판단하는 기능이 있을 수 있습니다.

다음 단계는 신뢰 수준을 높이기 위한 요령을 추가하는 것입니다.예를 들어 문자열이 대부분 공백으로 구분된 유효 음절 그룹을 포함하는 것처럼 보인다면 실제로 UTF-8이라고 훨씬 더 확신할 수 있습니다. 같은 방식으로 데이터가 HTML일 수 있다면 유효한 HTML 마크업을 확인하고 이를 사용하여 자신감을 높일 수 있습니다.

물론 다른 유형의 데이터에도 동일하게 적용됩니다.예를 들어, 데이터에 유효한 PE32 또는 ELF 헤더 또는 올바른 BMP 또는 JPG 또는 MP3 헤더가 있으면 UTF-8이 아니라고 훨씬 더 확신할 수 있습니다.

훨씬 더 나은 접근법은 문제의 실제 원인을 고치는 것입니다.예를 들어, 관심 있는 모든 파일의 시작 부분에 일종의 "문서 유형" 식별자를 추가하거나, "이 소프트웨어는 UTF-8을 가정하고 다른 어떤 것도 지원하지 않습니다"라고 말할 수 있습니다. 이렇게 하면 우선 의심스러운 추측을 할 필요가 없습니다.

Firefox에 통합된 UTF-8 디텍터를 사용할 수 있습니다.범용 문자 집합 감지기에서 볼 수 있으며 C++ 라이브러리를 따라 설치되어 있습니다.UTF-8을 인식하는 수업을 찾기가 매우 쉬워야 합니다.
이 클래스는 기본적으로 UTF-8에 고유한 문자 시퀀스를 탐지합니다.

  • 최신 파이어폭스 트렁크를 가져옵니다.
  • \mozilla\extensions\universal chardet\로 이동합니다.
  • UTF-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

제 계산에 따르면 3개의 랜덤 바이트는 유효한 UTF-8일 확률이 15.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)/1677216.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 문자열인 실제 읽기 쉬운 문자열(언어 및 인코딩)을 찾는 것도 유용합니다.이것은 매우 어렵기 때문에 실제 데이터의 문제가 아님을 알 수 있습니다.

오래된 스레드라는 것을 알고 있지만 @Christoph의 멋진 솔루션(업보트)에 비해 개선된 것 같아 여기에 제 솔루션을 게시하려고 생각했습니다.

저는 전문가가 아니라서 RFC를 잘못 읽었을 수도 있지만 256바이트 맵 대신 32바이트 맵을 사용할 수 있어 메모리와 시간을 모두 절약할 수 있을 것 같습니다.

이를 통해 UTF-8 문자 하나만큼 문자열 포인터를 발전시켜 UTF8 코드 포인트를 32비트 부호 정수로 저장하고 오류 발생 시 -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;
}

표현 코드를 사용한 추가 제안

저는 다음과 같은 코드를 제안합니다.더 나은 이해를 위해 조금 더 표현적이기를 바랍니다(이 게시물의 다른 제안처럼 빠르지 않을 수도 있음).

아래 테스트 사례에서는 첫 번째 질문에 어떻게 답하는지를 보여 줍니다.

#define __FALSE (0)
#define __TRUE  (!__FALSE)

#define MS1BITCNT_0_IS_0xxxxxxx_NO_SUCCESSOR        (0)
#define MS1BITCNT_1_IS_10xxxxxx_IS_SUCCESSOR        (1)
#define MS1BITCNT_2_IS_110xxxxx_HAS_1_SUCCESSOR     (2)
#define MS1BITCNT_3_IS_1110xxxx_HAS_2_SUCCESSORS    (3)
#define MS1BITCNT_4_IS_11110xxx_HAS_3_SUCCESSORS    (4)

typedef int __BOOL;

int CountMS1BitSequenceAndForward(const char **p) {
    int     Mask;
    int     Result = 0;
    char    c = **p;
    ++(*p);
    for (Mask=0x80;c&(Mask&0xFF);Mask>>=1,++Result);
    return Result;
}


int MS1BitSequenceCount2SuccessorByteCount(int MS1BitSeqCount) {
    switch (MS1BitSeqCount) {
    case MS1BITCNT_2_IS_110xxxxx_HAS_1_SUCCESSOR: return 1;
    case MS1BITCNT_3_IS_1110xxxx_HAS_2_SUCCESSORS: return 2;
    case MS1BITCNT_4_IS_11110xxx_HAS_3_SUCCESSORS: return 3;
    }
    return 0;
}

__BOOL ExpectUTF8SuccessorCharsOrReturnFalse(const char **Str, int NumberOfCharsToExpect) {
    while (NumberOfCharsToExpect--) {
        if (CountMS1BitSequenceAndForward(Str) != MS1BITCNT_1_IS_10xxxxxx_IS_SUCCESSOR) {
            return __FALSE;
        }
    }
    return __TRUE;
}

__BOOL IsMS1BitSequenceCountAValidUTF8Starter(int Number) {
    switch (Number) {
    case MS1BITCNT_0_IS_0xxxxxxx_NO_SUCCESSOR:
    case MS1BITCNT_2_IS_110xxxxx_HAS_1_SUCCESSOR:
    case MS1BITCNT_3_IS_1110xxxx_HAS_2_SUCCESSORS:
    case MS1BITCNT_4_IS_11110xxx_HAS_3_SUCCESSORS:
        return __TRUE;
    }
    return __FALSE;
}

#define NO_FURTHER_CHECKS_REQUIRED_IT_IS_NOT_UTF8       (-1)
#define NOT_ALL_EXPECTED_SUCCESSORS_ARE_10xxxxxx        (-1)

int CountValidUTF8CharactersOrNegativeOnBadUTF8(const char *Str) {
    int NumberOfValidUTF8Sequences = 0;
    if (!Str || !Str[0]) { return 0; }
    while (*Str) {
        int MS1BitSeqCount = CountMS1BitSequenceAndForward(&Str);
        if (!IsMS1BitSequenceCountAValidUTF8Starter(MS1BitSeqCount)) {
            return NO_FURTHER_CHECKS_REQUIRED_IT_IS_NOT_UTF8;
        }
        if (!ExpectUTF8SuccessorCharsOrReturnFalse(&Str, MS1BitSequenceCount2SuccessorByteCount(MS1BitSeqCount))) {
            return NOT_ALL_EXPECTED_SUCCESSORS_ARE_10xxxxxx;
        }
        if (MS1BitSeqCount) { ++NumberOfValidUTF8Sequences; }
    }
    return NumberOfValidUTF8Sequences;
}

몇 가지 테스트 케이스도 작성했습니다.

static void TestUTF8CheckOrDie(const char *Str, int ExpectedResult) {
    int Result = CountValidUTF8CharactersOrNegativeOnBadUTF8(Str);
    if (Result != ExpectedResult) {
        printf("TEST FAILED: %s:%i: check on '%s' returned %i, but expected was %i\n", __FILE__, __LINE__, Str, Result, ExpectedResult);
        exit(1);
    }
}

void SimpleUTF8TestCases(void) {
    TestUTF8CheckOrDie("abcd89234", 0);  // neither valid nor invalid UTF8 sequences
    TestUTF8CheckOrDie("", 0);           // neither valid nor invalid UTF8 sequences
    TestUTF8CheckOrDie(NULL, 0);
    TestUTF8CheckOrDie("asdföadkg", 1);  // contains one valid UTF8 character sequence
    TestUTF8CheckOrDie("asdföadäkg", 2); // contains two valid UTF8 character sequences
    TestUTF8CheckOrDie("asdf\xF8" "adäkg", -1); // contains at least one invalid UTF8 sequence
}

아래 프로그램은 stdin에서 utf-8 문자열(ascii, 유로 등의 비 아스키 문자)을 읽습니다.각 라인은 func_find_utf8로 전달됩니다.utf-8 문자는 멀티바이트 문자이므로 함수 func_find_utf8은 문자가 asci인지 non-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);

}

언급URL : https://stackoverflow.com/questions/1031645/how-to-detect-utf-8-in-plain-c

반응형