¿Está leyendo correctamente un archivo de texto utf-16 en una cadena sin bibliotecas externas?

He estado usando StackOverflow desde el principio, y en ocasiones he tenido la tentación de publicar preguntas, pero siempre las resolví o encontré respuestas publicadas eventualmente … hasta ahora. Parece que debería ser bastante simple, pero he estado vagando por Internet durante horas sin éxito, así que doy la vuelta aquí:

Tengo un archivo de texto utf-16 bastante estándar, con una mezcla de caracteres ingleses y chinos. Me gustaría que esos personajes terminen en una cadena (técnicamente, un wstring). He visto respuestas a muchas preguntas relacionadas (aquí y en otras partes), pero están buscando resolver el problema mucho más difícil de leer archivos arbitrarios sin conocer la encoding, o convertir entre codificaciones, o simplemente están confundidos acerca de “Unicode” “siendo un rango de codificaciones. Conozco la fuente del archivo de texto que bash leer, siempre será UTF16, tiene una lista de materiales y todo, y puede seguir así.

He estado usando la solución descrita aquí , que funcionaba para archivos de texto que eran todos ingleses, pero después de encontrar ciertos caracteres, dejó de leer el archivo. La única otra sugerencia que encontré fue usar ICU , que probablemente funcionaría, pero preferiría no incluir una gran biblioteca en una aplicación para distribución, solo para leer un archivo de texto en un solo lugar. Sin embargo, no me importa la independencia del sistema; solo necesito comstackr y trabajar en Windows. Una solución que no se basara en ese hecho sería más bonita , por supuesto, pero estaría igual de feliz por una solución que usara el stl mientras dependiera de suposiciones sobre la architecture de Windows, o incluso soluciones que involucraran funciones win32, o ATL; Simplemente no quiero tener que incluir otra gran biblioteca de terceros como ICU. ¿Todavía no he tenido suerte a menos que quiera volver a implementarlo todo yo mismo?

editar: Estoy atascado usando VS2008 para este proyecto en particular, por lo que el código C ++ 11 lamentablemente no ayudará.

edición 2: me di cuenta de que el código que había estado pidiendo prestado antes no falló en caracteres no ingleses como creía que estaba haciendo. Más bien, falla en caracteres específicos en mi documento de prueba, entre ellos ‘:’ (FULLWIDTH COLON, U + FF1A) y ‘)’ (FULLWIDTH RIGHT PARENTHESIS, U + FF09). La solución publicada de bames53 también funciona en su mayoría, pero ¿no le gustan esos mismos personajes?

edit 3 (¡y la respuesta!): el código original que había estado usando -did- en su mayoría funcionaba- como bames53 me ayudó a descubrir, el ifstream solo necesitaba abrirse en modo binario para que funcionara.

Cuando abre un archivo para UTF-16, debe abrirlo en modo binario. Esto se debe a que en el modo de texto, ciertos caracteres se interpretan especialmente; específicamente, 0x0d se filtra por completo y 0x1a marca el final del archivo. Hay algunos caracteres UTF-16 que tendrán uno de esos bytes como la mitad del código del carácter y arruinarán la lectura del archivo. Esto no es un error, es un comportamiento intencional y es la única razón para tener modos separados de texto y binarios.

Por la razón por la que 0x1a se considera el final de un archivo, consulte esta publicación de blog de Raymond Chen que rastrea el historial de Ctrl-Z. Básicamente, la compatibilidad con versiones anteriores se dispara.

La solución C ++ 11 (compatible, en su plataforma, por Visual Studio desde 2010, hasta donde yo sé), sería:

 #include  #include  #include  #include  int main() { // open as a byte stream std::wifstream fin("text.txt", std::ios::binary); // apply BOM-sensitive UTF-16 facet fin.imbue(std::locale(fin.getloc(), new std::codecvt_utf16)); // read for(wchar_t c; fin.get(c); ) std::cout < < std::showbase << std::hex << c << '\n'; } 

Editar:

Parece que el problema es que Windows trata ciertas secuencias de bytes mágicos como el final del archivo en modo texto. Esto se resuelve utilizando el modo binario para leer el archivo, std::ifstream fin("filename", std::ios::binary); y luego copiar los datos en un wstring como ya lo hace.



La solución más simple y no portátil sería simplemente copiar los datos del archivo en una matriz wchar_t. Esto se basa en el hecho de que wchar_t en Windows tiene 2 bytes y usa UTF-16 como su encoding.


Tendrá dificultades para convertir UTF-16 a la encoding wchar_t específica de la configuración regional de una manera completamente portátil.

Aquí está la funcionalidad de conversión Unicode disponible en la biblioteca estándar de C ++ (aunque VS 10 y 11 implementan solo los elementos 3, 4 y 5)

  1. codecvt
  2. codecvt
  3. codecvt_utf8
  4. codecvt_utf16
  5. codecvt_utf8_utf16
  6. c32rtomb / mbrtoc32
  7. c16rtomb / mbrtoc16

Y lo que hace cada uno

  1. Una faceta codecvt que siempre convierte entre UTF-8 y UTF-32
  2. convierte entre UTF-8 y UTF-16
  3. convierte entre UTF-8 y UCS-2 o UCS-4 dependiendo del tamaño del elemento de destino (los caracteres fuera de BMP están probablemente truncados)
  4. convierte entre una secuencia de caracteres mediante un esquema de encoding UTF-16 y UCS-2 o UCS-4
  5. convierte entre UTF-8 y UTF-16
  6. Si se define la macro __STDC_UTF_32__ estas funciones se convierten entre la encoding de caracteres de la localización actual y UTF-32
  7. Si se define la macro __STDC_UTF_16__ estas funciones se convierten entre la encoding de caracteres de la localización actual y UTF-16

Si se define __STDC_ISO_10646__ , la conversión directa usando codecvt_utf16 debería estar bien, ya que esa macro indica que los valores de wchar_t en todas las configuraciones regionales corresponden a los nombres cortos de las cartas Unicode (y eso implica que wchar_t es lo suficientemente grande como para contener dicho valor).

Lamentablemente, no hay nada definido que vaya directamente de UTF-16 a wchar_t. Es posible ir a UTF-16 -> UCS-4 -> mb (si __STDC_UTF_32__ ) -> wc, pero __STDC_UTF_32__ cualquier cosa que no sea representable en la encoding __STDC_UTF_32__ la configuración regional. Y, por supuesto, pase lo que pase, la conversión de UTF-16 a wchar_t perderá cualquier cosa no representable en la encoding wchar_t de la configuración regional.


Por lo tanto, probablemente no valga la pena ser portátil, y en su lugar puede simplemente leer los datos en una matriz wchar_t, o utilizar alguna otra función específica de Windows, como el modo _O_U16TEXT en los archivos.

Esto debería desarrollarse y ejecutarse en cualquier lugar, pero hace un montón de suposiciones para funcionar:

 #include  #include  #include  int main () { std::stringstream ss; std::ifstream fin("filename"); ss < < fin.rdbuf(); // dump file contents into a stringstream std::string const &s = ss.str(); if (s.size()%sizeof(wchar_t) != 0) { std::cerr << "file not the right size\n"; // must be even, two bytes per code unit return 1; } std::wstring ws; ws.resize(s.size()/sizeof(wchar_t)); std::memcpy(&ws[0],s.c_str(),s.size()); // copy data into wstring } 

Probablemente deberías al menos agregar código para manejar endianess y el 'BOM'. Además, las nuevas líneas de Windows no se convierten automáticamente, por lo que debe hacerlo manualmente.