web-dev-qa-db-fra.com

StreamWriter et UTF-8 Marques d'ordre d'ordre

J'ai un problème avec StreamWriter et Byte Order Marks. La documentation semble indiquer que l'encodage Encoding.UTF8 a les marques d'ordre d'octets activées, mais lorsque les fichiers sont en cours d'écriture, certains portent les marques, d'autres non. 

Je crée le rédacteur de flux de la manière suivante:

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

Toute idée sur ce qui pourrait se passer serait appréciée. 

50
Kevin

Comme quelqu'un l'a déjà souligné, appeler sans l'argument de codage fait l'affaire . Cependant, si vous voulez être explicite, essayez ceci:

using (var sw = new StreamWriter(this.Stream, new UTF8Encoding(false)))

La clé consiste à construire un nouveau UTF8Encoding (false) au lieu d'utiliser Encoding.UTF8Encoding. C'est pour contrôler si une nomenclature doit être ajoutée ou non.

C’est la même chose que d’appeler StreamWriter sans l’argument de codage. En interne, c’est la même chose.

74
HelloSam

Le problème est dû au fait que vous utilisez la propriété statique UTF8 sur la classe Encoding .

Lorsque la méthode GetPreamble est appelée sur l'instance de la classe Encoding renvoyée par la propriété UTF8, elle renvoie la marque d'ordre d'octet (le tableau d'octets de trois caractères) et est écrite dans le flux avant l'écriture de tout autre contenu le flux (en supposant un nouveau flux).

Vous pouvez éviter cela en créant l'instance de la classe UTF8Encoding vous-même, comme suit:

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

Selon la documentation du constructeur sans paramètre default (l'emphase mienne):

Ce constructeur crée une instance qui ne fournit pas de marque d'ordre d'octet Unicode et ne lève pas d'exception lorsqu'un encodage non valide est détecté.

Cela signifie que l'appel à GetPreamble renverra un tableau vide et que, par conséquent, aucune nomenclature ne sera écrite dans le flux sous-jacent.

15
casperOne

Ma réponse est basée sur celle de HelloSam qui contient toutes les informations nécessaires ..___ Je crois que ce que l’OP demande, c’est de savoir comment s’assurer que la nomenclature est bien enregistrée dans le fichier.

Ainsi, au lieu de transmettre false à UTF8Encoding ctor, vous devez transmettre true.

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

Essayez le code ci-dessous, ouvrez les fichiers résultants dans un éditeur hexadécimal et voyez lequel contient la nomenclature et qui n'en contient pas.

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Привет");
        }
    }
12
Nik

La seule fois où j'ai vu ce constructeur ne pas ajouter la nomenclature UTF-8, c'est si le flux n'est pas à la position 0 lorsque vous l'appelez. Par exemple, dans le code ci-dessous, la nomenclature n'est pas écrite:

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

Comme d'autres l'ont déjà dit, si vous utilisez le constructeur StreamWriter(stream), sans spécifier l'encodage, vous ne verrez pas la nomenclature.

12
Jim Mischel

Utilisez-vous le même constructeur de StreamWriter pour chaque fichier? Parce que la documentation dit:

Pour créer un StreamWriter utilisant le codage UTF-8 et une nomenclature, envisagez d'utiliser un constructeur qui spécifie le codage, tel que StreamWriter (String, Boolean, Encoding).

J'étais dans une situation similaire il y a un moment. J'ai fini par utiliser la méthode Stream.Write à la place de StreamWriter et d'écrire le résultat de Encoding.GetPreamble() avant d'écrire la Encoding.GetBytes(stringToWrite)

5
Philipp Grathwohl

J'ai trouvé cette réponse utile (grâce à @Philipp Grathwohl et @Nik), mais dans mon cas, j'utilise FileStream pour accomplir la tâche. Le code qui génère la nomenclature se présente ainsi:

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();
}
3
Rodrigo Caballero

Il semble que si le fichier existait déjà et ne contenait pas de nomenclature, il ne contiendrait pas de nomenclature une fois écrasé. En d'autres termes, StreamWriter préserve la nomenclature (ou son absence) lors du remplacement d'un fichier.

2
Gregory S. Chudov

Pourriez-vous s'il vous plaît montrer une situation où il ne produit pas? Le seul cas où le préambule n'est pas présent est le cas où rien n'est jamais écrit à l'écrivain (Jim Mischel semble avoir trouvé un autre problème logique et plus susceptible de vous concerner, voir la réponse).

Mon code de test:

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)
    );
0
Julien Roncaglia