aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.CSV
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/Tango.CSV')
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvDefinition.cs59
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvDestination.cs77
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvFile.cs430
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvFileLinqExtensions.cs46
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvFileReader.cs425
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvIgnoreAttribute.cs27
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvSource.cs95
-rw-r--r--Software/Visual_Studio/Tango.CSV/Properties/AssemblyInfo.cs9
-rw-r--r--Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj99
9 files changed, 1267 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.CSV/CsvDefinition.cs b/Software/Visual_Studio/Tango.CSV/CsvDefinition.cs
new file mode 100644
index 000000000..350c5ed8e
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/CsvDefinition.cs
@@ -0,0 +1,59 @@
+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
+{
+ /// <summary>
+ /// Represents a <see cref="CsvFile"/> configuration.
+ /// </summary>
+ public class CsvDefinition
+ {
+ /// <summary>
+ /// Gets or sets the header.
+ /// </summary>
+ public string Header { get; set; }
+
+ /// <summary>
+ /// Gets or sets the field separator.
+ /// </summary>
+ public char FieldSeparator { get; set; }
+
+ /// <summary>
+ /// Gets or sets the text qualifier.
+ /// </summary>
+ public char TextQualifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the columns.
+ /// </summary>
+ public IEnumerable<String> Columns { get; set; }
+
+ /// <summary>
+ /// Gets or sets the end of line.
+ /// </summary>
+ public string EndOfLine { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvDefinition"/> class.
+ /// </summary>
+ public CsvDefinition()
+ {
+ if (CsvFile.DefaultCsvDefinition != null)
+ {
+ FieldSeparator = CsvFile.DefaultCsvDefinition.FieldSeparator;
+ TextQualifier = CsvFile.DefaultCsvDefinition.TextQualifier;
+ EndOfLine = CsvFile.DefaultCsvDefinition.EndOfLine;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.CSV/CsvDestination.cs b/Software/Visual_Studio/Tango.CSV/CsvDestination.cs
new file mode 100644
index 000000000..d88a34684
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/CsvDestination.cs
@@ -0,0 +1,77 @@
+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
+{
+ /// <summary>
+ /// Represents a <see cref="CsvFile"/> destination.
+ /// </summary>
+ public class CsvDestination
+ {
+ public StreamWriter StreamWriter;
+
+ /// <summary>
+ /// Performs an implicit conversion from <see cref="System.String"/> to <see cref="CsvDestination"/>.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns>
+ /// The result of the conversion.
+ /// </returns>
+ public static implicit operator CsvDestination(string path)
+ {
+ return new CsvDestination(path);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvDestination"/> class.
+ /// </summary>
+ /// <param name="streamWriter">The stream writer.</param>
+ private CsvDestination(StreamWriter streamWriter)
+ {
+ this.StreamWriter = streamWriter;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvDestination"/> class.
+ /// </summary>
+ /// <param name="stream">The stream.</param>
+ private CsvDestination(Stream stream)
+ {
+ this.StreamWriter = new StreamWriter(stream);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvDestination"/> class.
+ /// </summary>
+ /// <param name="fullName">The full name.</param>
+ public CsvDestination(string fullName)
+ {
+ FixCsvFileName(ref fullName);
+ this.StreamWriter = new StreamWriter(fullName);
+ }
+
+ /// <summary>
+ /// Fixes the name of the CSV file.
+ /// </summary>
+ /// <param name="fullName">The full name.</param>
+ private static void FixCsvFileName(ref string fullName)
+ {
+ fullName = Path.GetFullPath(fullName);
+ var path = Path.GetDirectoryName(fullName);
+ if (path != null && !Directory.Exists(path))
+ Directory.CreateDirectory(path);
+ if (!String.Equals(Path.GetExtension(fullName), ".csv"))
+ fullName += ".csv";
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.CSV/CsvFile.cs b/Software/Visual_Studio/Tango.CSV/CsvFile.cs
new file mode 100644
index 000000000..ab5466da7
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/CsvFile.cs
@@ -0,0 +1,430 @@
+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
+{
+ /// <summary>
+ /// Represents a component for reading and writing CSV files.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
+ public class CsvFile : IDisposable
+ {
+ internal protected Stream BaseStream;
+ protected static DateTime DateTimeZero = new DateTime();
+
+ /// <summary>
+ /// Initializes the <see cref="CsvFile"/> class.
+ /// </summary>
+ static CsvFile()
+ {
+ DefaultCsvDefinition = new CsvDefinition
+ {
+ EndOfLine = "\r\n",
+ FieldSeparator = ',',
+ TextQualifier = '"'
+ };
+ UseLambdas = true;
+ UseTasks = true;
+ FastIndexOfAny = true;
+ }
+
+ /// <summary>
+ /// Gets or sets the default CSV definition.
+ /// </summary>
+ public static CsvDefinition DefaultCsvDefinition { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [use lambdas].
+ /// </summary>
+ public static bool UseLambdas { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [use tasks].
+ /// </summary>
+ public static bool UseTasks { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [fast index of any].
+ /// </summary>
+ public static bool FastIndexOfAny { get; set; }
+
+ /// <summary>
+ /// Reads the specified CSV source.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="csvSource">The CSV source.</param>
+ /// <returns></returns>
+ public static IEnumerable<T> Read<T>(CsvSource csvSource) where T : new()
+ {
+ var csvFileReader = new CsvFileReader<T>(csvSource);
+ return (IEnumerable<T>)csvFileReader;
+ }
+
+ /// <summary>
+ /// Gets the field separator.
+ /// </summary>
+ /// <value>
+ /// The field separator.
+ /// </value>
+ public char FieldSeparator { get; private set; }
+
+ /// <summary>
+ /// Gets the text qualifier.
+ /// </summary>
+ /// <value>
+ /// The text qualifier.
+ /// </value>
+ public char TextQualifier { get; private set; }
+
+ /// <summary>
+ /// Gets the columns.
+ /// </summary>
+ /// <value>
+ /// The columns.
+ /// </value>
+ public IEnumerable<String> Columns { get; private set; }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ // overriden in derived classes
+ }
+ }
+
+ /// <summary>
+ /// Represents a component for reading and writing CSV files from and to a collection of objects.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <seealso cref="System.IDisposable" />
+ public class CsvFile<T> : CsvFile
+ {
+ private readonly char fieldSeparator;
+ private readonly string fieldSeparatorAsString;
+ private readonly char[] invalidCharsInFields;
+ private readonly StreamWriter streamWriter;
+ private readonly char textQualifier;
+ private readonly String[] columns;
+ private Func<T, object>[] getters;
+ readonly bool[] isInvalidCharInFields;
+ private int linesToWrite;
+ private readonly BlockingCollection<string> csvLinesToWrite = new BlockingCollection<string>(5000);
+ private readonly Thread writeCsvLinesTask;
+ private Task addAsyncTask;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvFile{T}"/> class.
+ /// </summary>
+ /// <param name="csvDestination">The CSV destination.</param>
+ public CsvFile(CsvDestination csvDestination)
+ : this(csvDestination, null)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvFile{T}"/> class.
+ /// </summary>
+ public CsvFile()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvFile{T}"/> class.
+ /// </summary>
+ /// <param name="csvDestination">The CSV destination.</param>
+ /// <param name="csvDefinition">The CSV definition.</param>
+ public CsvFile(CsvDestination csvDestination, CsvDefinition csvDefinition)
+ {
+ if (csvDefinition == null)
+ csvDefinition = DefaultCsvDefinition;
+ this.columns = (csvDefinition.Columns ?? InferColumns(typeof(T))).ToArray();
+ this.fieldSeparator = csvDefinition.FieldSeparator;
+ this.fieldSeparatorAsString = this.fieldSeparator.ToString(CultureInfo.InvariantCulture);
+ this.textQualifier = csvDefinition.TextQualifier;
+ this.streamWriter = csvDestination.StreamWriter;
+
+ this.invalidCharsInFields = new[] { '\r', '\n', this.textQualifier, this.fieldSeparator };
+ this.isInvalidCharInFields = new bool[256];
+
+ foreach (var c in this.invalidCharsInFields)
+ {
+ this.isInvalidCharInFields[c] = true;
+ }
+ this.WriteHeader();
+
+ this.CreateGetters();
+ if (CsvFile.UseTasks)
+ {
+ writeCsvLinesTask = new Thread((o) => this.WriteCsvLines());
+ writeCsvLinesTask.Start();
+ }
+ this.addAsyncTask = Task.Factory.StartNew(() => { });
+
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // free managed resources
+ addAsyncTask.Wait();
+ if (csvLinesToWrite != null)
+ {
+ csvLinesToWrite.CompleteAdding();
+ }
+ if (writeCsvLinesTask != null)
+ writeCsvLinesTask.Join();
+ this.streamWriter.Close();
+ }
+ }
+
+ /// <summary>
+ /// Infers the columns.
+ /// </summary>
+ /// <param name="recordType">Type of the record.</param>
+ /// <returns></returns>
+ protected static IEnumerable<string> InferColumns(Type recordType)
+ {
+ var columns = recordType
+ .GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(pi => pi.GetIndexParameters().Length == 0
+ && pi.GetSetMethod() != null
+ && !Attribute.IsDefined(pi, typeof(CsvIgnoreAttribute)))
+ .Select(pi => pi.Name)
+ .Concat(recordType
+ .GetFields(BindingFlags.Public | BindingFlags.Instance)
+ .Where(fi => !Attribute.IsDefined(fi, typeof(CsvIgnoreAttribute)))
+ .Select(fi => fi.Name))
+ .ToList();
+ return columns;
+ }
+
+ /// <summary>
+ /// Writes the CSV lines.
+ /// </summary>
+ private void WriteCsvLines()
+ {
+ int written = 0;
+ foreach (var csvLine in csvLinesToWrite.GetConsumingEnumerable())
+ {
+ this.streamWriter.WriteLine(csvLine);
+ written++;
+ }
+ Interlocked.Add(ref this.linesToWrite, -written);
+ }
+
+ /// <summary>
+ /// Appends the specified record.
+ /// </summary>
+ /// <param name="record">The record.</param>
+ public void Append(T record)
+ {
+
+ if (CsvFile.UseTasks)
+ {
+
+ var linesWaiting = Interlocked.Increment(ref this.linesToWrite);
+ Action<Task> addRecord = (t) =>
+ {
+ var csvLine = this.ToCsv(record);
+ this.csvLinesToWrite.Add(csvLine);
+ };
+
+ if (linesWaiting < 10000)
+ this.addAsyncTask = this.addAsyncTask.ContinueWith(addRecord);
+ else
+ addRecord(null);
+ }
+ else
+ {
+ var csvLine = this.ToCsv(record);
+ this.streamWriter.WriteLine(csvLine);
+ }
+ }
+
+ /// <summary>
+ /// Finds the getter.
+ /// </summary>
+ /// <param name="c">The c.</param>
+ /// <param name="staticMember">if set to <c>true</c> [static member].</param>
+ /// <returns></returns>
+ private static Func<T, object> FindGetter(string c, int index, bool staticMember)
+ {
+ var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | (staticMember ? BindingFlags.Static : BindingFlags.Instance);
+ Func<T, object> func = null;
+
+ PropertyInfo pi = null;
+ pi = typeof(T).GetProperty(c, flags);
+
+ if (pi == null) //Then try get by column index,
+ {
+ pi = typeof(T).GetProperties(flags)[index];
+ }
+
+ if (CsvFile.UseLambdas)
+ {
+ Expression expr = null;
+ ParameterExpression parameter = Expression.Parameter(typeof(T), "r");
+ Type type = null;
+
+ if (pi != null)
+ {
+ type = pi.PropertyType;
+ expr = Expression.Property(parameter, pi.Name);
+ }
+ if (expr != null)
+ {
+ Expression<Func<T, object>> lambda;
+ if (type.IsValueType)
+ {
+ lambda = Expression.Lambda<Func<T, object>>(Expression.TypeAs(expr, typeof(object)), parameter);
+ }
+ else
+ {
+ lambda = Expression.Lambda<Func<T, object>>(expr, parameter);
+ }
+ func = lambda.Compile();
+ }
+ }
+ else
+ {
+ if (pi != null)
+ func = o => pi.GetValue(o, null);
+ }
+ return func;
+ }
+
+ /// <summary>
+ /// Creates the getters.
+ /// </summary>
+ private void CreateGetters()
+ {
+ var list = new List<Func<T, object>>();
+
+ for (int i = 0; i < columns.Length; i++)
+ {
+ Func<T, Object> func = null;
+ var propertyName = (columns[i].IndexOf(' ') < 0 ? columns[i] : columns[i].Replace(" ", ""));
+ func = FindGetter(columns[i], i, false) ?? FindGetter(columns[i], i, true);
+
+ list.Add(func);
+ }
+ this.getters = list.ToArray();
+ }
+
+ /// <summary>
+ /// To the CSV.
+ /// </summary>
+ /// <param name="record">The record.</param>
+ /// <returns></returns>
+ /// <exception cref="System.ArgumentException">Cannot be null;record</exception>
+ private string ToCsv(T record)
+ {
+ if (record == null)
+ throw new ArgumentException("Cannot be null", "record");
+
+ string[] csvStrings = new string[getters.Length];
+
+ for (int i = 0; i < getters.Length; i++)
+ {
+ var getter = getters[i];
+ object fieldValue = getter == null ? null : getter(record);
+ csvStrings[i] = this.ToCsvString(fieldValue);
+ }
+ return string.Join(this.fieldSeparatorAsString, csvStrings);
+
+ }
+
+ /// <summary>
+ /// To the CSV string.
+ /// </summary>
+ /// <param name="o">The o.</param>
+ /// <returns></returns>
+ private string ToCsvString(object o)
+ {
+ if (o != null)
+ {
+ string valueString = o as string ?? Convert.ToString(o, CultureInfo.CurrentUICulture);
+ if (RequireQuotes(valueString))
+ {
+ var csvLine = new StringBuilder();
+ csvLine.Append(this.textQualifier);
+ foreach (char c in valueString)
+ {
+ if (c == this.textQualifier)
+ csvLine.Append(c); // double the double quotes
+ csvLine.Append(c);
+ }
+ csvLine.Append(this.textQualifier);
+ return csvLine.ToString();
+ }
+ else
+ return valueString;
+ }
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Requires the quotes.
+ /// </summary>
+ /// <param name="valueString">The value string.</param>
+ /// <returns></returns>
+ private bool RequireQuotes(string valueString)
+ {
+ if (CsvFile.FastIndexOfAny)
+ {
+ var len = valueString.Length;
+ for (int i = 0; i < len; i++)
+ {
+ char c = valueString[i];
+ if (c <= 255 && this.isInvalidCharInFields[c])
+ return true;
+ }
+ return false;
+ }
+ else
+ {
+ return valueString.IndexOfAny(this.invalidCharsInFields) >= 0;
+ }
+ }
+
+ /// <summary>
+ /// Writes the header.
+ /// </summary>
+ private void WriteHeader()
+ {
+ var csvLine = new StringBuilder();
+ for (int i = 0; i < this.columns.Length; i++)
+ {
+ if (i > 0)
+ csvLine.Append(this.fieldSeparator);
+ csvLine.Append(this.ToCsvString(this.columns[i]));
+ }
+ this.streamWriter.WriteLine(csvLine.ToString());
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.CSV/CsvFileLinqExtensions.cs b/Software/Visual_Studio/Tango.CSV/CsvFileLinqExtensions.cs
new file mode 100644
index 000000000..8f99b2014
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/CsvFileLinqExtensions.cs
@@ -0,0 +1,46 @@
+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;
+using Tango.CSV;
+
+
+public static class CsvFileLinqExtensions
+{
+ /// <summary>
+ /// Export the collection to CSV file.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">The source.</param>
+ /// <param name="csvDestination">The CSV destination.</param>
+ public static void ToCsv<T>(this IEnumerable<T> source, CsvDestination csvDestination)
+ {
+ source.ToCsv(csvDestination, null);
+ }
+
+ /// <summary>
+ /// Export the collection to CSV file.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">The source.</param>
+ /// <param name="csvDestination">The CSV destination.</param>
+ /// <param name="csvDefinition">The CSV definition.</param>
+ public static void ToCsv<T>(this IEnumerable<T> source, CsvDestination csvDestination, CsvDefinition csvDefinition)
+ {
+ using (var csvFile = new CsvFile<T>(csvDestination, csvDefinition))
+ {
+ foreach (var record in source)
+ {
+ csvFile.Append(record);
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.CSV/CsvFileReader.cs b/Software/Visual_Studio/Tango.CSV/CsvFileReader.cs
new file mode 100644
index 000000000..1ae9404a7
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/CsvFileReader.cs
@@ -0,0 +1,425 @@
+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
+{
+ /// <summary>
+ /// Represents a CSV file reader.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <seealso cref="Tango.CSV.CsvFile" />
+ /// <seealso cref="System.Collections.Generic.IEnumerable{T}" />
+ /// <seealso cref="System.Collections.Generic.IEnumerator{T}" />
+ internal class CsvFileReader<T> : CsvFile, IEnumerable<T>, IEnumerator<T>
+ where T : new()
+ {
+ private readonly Dictionary<Type, List<Action<T, String>>> allSetters = new Dictionary<Type, List<Action<T, String>>>();
+ 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();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvFileReader{T}"/> class.
+ /// </summary>
+ /// <param name="csvSource">The CSV source.</param>
+ public CsvFileReader(CsvSource csvSource)
+ : this(csvSource, null)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvFileReader{T}"/> class.
+ /// </summary>
+ /// <param name="csvSource">The CSV source.</param>
+ /// <param name="csvDefinition">The CSV definition.</param>
+ 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);
+
+ }
+
+ /// <summary>
+ /// Gets the element in the collection at the current position of the enumerator.
+ /// </summary>
+ public T Current
+ {
+ get { return this.record; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="CsvFileReader{T}"/> is EOF.
+ /// </summary>
+ public bool Eof
+ {
+ get { return this.line == null; }
+ }
+
+ /// <summary>
+ /// Gets the element in the collection at the current position of the enumerator.
+ /// </summary>
+ object IEnumerator.Current
+ {
+ get { return this.Current; }
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // free managed resources
+ this.textReader.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through a collection.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
+ /// </returns>
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the collection.
+ /// </summary>
+ /// <returns>
+ /// An enumerator that can be used to iterate through the collection.
+ /// </returns>
+ public IEnumerator<T> GetEnumerator()
+ {
+ return this;
+ }
+
+ /// <summary>
+ /// Advances the enumerator to the next element of the collection.
+ /// </summary>
+ /// <returns>
+ /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
+ /// </returns>
+ 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<Action<T, String>> 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);
+ }
+
+ /// <summary>
+ /// Sets the enumerator to its initial position, which is before the first element in the collection.
+ /// </summary>
+ /// <exception cref="System.NotImplementedException">Cannot reset CsvFileReader enumeration.</exception>
+ public void Reset()
+ {
+ throw new NotImplementedException("Cannot reset CsvFileReader enumeration.");
+ }
+
+ /// <summary>
+ /// Emits the set value action.
+ /// </summary>
+ /// <param name="mi">The mi.</param>
+ /// <param name="func">The function.</param>
+ /// <returns></returns>
+ /// <exception cref="System.NotImplementedException"></exception>
+ private static Action<T, string> EmitSetValueAction(MemberInfo mi, Func<string, object> 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<Action<T, object>>(
+ 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<Action<T, object>>
+ (assignExp, paramExpT, paramExpObj).Compile();
+
+ return (o, s) => setter(o, func(s));
+ }
+ return ((o, v) => fi.SetValue(o, func(v)));
+ }
+ }
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Finds the setter.
+ /// </summary>
+ /// <param name="c">The c.</param>
+ /// <param name="staticMember">if set to <c>true</c> [static member].</param>
+ /// <returns></returns>
+ private static Action<T, string> FindSetter(string c, bool staticMember)
+ {
+ var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | (staticMember ? BindingFlags.Static : BindingFlags.Instance);
+ Action<T, string> action = null;
+ PropertyInfo pi = typeof(T).GetProperty(c, flags);
+ if (pi != null)
+ {
+ var pFunc = StringToObject(pi.PropertyType);
+ action = EmitSetValueAction(pi, pFunc);
+ }
+ FieldInfo fi = typeof(T).GetField(c, flags);
+ if (fi != null)
+ {
+ var fFunc = StringToObject(fi.FieldType);
+ action = EmitSetValueAction(fi, fFunc);
+ }
+ return action;
+ }
+
+ /// <summary>
+ /// Strings to object.
+ /// </summary>
+ /// <param name="propertyType">Type of the property.</param>
+ /// <returns></returns>
+ /// <exception cref="System.NotImplementedException"></exception>
+ private static Func<string, object> 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
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Creates the setters.
+ /// </summary>
+ /// <returns></returns>
+ private List<Action<T, string>> CreateSetters()
+ {
+ var list = new List<Action<T, string>>();
+ for (int i = 0; i < this.columns.Length; i++)
+ {
+ string columnName = this.columns[i];
+ Action<T, string> action = null;
+ if (columnName.IndexOf(' ') >= 0)
+ columnName = columnName.Replace(" ", "");
+ action = FindSetter(columnName, false) ?? FindSetter(columnName, true);
+
+ list.Add(action);
+ }
+ return list;
+ }
+
+ /// <summary>
+ /// Next character.
+ /// </summary>
+ private void NextChar()
+ {
+ if (this.pos < this.len)
+ {
+ this.pos++;
+ this.curChar = this.pos < this.len ? this.line[this.pos] : '\0';
+ }
+ }
+
+ /// <summary>
+ /// Parses the end of line.
+ /// </summary>
+ /// <exception cref="System.NotImplementedException"></exception>
+ private void ParseEndOfLine()
+ {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Parses the field.
+ /// </summary>
+ /// <returns></returns>
+ 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();
+ }
+
+ /// <summary>
+ /// Reads the header.
+ /// </summary>
+ /// <param name="header">The header.</param>
+ 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>();
+ string columnName;
+ while ((columnName = this.ParseField()) != null)
+ {
+ readColumns.Add(columnName);
+ if (this.curChar == this.fieldSeparator)
+ this.NextChar();
+ else
+ break;
+ }
+ this.columns = readColumns.ToArray();
+ }
+
+ /// <summary>
+ /// Reads the next line.
+ /// </summary>
+ 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();
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.CSV/CsvIgnoreAttribute.cs b/Software/Visual_Studio/Tango.CSV/CsvIgnoreAttribute.cs
new file mode 100644
index 000000000..87ae77516
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/CsvIgnoreAttribute.cs
@@ -0,0 +1,27 @@
+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
+{
+ /// <summary>
+ /// Represents an attribute for decorating properties to exclude from the CSV file.
+ /// </summary>
+ /// <seealso cref="System.Attribute" />
+ public class CsvIgnoreAttribute : Attribute
+ {
+ public override string ToString()
+ {
+ return "Ignore Property";
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.CSV/CsvSource.cs b/Software/Visual_Studio/Tango.CSV/CsvSource.cs
new file mode 100644
index 000000000..ba7e458e6
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/CsvSource.cs
@@ -0,0 +1,95 @@
+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
+{
+ /// <summary>
+ /// Represents a <see cref="CsvFile"/> source.
+ /// </summary>
+ public class CsvSource
+ {
+ public readonly TextReader TextReader;
+
+ /// <summary>
+ /// Performs an implicit conversion from <see cref="CsvFile"/> to <see cref="CsvSource"/>.
+ /// </summary>
+ /// <param name="csvFile">The CSV file.</param>
+ /// <returns>
+ /// The result of the conversion.
+ /// </returns>
+ public static implicit operator CsvSource(CsvFile csvFile)
+ {
+ return new CsvSource(csvFile);
+ }
+
+ /// <summary>
+ /// Performs an implicit conversion from <see cref="System.String"/> to <see cref="CsvSource"/>.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns>
+ /// The result of the conversion.
+ /// </returns>
+ public static implicit operator CsvSource(string path)
+ {
+ return new CsvSource(path);
+ }
+
+ /// <summary>
+ /// Performs an implicit conversion from <see cref="TextReader"/> to <see cref="CsvSource"/>.
+ /// </summary>
+ /// <param name="textReader">The text reader.</param>
+ /// <returns>
+ /// The result of the conversion.
+ /// </returns>
+ public static implicit operator CsvSource(TextReader textReader)
+ {
+ return new CsvSource(textReader);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvSource"/> class.
+ /// </summary>
+ /// <param name="textReader">The text reader.</param>
+ public CsvSource(TextReader textReader)
+ {
+ this.TextReader = textReader;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvSource"/> class.
+ /// </summary>
+ /// <param name="stream">The stream.</param>
+ public CsvSource(Stream stream)
+ {
+ this.TextReader = new StreamReader(stream);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvSource"/> class.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ public CsvSource(string path)
+ {
+ this.TextReader = new StreamReader(path);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CsvSource"/> class.
+ /// </summary>
+ /// <param name="csvFile">The CSV file.</param>
+ public CsvSource(CsvFile csvFile)
+ {
+ this.TextReader = new StreamReader(csvFile.BaseStream);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.CSV/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.CSV/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..9696745ee
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/Properties/AssemblyInfo.cs
@@ -0,0 +1,9 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+[assembly: AssemblyTitle("Tango - CSV Library")]
+[assembly: AssemblyVersion("2.0.17.1657")]
+[assembly: ComVisible(false)] \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj b/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj
new file mode 100644
index 000000000..48bda8bdb
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{58E8825F-0C96-449C-B320-1E82B0AA876B}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Tango.CSV</RootNamespace>
+ <AssemblyName>Tango.CSV</AssemblyName>
+ <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <TargetFrameworkProfile />
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\Build\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\Build\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\x64\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>x64</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+ <OutputPath>bin\x64\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <Optimize>true</Optimize>
+ <DebugType>pdbonly</DebugType>
+ <PlatformTarget>x64</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\x86\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>x86</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+ <OutputPath>bin\x86\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <Optimize>true</Optimize>
+ <DebugType>pdbonly</DebugType>
+ <PlatformTarget>x86</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\Versioning\GlobalVersionInfo.cs">
+ <Link>GlobalVersionInfo.cs</Link>
+ </Compile>
+ <Compile Include="CsvDefinition.cs" />
+ <Compile Include="CsvDestination.cs" />
+ <Compile Include="CsvFile.cs" />
+ <Compile Include="CsvFileLinqExtensions.cs" />
+ <Compile Include="CsvFileReader.cs" />
+ <Compile Include="CsvIgnoreAttribute.cs" />
+ <Compile Include="CsvSource.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file