 |
Club Builder Foro de C++Builder en español
|
| Ver tema anterior :: Ver tema siguiente |
| Autor |
Mensaje |
Pugile Ens

Registrado: 26 Dic 2006Mensajes: 29Ubicación: Mexico
|
Publicado: Vie 22 Abr, 2011 2:35 am Asunto: Ayuda. ¿Como manejar Unicode y archivos? |
|
|
Hola a todos. Abrí este hilo con la esperanza de que algún camarada pueda ayudarme (y quizá a muchas otras personas del foro) en el sentido del manejo adecuado de Unicode con archivos.
Estoy trabajando con la versión XE de Builder, la cual rezaga mucho al viejo char y se inclina más por wchar_t.
La cuestión es la siguiente: tengo una pila de documentos con distintas codificaciones, principalmente ISO-8859-1, UTF-8, UTF-16LE. Estaba muy acostumbrado a abrir y leer los archivos con fopen() y fread() respectivamente. El problema es que fopen no soporta Unicode, sé que existe una versión _wfopen para usar con wchar_t, pero como trabajo directamente con VCL prefiero utilizar FileOpen, FileWrite, FileRead de SysUtils.hpp. O mejor aún utilizar un TStringStream y carga el contenido en memoria y evitarme así un poco de trabajo.
Muchos dirán que la memoria, que como cargar todo el archivo directamente a la memoria etc. La verdad es que ese punto no me preocupa pues los archivos son entre 1MB y 40MB como máximo. Además de que es forzosa la carga en memoria pues posteriormente de RAM se pasan a la VRAM (Video RAM) para ser utilizados por la GPU.
Bueno volviendo al problema es que antes de enviarlos a GPU tengo que lematizar mi texto y para ello necesito una correcta representación de los caracteres. Es aquí donde tengo mucho problemas con UNICODE y ya me estoy volviendo loco. Opte por usar TStringStream pero los caracteres se muestran como si se utilizara un char y fopen para cargarlos. Ilustro con algunos ejemplos y palabras como:
| Cita: | | árbol, análisis, niño |
| Código: | TStringStream *Carga = new TStringStream;
Carga->LoadFromFile(Archivo);
Carga->DataString; // Aquí están los caracteres |
Si cargo un archivo con codificación UTF-8 los caracteres se muestran con 2 bytes para caracteres especiales como ñ, acentos, etc. Seria:
| Cita: | | árbol, análisis, Niño |
Si cargo un archivo con codificación UTF-16LE los caracteres se muestran con 2 bytes, algo así:
| Cita: | | \0a\0n\0á\0l\0i\0s\0i\0s\0 \0á\0r\0b\0o\0l\0 \0n\0i\0ñ\0o\0.\0 |
Si cargo un archivo con codificación ANSI ISO-8859-1 no tengo ningún problema.
Pues buscando he encontrado algunas formas de manejar un poquito mejor este relajo pero aún no he podido resolver el problema del todo.
Por ejemplo la clase TEncoding permite detectar el tipo de codificación utilizada por el archivo pero según yo, me he percatado que solo detecta la codificación utilizada si el archivo tiene BOM (Byte Order Mark). Según he visto en la ayuda de Builder esta se puede utilizar en conjunto con TStringStream para indicarle que codificación usar. Pero la verdad es que no le hallo. Según la ayuda se debe indicar en el constructor del TStringStream asi:
| Código: | __fastcall TStringStream(const System::UnicodeString AString, Sysutils::TEncoding* AEncoding, bool AOwnsEncoding)/* overload */; |
Pero no se cuál es la utilidad de AString ni de AOwnsEncoding. Intente lo siguiente pero no va:
| Código: | TStringStream(MiString, Tencoding :: UTF8, false); |
Al cargar el archivo los caracteres se siguen mostrando igual.
Posteriormente intente hacer la conversión de la siguiente manera, basándome en un ejemplo de los Foros de Embarcadero. El problema es que volvemos a lo mismo:
1.- Según el ejemplo se necesita el BOM
2.- Los datos se queda en un TByte no en un String
Aquí mi ejemplo:
| Código: | int index;
TBytes Convert;
String FileData;
TEncoding *Encode = NULL;
TStringStream *Data = new TStringStream;
Data->LoadFromFile(Archivo);
index = Data->Encoding->GetBufferEncoding(Data->Bytes,Encode); // El ejemplo original indica que si el valor de index es 0 (No está presente el BOM) el tipo de codificación usada es desconocida y no continuaba.
Convert = TEncoding :: Convert(Encode, TEncoding :: Default , Data->Bytes, index, Data->Size - index);
FileData = Data->Encoding->GetString(Convert); |
Como pueden ver para poder hacer la conversión se necesita 1 String, 1 TBytes y un TStringStream. Está bien que no me importe demasiado la memoria pero lo mismo 3 veces en memoria pues como que tampoco. Si cargo 1 archivo de 40MB tendría 120MB ocupados de golpe. Y mi objetivo es usar Threads para cargar varios archivos a la vez. Si por mala suerte a cada Thread le toca un archivo de 40MB pues adiós memoria.
Y finalmente lo último que probé es lo siguiente:
| Código: | String line;
String File = Mi archivo a cargar;
TStreamReader* reader;
reader = new TStreamReader(new TFileStream(File, fmOpenRead), TEncoding :: Default );
line = reader->ReadToEnd();
reader->Close(); |
Con este último código casi todo va bien, y según entiendo al cargar el archivo obliga a hacer la conversión TEncoding :: Default a la que se esté usando en el sistema operativo. En mi caso es ANSI ISO 8859-1. El casi todo, es porque algunos archivos no los carga, por ejemplo aquellos que son ISO 8859-1 no mas no los carga, además no sé si siempre funcionara con cualquier codificación que se encuentre.
Por favor si alguien conoce una forma de cargar archivos UNICODE con X codificación y poder trabajar con los caracteres en memoria se lo agradeceré eternamente. |
|
| Volver arriba |
|
 |
_Leo Site Admin

Registrado: 11 Ago 2002Mensajes: 1875Ubicación: Lanzarote - Islas Canarias (España)
|
Publicado: Vie 22 Abr, 2011 6:15 am Asunto: |
|
|
Podrías probar con la siguiente función para averiguar la codificación del archivo antes de cargarlo:
(Añadimos la cabecera: #include<memory>)
| Código: | TEncoding* GetEncoding(String file)
{
// Cargamos una pequeña parte del archivo para averiguar su codificación
std::auto_ptr<TFileStream> fs(new TFileStream(file, fmOpenRead));
TByteDynArray buffer;
buffer.Length = 4;
fs->Read(&buffer[0], buffer.Length);
TEncoding* enc = 0;
TEncoding::GetBufferEncoding(buffer, enc);
return enc;
} |
Y la usaríamos del siguiente modo:
| Código: | String file = "d:\\texto.txt";
TEncoding* enc = GetEncoding(file);
std::auto_ptr<TStringStream> ss(new TStringStream(L"", enc, true));
ss->LoadFromFile(file);
ShowMessage(ss->DataString.SubString(1, 40)); |
|
|
| Volver arriba |
|
 |
Pugile Ens

Registrado: 26 Dic 2006Mensajes: 29Ubicación: Mexico
|
Publicado: Vie 22 Abr, 2011 9:33 pm Asunto: Gracias _Leo |
|
|
Muchas gracias por una respuesta tan rápida _Leo. Enfocándonos en el problema te comento lo siguiente:
El método de la clase TEncoding :: GetBufferEncoding(Buffer, Encoded) detecta la codificación utilizada por el archivo, Si y solo Si está presente el BOM. De lo contrario si el BOM está ausente, no sabe que codificación se está utilizando y en sus propiedades, activa todas las codificaciones soportadas. Por ejemplo:
Si cargamos un archivo con BOM y Codificación UTF-8 aparecería algo así:
| Código: |
FEncoding:
FASCIIEncoding NULL
FBIgEndianUnicodeEncoding NULL
FDefaultEncoding :02BDAC30 // Siempre va a estar activa.
FUnicodeEncoding NULL
FUTF8Encoding :021D0C30
|
Si cargamos un archivo sin BOM y codificación UTF-8 aparecería algo así:
| Código: |
FEncoding:
FASCIIEncoding :01EDAC30
FBIgEndianUnicodeEncoding :05CDAC30
FDefaultEncoding :04BDAC30
FUnicodeEncoding :07FDAC30
FUTF8Encoding :021D0C30
|
Se puede apreciar que si el BOM está presente sabe qué propiedad activar, y este valor será el que tomara el objeto Encoded, que es el que se coloca en el método.
| Código: | TEncoding :: GetBufferEncoding(Buffer, Encoded) |
La forma que tú me presentas solo carga los 4 primeros bytes del archivo, con el fin de cargar el BOM (corrígeme si me equivoco). Pero repito si el BOM está ausente pues no sabría que tipo de codificación usa el archivo.
Como expuse al comienzo de este hilo, he estado probando con diversas formas para poder detectar la codificación usada por el archivo. Y bueno encontré una forma más o menos aceptable que permite detectar la codificación del archivo:
Lo primero es configurar el constructor del TStringStream para que utilice alguna Codificación.
| Cita: | Si no se configura el constructor de esta clase el sistema usa la codificación por default, que es la que se utiliza en el S.O. (En mi caso la ISO-8859-1).
Por eso cuando comencé a escribir este Hilo indicaba que al cargar un archivo parecía que se cargaba como si estuviese utilizando char y fopen(). Como bien sabemos char puede tener de 0 a 255 valores lo que permite representar el código ASCII y el ASCII extendido, lo que es ISO-8859-1.
Como no configuraba el TStringStream lo cargaba en ISO y ese era el motivo por el cual veía los caracteres como si estuviesen en un char. |
Bueno retomando el problema, presento la siguiente función:
| Código: |
short ConvertX :: LoadFile(String File)
{
int index; // Si hay BOM guardara la Longitud del BOM
TBytes Converted; // Almacena el documento convertido.
TEncoding *Encode = NULL; // Tendra el tipo de codificacion usada o en su defecto activara todas.
TStringStream *Data = new TStringStream(NULL, TEncoding :: UTF8, false); // En el constructor le especifico que use UTF-8
Data->LoadFromFile(File); // Cargo el archivo
if(Data->DataString == "") { // En el constructor indique que cargara el archivo como UTF-8. Si no se pudo sera "".
index = Data->Encoding->GetBufferEncoding(Data->Bytes,Encode); // Verifico si tiene BOM
Converted = TEncoding :: Convert(Encode, TEncoding :: UTF8, Data->Bytes, index, Data->Size - index); // Lo convierto a UTF-8
Data->Clear(); // Libero los bytes ocupados por Data
FileData = Data->Encoding->GetString(Converted); // Convierto los TBYTES a String
Converted.~DynamicArray(); // Libero los bytes ocupados por Converted.
}
else FileData = Data->DataString; // Guardo el archivo en un String para manipularlo.
delete Data; // Elimino Data
if(FileData.IsEmpty()) return NOT_LOAD; // Por si las dudas, si no se cargó lo indico.
return GOOD; // Todo salio bien.
}
|
Ahora tratare de explicar ciertas partes que pueden resultar algo confusas:
| Código: | TEncoding *Encode = NULL; |
Es un apuntador a la clase TEncoding, que almacenara el tipo de codificación usada por el archivo. Si no tiene BOM activara todas las codificaciones soportadas. Ver el ejemplo que puse “FEncoding”
| Código: | TStringStream *Data = new TStringStream(NULL, TEncoding :: UTF8, false); |
De no pasarle los parámetros al constructor, se utilizaría TEncoding :: Default para cargar el archivo. Al pasarle dichos parámetros, le estoy indicando que cuando cargue un archivo, lo haga como UTF-8. Ya no importa si el archivo tiene BOM o no, se intentara codificar el archivo como UTF-8 (realmente si el archivo está usando UTF-8 no tendrá problemas pero si usa otra codificación no lo podrá codificar).
| Código: | if(Data->DataString == "") |
Cuando se cargó el archivo, se utilizó UTF-8. Si el archivo no se pudo codificar en UTF-8, La cadena de caracteres estará vacía. Pero el archivo estará cargado como Bytes: Data->Bytes.
El que este vacía, significa que aún no se ha codificado el archivo por ello tendremos que hacer nuestra conversión.
| Código: | index = Data->Encoding->GetBufferEncoding(Data->Bytes,Encode); |
En esta parte se intenta averiguar que tipo de codificación usa el archivo basándose en el BOM. Si no hay BOM index será 0 y Encode tendrá activas todas las codificaciones soportadas.
_LEO en su ejemplo plantea lo mismo pero utilizando el operador de acceso a ámbito.
| Código: | TEncoding :: GetBufferEncoding(); |
Hay que recordar que TStringStream Hereda de TEncoding, por ello podemos utilizarlo directamente. A y recordar que TEncoding es una clase abstracta. No intenten instanciarla.
| Código: | Converted = TEncoding :: Convert(Encode, TEncoding :: UTF8, Data->Bytes, index, Data->Size - index); |
Aquí convertimos el archivo a UTF-8, siempre y cuando se tenga el BOM presente, si el BOM no está presente, no se efectuara la conversión, y dejara al archivo tal cual se cargó “Sin convertir”.
| Código: | FileData = Data->Encoding->GetString(Converted); |
En esta última parte lo que se hace es convertir de Tbytes a String. Es curioso pero si queremos utilizar el operador de acceso a ámbito así:
| Código: | FileData = TEncoding :: GetString(Converted); |
Marca error, al parecer hay un error en esta parte con Builder.
Pues bueno quizá no sea la mejor forma para trabajar con archivos en múltiples codificaciones, pero de momento es lo mejor que he conseguido. Y sigo dejando abierta la propuesta: Si alguien encuentra, conoce o desarrolla una mejor opción que nos la facilite a toda la comunidad. Gracias. |
|
| Volver arriba |
|
 |
_Leo Site Admin

Registrado: 11 Ago 2002Mensajes: 1875Ubicación: Lanzarote - Islas Canarias (España)
|
Publicado: Vie 22 Abr, 2011 10:07 pm Asunto: |
|
|
| Pugile escribió: | | La forma que tú me presentas solo carga los 4 primeros bytes del archivo, con el fin de cargar el BOM (corrígeme si me equivoco). Pero repito si el BOM está ausente pues no sabría que tipo de codificación usa el archivo. |
Cierto. El problema si se omite el BOM y además dentro del texto no hay algún parámetro que permita saber la codificación, la única forma de saberla es utilizando algún método estadístico revisando su contenido |
|
| Volver arriba |
|
 |
|
|
No puedes publicar temas nuevos en este foro No puedes responder a temas en este foro No puedes editar tus mensajes en este foro No puedes borrar tus mensajes en este foro No puedes votar en las encuestas de este foro
|
© 2002–2026 Club Builder · Funciona con phpBB.
|