Sunday, February 06, 2005 - Posts

A simple LineNumberReader

For a small personal project I have been developing a parser, as part of the parser I developed the LineNumberReader modeled after the Java(TM) class of the same name. Since the class has been useful, I thought I would share it. For the purposes of the post I have removed comments and a number of the constructors.

One of the nice features is the ability to set a marker in the stream using the Mark() method and returning to that point in the stream with the Reset() method. Enjoy!

   1:  class LineNumberReader : StreamReader
   2:      {
   3:        public LineNumberReader(Stream stream) : base(stream){}
   4:   
   5:        public LineNumberReader(
   6:          Stream stream, 
   7:          System.Text.Encoding encoding) : base(stream, encoding){}
   8:        // More constructors matching those from StreamReader ...
   9:   
  10:        public int LineNumber
  11:        {
  12:          get { return _lineNumber; }
  13:          set 
  14:          { 
  15:            if (value < 0 ) throw new ArgumentOutOfRangeException( 
  16:              "LineNumber", "Must be greater or equal to 0.");
  17:            _lineNumber = value; 
  18:          }
  19:        }
  20:   
  21:        public void Mark()
  22:        {
  23:          if (!BaseStream.CanSeek) throw new NotSupportedException(
  24:            "Mark is not supported, underlying stream is not seekable.");
  25:          _mark = new MarkData(_lineNumber, BaseStream.Position);
  26:          _markset = true;
  27:        }
  28:   
  29:        public void Reset()
  30:        {
  31:          if (!_markset) throw new IOException("Reader not marked");
  32:          _markset = false;
  33:          DiscardBufferedData();
  34:          BaseStream.Seek(_mark.Position, SeekOrigin.Begin);
  35:          _lineNumber = _mark.LineNumber;
  36:        }
  37:   
  38:        public override int Read()
  39:        {
  40:          int retval = base.Read();  
  41:          if (retval == (int)'\r')
  42:          {
  43:            if (Peek() == (int)'\n')
  44:            {
  45:              base.Read();    
  46:            }
  47:            retval = (int)'\n';
  48:          }
  49:   
  50:          if (retval == (int)'\n')
  51:            _lineNumber++;
  52:   
  53:          return retval;
  54:        }
  55:      
  56:        public override int Read(char[] buffer, int index, int count)
  57:        {
  58:          int bytesRead = base.Read(buffer, index, count);
  59:          _lineNumber += CountLinesInBuffer(buffer, index, bytesRead);
  60:          return bytesRead;
  61:        }
  62:   
  63:        public override string ReadLine()
  64:        {
  65:          string retval = base.ReadLine(); 
  66:          if (retval != null)
  67:            _lineNumber++;
  68:          return retval; 
  69:        }
  70:   
  71:        private int CountLinesInBuffer(char[] buffer, int index, int count)
  72:        {
  73:          int lineCount = 0;
  74:          int i = index;
  75:          int lastIndex = index + count;
  76:   
  77:          do
  78:          {
  79:            char ch = buffer[i];
  80:            if (ch == '\r')
  81:            {
  82:              if ((i + 1 < lastIndex) && (buffer[i + 1] == '\n'))
  83:                i++;
  84:   
  85:              ch = '\n';
  86:            }
  87:   
  88:            if (ch == '\n')
  89:              lineCount++;
  90:          } while (++i < lastIndex);
  91:          
  92:          return lineCount;
  93:        }
  94:   
  95:        private struct MarkData
  96:        {
  97:          public MarkData(int lineNumber, long position)
  98:          {
  99:            LineNumber = lineNumber;
 100:            Position = position;
 101:          }
 102:          public int LineNumber;
 103:          public long Position;
 104:        }
 105:   
 106:        private int _lineNumber = 0;
 107:        private MarkData _mark;
 108:        private bool _markset = false;
 109:      }