2011-03-10 18 views
44

Tengo un problema con StreamWriter y Byte Order Marks. La documentación parece indicar que la codificación Encoding.UTF8 tiene marcas de orden de bytes habilitadas, pero cuando se escriben algunos, algunos tienen las marcas y otros no.Marcas de orden de bytes StreamWriter y UTF-8

estoy creando el escritor corriente de la siguiente manera:

this.Writer = new StreamWriter(this.Stream , System.Text.Encoding.UTF8); 

¿Alguna idea sobre sería apreciado lo que podría estar sucediendo.

+1

Tenga en cuenta que, aunque está técnicamente permitido en UTF-8, UnMode no requiere ni recomienda una lista de materiales (véase [ref] (http://www.unicode.org/versions/Unicode5.0.0/ch02.pdf).)). Por un lado, es inútil (a diferencia de, por ejemplo, UTF-16): el estándar especifica el orden de bytes UTF-8. Por otro lado, puede arruinar el procesamiento de texto. Por ejemplo, muchos analizadores XML se ahogarán si hay caracteres antes del prólogo XML. –

+1

¿Estás seguro de que estás especificando UTF8? Porque si no lo especifica, aún escribirá un UTF8, pero sin la BOM – xanatos

+0

de Unicode Standard 5.0: * El estándar Unicode también especifica el uso de una marca de orden de bytes inicial (BOM) para diferenciar explícitamente big- datos endian o little endian en algunos de los esquemas de codificación Unicode. * –

Respuesta

5

¿Utiliza el mismo constructor de StreamWriter para cada archivo? Debido a que la documentación dice:

Para crear un StreamWriter utilizando codificación UTF-8 y una lista de materiales, considerar el uso de un constructor que especifica la codificación, como StreamWriter (String, Boolean, codificación).

Me encontré en una situación similar hace un tiempo. Terminé usando el método en lugar del StreamWriter Stream.Write y escribí el resultado de Encoding.GetPreamble() antes de escribir el Encoding.GetBytes(stringToWrite)

11

La única vez que he visto que el constructor no añadir el BOM UTF-8 es si la corriente no está en la posición 0 cuando lo llamas Por ejemplo, en el código siguiente, la lista de materiales no se escribe:

using (var s = File.Create("test2.txt")) 
{ 
    s.WriteByte(32); 
    using (var sw = new StreamWriter(s, Encoding.UTF8)) 
    { 
     sw.WriteLine("hello, world"); 
    } 
} 

Como otros han dicho, si usted está utilizando el constructor StreamWriter(stream), sin especificar la codificación, entonces no podrá ver la lista de materiales.

0

¿Podría mostrar una situación en la que no se produce? El único caso en el que el preámbulo no está presente y que puedo encontrar es cuando nunca se escribe nada al escritor (Jim Mischel parece haber encontrado otro, lógico y más probable que sea su problema, vea que es la respuesta).

Mi código de prueba:

var stream = new MemoryStream(); 
using(var writer = new StreamWriter(stream, System.Text.Encoding.UTF8)) 
{ 
    writer.Write('a'); 
} 
Console.WriteLine(stream.ToArray() 
    .Select(b => b.ToString("X2")) 
    .Aggregate((i, a) => i + " " + a) 
    ); 
2

parece que si el archivo ya existía y no contenía la lista de materiales, entonces no va a contener la lista de materiales cuando sobrescrito, en otras palabras StreamWriter conserva lista de materiales (o es la ausencia) cuando sobrescribe un archivo.

61

Como alguien ya lo señaló, la llamada sin el argumento de codificación funciona. Sin embargo, si usted quiere ser explícita, intente esto:

using (var sw = new StreamWriter("text.txt", new UTF8Encoding(false))) 

La clave es la construcción de un nuevo UTF8Encoding (falso), en lugar de utilizar Encoding.UTF8Encoding. Eso es para controlar si se debe agregar o no BOM.

Esto es lo mismo que llamar a StreamWriter sin el argumento de codificación, internamente solo hace lo mismo.

13

El problema se debe al hecho de que está utilizando la estática UTF8 property en el Encoding class.

Cuando el GetPreamble method se llama en la instancia de la clase Encoding devuelto por la propiedad UTF8, devuelve la marca de orden de bytes (la matriz de bytes de tres caracteres) y se escribe en la corriente antes de cualquier otro tipo de contenido se escribe en el transmisión (suponiendo una nueva transmisión).

Esto se puede evitar mediante la creación de la instancia de la UTF8Encoding class a sí mismo, así:

// As before. 
this.Writer = new StreamWriter(this.Stream, 
    // Create yourself, passing false will prevent the BOM from being written. 
    new System.Text.UTF8Encoding()); 

De acuerdo con la documentación para el (la cursiva es mía) default parameterless constructor:

Esto crea una instancia que no proporciona una marca de orden de bytes Unicode y no arroja una excepción cuando se detecta una codificación no válida.

Esto significa que la llamada a GetPreamble devolverá una matriz vacía y, por lo tanto, no se escribirá ninguna lista de materiales en la secuencia subyacente.

+0

La codificación es una configuración de usuario en nuestro programa (que envía mensajes de texto a través de TCP) ... se recupera con un simple análisis con 'enc = Encoding.GetEncoding (...)'. La única forma que encontré fue agregar 'if (enc es UTF8Encoding) enc = new UTF8Encoding (false);' detrás de él. Una corrección bastante sucia, pero no veo otra forma de resolverlo ... – Nyerguds

+0

@Nyerguds Esa no es la única manera. Puede abstraer la obtención de la codificación en una interfaz que, dado un parámetro, obtiene la codificación. Luego pasa/inyecta una implementación de esa interfaz en su código. Luego hace todo bastante limpio. – casperOne

+0

Eso simplemente mueve lo mismo a una clase diferente. En general, me parece completamente extraño que GetEncoding de alguna manera se las arregla para no usar el constructor predeterminado. Ah bueno. – Nyerguds

9

Mi respuesta se basa en la de HelloSam que contiene toda la información necesaria. Solo creo que lo que OP está pidiendo es cómo asegurarse de que la LDM se emita en el archivo.

Por lo tanto, en lugar de pasar el código falso a UTF8Encoding ctor debe pasar cierto.

using (var sw = new StreamWriter("text.txt", new UTF8Encoding(true))) 

Prueba el código de abajo, abrir los archivos resultantes en un editor hexadecimal y ver cuál contiene la lista de materiales y no el que lo hace.

class Program 
{ 
    static void Main(string[] args) 
    { 
     const string nobomtxt = "nobom.txt"; 
     File.Delete(nobomtxt); 

     using (Stream stream = File.OpenWrite(nobomtxt)) 
     using (var writer = new StreamWriter(stream, new UTF8Encoding(false))) 
     { 
      writer.WriteLine("HelloПривет"); 
     } 

     const string bomtxt = "bom.txt"; 
     File.Delete(bomtxt); 

     using (Stream stream = File.OpenWrite(bomtxt)) 
     using (var writer = new StreamWriter(stream, new UTF8Encoding(true))) 
     { 
      writer.WriteLine("HelloПривет"); 
     } 
    } 
3

encontré esta respuesta útil (gracias a @Philipp Grathwohl y @Nik), pero en mi caso estoy usando FileStream para realizar la tarea, por lo que, el código que genera la lista de materiales dice así:

using (FileStream vStream = File.Create(pfilePath)) 
{ 
    // Creates the UTF-8 encoding with parameter "encoderShouldEmitUTF8Identifier" set to true 
    Encoding vUTF8Encoding = new UTF8Encoding(true); 
    // Gets the preamble in order to attach the BOM 
    var vPreambleByte = vUTF8Encoding.GetPreamble(); 

    // Writes the preamble first 
    vStream.Write(vPreambleByte, 0, vPreambleByte.Length); 

    // Gets the bytes from text 
    byte[] vByteData = vUTF8Encoding.GetBytes(pTextToSaveToFile); 
    vStream.Write(vByteData, 0, vByteData.Length); 
    vStream.Close(); 
} 
+1

La mayoría de las veces me pareció útil el 'nuevo constructor UTF8Encoding (true)'. –

Cuestiones relacionadas