2010-06-01 29 views
6

Giả sử tôi có nội dung UTF-8 được lưu trữ trong bộ nhớ, làm cách nào để đọc các ký tự bằng cách sử dụng con trỏ? Tôi đoán tôi cần phải xem cho bit thứ 8 cho thấy một ký tự nhiều byte, nhưng làm thế nào chính xác để biến chuỗi thành một ký tự Unicode hợp lệ? Ngoài ra, có phải là wchar_t loại thích hợp để lưu trữ một ký tự Unicode không?Làm cách nào để đọc các ký tự UTF-8 qua một con trỏ?

Đây là những gì tôi có trong tâm trí:

 

    wchar_t readNextChar (char*& p) 
    { 
     wchar_t unicodeChar; 
     char ch = *p++; 

     if ((ch & 128) != 0) 
     { 
      // This is a multi-byte character, what do I do now? 
      // char chNext = *p++; 
      // ... but how do I assemble the Unicode character? 
      ... 
     } 
     ... 
     return unicodeChar; 
    } 
 
+3

Không có ý nghĩa khi nói "chiều rộng của ký tự Unicode". Bạn cần giải quyết mã hóa. Tùy thuộc vào nền tảng của bạn, 'wchar_t' có thể có kích thước khác nhau. Trên một hệ điều hành giống Unix, nó thường là 32bit, vì vậy bạn có thể lưu trữ các ký tự Unicode được mã hóa UTF-32 trong đó, trên Windows là 16bit, vì vậy nó có thể lấy các ký tự Unicode được mã hóa UTF-16. – sbi

+0

Ngoài việc trả về ký tự rộng, hàm 'readNextChar' của bạn phải cung cấp thông tin để cập nhật đúng' p'. UTF-8 (và UTF-16, cho rằng vấn đề) là mã hóa độ dài biến đổi và người gọi của bạn không thể giả định một sự gia tăng liên tục hoặc đơn giản trong con trỏ. – mpez0

Trả lời

7

Bạn phải giải mã UTF- Mẫu 8 bit với biểu diễn UTF-32 chưa được mã hóa của nó. Nếu bạn muốn bảng mã Unicode thực, bạn phải sử dụng kiểu dữ liệu 32 bit.

Trên Windows, wchar_t KHÔNG đủ lớn vì chỉ có 16 bit. Thay vào đó, bạn phải sử dụng một số unsigned int hoặc unsigned long. Chỉ sử dụng wchar_t khi giao dịch với mã UTF-16 thay thế.

Trên các nền tảng khác, wchar_t thường là 32 bit. Nhưng khi viết mã di động, bạn nên tránh xa wchar_t trừ khi cần thiết (như std::wstring).

Hãy thử một cái gì đó như thế này:

#define IS_IN_RANGE(c, f, l) (((c) >= (f)) && ((c) <= (l))) 

u_long readNextChar (char* &p) 
{ 
    // TODO: since UTF-8 is a variable-length 
    // encoding, you should pass in the input 
    // buffer's actual byte length so that you 
    // can determine if a malformed UTF-8 
    // sequence would exceed the end of the buffer... 

    u_char c1, c2, *ptr = (u_char*) p; 
    u_long uc = 0; 
    int seqlen; 
    // int datalen = ... available length of p ...;  

    /* 
    if(datalen < 1) 
    { 
     // malformed data, do something !!! 
     return (u_long) -1; 
    } 
    */ 

    c1 = ptr[0]; 

    if((c1 & 0x80) == 0) 
    { 
     uc = (u_long) (c1 & 0x7F); 
     seqlen = 1; 
    } 
    else if((c1 & 0xE0) == 0xC0) 
    { 
     uc = (u_long) (c1 & 0x1F); 
     seqlen = 2; 
    } 
    else if((c1 & 0xF0) == 0xE0) 
    { 
     uc = (u_long) (c1 & 0x0F); 
     seqlen = 3; 
    } 
    else if((c1 & 0xF8) == 0xF0) 
    { 
     uc = (u_long) (c1 & 0x07); 
     seqlen = 4; 
    } 
    else 
    { 
     // malformed data, do something !!! 
     return (u_long) -1; 
    } 

    /* 
    if(seqlen > datalen) 
    { 
     // malformed data, do something !!! 
     return (u_long) -1; 
    } 
    */ 

    for(int i = 1; i < seqlen; ++i) 
    { 
     c1 = ptr[i]; 

     if((c1 & 0xC0) != 0x80) 
     { 
      // malformed data, do something !!! 
      return (u_long) -1; 
     } 
    } 

    switch(seqlen) 
    { 
     case 2: 
     { 
      c1 = ptr[0]; 

      if(!IS_IN_RANGE(c1, 0xC2, 0xDF)) 
      { 
       // malformed data, do something !!! 
       return (u_long) -1; 
      } 

      break; 
     } 

     case 3: 
     { 
      c1 = ptr[0]; 
      c2 = ptr[1]; 

      switch (c1) 
      { 
       case 0xE0: 
        if (!IS_IN_RANGE(c2, 0xA0, 0xBF)) 
        { 
         // malformed data, do something !!! 
         return (u_long) -1; 
        } 
        break; 

       case 0xED: 
        if (!IS_IN_RANGE(c2, 0x80, 0x9F)) 
        { 
         // malformed data, do something !!! 
         return (u_long) -1; 
        } 
        break; 

       default: 
        if (!IS_IN_RANGE(c1, 0xE1, 0xEC) && !IS_IN_RANGE(c1, 0xEE, 0xEF)) 
        { 
         // malformed data, do something !!! 
         return (u_long) -1; 
        } 
        break; 
      } 

      break; 
     } 

     case 4: 
     { 
      c1 = ptr[0]; 
      c2 = ptr[1]; 

      switch (c1) 
      { 
       case 0xF0: 
        if (!IS_IN_RANGE(c2, 0x90, 0xBF)) 
        { 
         // malformed data, do something !!! 
         return (u_long) -1; 
        } 
        break; 

       case 0xF4: 
        if (!IS_IN_RANGE(c2, 0x80, 0x8F)) 
        { 
         // malformed data, do something !!! 
         return (u_long) -1; 
        } 
        break; 

       default: 
        if (!IS_IN_RANGE(c1, 0xF1, 0xF3)) 
        { 
         // malformed data, do something !!! 
         return (u_long) -1; 
        } 
        break;     
      } 

      break; 
     } 
} 

    for(int i = 1; i < seqlen; ++i) 
    { 
     uc = ((uc << 6) | (u_long)(ptr[i] & 0x3F)); 
    } 

    p += seqlen; 
    return uc; 
} 
+0

Hoàn hảo, cảm ơn! –

+0

@Remy & @Jen: Chiều rộng chính xác của 'wchar_t' không được xác định. Trên GCC (ít nhất là trên Linux) 'wchar_t' là 32bit, do đó, nó chắc chắn sẽ đủ để giữ các ký tự Unicode mà không cần mã hóa nhiều byte. – sbi

+0

Tôi vừa biên dịch mã này bằng cách sử dụng g ++ 3.3.4, và tôi khá ấn tượng: Trình biên dịch đã di chuyển tất cả mã từ câu lệnh 'switch' lớn lên tới nơi biến' seqlen' được đặt. Có lẽ điều đó cũng tốt cho mã ban đầu, để trở nên dễ đọc hơn. –

2

Nếu bạn cần để giải mã UTF-8 bạn cần làm phát triển một phân tích cú pháp UTF-8. UTF-8 là một mã hóa có độ dài thay đổi (1 đến 4 byte), do đó bạn thực sự phải viết một trình phân tích cú pháp tuân thủ tiêu chuẩn: xem wikipedia chẳng hạn.

Nếu bạn không muốn viết trình phân tích cú pháp của riêng mình, tôi khuyên bạn nên sử dụng thư viện. Bạn sẽ thấy rằng trong glib ví dụ (tôi personnaly đã sử dụng Glib :: ustring, wrapper C++ của glib) mà còn trong bất kỳ thư viện mục đích chung tốt.

Edit:

Tôi nghĩ rằng C++ 0x sẽ bao gồm hỗ trợ UTF-8 cũng vậy, nhưng tôi không phải là chuyên ...

my2c

1

Ngoài ra, là wchar_t loại thích hợp để lưu trữ một ký tự Unicode không?

Trên Linux, có. Trên Windows, wchar_t đại diện cho đơn vị mã UTF-16, không nhất thiết phải là ký tự.

Chuẩn C++ 0x sắp tới sẽ cung cấp các loại char16_tchar32_t được thiết kế để đại diện cho UTF-16 và UTF-32.

Nếu trên hệ thống có char32_t không khả dụng và wchar_t không đủ, hãy sử dụng uint32_t để lưu trữ các ký tự Unicode.

4

Đây là một vĩ mô nhanh chóng mà sẽ đếm UTF-8 byte

#define UTF8_CHAR_LEN(byte) ((0xE5000000 >> ((byte >> 3) & 0x1e)) & 3) + 1 

Điều này sẽ giúp bạn phát hiện kích thước của ký tự UTF-8 cho phân tích dễ dàng hơn.

+0

Helpfull! Cách nhanh để thay thế mblen(), khi nó không hoạt động. – southerton

1

Đây là giải pháp của tôi, trong ANSI-C thuần túy, bao gồm thử nghiệm đơn vị cho các trường hợp góc.

Hãy coi chừng rằng int phải rộng ít nhất 32 bit. Nếu không, bạn phải thay đổi định nghĩa của codepoint.

#include <assert.h> 
#include <errno.h> 
#include <stdio.h> 
#include <stdlib.h> 

typedef unsigned char byte; 
typedef unsigned int codepoint; 

/** 
* Reads the next UTF-8-encoded character from the byte array ranging 
* from {@code *pstart} up to, but not including, {@code end}. If the 
* conversion succeeds, the {@code *pstart} iterator is advanced, 
* the codepoint is stored into {@code *pcp}, and the function returns 
* 0. Otherwise the conversion fails, {@code errno} is set to 
* {@code EILSEQ} and the function returns -1. 
*/ 
int 
from_utf8(const byte **pstart, const byte *end, codepoint *pcp) { 
     size_t len, i; 
     codepoint cp, min; 
     const byte *buf; 

     buf = *pstart; 
     if (buf == end) 
       goto error; 

     if (buf[0] < 0x80) { 
       len = 1; 
       min = 0; 
       cp = buf[0]; 
     } else if (buf[0] < 0xC0) { 
       goto error; 
     } else if (buf[0] < 0xE0) { 
       len = 2; 
       min = 1 << 7; 
       cp = buf[0] & 0x1F; 
     } else if (buf[0] < 0xF0) { 
       len = 3; 
       min = 1 << (5 + 6); 
       cp = buf[0] & 0x0F; 
     } else if (buf[0] < 0xF8) { 
       len = 4; 
       min = 1 << (4 + 6 + 6); 
       cp = buf[0] & 0x07; 
     } else { 
       goto error; 
     } 

     if (buf + len > end) 
       goto error; 

     for (i = 1; i < len; i++) { 
       if ((buf[i] & 0xC0) != 0x80) 
         goto error; 
       cp = (cp << 6) | (buf[i] & 0x3F); 
     } 

     if (cp < min) 
       goto error; 

     if (0xD800 <= cp && cp <= 0xDFFF) 
       goto error; 

     if (0x110000 <= cp) 
       goto error; 

     *pstart += len; 
     *pcp = cp; 
     return 0; 

error: 
     errno = EILSEQ; 
     return -1; 
} 

static void 
assert_valid(const byte **buf, const byte *end, codepoint expected) { 
     codepoint cp; 

     if (from_utf8(buf, end, &cp) == -1) { 
       fprintf(stderr, "invalid unicode sequence for codepoint %u\n", expected); 
       exit(EXIT_FAILURE); 
     } 

     if (cp != expected) { 
       fprintf(stderr, "expected %u, got %u\n", expected, cp); 
       exit(EXIT_FAILURE); 
     } 
} 

static void 
assert_invalid(const char *name, const byte **buf, const byte *end) { 
     const byte *p; 
     codepoint cp; 

     p = *buf + 1; 
     if (from_utf8(&p, end, &cp) == 0) { 
       fprintf(stderr, "unicode sequence \"%s\" unexpectedly converts to %#x.\n", name, cp); 
       exit(EXIT_FAILURE); 
     } 
     *buf += (*buf)[0] + 1; 
} 

static const byte valid[] = { 
     0x00, /* first ASCII */ 
     0x7F, /* last ASCII */ 
     0xC2, 0x80, /* first two-byte */ 
     0xDF, 0xBF, /* last two-byte */ 
     0xE0, 0xA0, 0x80, /* first three-byte */ 
     0xED, 0x9F, 0xBF, /* last before surrogates */ 
     0xEE, 0x80, 0x80, /* first after surrogates */ 
     0xEF, 0xBF, 0xBF, /* last three-byte */ 
     0xF0, 0x90, 0x80, 0x80, /* first four-byte */ 
     0xF4, 0x8F, 0xBF, 0xBF /* last codepoint */ 
}; 

static const byte invalid[] = { 
     1, 0x80, 
     1, 0xC0, 
     1, 0xC1, 
     2, 0xC0, 0x80, 
     2, 0xC2, 0x00, 
     2, 0xC2, 0x7F, 
     2, 0xC2, 0xC0, 
     3, 0xE0, 0x80, 0x80, 
     3, 0xE0, 0x9F, 0xBF, 
     3, 0xED, 0xA0, 0x80, 
     3, 0xED, 0xBF, 0xBF, 
     4, 0xF0, 0x80, 0x80, 0x80, 
     4, 0xF0, 0x8F, 0xBF, 0xBF, 
     4, 0xF4, 0x90, 0x80, 0x80 
}; 

int 
main() { 
     const byte *p, *end; 

     p = valid; 
     end = valid + sizeof valid; 
     assert_valid(&p, end, 0x000000); 
     assert_valid(&p, end, 0x00007F); 
     assert_valid(&p, end, 0x000080); 
     assert_valid(&p, end, 0x0007FF); 
     assert_valid(&p, end, 0x000800); 
     assert_valid(&p, end, 0x00D7FF); 
     assert_valid(&p, end, 0x00E000); 
     assert_valid(&p, end, 0x00FFFF); 
     assert_valid(&p, end, 0x010000); 
     assert_valid(&p, end, 0x10FFFF); 

     p = invalid; 
     end = invalid + sizeof invalid; 
     assert_invalid("80", &p, end); 
     assert_invalid("C0", &p, end); 
     assert_invalid("C1", &p, end); 
     assert_invalid("C0 80", &p, end); 
     assert_invalid("C2 00", &p, end); 
     assert_invalid("C2 7F", &p, end); 
     assert_invalid("C2 C0", &p, end); 
     assert_invalid("E0 80 80", &p, end); 
     assert_invalid("E0 9F BF", &p, end); 
     assert_invalid("ED A0 80", &p, end); 
     assert_invalid("ED BF BF", &p, end); 
     assert_invalid("F0 80 80 80", &p, end); 
     assert_invalid("F0 8F BF BF", &p, end); 
     assert_invalid("F4 90 80 80", &p, end); 

     return 0; 
} 
+0

Thất bại sử thi. Các poster là trong C + +, và bạn vi phạm rất nhiều thành ngữ C++, tôi thậm chí không thể bắt đầu đếm. – Puppy

+0

Vì vậy, tôi sẽ tính cho bạn. (1) Tôi bao gồm các tiêu đề C thay vì các tiêu đề C++. (2) Tôi đã sử dụng con trỏ thay vì tham chiếu. (3) Tôi không sử dụng các không gian tên, mà thay vào đó đã khai báo các hàm của tôi 'static'. (4) Tôi đã khai báo biến vòng lặp với phạm vi toàn hàm. Nhưng mặt khác, tôi không phát minh ra tên kiểu lạ ('u_long',' u_char') và sử dụng chúng không nhất quán ('u_char' so với' uchar') và không khai báo chúng. Tôi cũng đã xoay xở để tránh hoàn toàn bất kỳ loại cast nào (câu trả lời được chấp nhận sử dụng rất nhiều, và đó cũng là kiểu C). –

+0

Và bằng cách này, việc sử dụng con trỏ của tôi thay vì tham chiếu là hoàn toàn có chủ ý trong trường hợp này, vì 'pstart' và' end' trông sẽ rất giống với phía người gọi. 'from_utf8 (bắt đầu, kết thúc, & cp)'. Làm thế nào mọi người nên đoán rằng 'bắt đầu' được sửa đổi và' kết thúc 'không? –

Các vấn đề liên quan