// LumenWorks.Framework.IO.CSV.CachedCsvReader // Copyright (c) 2005 Sébastien Lorion // // MIT license (http://en.wikipedia.org/wiki/MIT_License) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.IO; using LumenWorks.Framework.IO.Csv.Resources; namespace LumenWorks.Framework.IO.Csv { /// /// Represents a reader that provides fast, cached, dynamic access to CSV data. /// /// The number of records is limited to - 1. public partial class CachedCsvReader : CsvReader, IListSource { #region Fields /// /// Contains the cached records. /// private List _records; /// /// Contains the current record index (inside the cached records array). /// private long _currentRecordIndex; /// /// Indicates if a new record is being read from the CSV stream. /// private bool _readingStream; /// /// Contains the binding list linked to this reader. /// private CsvBindingList _bindingList; #endregion #region Constructors /// /// Initializes a new instance of the CsvReader class. /// /// A pointing to the CSV file. /// if field names are located on the first non commented line, otherwise, . /// /// is a . /// /// /// Cannot read from . /// public CachedCsvReader(TextReader reader, bool hasHeaders) : this(reader, hasHeaders, DefaultBufferSize) { } /// /// Initializes a new instance of the CsvReader class. /// /// A pointing to the CSV file. /// if field names are located on the first non commented line, otherwise, . /// The buffer size in bytes. /// /// is a . /// /// /// Cannot read from . /// public CachedCsvReader(TextReader reader, bool hasHeaders, int bufferSize) : this(reader, hasHeaders, DefaultDelimiter, DefaultQuote, DefaultEscape, DefaultComment, ValueTrimmingOptions.UnquotedOnly, bufferSize) { } /// /// Initializes a new instance of the CsvReader class. /// /// A pointing to the CSV file. /// if field names are located on the first non commented line, otherwise, . /// The delimiter character separating each field (default is ','). /// /// is a . /// /// /// Cannot read from . /// public CachedCsvReader(TextReader reader, bool hasHeaders, char delimiter) : this(reader, hasHeaders, delimiter, DefaultQuote, DefaultEscape, DefaultComment, ValueTrimmingOptions.UnquotedOnly, DefaultBufferSize) { } /// /// Initializes a new instance of the CsvReader class. /// /// A pointing to the CSV file. /// if field names are located on the first non commented line, otherwise, . /// The delimiter character separating each field (default is ','). /// The buffer size in bytes. /// /// is a . /// /// /// Cannot read from . /// public CachedCsvReader(TextReader reader, bool hasHeaders, char delimiter, int bufferSize) : this(reader, hasHeaders, delimiter, DefaultQuote, DefaultEscape, DefaultComment, ValueTrimmingOptions.UnquotedOnly, bufferSize) { } /// /// Initializes a new instance of the CsvReader class. /// /// A pointing to the CSV file. /// if field names are located on the first non commented line, otherwise, . /// The delimiter character separating each field (default is ','). /// The quotation character wrapping every field (default is '''). /// /// The escape character letting insert quotation characters inside a quoted field (default is '\'). /// If no escape character, set to '\0' to gain some performance. /// /// The comment character indicating that a line is commented out (default is '#'). /// Determines how values should be trimmed. /// /// is a . /// /// /// Cannot read from . /// public CachedCsvReader(TextReader reader, bool hasHeaders, char delimiter, char quote, char escape, char comment, ValueTrimmingOptions trimmingOptions) : this(reader, hasHeaders, delimiter, quote, escape, comment, trimmingOptions, DefaultBufferSize) { } /// /// Initializes a new instance of the CsvReader class. /// /// A pointing to the CSV file. /// if field names are located on the first non commented line, otherwise, . /// The delimiter character separating each field (default is ','). /// The quotation character wrapping every field (default is '''). /// /// The escape character letting insert quotation characters inside a quoted field (default is '\'). /// If no escape character, set to '\0' to gain some performance. /// /// The comment character indicating that a line is commented out (default is '#'). /// if spaces at the start and end of a field are trimmed, otherwise, . Default is . /// The buffer size in bytes. /// /// is a . /// /// /// must be 1 or more. /// public CachedCsvReader(TextReader reader, bool hasHeaders, char delimiter, char quote, char escape, char comment, ValueTrimmingOptions trimmingOptions, int bufferSize) : base(reader, hasHeaders, delimiter, quote, escape, comment, trimmingOptions, bufferSize) { _records = new List(); _currentRecordIndex = -1; } #endregion #region Properties #region State /// /// Gets the current record index in the CSV file. /// /// The current record index in the CSV file. public override long CurrentRecordIndex { get { return _currentRecordIndex; } } /// /// Gets a value that indicates whether the current stream position is at the end of the stream. /// /// if the current stream position is at the end of the stream; otherwise . public override bool EndOfStream { get { if (_currentRecordIndex < base.CurrentRecordIndex) return false; else return base.EndOfStream; } } #endregion #endregion #region Indexers /// /// Gets the field at the specified index. /// /// The field at the specified index. /// /// must be included in [0, [. /// /// /// No record read yet. Call ReadLine() first. /// /// /// The CSV data appears to be missing a field. /// /// /// The CSV appears to be corrupt at the current position. /// /// /// The instance has been disposed of. /// public override String this[int field] { get { if (_readingStream) return base[field]; else if (_currentRecordIndex > -1) { if (field > -1 && field < this.FieldCount) return _records[(int) _currentRecordIndex][field]; else throw new ArgumentOutOfRangeException("field", field, string.Format(CultureInfo.InvariantCulture, ExceptionMessage.FieldIndexOutOfRange, field)); } else throw new InvalidOperationException(ExceptionMessage.NoCurrentRecord); } } #endregion #region Methods #region Read /// /// Reads the CSV stream from the current position to the end of the stream. /// /// /// The instance has been disposed of. /// public virtual void ReadToEnd() { _currentRecordIndex = base.CurrentRecordIndex; while (ReadNextRecord()) ; } /// /// Reads the next record. /// /// /// Indicates if the reader will proceed to the next record after having read headers. /// if it stops after having read headers; otherwise, . /// /// /// Indicates if the reader will skip directly to the next line without parsing the current one. /// To be used when an error occurs. /// /// if a record has been successfully reads; otherwise, . /// /// The instance has been disposed of. /// protected override bool ReadNextRecord(bool onlyReadHeaders, bool skipToNextLine) { if (_currentRecordIndex < base.CurrentRecordIndex) { _currentRecordIndex++; return true; } else { _readingStream = true; try { bool canRead = base.ReadNextRecord(onlyReadHeaders, skipToNextLine); if (canRead) { string[] record = new string[this.FieldCount]; if (base.CurrentRecordIndex > -1) { CopyCurrentRecordTo(record); _records.Add(record); } else { if (MoveTo(0)) CopyCurrentRecordTo(record); MoveTo(-1); } if (!onlyReadHeaders) _currentRecordIndex++; } else { // No more records to read, so set array size to only what is needed _records.Capacity = _records.Count; } return canRead; } finally { _readingStream = false; } } } #endregion #region Move /// /// Moves before the first record. /// public void MoveToStart() { _currentRecordIndex = -1; } /// /// Moves to the last record read so far. /// public void MoveToLastCachedRecord() { _currentRecordIndex = base.CurrentRecordIndex; } /// /// Moves to the specified record index. /// /// The record index. /// true if the operation was successful; otherwise, false. /// /// The instance has been disposed of. /// public override bool MoveTo(long record) { if (record < -1) record = -1; if (record <= base.CurrentRecordIndex) { _currentRecordIndex = record; return true; } else { _currentRecordIndex = base.CurrentRecordIndex; return base.MoveTo(record); } } #endregion #endregion #region IListSource Members bool IListSource.ContainsListCollection { get { return false; } } System.Collections.IList IListSource.GetList() { if (_bindingList == null) _bindingList = new CsvBindingList(this); return _bindingList; } #endregion } }