c++ - Escribir utf16 en el archivo en modo binario
unicode utf-16 (6)
Aquí nos encontramos con las pequeñas propiedades locales utilizadas. Si emite su cadena como una cadena (en lugar de datos brutos), puede hacer que la configuración regional realice la conversión adecuada de forma automática.
NB Este código no tiene en cuenta la edianidad del carácter wchar_t.
#include <locale>
#include <fstream>
#include <iostream>
// See Below for the facet
#include "UTF16Facet.h"
int main(int argc,char* argv[])
{
// construct a custom unicode facet and add it to a local.
UTF16Facet *unicodeFacet = new UTF16Facet();
const std::locale unicodeLocale(std::cout.getloc(), unicodeFacet);
// Create a stream and imbue it with the facet
std::wofstream saveFile;
saveFile.imbue(unicodeLocale);
// Now the stream is imbued we can open it.
// NB If you open the file stream first. Any attempt to imbue it with a local will silently fail.
saveFile.open("output.uni");
saveFile << L"This is my Data/n";
return(0);
}
El archivo: UTF16Facet.h
#include <locale>
class UTF16Facet: public std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type>
{
typedef std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type> MyType;
typedef MyType::state_type state_type;
typedef MyType::result result;
/* This function deals with converting data from the input stream into the internal stream.*/
/*
* from, from_end: Points to the beginning and end of the input that we are converting ''from''.
* to, to_limit: Points to where we are writing the conversion ''to''
* from_next: When the function exits this should have been updated to point at the next location
* to read from. (ie the first unconverted input character)
* to_next: When the function exits this should have been updated to point at the next location
* to write to.
*
* status: This indicates the status of the conversion.
* possible values are:
* error: An error occurred the bad file bit will be set.
* ok: Everything went to plan
* partial: Not enough input data was supplied to complete any conversion.
* nonconv: no conversion was done.
*/
virtual result do_in(state_type &s,
const char *from,const char *from_end,const char* &from_next,
wchar_t *to, wchar_t *to_limit,wchar_t* &to_next) const
{
// Loop over both the input and output array/
for(;(from < from_end) && (to < to_limit);from += 2,++to)
{
/*Input the Data*/
/* As the input 16 bits may not fill the wchar_t object
* Initialise it so that zero out all its bit''s. This
* is important on systems with 32bit wchar_t objects.
*/
(*to) = L''/0'';
/* Next read the data from the input stream into
* wchar_t object. Remember that we need to copy
* into the bottom 16 bits no matter what size the
* the wchar_t object is.
*/
reinterpret_cast<char*>(to)[0] = from[0];
reinterpret_cast<char*>(to)[1] = from[1];
}
from_next = from;
to_next = to;
return((from > from_end)?partial:ok);
}
/* This function deals with converting data from the internal stream to a C/C++ file stream.*/
/*
* from, from_end: Points to the beginning and end of the input that we are converting ''from''.
* to, to_limit: Points to where we are writing the conversion ''to''
* from_next: When the function exits this should have been updated to point at the next location
* to read from. (ie the first unconverted input character)
* to_next: When the function exits this should have been updated to point at the next location
* to write to.
*
* status: This indicates the status of the conversion.
* possible values are:
* error: An error occurred the bad file bit will be set.
* ok: Everything went to plan
* partial: Not enough input data was supplied to complete any conversion.
* nonconv: no conversion was done.
*/
virtual result do_out(state_type &state,
const wchar_t *from, const wchar_t *from_end, const wchar_t* &from_next,
char *to, char *to_limit, char* &to_next) const
{
for(;(from < from_end) && (to < to_limit);++from,to += 2)
{
/* Output the Data */
/* NB I am assuming the characters are encoded as UTF-16.
* This means they are 16 bits inside a wchar_t object.
* As the size of wchar_t varies between platforms I need
* to take this into consideration and only take the bottom
* 16 bits of each wchar_t object.
*/
to[0] = reinterpret_cast<const char*>(from)[0];
to[1] = reinterpret_cast<const char*>(from)[1];
}
from_next = from;
to_next = to;
return((to > to_limit)?partial:ok);
}
};
Intento escribir un wstring para archivar con ofstream en modo binario, pero creo que estoy haciendo algo mal. Esto es lo que he intentado:
ofstream outFile("test.txt", std::ios::out | std::ios::binary);
wstring hello = L"hello";
outFile.write((char *) hello.c_str(), hello.length() * sizeof(wchar_t));
outFile.close();
Al abrir test.txt en, por ejemplo, Firefox con la codificación establecida en UTF16, se mostrará como:
h e l l o
¿Alguien podría decirme por qué sucede esto?
EDITAR:
Al abrir el archivo en un editor hexadecimal obtengo:
FF FE 68 00 00 00 65 00 00 00 6C 00 00 00 6C 00 00 00 6F 00 00 00
Parece que tengo dos bytes adicionales entre cada personaje por alguna razón?
Debería mirar el archivo de salida en un editor hexadecimal como WinHex para que pueda ver los bits y bytes reales, para verificar que el resultado sea realmente UTF-16. Póngalo aquí y díganos el resultado. Eso nos dirá si culpar a Firefox o su programa C ++.
Pero me parece que su programa C ++ funciona y Firefox no está interpretando correctamente su UTF-16. UTF-16 requiere dos bytes para cada personaje. Pero Firefox está imprimiendo el doble de caracteres que debería, por lo que probablemente intente interpretar su cadena como UTF-8 o ASCII, que generalmente solo tiene 1 byte por carácter.
Cuando dices "Firefox con codificación establecida en UTF16", ¿a qué te refieres? Soy escéptico de que ese trabajo funcione.
Sospecho que sizeof (wchar_t) es 4 en su entorno, es decir, está escribiendo UTF-32 / UCS-4 en lugar de UTF-16. Eso es ciertamente lo que parece el volcado hexadecimal.
Eso es bastante fácil de probar (solo imprime sizeof (wchar_t)) pero estoy bastante seguro de que es lo que está pasando.
Para pasar de un UTF-32 wstring a un UTF-16 necesitarás aplicar una codificación adecuada, ya que los pares de sustitución entran en juego.
En las ventanas que usan wofstream y utf16, la faceta definida anteriormente falla porque wofstream convierte todos los bytes con el valor 0A a 2 bytes 0D 0A, independientemente de cómo pase el byte 0A en '', x'' A '', L'' / x0A '', L ''/ x000A'', ''/ n'', L ''/ n'' y std :: endl dan todos el mismo resultado. En Windows debe abrir el archivo con un flujo continuo (no un wofsteam) en modo binario y escribir el resultado tal como se hace en la publicación original.
El Utf16Facet
provisto no funcionaba en gcc
para grandes cadenas, aquí está la versión que funcionó para mí ... De esta forma el archivo se guardará en UTF-16LE
. Para UTF-16BE
, simplemente invierta las asignaciones en do_in
y do_out
, por ejemplo, to[0] = from[1]
y to[1] = from[0]
#include <locale>
#include <bits/codecvt.h>
class UTF16Facet: public std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type>
{
typedef std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type> MyType;
typedef MyType::state_type state_type;
typedef MyType::result result;
/* This function deals with converting data from the input stream into the internal stream.*/
/*
* from, from_end: Points to the beginning and end of the input that we are converting ''from''.
* to, to_limit: Points to where we are writing the conversion ''to''
* from_next: When the function exits this should have been updated to point at the next location
* to read from. (ie the first unconverted input character)
* to_next: When the function exits this should have been updated to point at the next location
* to write to.
*
* status: This indicates the status of the conversion.
* possible values are:
* error: An error occurred the bad file bit will be set.
* ok: Everything went to plan
* partial: Not enough input data was supplied to complete any conversion.
* nonconv: no conversion was done.
*/
virtual result do_in(state_type &s,
const char *from,const char *from_end,const char* &from_next,
wchar_t *to, wchar_t *to_limit,wchar_t* &to_next) const
{
for(;from < from_end;from += 2,++to)
{
if(to<=to_limit){
(*to) = L''/0'';
reinterpret_cast<char*>(to)[0] = from[0];
reinterpret_cast<char*>(to)[1] = from[1];
from_next = from;
to_next = to;
}
}
return((to != to_limit)?partial:ok);
}
/* This function deals with converting data from the internal stream to a C/C++ file stream.*/
/*
* from, from_end: Points to the beginning and end of the input that we are converting ''from''.
* to, to_limit: Points to where we are writing the conversion ''to''
* from_next: When the function exits this should have been updated to point at the next location
* to read from. (ie the first unconverted input character)
* to_next: When the function exits this should have been updated to point at the next location
* to write to.
*
* status: This indicates the status of the conversion.
* possible values are:
* error: An error occurred the bad file bit will be set.
* ok: Everything went to plan
* partial: Not enough input data was supplied to complete any conversion.
* nonconv: no conversion was done.
*/
virtual result do_out(state_type &state,
const wchar_t *from, const wchar_t *from_end, const wchar_t* &from_next,
char *to, char *to_limit, char* &to_next) const
{
for(;(from < from_end);++from, to += 2)
{
if(to <= to_limit){
to[0] = reinterpret_cast<const char*>(from)[0];
to[1] = reinterpret_cast<const char*>(from)[1];
from_next = from;
to_next = to;
}
}
return((to != to_limit)?partial:ok);
}
};
Es fácil si usa el estándar C++11
(porque hay muchos adicionales adicionales como "utf8"
que resuelve este problema para siempre).
Pero si desea utilizar un código multiplataforma con estándares anteriores, puede usar este método para escribir con transmisiones:
- Lea el artículo sobre el convertidor UTF para transmisiones
- Agregue
stxutif.h
a su proyecto desde las fuentes anteriores Abra el archivo en modo ANSI y agregue la lista de materiales al inicio de un archivo, como este:
std::ofstream fs; fs.open(filepath, std::ios::out|std::ios::binary); unsigned char smarker[3]; smarker[0] = 0xEF; smarker[1] = 0xBB; smarker[2] = 0xBF; fs << smarker; fs.close();
Luego abre el archivo como
UTF
y escribe tu contenido allí:std::wofstream fs; fs.open(filepath, std::ios::out|std::ios::app); std::locale utf8_locale(std::locale(), new utf8cvt<false>); fs.imbue(utf8_locale); fs << .. // Write anything you want...