|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
|
|
|
using System.Text;
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
|
|
namespace Ideafixxxer.CsvParser
|
|
|
|
{
|
|
|
|
public class CsvParser
|
|
|
|
{
|
|
|
|
private const char CommaCharacter = ',';
|
|
|
|
private const char QuoteCharacter = '"';
|
|
|
|
|
|
|
|
#region Nested types
|
|
|
|
|
|
|
|
private abstract class ParserState
|
|
|
|
{
|
|
|
|
public static readonly LineStartState LineStartState = new LineStartState();
|
|
|
|
public static readonly ValueStartState ValueStartState = new ValueStartState();
|
|
|
|
public static readonly ValueState ValueState = new ValueState();
|
|
|
|
public static readonly QuotedValueState QuotedValueState = new QuotedValueState();
|
|
|
|
public static readonly QuoteState QuoteState = new QuoteState();
|
|
|
|
|
|
|
|
public abstract ParserState AnyChar(char ch, ParserContext context);
|
|
|
|
public abstract ParserState Comma(ParserContext context);
|
|
|
|
public abstract ParserState Quote(ParserContext context);
|
|
|
|
public abstract ParserState EndOfLine(ParserContext context);
|
|
|
|
}
|
|
|
|
|
|
|
|
private class LineStartState : ParserState
|
|
|
|
{
|
|
|
|
public override ParserState AnyChar(char ch, ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddChar(ch);
|
|
|
|
return ValueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState Comma(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddValue();
|
|
|
|
return ValueStartState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState Quote(ParserContext context)
|
|
|
|
{
|
|
|
|
return QuotedValueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState EndOfLine(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddLine();
|
|
|
|
return LineStartState;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private class ValueStartState : LineStartState
|
|
|
|
{
|
|
|
|
public override ParserState EndOfLine(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddValue();
|
|
|
|
context.AddLine();
|
|
|
|
return LineStartState;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private class ValueState : ParserState
|
|
|
|
{
|
|
|
|
public override ParserState AnyChar(char ch, ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddChar(ch);
|
|
|
|
return ValueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState Comma(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddValue();
|
|
|
|
return ValueStartState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState Quote(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddChar(QuoteCharacter);
|
|
|
|
return ValueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState EndOfLine(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddValue();
|
|
|
|
context.AddLine();
|
|
|
|
return LineStartState;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private class QuotedValueState : ParserState
|
|
|
|
{
|
|
|
|
public override ParserState AnyChar(char ch, ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddChar(ch);
|
|
|
|
return QuotedValueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState Comma(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddChar(CommaCharacter);
|
|
|
|
return QuotedValueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState Quote(ParserContext context)
|
|
|
|
{
|
|
|
|
return QuoteState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState EndOfLine(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddChar('\r');
|
|
|
|
context.AddChar('\n');
|
|
|
|
return QuotedValueState;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private class QuoteState : ParserState
|
|
|
|
{
|
|
|
|
public override ParserState AnyChar(char ch, ParserContext context)
|
|
|
|
{
|
|
|
|
//undefined, ignore "
|
|
|
|
context.AddChar(ch);
|
|
|
|
return QuotedValueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState Comma(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddValue();
|
|
|
|
return ValueStartState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState Quote(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddChar(QuoteCharacter);
|
|
|
|
return QuotedValueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override ParserState EndOfLine(ParserContext context)
|
|
|
|
{
|
|
|
|
context.AddValue();
|
|
|
|
context.AddLine();
|
|
|
|
return LineStartState;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private class ParserContext
|
|
|
|
{
|
|
|
|
private readonly StringBuilder _currentValue = new StringBuilder();
|
|
|
|
private readonly List<string[]> _lines = new List<string[]>();
|
|
|
|
private readonly List<string> _currentLine = new List<string>();
|
|
|
|
|
|
|
|
public void AddChar(char ch)
|
|
|
|
{
|
|
|
|
_currentValue.Append(ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void AddValue()
|
|
|
|
{
|
|
|
|
_currentLine.Add(_currentValue.ToString());
|
|
|
|
_currentValue.Remove(0, _currentValue.Length);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void AddLine()
|
|
|
|
{
|
|
|
|
_lines.Add(_currentLine.ToArray());
|
|
|
|
_currentLine.Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
public List<string[]> GetAllLines()
|
|
|
|
{
|
|
|
|
if (_currentValue.Length > 0)
|
|
|
|
{
|
|
|
|
AddValue();
|
|
|
|
}
|
|
|
|
if (_currentLine.Count > 0)
|
|
|
|
{
|
|
|
|
AddLine();
|
|
|
|
}
|
|
|
|
return _lines;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
public string[][] Parse(string csvData)
|
|
|
|
{
|
|
|
|
var context = new ParserContext();
|
|
|
|
|
|
|
|
// Handle both Windows and Mac line endings
|
|
|
|
var lines = Regex.Split(csvData, "\n|\r\n");
|
|
|
|
|
|
|
|
ParserState currentState = ParserState.LineStartState;
|
|
|
|
for (int i = 0; i < lines.Length; i++) {
|
|
|
|
var next = lines [i];
|
|
|
|
|
|
|
|
// Skip empty entries
|
|
|
|
if (next.Length == 0)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int j = 0; j < next.Length; j++) {
|
|
|
|
var ch = next [j];
|
|
|
|
|
|
|
|
switch (ch) {
|
|
|
|
case CommaCharacter:
|
|
|
|
currentState = currentState.Comma (context);
|
|
|
|
break;
|
|
|
|
case QuoteCharacter:
|
|
|
|
currentState = currentState.Quote (context);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
currentState = currentState.AnyChar (ch, context);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currentState = currentState.EndOfLine (context);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<string[]> allLines = context.GetAllLines();
|
|
|
|
return allLines.ToArray();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|