2015-08-03 2 views
2

У меня есть несколько файлов XML-файлов с незаконными символами (0x1 и т. Д.). Файлы сторонние, я не могу изменить процесс их написания.Как написать обтекатель потока «фильтр» для XML?

Я хотел бы обрабатывать эти файлы с помощью XmlReader, но это взрывает эти незаконные символы.

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

То, что я хотел бы сделать что-то вроде этого:

using(var origStream = File.OpenRead(fileName)) 
using(var cleanStream = new CleansedXmlStream(origStream)) 
using(var streamReader = new StreamReader(cleanStream)) 
using(var xmlReader = XmlReader.Create(streamReader)) 
{ 
    //do stuff with reader 
} 

Я попытался унаследовать от Stream, но когда я добрался до реализации Read(byte[] buffer, int offset, int count) я потерял некоторую уверенность. В конце концов, я планировал удалить символы, поэтому показалось, что счет будет выключен, и мне пришлось бы перевести каждый байт на char, который казался дорогим (особенно на больших файлах), и я не понял, как это будет работать с Кодировка Unicode, но ответы на мои вопросы не были интуитивно очевидными.

Когда googling для «C# stream wrapper» или «поток фильтра C#», я не получаю удовлетворительных результатов. Возможно, я использую неправильные слова или описываю неправильную концепцию, поэтому я надеюсь, что сообщество SO может меня удержать.

Используя приведенный выше пример, что будет CleansedXmlStream?

Вот что моя первая попытка была похожа:

public class CleansedXmlStream : Stream 
{ 
    private readonly Stream _baseStream; 

    public CleansedXmlStream(Stream stream) 
    { 
     this._baseStream = stream; 
    } 

    public new void Dispose() 
    { 
     if (this._baseStream != null) 
     { 
      this._baseStream.Dispose(); 
     } 
     base.Dispose(); 
    } 

    public override bool CanRead 
    { 
     get { return this._baseStream.CanRead; } 
    } 

    public override bool CanSeek 
    { 
     get { return this._baseStream.CanSeek; } 
    } 

    public override bool CanWrite 
    { 
     get { return this._baseStream.CanWrite; } 
    } 

    public override long Length 
    { 
     get { return this._baseStream.Length; } 
    } 

    public override long Position 
    { 
     get { return this._baseStream.Position; } 
     set { this._baseStream.Position = value; } 
    } 

    public override void Flush() 
    { 
     this._baseStream.Flush(); 
    } 

    public override int Read(byte[] buffer, int offset, int count) 
    { 
     //what does this look like? 

     throw new NotImplementedException(); 
    } 

    public override long Seek(long offset, SeekOrigin origin) 
    { 
     return this._baseStream.Seek(offset, origin); 
    } 

    public override void SetLength(long value) 
    { 
     this._baseStream.SetLength(value); 
    } 

    public override void Write(byte[] buffer, int offset, int count) 
    { 
     throw new NotSupportedException(); 
    } 
} 
+0

0x01 SOH. Потоковые классы по умолчанию кодируются ASCII. Я бы установил класс потока в UTF8. Попробуйте что-то вроде этого: StreamReader stream = new StreamReader (имя файла, Encoding.UTF8); – jdweng

+0

@jdweng по [документам] (https://msdn.microsoft.com/en-us/library/yhfzs7at (v = vs.110) .aspx), 'new StreamReader (Stream)' по умолчанию соответствует UTF8, поэтому это будет не имеет значения. –

+0

Возможно, вам нужно работать на более высоком уровне абстракции. «Поток» - это двоичные данные, тогда как ваши недопустимые символы являются результатом декодирования этих двоичных данных. Может быть, вам нужно украсить «TextReader», а не украшать «Поток»? –

ответ

1

Вдохновленный @ комментарий CharlesMager, я в конечном итоге не делает Stream, а скорее StreamReader так:

public class CleanTextReader : StreamReader 
{ 
    private readonly ILog _logger; 

    public CleanTextReader(Stream stream, ILog logger) : base(stream) 
    { 
     this._logger = logger; 
    } 

    public CleanTextReader(Stream stream) : this(stream, LogManager.GetLogger<CleanTextReader>()) 
    { 
     //nothing to do here. 
    } 

    /// <summary> 
    ///  Reads a specified maximum of characters from the current stream into a buffer, beginning at the specified index. 
    /// </summary> 
    /// <returns> 
    ///  The number of characters that have been read, or 0 if at the end of the stream and no data was read. The number 
    ///  will be less than or equal to the <paramref name="count" /> parameter, depending on whether the data is available 
    ///  within the stream. 
    /// </returns> 
    /// <param name="buffer"> 
    ///  When this method returns, contains the specified character array with the values between 
    ///  <paramref name="index" /> and (<paramref name="index + count - 1" />) replaced by the characters read from the 
    ///  current source. 
    /// </param> 
    /// <param name="index">The index of <paramref name="buffer" /> at which to begin writing. </param> 
    /// <param name="count">The maximum number of characters to read. </param> 
    /// <exception cref="T:System.ArgumentException"> 
    ///  The buffer length minus <paramref name="index" /> is less than 
    ///  <paramref name="count" />. 
    /// </exception> 
    /// <exception cref="T:System.ArgumentNullException"><paramref name="buffer" /> is null. </exception> 
    /// <exception cref="T:System.ArgumentOutOfRangeException"> 
    ///  <paramref name="index" /> or <paramref name="count" /> is 
    ///  negative. 
    /// </exception> 
    /// <exception cref="T:System.IO.IOException">An I/O error occurs, such as the stream is closed. </exception> 
    public override int Read(char[] buffer, int index, int count) 
    { 
     try 
     { 
      var rVal = base.Read(buffer, index, count); 
      var filteredBuffer = buffer.Select(x => XmlConvert.IsXmlChar(x) ? x : ' ').ToArray(); 
      Buffer.BlockCopy(filteredBuffer, 0, buffer, 0, count); 
      return rVal; 
     } 
     catch (Exception ex) 
     { 
      this._logger.Error("Read(char[], int, int)", ex); 
      throw; 
     } 
    } 

    /// <summary> 
    ///  Reads a maximum of <paramref name="count" /> characters from the current stream, and writes the data to 
    ///  <paramref name="buffer" />, beginning at <paramref name="index" />. 
    /// </summary> 
    /// <returns> 
    ///  The position of the underlying stream is advanced by the number of characters that were read into 
    ///  <paramref name="buffer" />.The number of characters that have been read. The number will be less than or equal to 
    ///  <paramref name="count" />, depending on whether all input characters have been read. 
    /// </returns> 
    /// <param name="buffer"> 
    ///  When this method returns, this parameter contains the specified character array with the values 
    ///  between <paramref name="index" /> and (<paramref name="index" /> + <paramref name="count" /> -1) replaced by the 
    ///  characters read from the current source. 
    /// </param> 
    /// <param name="index">The position in <paramref name="buffer" /> at which to begin writing.</param> 
    /// <param name="count">The maximum number of characters to read. </param> 
    /// <exception cref="T:System.ArgumentNullException"><paramref name="buffer" /> is null. </exception> 
    /// <exception cref="T:System.ArgumentException"> 
    ///  The buffer length minus <paramref name="index" /> is less than 
    ///  <paramref name="count" />. 
    /// </exception> 
    /// <exception cref="T:System.ArgumentOutOfRangeException"> 
    ///  <paramref name="index" /> or <paramref name="count" /> is 
    ///  negative. 
    /// </exception> 
    /// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.IO.TextReader" /> is closed. </exception> 
    /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> 
    public override int ReadBlock(char[] buffer, int index, int count) 
    { 
     try 
     { 
      var rVal = base.ReadBlock(buffer, index, count); 
      var filteredBuffer = buffer.Select(x => XmlConvert.IsXmlChar(x) ? x : ' ').ToArray(); 
      Buffer.BlockCopy(filteredBuffer, 0, buffer, 0, count); 
      return rVal; 
     } 
     catch (Exception ex) 
     { 
      this._logger.Error("ReadBlock(char[], in, int)", ex); 
      throw; 
     } 
    } 

    /// <summary> 
    ///  Reads the stream from the current position to the end of the stream. 
    /// </summary> 
    /// <returns> 
    ///  The rest of the stream as a string, from the current position to the end. If the current position is at the end of 
    ///  the stream, returns an empty string (""). 
    /// </returns> 
    /// <exception cref="T:System.OutOfMemoryException"> 
    ///  There is insufficient memory to allocate a buffer for the returned 
    ///  string. 
    /// </exception> 
    /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> 
    public override string ReadToEnd() 
    { 
     var chars = new char[4096]; 
     int len; 
     var sb = new StringBuilder(4096); 
     while ((len = Read(chars, 0, chars.Length)) != 0) 
     { 
      sb.Append(chars, 0, len); 
     } 
     return sb.ToString(); 
    } 
} 

модульного тестирования My выглядит следующим образом:

[TestMethod] 
public void CleanTextReaderCleans() 
{ 
    //arrange 
    var originalString = "The quick brown fox jumped over the lazy dog."; 
    var badChars = new string(new[] {(char) 0x1}); 
    var concatenated = string.Concat(badChars, originalString); 

    //act 
    using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(concatenated))) 
    { 
     using (var reader = new CleanTextReader(stream)) 
     { 
      var newString = reader.ReadToEnd().Trim(); 
      //assert 
      Assert.IsTrue(originalString.Equals(newString)); 
     } 
    } 
} 

... и использование l ooks вот так:

using(var origStream = File.OpenRead(fileName)) 
using(var streamReader = new CleanTextReader(origStream)) 
using(var xmlReader = XmlReader.Create(streamReader)) 
{ 
    //do stuff with reader 
} 

Если у кого есть предложения по улучшению, я был бы рад их услышать.

1

Я попытался @JeremyHolovacs поток реализации, но он до сих пор не было достаточно для моего случая использования:

using (var fstream = File.OpenRead(dlpath)) 
{ 
    using (var zstream = new GZipStream(fstream, CompressionMode.Decompress)) 
    { 
     using (var xstream = new CleanTextReader(zstream)) 
     { 
      var ser = new XmlSerializer(typeof(MyType)); 
      prods = ser.Deserialize(XmlReader.Create(xstream, new XmlReaderSettings() { CheckCharacters = false })) as MyType; 
     } 
    } 
} 

Как-то не все соответствующие перегрузки должны быть реализованы. Я адаптировал класс следующим образом:

public class CleanTextReader : StreamReader 
{ 
    public CleanTextReader(Stream stream) : base(stream) 
    { 
    } 

    public override int Read() 
    { 
     var val = base.Read(); 
     return XmlConvert.IsXmlChar((char)val) ? val : (char)' '; 
    } 

    public override int Read(char[] buffer, int index, int count) 
    { 
     var ret = base.Read(buffer, index, count); 

     for (int i=0; i<ret; i++) 
     { 
      int idx = index + i; 
      if (!XmlConvert.IsXmlChar(buffer[idx])) 
       buffer[idx] = ' '; 
     } 

     return ret; 
    } 

    public override int ReadBlock(char[] buffer, int index, int count) 
    { 
     var ret = base.ReadBlock(buffer, index, count); 

     for (int i = 0; i < ret; i++) 
     { 
      int idx = index + i; 
      if (!XmlConvert.IsXmlChar(buffer[idx])) 
       buffer[idx] = ' '; 
     } 

     return ret; 
    } 
} 
Смежные вопросы