none
El dichoso UTF-8 RRS feed

  • Pregunta

  • De vez en cuando me vuelvo a topar con los típicos problemas de la codificación de caracteres (ya sabéis, los acentos , las eñes, etc) y la verdad es que es descorazonador el dichoso problemilla.
    Os cuento:
    Recojo información de varios archivos de Internet con la función URLDownloadToFile. 
    Luego los voy abriendo uno a uno, el programa los lee y crea un archivo final con los distintos archivos reunidos.
    Lo hago con algo del tipo
    FILE *fS, *fs1......;
    ...
    while ((ch = fgetc(fs1)) != EOF)
    {
    fputc(ch, fS);
    }

    Hasta aquí todo parece funcionar bien. Si abro el archivo están todos los acentos , eñes, etc.
    Ahora tengo que parsear el archivo para sacar distintos datos. Lo hago básicamente así:
    CFile file;
    BYTE buffer;

    ......

    std::string tempStr;
    while (file.Read(&buffer, 1) == 1 && buffer != ';')
    {
    tempStr += buffer;
    }

    Pero aquí, ya con el file.Read, me desaparecen los acentos y los textos se convierten en algo como: "Ávila", "San Andrés"
    Si abro el fichero con el Notepad++ y le digo 'Convertir en UTF-8' el problema se soluciona, pero ¿cómo podría solucionarlo con programación?
    ¿Alguna sugerencia? Gracias.

    martes, 27 de octubre de 2020 18:55

Respuestas

  • Cuando lees el archivo byte-a-byte, el problema es que si usas una codificación tal como el UTF-8 que almacena ciertos caracteres an más de un byte entonces tienes que meter lógica compleja para saber cuántos bytes tienes que leer por cada carácter.

    Esencialmente el truco es que cuando lees un byte y es mayor que 127, hay que examinar los bits más significativos y esos te dicen cuántos bytes adicionales hay que leer para formar un carácter. Si empieza por 110 hay que leer un segundo bytes. Si empieza por 1110, dos bytes adicionales. Y si es 11110, tres bytes adicionales. Esos "bytes adicionales" empiezan siempre por 10, lo cual te permite "resincronizarte" si fuera necesario, gracias a que no empieza por esa combinación ningún byte que no sea "de continuación".

    Por ejemplo, el símbolo de Euro se codifica en UTF8 como E2 82 AC. Fíjate en el primer byte, E2, que en binario es 1110 0010. Como empieza por 1110 eso te dice que tienes que leer detrás de él los otros dos bytes.

    ¿Que esto es molesto de programar? Sí. Se usa porque permite codificar caracteres Unicode de forma compacta. Como alternativa, puedes convertir tus archivos a otro sistema de codificación que tenga longitud constante para cada carácter. Por ejemplo, si solo necesitas la página base de Unicode, puedes usar UTF16, que usa 16 bits por cada carácter. O, si no necesitas ningún carácter especial aparte de los españoles, puedes convertirlo en ISO-8859-1, que usa un único byte por cada carácter.

    • Marcado como respuesta jlsogorb jueves, 29 de octubre de 2020 9:10
    martes, 27 de octubre de 2020 20:44

Todas las respuestas

  • Cuando lees el archivo byte-a-byte, el problema es que si usas una codificación tal como el UTF-8 que almacena ciertos caracteres an más de un byte entonces tienes que meter lógica compleja para saber cuántos bytes tienes que leer por cada carácter.

    Esencialmente el truco es que cuando lees un byte y es mayor que 127, hay que examinar los bits más significativos y esos te dicen cuántos bytes adicionales hay que leer para formar un carácter. Si empieza por 110 hay que leer un segundo bytes. Si empieza por 1110, dos bytes adicionales. Y si es 11110, tres bytes adicionales. Esos "bytes adicionales" empiezan siempre por 10, lo cual te permite "resincronizarte" si fuera necesario, gracias a que no empieza por esa combinación ningún byte que no sea "de continuación".

    Por ejemplo, el símbolo de Euro se codifica en UTF8 como E2 82 AC. Fíjate en el primer byte, E2, que en binario es 1110 0010. Como empieza por 1110 eso te dice que tienes que leer detrás de él los otros dos bytes.

    ¿Que esto es molesto de programar? Sí. Se usa porque permite codificar caracteres Unicode de forma compacta. Como alternativa, puedes convertir tus archivos a otro sistema de codificación que tenga longitud constante para cada carácter. Por ejemplo, si solo necesitas la página base de Unicode, puedes usar UTF16, que usa 16 bits por cada carácter. O, si no necesitas ningún carácter especial aparte de los españoles, puedes convertirlo en ISO-8859-1, que usa un único byte por cada carácter.

    • Marcado como respuesta jlsogorb jueves, 29 de octubre de 2020 9:10
    martes, 27 de octubre de 2020 20:44
  • Ok, intentaré investigar esto que comentas, aunque parece un poco engorroso.
    • Editado jlsogorb miércoles, 28 de octubre de 2020 16:52
    miércoles, 28 de octubre de 2020 16:51
  • En realidad solo necesito los caracteres especiales españoles (los acentos y las eñes).
    En una primera investigación lo que recibo es lo siguiente, 2 bytes por cada letra especial y son (en decimal):

    á: 195-161
    é: 195-169
    í: 195-173
    ó: 195-179
    ú: 195-186
    Á: 195-129
    É: 195-137
    Í: 195-141
    Ó: 195-147
    Ú: 195-154
    ñ: 195-177
    Ñ: 195-145 

    O sea que siempre comienza por 195. Igual por ahí puedo hacer algo.

    miércoles, 28 de octubre de 2020 17:46
  • O sea que siempre comienza por 195. Igual por ahí puedo hacer algo.

    El 195 decimal una vez convertido a binario es 11000011.

    Fíjate que comienza por 110 como yo te decía. Por eso detrás tienes que tomar un segundo byte cuando se recibe alguno de estos caracteres que has indicado.

    En lugar de comparar únicamente 195, yo te recomendaría comparar solo los tres bits. y así tienes más flexibilidad para el caso de que por casualidad te encuentres algún carácter que comience por otro valor, por ejemplo 194 o 196.

    if (byte & 0xe0  == 0xc0) ... // Empieza por 110.

    miércoles, 28 de octubre de 2020 17:59
  • Bueno, de momento, a bote pronto he encontrado una solución, no sé si es muy elegante, pero funciona perfectamente:

    BOOL dosBytes=FALSE;
    BOOL segundoByte=FALSE;
    ...
    std::string tempStr;
    while (file.Read(&buffer, 1) == 1 && buffer != ',')
    {
    			if (buffer == 195)
    				{
    					dosBytes = TRUE;
    				}
    
    			switch (buffer) {
    				case 161:
    					buffer = 'á';
    					segundoByte = TRUE;
    					break;
    				case 169:
    					buffer = 'é';
    					segundoByte = TRUE;
    					break;
    				case 173:
    					buffer = 'í';
    					segundoByte = TRUE;
    					break;
    				case 179:
    					buffer = 'ó';
    					segundoByte = TRUE;
    					break;
    				case 186:
    					buffer = 'ú';
    					segundoByte = TRUE;
    					break;
    				case 129:
    					buffer = 'Á';
    					segundoByte = TRUE;
    					break;
    				case 137:
    					buffer = 'É';
    					segundoByte = TRUE;
    					break;
    				case 141:
    					buffer = 'Í';
    					segundoByte = TRUE;
    					break;
    				case 147:
    					buffer = 'Ó';
    					segundoByte = TRUE;
    					break;
    				case 154:
    					buffer = 'Ú';
    					segundoByte = TRUE;
    					break;
    				case 177:
    					buffer = 'ñ';
    					segundoByte = TRUE;
    					break;
    				case 145:
    					buffer = 'Ñ';
    					segundoByte = TRUE;
    					break;
    				default:
    					break;
    				}
    if (dosBytes == FALSE)
    {
    tempStr += buffer;
    }
    else if (dosBytes==TRUE&&segundoByte == TRUE)
    {
    tempStr += buffer;
    dosBytes = FALSE;
    segundoByte = FALSE;
    }
    
    }
    Siempre se podrá mejorar, pero tampoco ha costado mucho de hacer. 

    miércoles, 28 de octubre de 2020 18:29
  • O sea que siempre comienza por 195. Igual por ahí puedo hacer algo.

    El 195 decimal una vez convertido a binario es 11000011.

    Fíjate que comienza por 110 como yo te decía. Por eso detrás tienes que tomar un segundo byte cuando se recibe alguno de estos caracteres que has indicado.

    En lugar de comparar únicamente 195, yo te recomendaría comparar solo los tres bits. y así tienes más flexibilidad para el caso de que por casualidad te encuentres algún carácter que comience por otro valor, por ejemplo 194 o 196.

    if (byte & 0xe0  == 0xc0) ... // Empieza por 110.

    Creo que al final voy a tener que usar algo como lo que tú comentas, porque entre las palabras a parsear se cuela alguna portuguesa con otro tipo de acentos (p.ej: CovilhãCâmara ,etc) y mi código no valdría. Sigo investigando.
    miércoles, 28 de octubre de 2020 19:43
  • Bueno, al final el archivo que se genera tengo que transformarlo en un fichero de Javascript para integrarlo en un HTML y me he dado cuenta de que a pesar de todo lo que ocurre con el copiado,el pegado y las transformaciones internas en el código, el resultado final es un archivo en el que se ven los acentos (sin necesidad de procesar los bytes y la codificación). Si lo abro con el Bloc de notas normal se ve todo perfectamente y si miro cómo está codificado me dice que es un UTF-8. Por tanto el problema es que como lo trata luego el Javascript y por ello buscaré la solución por ahí. De todas formas todo lo que habéis comentado me sirve de mucha ayuda para encauzar el problema. Doy este hilo por terminado y comenzaré a investigar por ese lado. Gracias.
    jueves, 29 de octubre de 2020 9:09