Skip to content

Instantly share code, notes, and snippets.

@zapthedingbat
Created November 26, 2016 14:06
Show Gist options
  • Save zapthedingbat/467b04dfd4a8d34c2e3f84c7c338d2f0 to your computer and use it in GitHub Desktop.
Save zapthedingbat/467b04dfd4a8d34c2e3f84c7c338d2f0 to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace ZapTheDingbat.IO
{
public class CsvReader : IEnumerable<IReadOnlyList<string>>, IEnumerator<IReadOnlyList<string>>
{
private readonly TextReader _reader;
private readonly IList<string> _currentRow;
private readonly StringBuilder _currentValue;
private IReadOnlyList<string> _current;
private State _state;
public CsvReader(TextReader reader)
{
_reader = reader;
_state = State.DataStart;
_currentRow = new List<string>();
_currentValue = new StringBuilder();
}
private enum State
{
DataStart,
DataStartQuoteStart,
DataStartQuote,
DataQuoted,
DataQuotedQuoteStart,
Data,
DataEnd,
NewLineStart,
}
public bool MoveNext()
{
var c = _reader.Read();
while (c != -1)
{
if (MoveNextChar((char)c))
{
_current = _currentRow.ToArray();
_currentRow.Clear();
return true;
}
c = _reader.Read();
}
return false;
}
public IReadOnlyList<string> Current => _current;
object IEnumerator.Current => Current;
private bool MoveNextChar(char c)
{
if (_state == State.DataStart)
{
_currentValue.Clear();
switch (c)
{
case '"':
_state = State.DataStartQuoteStart;
break;
case ',':
_state = State.DataStart;
_currentRow.Add(_currentValue.ToString());
_currentValue.Clear();
break;
default:
_state = State.Data;
_currentValue.Append(c);
break;
}
}
else if (_state == State.DataStartQuoteStart)
{
if (c == '"')
{
_state = State.DataStartQuote;
}
else
{
_state = State.DataQuoted;
_currentValue.Append(c);
}
}
else if (_state == State.DataStartQuote)
{
switch (c)
{
case '"':
_state = State.DataQuoted;
_currentValue.Append(c);
break;
case ',':
_state = State.DataStart;
_currentRow.Add(_currentValue.ToString());
_currentValue.Clear();
break;
default:
_state = State.DataEnd;
break;
}
}
else if (_state == State.DataEnd)
{
if (c == ',')
{
_state = State.DataStart;
_currentRow.Add(_currentValue.ToString());
_currentValue.Clear();
}
}
else if (_state == State.Data)
{
switch (c)
{
case ',':
_state = State.DataStart;
_currentRow.Add(_currentValue.ToString());
_currentValue.Clear();
break;
case '\r':
_state = State.NewLineStart;
break;
default:
_currentValue.Append(c);
break;
}
}
else if (_state == State.DataQuoted)
{
if (c == '"')
{
_state = State.DataQuotedQuoteStart;
}
else
{
_currentValue.Append(c);
}
}
else if (_state == State.DataQuotedQuoteStart)
{
switch (c)
{
case '"':
_state = State.DataQuoted;
_currentValue.Append(c);
break;
case ',':
_state = State.DataStart;
_currentRow.Add(_currentValue.ToString());
_currentValue.Clear();
break;
default:
_state = State.DataEnd;
break;
}
}
else if (_state == State.NewLineStart)
{
if (c != '\n')
{
throw new FormatException("Invalid NewLine seperator");
}
_currentRow.Add(_currentValue.ToString());
_currentValue.Clear();
_state = State.DataStart;
return true;
}
return false;
}
public void Dispose()
{
_reader.Dispose();
}
public void Reset()
{
throw new NotSupportedException();
}
public IEnumerator<IReadOnlyList<string>> GetEnumerator()
{
return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment