// This code is in the public domain. I can't remember where I found it on the Web, but it // didn't come with any license. using System; using System.Collections; using System.IO; using System.Text; namespace CSVReader { /// /// A data-reader style interface for reading CSV files. /// public class CSVReader : IDisposable { #region Private variables private Stream stream; private StreamReader reader; #endregion /// /// Create a new reader for the given stream. /// /// The stream to read the CSV from. public CSVReader(Stream s) : this(s, null) { } /// /// Create a new reader for the given stream and encoding. /// /// The stream to read the CSV from. /// The encoding used. public CSVReader(Stream s, Encoding enc) { this.stream = s; if (!s.CanRead) { throw new CSVReaderException("Could not read the given CSV stream!"); } reader = (enc != null) ? new StreamReader(s, enc) : new StreamReader(s); } /// /// Creates a new reader for the given text file path. /// /// The name of the file to be read. public CSVReader(string filename) : this(filename, null) { } /// /// Creates a new reader for the given text file path and encoding. /// /// The name of the file to be read. /// The encoding used. public CSVReader(string filename, Encoding enc) : this(new FileStream(filename, FileMode.Open), enc) { } /// /// Returns the fields for the next row of CSV data (or null if at eof) /// /// A string array of fields or null if at the end of file. public string[] GetCSVLine() { string data = reader.ReadLine(); if (data == null) return null; if (data.Length == 0) return new string[0]; ArrayList result = new ArrayList(); ParseCSVFields(result, data); return (string[])result.ToArray(typeof(string)); } // Parses the CSV fields and pushes the fields into the result arraylist private void ParseCSVFields(ArrayList result, string data) { int pos = -1; while (pos < data.Length) result.Add(ParseCSVField(data, ref pos)); } // Parses the field at the given position of the data, modified pos to match // the first unparsed position and returns the parsed field private string ParseCSVField(string data, ref int startSeparatorPosition) { if (startSeparatorPosition == data.Length-1) { startSeparatorPosition++; // The last field is empty return ""; } int fromPos = startSeparatorPosition + 1; // Determine if this is a quoted field if (data[fromPos] == '"') { // If we're at the end of the string, let's consider this a field that // only contains the quote if (fromPos == data.Length-1) { fromPos++; return "\""; } // Otherwise, return a string of appropriate length with double quotes collapsed // Note that FSQ returns data.Length if no single quote was found int nextSingleQuote = FindSingleQuote(data, fromPos+1); startSeparatorPosition = nextSingleQuote+1; return data.Substring(fromPos+1, nextSingleQuote-fromPos-1).Replace("\"\"", "\""); } // The field ends in the next comma or EOL int nextComma = data.IndexOf(',', fromPos); if (nextComma == -1) { startSeparatorPosition = data.Length; return data.Substring(fromPos); } else { startSeparatorPosition = nextComma; return data.Substring(fromPos, nextComma-fromPos); } } // Returns the index of the next single quote mark in the string // (starting from startFrom) private int FindSingleQuote(string data, int startFrom) { int i = startFrom-1; while (++i < data.Length) if (data[i] == '"') { // If this is a double quote, bypass the chars if (i < data.Length-1 && data[i+1] == '"') { i++; continue; } else return i; } // If no quote found, return the end value of i (data.Length) return i; } /// /// Disposes the CSVReader. The underlying stream is closed. /// public void Dispose() { // Closing the reader closes the underlying stream, too if (reader != null) reader.Close(); else if (stream != null) stream.Close(); // In case we failed before the reader was constructed GC.SuppressFinalize(this); } } /// /// Exception class for CSVReader exceptions. /// public class CSVReaderException : ApplicationException { /// /// Constructs a new exception object with the given message. /// /// The exception message. public CSVReaderException(string message) : base(message) { } } }