Reading and writing CSV files

Introduction

Comma-separated values (abbreviated as CSV) is a file format based on text files used for importing and exporting (for example from spreadsheets or databases) of a data table.

In this format, each row of the table (or database record) is normally represented by a line of text, which in turn is divided into fields (the individual columns) separated by a special separator character, each of which represents a value.

To facilitate the reading and generation of files in CSV format, the CSVFileReader and CSVFileWriter C# classes are available.

CSVFileReader

Documentation CSVFileReader.

using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
private class CSVFileReader : IDisposable
{
    public char FieldDelimiter { get; set; } = ',';
    public char QuoteChar { get; set; } = '"';
    public bool IgnoreMalformedLines { get; set; } = false;
    public CSVFileReader(string filePath, Encoding encoding)
    {
        streamReader = new StreamReader(filePath, encoding);
    }
    public CSVFileReader(string filePath)
    {
        streamReader = new StreamReader(filePath, Encoding.UTF8);
    }
    public CSVFileReader(System.IO.StreamReader streamReader)
    {
        this.streamReader = streamReader;
    }
    public void Dispose()
    {
        Dispose(true);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;
        if (disposing)
            streamReader.Dispose();
        disposed = true;
    }
    public bool EndOfFile()
    {
        return streamReader.EndOfStream;
    }
    public List<string> ReadLine()
    {
        if (EndOfFile())
            return null;
        var line = streamReader.ReadLine();
        return ParseLine(line);
    }
    public List<List<string>> ReadAll()
    {
        var result = new List<List<string>>();
        while (!EndOfFile())
            result.Add(ReadLine());
        return result;
    }
    private List<string> ParseLine(string line)
    {
        var fields = new List<string>();
        var buffer = new StringBuilder("");
        var fieldParsing = false;
        int i = 0;
        while (i < line.Length)
        {
            if (!fieldParsing)
            {
                if (IsWhiteSpace(line, i))
                {
                    ++i;
                    continue;
                }
                if (i == 0)
                {
                    // A line must begin with the quotation mark
                    if (!IsQuoteChar(line, i))
                    {
                        if (IgnoreMalformedLines)
                            return null;
                        else
                            throw new FormatException("Expected quotation marks at " + i);
                    }
                    fieldParsing = true;
                }
                else
                {
                    if (IsQuoteChar(line, i))
                        fieldParsing = true;
                    else if (!IsFieldDelimiter(line, i))
                    {
                        if (IgnoreMalformedLines)
                            return null;
                        else
                            throw new FormatException("Wrong field delimiter at " + i);
                    }
                }
                ++i;
            }
            else
            {
                if (IsEscapedQuoteChar(line, i))
                {
                    i += 2;
                    buffer.Append(QuoteChar);
                }
                else if (IsQuoteChar(line, i))
                {
                    fields.Add(buffer.ToString());
                    buffer.Clear();
                    fieldParsing = false;
                    ++i;
                }
                else
                {
                    buffer.Append(line[i]);
                    ++i;
                }
            }
        }
        return fields;
    }
    private bool IsEscapedQuoteChar(string line, int i)
    {
        return line[i] == QuoteChar && i != line.Length - 1 && line[i + 1] == QuoteChar;
    }

    private bool IsQuoteChar(string line, int i)
    {
        return line[i] == QuoteChar;
    }

    private bool IsFieldDelimiter(string line, int i)
    {
        return line[i] == FieldDelimiter;
    }

    private bool IsWhiteSpace(string line, int i)
    {
        return Char.IsWhiteSpace(line[i]);
    }

    bool disposed = false;
    StreamReader streamReader;
}

CSVFileWriter

Documentation CSVFileWriter.

using System.Text;
using System.IO;

private class CSVFileWriter : IDisposable
{
    public char FieldDelimiter { get; set; } = ',';

    public char QuoteChar { get; set; } = '"';

    public CSVFileWriter(string filePath)
    {
        streamWriter = new StreamWriter(filePath, false, Encoding.UTF8);
    }

    public CSVFileWriter(string filePath, Encoding encoding)
    {
        streamWriter = new StreamWriter(filePath, false, encoding);
    }

    public CSVFileWriter(System.IO.StreamWriter streamReader)
    {
        this.streamWriter = streamReader;
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;

        if (disposing)
            streamWriter.Dispose();

        disposed = true;
    }

    public void WriteLine(string[] fields)
    {
        var stringBuilder = new StringBuilder();

        for (var i = 0; i < fields.Length; ++i)
        {
            stringBuilder.AppendFormat("{0}{1}{0}", QuoteChar, EscapeField(fields[i]), QuoteChar);
            if (i != fields.Length - 1)
                stringBuilder.Append(FieldDelimiter);
        }

        streamWriter.WriteLine(stringBuilder.ToString());
        streamWriter.Flush();
    }

    private string EscapeField(string field)
    {
        var quoteCharString = QuoteChar.ToString();
        return field.Replace(quoteCharString, quoteCharString + quoteCharString);
    }

    bool disposed = false;
    StreamWriter streamWriter;
}