Вернуть StreamReader в начало, когда его BaseStream имеет спецификацию - PullRequest
1 голос
/ 24 июня 2011

Я ищу безошибочный способ вернуть StreamReader в начало, особенно когда его базовый BaseStream начинается с BOM, но также должен работать, когда BOM отсутствует.Создание нового StreamReader, который читает с начала потока, также допустимо.

Исходный StreamReader может быть создан с любой кодировкой, а для detectEncodingFromByteOrderMarks установлено значение true или false.Кроме того, чтение может быть сделано или нет до вызова сброса.

Поток может быть произвольным текстом, а файлы, начинающиеся с байтов 0xef, 0xbb, 0xbf, могут быть файлами с спецификацией или файлами, начинающимися с допустимой последовательности символов (например, «» », если ISO-8859-1кодирование), в зависимости от параметров, использованных при создании StreamReader.

Я видел других решений , но они не работают должным образом, когда BaseStream начинается с спецификации.StreamReader запоминает, что он уже обнаружил спецификацию, и первый символ, который возвращается при выполнении чтения, - это специальный символ спецификации.

Также я могу создать новый StreamReader, но не могу знать,исходный StreamReader был создан с параметром detectEncodingFromByteOrderMarks, установленным в значение true или в значение false.

Это то, что я пробовал в первую очередь:

    //fails with TestMethod1
    void ResetStream1(ref StreamReader sr) {
        sr.BaseStream.Position = 0;
        sr.DiscardBufferedData();
    }

    //fails with TestMethod2
    void ResetStream2(ref StreamReader sr) {
        sr.BaseStream.Position = 0;
        sr = new StreamReader(sr.BaseStream, sr.CurrentEncoding, true);
    }

    //fails with TestMethod3
    void ResetStream3(ref StreamReader sr) {
        sr.BaseStream.Position = 0;
        sr = new StreamReader(sr.BaseStream, sr.CurrentEncoding, false);
    }

И вот эти методы метода:

    Stream StreamWithBOM = new MemoryStream(new byte[] {0xef,0xbb,0xbf,(byte)'X'});


    [TestMethod]
    public void TestMethod1() {
        StreamReader sr=new StreamReader(StreamWithBOM);
        int before=sr.Read(); //reads X

        ResetStream(ref sr);
        int after=sr.Read();

        Assert.AreEqual(before, after);
    }

    [TestMethod]
    public void TestMethod2() {
        StreamReader sr = new StreamReader(StreamWithBOM,Encoding.GetEncoding("ISO-8859-1"),false);
        int before = sr.Read(); //reads ï

        ResetStream(ref sr);
        int after = sr.Read();

        Assert.AreEqual(before, after);
    }

    [TestMethod]
    public void TestMethod3() {
        StreamReader sr = new StreamReader(StreamWithBOM, Encoding.GetEncoding("ISO-8859-1"), true);
        int expected = (int)'X'; //no Read() done before reset

        ResetStream(ref sr);
        int after = sr.Read();

        Assert.AreEqual(expected, after);
    }

Наконец, я нашел решение (см. Мой собственный ответ), которое проходит все 3 теста, но я хочу посмотреть, возможно ли более элегантное или быстрое решение.

Ответы [ 2 ]

2 голосов
/ 24 июня 2011
    //pass all 3 tests
    void ResetStream(ref StreamReader sr){
        sr.Read(); //ensure that BOM is detected if configured to do so
        sr.BaseStream.Position=0;
        sr=new StreamReader(sr.BaseStream, sr.CurrentEncoding, false);
    }
0 голосов
/ 08 ноября 2018

Это позволяет без необходимости создавать новый StreamReader:

  void ResetStream(StreamReader sr)
  {
      sr.BaseStream.Position = sr.CurrentEncoding.GetPreamble().Length;
      sr.DiscardBufferedData();
  }

GetPreamble () вернет пустой байтовый массив, если нет спецификации.

Это должно работать с или без спецификации, потому что класс UTF8Encoding (и другие, например, UTF32Encoding, UnicodeEncoding) имеет внутреннее поле, которое отслеживает, включена ли спецификация и устанавливается StreamReader, когда вы впервые выполняете чтение ().

Однако, похоже, вам нужно передать кодировку конструктору StreamReader с отключенным флагом идентификатора спецификации, и тогда он будет правильно обнаруживать наличие спецификации. Если вы просто передаете поток в качестве единственного параметра, как в TestMethod1 выше, то по какой-то причине он устанавливает для CurrentEncoding значение UTF8 с спецификацией, даже если в вашем потоке нет спецификации. Задание для trueEncodingFromByteOrderMarks значения true также не помогает, поскольку по умолчанию используется значение true.

Оба приведенных ниже теста пройдены, поскольку по умолчанию для кодировки UTF8 отключена спецификация.

    Stream StreamWithBOM = new MemoryStream(new byte[] { 0xef, 0xbb, 0xbf, (byte)'X' });
    Stream StreamWithoutBOM = new MemoryStream(new byte[] { (byte)'X' });

    [TestMethod]
    public void TestMethod4()
    {
        StreamReader sr = new StreamReader(StreamWithBOM, new UTF8Encoding());
        int before = sr.Read(); //reads X

        ResetStream(sr);
        int after = sr.Read();

        Assert.AreEqual(before, after);
    }

    [TestMethod]
    public void TestMethod5()
    {
        StreamReader sr = new StreamReader(StreamWithoutBOM, new UTF8Encoding());
        int before = sr.Read(); //reads X

        ResetStream(sr);
        int after = sr.Read();

        Assert.AreEqual(before, after);
    }
...