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(); } } } }