2012-07-28 14 views
22

En .NET, estoy tratando de usar el método Encoding.UTF8.GetString, que toma una matriz de bytes y la convierte en string.Encoding.UTF8.GetString no tiene en cuenta el Preámbulo/BOM

Parece que este método ignora el BOM (Byte Order Mark), que podría formar parte de una representación binaria legítima de una cadena UTF8, y lo toma como un carácter.

Sé que puedo usar un TextReader para digerir la lista de materiales según sea necesario, pero pensé que el método GetString debería ser una especie de macro que hace que nuestro código sea más corto.

¿Echo de menos algo? ¿Es esto tan intencionalmente?

Aquí hay un código de reproducción:

static void Main(string[] args) 
{ 
    string s1 = "abc"; 
    byte[] abcWithBom; 
    using (var ms = new MemoryStream()) 
    using (var sw = new StreamWriter(ms, new UTF8Encoding(true))) 
    { 
     sw.Write(s1); 
     sw.Flush(); 
     abcWithBom = ms.ToArray(); 
     Console.WriteLine(FormatArray(abcWithBom)); // ef, bb, bf, 61, 62, 63 
    } 

    byte[] abcWithoutBom; 
    using (var ms = new MemoryStream()) 
    using (var sw = new StreamWriter(ms, new UTF8Encoding(false))) 
    { 
     sw.Write(s1); 
     sw.Flush(); 
     abcWithoutBom = ms.ToArray(); 
     Console.WriteLine(FormatArray(abcWithoutBom)); // 61, 62, 63 
    } 

    var restore1 = Encoding.UTF8.GetString(abcWithoutBom); 
    Console.WriteLine(restore1.Length); // 3 
    Console.WriteLine(restore1); // abc 

    var restore2 = Encoding.UTF8.GetString(abcWithBom); 
    Console.WriteLine(restore2.Length); // 4 (!) 
    Console.WriteLine(restore2); // ?abc 
} 

private static string FormatArray(byte[] bytes1) 
{ 
    return string.Join(", ", from b in bytes1 select b.ToString("x")); 
} 

Respuesta

18

Parece que este método hace caso omiso de la lista de materiales (Byte Order Mark), lo que podría ser parte de una representación binaria legítimo de una cadena UTF8, y se lo lleva como un personaje.

No parece que lo "ignore" en absoluto, lo convierte fielmente en el carácter BOM. Eso es lo que es, después de todo.

Si usted desea hacer su código ignoran la lista de materiales en cualquier cadena se convierte, eso depende de usted para hacer ... o utilizar StreamReader.

Tenga en cuenta que si bien uso Encoding.GetBytes seguido por Encoding.GetStringo uso StreamWriter seguido por StreamReader, ambas formas o bien se producen luego trague o no producir la lista de materiales. Solo cuando mezcla con un StreamWriter (que usa Encoding.GetPreamble) con una llamada directa Encoding.GetString, termina con el carácter "extra". (! Gracias)

+0

veo. ¡Gracias por la aclaración! –

+5

@RonKlein Además, podría decir 'restore2 = restore2.TrimStart ('\ uFEFF')' para eliminar los principales caracteres de la BOM. También me he preguntado por qué '(nuevo UTF8Encoding (true)). GetBytes (" abc ")' y '(new UTF8Encoding (false)). GetBytes (" abc ")' produce el mismo resultado, pero como probablemente Sé que hasta ahora, 'GetBytes' no supone que estás en el comienzo de un archivo, por lo que nunca usa' GetPreamble'. Tienes que 'GetPreamble' explícitamente, o saltear el preámbulo explícitamente, si usas 'GetBytes' o 'GetString'. –

7

Sobre la base de la respuesta de Jon Skeet, así es como simplemente lo hice:

var memoryStream = new MemoryStream(byteArray); 
var s = new StreamReader(memoryStream).ReadToEnd(); 

Tenga en cuenta que esto probablemente sólo funcionará correctamente si existe es una lista de materiales en la matriz de bytes estás leyendo de. Si no es así, es posible que desee buscar en another StreamReader constructor overload, que toma un parámetro de codificación para que pueda decir lo que contiene la matriz de bytes.

+0

Creo que podría querer [esta sobrecarga de constructor] (https://msdn.microsoft.com/en-us/library/ms143457 (v = vs.110) .aspx) que le permite especificar si debería buscar un BOM para averiguar la codificación. – drzaus

0

Yo sé que soy un poco tarde a la fiesta, pero aquí está el código que estoy utilizando (no dude en adaptarse a C#) si es necesario:

Public Function Serialize(Of YourXMLClass)(ByVal obj As YourXMLClass, 
                 Optional ByVal omitXMLDeclaration As Boolean = True, 
                 Optional ByVal omitXMLNamespace As Boolean = True) As String 

     Dim serializer As New XmlSerializer(obj.GetType) 
     Using memStream As New MemoryStream() 
      Dim settings As New XmlWriterSettings() With { 
        .Encoding = Encoding.UTF8, 
        .Indent = True, 
        .OmitXmlDeclaration = omitXMLDeclaration} 

      Using writer As XmlWriter = XmlWriter.Create(memStream, settings) 
       Dim xns As New XmlSerializerNamespaces 
       If (omitXMLNamespace) Then xns.Add("", "") 
       serializer.Serialize(writer, obj, xns) 
      End Using 

      Return Encoding.UTF8.GetString(memStream.ToArray()) 
     End Using 
    End Function 

Public Function Deserialize(Of YourXMLClass)(ByVal obj As YourXMLClass, ByVal xml As String) As YourXMLClass 
     Dim result As YourXMLClass 
     Dim serializer As New XmlSerializer(GetType(YourXMLClass)) 

     Using memStream As New MemoryStream() 
      Dim bytes As Byte() = Encoding.UTF8.GetBytes(xml.ToArray) 
      memStream.Write(bytes, 0, bytes.Count) 
      memStream.Seek(0, SeekOrigin.Begin) 

      Using reader As XmlReader = XmlReader.Create(memStream) 
       result = DirectCast(serializer.Deserialize(reader), YourXMLClass) 
      End Using 

     End Using 
     Return result 
    End Function 
Cuestiones relacionadas