using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace Tango.CSV
{
///
/// Represents a CSV file reader.
///
///
///
///
///
internal class CsvFileReader : CsvFile, IEnumerable, IEnumerator
where T : new()
{
private readonly Dictionary>> allSetters = new Dictionary>>();
private string[] columns;
private char curChar;
private int len;
private string line;
private int pos;
private T record;
private readonly char fieldSeparator;
private readonly TextReader textReader;
private readonly char textQualifier;
private readonly StringBuilder parseFieldResult = new StringBuilder();
///
/// Initializes a new instance of the class.
///
/// The CSV source.
public CsvFileReader(CsvSource csvSource)
: this(csvSource, null)
{
}
///
/// Initializes a new instance of the class.
///
/// The CSV source.
/// The CSV definition.
public CsvFileReader(CsvSource csvSource, CsvDefinition csvDefinition)
{
var streamReader = csvSource.TextReader as StreamReader;
if (streamReader != null)
this.BaseStream = streamReader.BaseStream;
if (csvDefinition == null)
csvDefinition = DefaultCsvDefinition;
this.fieldSeparator = csvDefinition.FieldSeparator;
this.textQualifier = csvDefinition.TextQualifier;
this.textReader = csvSource.TextReader;// new FileStream(csvSource.TextReader, FileMode.Open);
this.ReadHeader(csvDefinition.Header);
}
///
/// Gets the element in the collection at the current position of the enumerator.
///
public T Current
{
get { return this.record; }
}
///
/// Gets a value indicating whether this is EOF.
///
public bool Eof
{
get { return this.line == null; }
}
///
/// Gets the element in the collection at the current position of the enumerator.
///
object IEnumerator.Current
{
get { return this.Current; }
}
///
/// Releases unmanaged and - optionally - managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (disposing)
{
// free managed resources
this.textReader.Dispose();
}
}
///
/// Returns an enumerator that iterates through a collection.
///
///
/// An object that can be used to iterate through the collection.
///
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// An enumerator that can be used to iterate through the collection.
///
public IEnumerator GetEnumerator()
{
return this;
}
///
/// Advances the enumerator to the next element of the collection.
///
///
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
///
public bool MoveNext()
{
this.ReadNextLine();
if (this.line == null && (this.line = this.textReader.ReadLine()) == null)
{
this.record = default(T);
}
else
{
this.record = new T();
Type recordType = typeof(T);
List> setters;
if (!this.allSetters.TryGetValue(recordType, out setters))
{
setters = this.CreateSetters();
this.allSetters[recordType] = setters;
}
var fieldValues = new string[setters.Count];
for (int i = 0; i < setters.Count; i++)
{
fieldValues[i] = this.ParseField();
if (this.curChar == this.fieldSeparator)
this.NextChar();
else
break;
}
for (int i = 0; i < setters.Count; i++)
{
var setter = setters[i];
if (setter != null)
{
setter(this.record, fieldValues[i]);
}
}
}
return (this.record != null);
}
///
/// Sets the enumerator to its initial position, which is before the first element in the collection.
///
/// Cannot reset CsvFileReader enumeration.
public void Reset()
{
throw new NotImplementedException("Cannot reset CsvFileReader enumeration.");
}
///
/// Emits the set value action.
///
/// The mi.
/// The function.
///
///
private static Action EmitSetValueAction(MemberInfo mi, Func func)
{
ParameterExpression paramExpObj = Expression.Parameter(typeof(object), "obj");
ParameterExpression paramExpT = Expression.Parameter(typeof(T), "instance");
{
var pi = mi as PropertyInfo;
if (pi != null)
{
if (CsvFile.UseLambdas)
{
var callExpr = Expression.Call(
paramExpT,
pi.GetSetMethod(),
Expression.ConvertChecked(paramExpObj, pi.PropertyType));
var setter = Expression.Lambda>(
callExpr,
paramExpT,
paramExpObj).Compile();
return (o, s) => setter(o, func(s));
}
return (o, v) => pi.SetValue(o, (object)func(v), null);
}
}
{
var fi = mi as FieldInfo;
if (fi != null)
{
if (CsvFile.UseLambdas)
{
//ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");
var valueExp = Expression.ConvertChecked(paramExpObj, fi.FieldType);
// Expression.Property can be used here as well
MemberExpression fieldExp = Expression.Field(paramExpT, fi);
BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp);
var setter = Expression.Lambda>
(assignExp, paramExpT, paramExpObj).Compile();
return (o, s) => setter(o, func(s));
}
return ((o, v) => fi.SetValue(o, func(v)));
}
}
throw new NotImplementedException();
}
///
/// Finds the setter.
///
/// The c.
/// if set to true [static member].
///
private static Action FindSetter(string c, bool staticMember, int? index = null)
{
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | (staticMember ? BindingFlags.Static : BindingFlags.Instance);
Action action = null;
var propByIndex = typeof(T).GetProperties(flags).SingleOrDefault(x => x.GetCustomAttribute() != null && x.GetCustomAttribute().Index == index);
if (propByIndex != null)
{
var pFunc = StringToObject(propByIndex.PropertyType);
action = EmitSetValueAction(propByIndex, pFunc);
return action;
}
PropertyInfo pi = typeof(T).GetProperty(c, flags);
if (pi != null)
{
var pFunc = StringToObject(pi.PropertyType);
action = EmitSetValueAction(pi, pFunc);
}
if (action == null)
{
FieldInfo fi = typeof(T).GetField(c, flags);
if (fi != null)
{
var fFunc = StringToObject(fi.FieldType);
action = EmitSetValueAction(fi, fFunc);
}
}
return action;
}
///
/// Strings to object.
///
/// Type of the property.
///
///
private static Func StringToObject(Type propertyType)
{
if (propertyType == typeof(string))
return (s) => s ?? String.Empty;
else if (propertyType == typeof(Int32))
return (s) => String.IsNullOrEmpty(s) ? 0 : Int32.Parse(s);
if (propertyType == typeof(DateTime))
return (s) => String.IsNullOrEmpty(s) ? DateTimeZero : DateTime.Parse(s);
else if (propertyType == typeof(Double))
return (s) => String.IsNullOrEmpty(s) ? 0.0 : Double.Parse(s);
else
throw new NotImplementedException();
}
///
/// Creates the setters.
///
///
private List> CreateSetters()
{
var list = new List>();
for (int i = 0; i < this.columns.Length; i++)
{
string columnName = this.columns[i];
Action action = null;
if (columnName.IndexOf(' ') >= 0)
columnName = columnName.Replace(" ", "");
action = FindSetter(columnName, false, i) ?? FindSetter(columnName, true, i);
list.Add(action);
}
return list;
}
///
/// Next character.
///
private void NextChar()
{
if (this.pos < this.len)
{
this.pos++;
this.curChar = this.pos < this.len ? this.line[this.pos] : '\0';
}
}
///
/// Parses the end of line.
///
///
private void ParseEndOfLine()
{
throw new NotImplementedException();
}
///
/// Parses the field.
///
///
private string ParseField()
{
parseFieldResult.Length = 0;
if (this.line == null || this.pos >= this.len)
return null;
while (this.curChar == ' ' || this.curChar == '\t')
{
this.NextChar();
}
if (this.curChar == this.textQualifier)
{
this.NextChar();
while (this.curChar != 0)
{
if (this.curChar == this.textQualifier)
{
this.NextChar();
if (this.curChar == this.textQualifier)
{
this.NextChar();
parseFieldResult.Append(this.textQualifier);
}
else
return parseFieldResult.ToString();
}
else if (this.curChar == '\0')
{
if (this.line == null)
return parseFieldResult.ToString();
this.ReadNextLine();
}
else
{
parseFieldResult.Append(this.curChar);
this.NextChar();
}
}
}
else
{
while (this.curChar != 0 && this.curChar != this.fieldSeparator && this.curChar != '\r' && this.curChar != '\n')
{
parseFieldResult.Append(this.curChar);
this.NextChar();
}
}
return parseFieldResult.ToString();
}
///
/// Reads the header.
///
/// The header.
private void ReadHeader(string header)
{
if (header == null)
{
this.ReadNextLine();
}
else
{
// we read the first line from the given header
this.line = header;
this.pos = -1;
this.len = this.line.Length;
this.NextChar();
}
var readColumns = new List();
string columnName;
while ((columnName = this.ParseField()) != null)
{
readColumns.Add(columnName);
if (this.curChar == this.fieldSeparator)
this.NextChar();
else
break;
}
this.columns = readColumns.ToArray();
Columns = this.columns;
}
///
/// Reads the next line.
///
private void ReadNextLine()
{
this.line = this.textReader.ReadLine();
this.pos = -1;
if (this.line == null)
{
this.len = 0;
this.curChar = '\0';
}
else
{
this.len = this.line.Length;
this.NextChar();
}
}
}
}