// 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
}
}