2010-06-01 16 views
6

Supongamos que tengo contenido UTF-8 almacenado en la memoria, ¿cómo leo los caracteres con un puntero? Supongo que necesito ver el octavo bit que indica un carácter de varios bytes, pero ¿cómo exactamente convierto la secuencia en un personaje Unicode válido? Además, ¿es wchar_t el tipo correcto para almacenar un único carácter Unicode?¿Cómo leo caracteres UTF-8 a través de un puntero?

Esto es lo que tengo en mente:

 

    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

No tiene sentido hablar del "ancho de un carácter Unicode". Debe conformarse con una codificación. Dependiendo de su plataforma, 'wchar_t' puede ser de diferente tamaño. En un sistema operativo tipo Unix, normalmente es de 32 bits, por lo que puede almacenar caracteres Unicode codificados en UTF-32, en Windows es de 16 bits, por lo que puede tomar caracteres Unicode codificados en UTF-16. – sbi

+0

Además de devolver el carácter ancho, su función 'readNextChar' debe proporcionar información para actualizar' p' correctamente. UTF-8 (y UTF-16, para el caso) son codificaciones de longitud variable y su llamador no puede suponer un incremento constante o simple en el puntero. – mpez0

Respuesta

7

Tienes que descifrar el UTF Patrón de 8 bits a su representación UTF-32 no codificada. Si desea el punto de código Unicode real, debe usar un tipo de datos de 32 bits.

En Windows, wchar_t NO es lo suficientemente grande, ya que solo es de 16 bits. En su lugar, debe usar unsigned int o unsigned long. Use wchar_t solo cuando trabaje con unidades de código UTF-16 en su lugar.

En otras plataformas, wchar_t suele ser de 32 bits. Pero al escribir código portátil, debe mantenerse alejado de wchar_t, excepto cuando sea absolutamente necesario (como std::wstring).

probar algo de la misma familia:

#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

Perfecto, gracias! –

+0

@Remy & @Jen: el ancho exacto de 'wchar_t' no está definido. En GCC (al menos en Linux) 'wchar_t' es de 32 bits, por lo que sin duda sería suficiente para contener caracteres Unicode sin codificación de múltiples bytes. – sbi

+0

Acabo de compilar este código usando g ++ 3.3.4, y estoy bastante impresionado: el compilador movió todo el código de la declaración grande 'switch' hasta donde se establece la variable' seqlen'. Tal vez eso sería bueno para el código original, también, para ser más legible. –

2

Si necesita decodificar UTF-8 no necesita desarrollar un programa de análisis UTF-8. UTF-8 es una codificación de longitud variable (de 1 a 4 bytes) por lo que realmente debe escribir un analizador que cumpla con el estándar: consulte wikipedia por ejemplo.

Si no desea escribir su propio analizador, le sugiero usar una biblioteca. Encontrará eso en glib, por ejemplo (personalmente usé Glib :: ustring, el contenedor C++ de glib) pero también en cualquier biblioteca de propósito general.

Editar:

creo que C++ 0x incluirá soporte UTF-8 también, pero yo no soy un especialista ...

MY2C

1

Además, es wchar_t del tipo adecuado para almacenar un solo carácter Unicode?

En Linux, sí. En Windows, wchar_t representa una unidad de código UTF-16, que no es necesariamente un carácter.

El próximo estándar C++ 0x proporcionará los tipos char16_t y char32_t diseñados para representar UTF-16 y UTF-32.

Si en un sistema donde char32_t no está disponible y wchar_t es inadecuado, use uint32_t para almacenar caracteres Unicode.

4

Aquí es una macro rápida que contará UTF-8 bytes

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

Esto le ayudará a detectar el tamaño del carácter UTF-8 para el análisis más fácil.

+0

¡Útil! Forma rápida de reemplazar mblen(), cuando no funciona. – southerton

1

Esta es mi solución, en pura ANSI-C, que incluye una prueba de unidad para las cajas de esquina.

Tenga en cuenta que int debe tener al menos 32 bits de ancho. De lo contrario, debe cambiar la definición de 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

Epic fail. El póster está en C++, y has violado tantos modismos en C++, ni siquiera puedo empezar a contar. – Puppy

+0

Así que voy a contar por ti. (1) Incluí los encabezados C en lugar de los encabezados C++. (2) Usé punteros en lugar de referencias. (3) No usé espacios de nombres, sino que declare mis funciones 'static'. (4) Declaré la variable de bucle con un alcance de toda la función. Pero, por otro lado, no inventé los nombres de tipos raros ('u_long',' u_char') y los usé de forma inconsistente ('u_char' contra' uchar') y sin declararlos. También me las arreglé para evitar por completo cualquier tipo de conversión (que la respuesta aceptada usa mucho, y eso también es estilo C).) –

+0

Y, por cierto, mi uso de punteros en lugar de referencias es completamente intencionado en este caso, ya que 'pstart' y' end' se verían muy similares en el lado de la persona que llama. 'from_utf8 (inicio, final, y cp)'. ¿Cómo debería alguien adivinar que 'start' se modifica y' end' no? –

Cuestiones relacionadas