Pages

Wednesday, May 23, 2012

CSV file Parser

using System;
using System.Data;
using System.IO; //not used by default
using System.Data.OleDb; //not used by default
namespace CSVParserExample
{
  class CSVParser
  {
    public static DataTable ParseCSV(string path)
    {
      if (!File.Exists(path))
        return null;
      string full = Path.GetFullPath(path);
      string file = Path.GetFileName(full);
      string dir = Path.GetDirectoryName(full);
      //create the "database" connection string
      string connString = "Provider=Microsoft.Jet.OLEDB.4.0;"
        + "Data Source=\"" + dir + "\\\";"
        + "Extended Properties=\"text;HDR=No;FMT=Delimited\"";
      //create the database query
      string query = "SELECT * FROM " + file;
      //create a DataTable to hold the query results
      DataTable dTable = new DataTable();
      //create an OleDbDataAdapter to execute the query
      OleDbDataAdapter dAdapter = new OleDbDataAdapter(query, connString);
      try
      {
        //fill the DataTable
        dAdapter.Fill(dTable);
      }
      catch (InvalidOperationException /*e*/)
      { }
      dAdapter.Dispose();
      return dTable;
    }
  }
}
The weird thing about all of this is that the CSV file gets treated as a database table. We need to create a connection string with the Jet OLEDB provider, and you set the Data Source to be the directory that contains the CSV file. Under extended properties, 'text' means that we are parsing a text file (as opposed to, say, an Excel file), the HDR property can be set to 'Yes' (the first row in the CSV files is header information) or 'No' (there is no header row), and setting the FMT property set to 'Delimited' essentially says that we will be working with a comma separated value file. You can also set FMT to 'FixedLength', for files where the fields are fixed length - but that wouldn't be a CSV file anymore, would it?
Now we are into normal OLEDB territory - we create a DataTable that we will be filling with results, and we create a OleDbDataAdapter to actually execute the query. Then (inside of a try block, because it can throw an exception) we fill the data table with the results of the query. Afterwords, we clean up after ourselves by disposing the OleDbDataAdapter, and we return the now filled data table.

Using the FileSystemWatcher Class

The FileSystemWatcher class does exactly what the name implies - it watches the file system. Basically you just give it a path and it fires events when certain things happen to that path. The FileSystemWatcher can listen for rename, delete, change, and create. Let's hook it up and listen for renamed files.

FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = @"C:\MyDirectory";
watcher.Renamed += new RenamedEventHandler(watcher_Renamed)
watcher.EnableRaisingEvents = true;

Once you hook the events you want, in this case Renamed, you have to set EnableRaisingEvents to true to begin watching. In this example, the method watcher_Renamed will be called whenever any file in the folder C:\MyDirectory is renamed. The RenamedEventHandler contains lots of good information about the renamed file - like the old file name and the new file name.

void watcher_Renamed(object sender, RenamedEventArgs e)
{
  Console.WriteLine("File Renamed: Old Name: " + e.OldName +
    " New Name: " + e.Name);
}
All of the other events in the FileSystemWatcher class will pass a FileSystemEventArgs object to the event handler.

watcher.Deleted += new FileSystemEventHandler(watcher_Deleted);
watcher.Changed += new FileSystemEventHandler(watcher_Changed);
watcher.Created += new FileSystemEventHandler(watcher_Created);

FileSystemEventArgs contain the filename, the full path, and what action caused the event.
So what if you want to watch a single file or a single type of file (like .txt files)? The FileSystemWatcher contains a Filter property that can be used to do that.

//watches only myFile.txt
watcher.Filter = "myFile.txt";
//watches all files with a .txt extension
watcher.Filter = "*.txt";

Like the second example in the above snippet shows, the Filter property can accept wildcards. In this case, events will only be fired when a file with a .txt extension changes.
The last important piece of the FileSystemWatcher is the property IncludeSubdirectories. The property tells the class to watch the folder you gave and all folders contained in it - and folders contained within those, etc.

How to Get an Enum from a Number

Tutorial on how to convert an integer to an enum. This is useful when you've got a number, either from a file or transferred over a socket, and you want to convert that number to an enum.

What we're going to create is a generic method that can convert a number to any type of enum. I like using a generic method for this because now I can convert numbers to any type of enum using the same function and without having to cast the return value for each enum separately.
Here's the function that will be doing all the work:
public T NumToEnum<T>(int number)
{
   return (T)Enum.ToObject(typeof(T), number);
}
This is a generic function that can convert a number to any type of enum. Let's see some sample code that makes use of this function. First, let's create some enums.
public enum DaysOfWeek
{
   Monday,
   Tuesday,
   Wednesday,
   Thursday,
   Friday,
   Saturday,
   Sunday
}
public enum MonthsInYear
{
   January,
   February,
   March,
   April,
   May,
   June,
   July,
   August,
   September,
   October,
   November,
   December
}
Now, let's see some code that uses NumToEnum.
int day = 3;
int month = 10;
DaysOfWeek d  = NumToEnum<DaysOfWeek>(day);
//d is now DaysOfWeek.Thursday
MonthsInYear m = NumToEnum<MonthsInYear>(month);
//m is now MonthsInYear.November
I know this is something I've needed on several occasions, so hopefully this will help you out as well.