Skip to content

Instantly share code, notes, and snippets.

@pierre3
Last active August 29, 2015 13:57
Show Gist options
  • Save pierre3/9427491 to your computer and use it in GitHub Desktop.
Save pierre3/9427491 to your computer and use it in GitHub Desktop.
Parse a csv
public static IEnumerable<string> Parse(string line,
ICollection<string> delimiters, Func<string> followingLineSelector)
{
//読み取った文字の種類に応じて処理を振り分けるために使用するTokenState 列挙型
var state = TokenState.Empty;
string token = "";
// -フィールドを1文字ずつ読み取り、token へ格納する
// -1フィールド分を読み取ったら yield return token; で出力
foreach (var c in line)
{
switch (state)
{
case TokenState.Empty:
case TokenState.AfterSeparator:
//1. フィールド読み取り前処理
state = TokenState.Empty;
// -フィールド前方の空白文字をスキップ。
// -但し、指定した区切り文字の先頭と一致する空白文字はスキップしない。
if (char.IsWhiteSpace(c)
&& !delimiters.Any(s => s.StartsWith(c.ToString())))
{ break; }
if (c == '"')
{
// ⇒ダブルクオート フィールドへ
state = TokenState.QuotedField;
token += c;
break;
}
// ⇒通常フィールドへ
state = TokenState.NormalField;
goto case TokenState.NormalField;
case TokenState.NormalField:
//2. 通常のフィールドの読み取り処理
{
token += c;
// - 区切り文字を読み取ったら、区切り文字と末尾の空白を削除して yield return !
var delimiter = delimiters
.FirstOrDefault(s => token.ToString().EndsWith(s));
if (delimiter != null)
{
yield return token
.Substring(0, token.Length - delimiter.Length).TrimEnd();
// ⇒フィールド読み取前処理へ
state = TokenState.AfterSeparator;
token = "";
}
break;
}
case TokenState.QuotedField:
//3. ダブルクオートフィールドの読み取り処理
token += c;
if (c == '"')
{
// ⇒ダブルクオート(閉じ)判定へ
state = TokenState.EndQuote;
}
break;
case TokenState.EndQuote:
//4. ダブルクオート(閉じ)の判定処理
if (c == '"')
{
// ⇒エスケープ(””)なのでダブルクオートフィールド読み取りへ戻る
token += c;
state = TokenState.QuotedField;
break;
}
// -閉じダブルクオートだった。
// -前後のダブルクオートと、エスケープされたダブルクオートを削除して yield return !
yield return token.Substring(1, token.Length - 2)
.Replace("\"\"", "\"");
token = "";
// ⇒フィールド読み取前処理へ
state = TokenState.AfterQuote;
goto case TokenState.AfterQuote;
case TokenState.AfterQuote:
//5. ダブルクオートフィールド後の処理
{
token += c;
var delimiter = delimiters
.FirstOrDefault(s => token.ToString().EndsWith(s));
if (delimiter != null)
{
state = TokenState.AfterSeparator;
token = "";
}
break;
}
}
}
//6. 行末まで読み取った後の処理
if (state == TokenState.AfterQuote)
{ yield break; }
if (state == TokenState.QuotedField)
{
// -ダブルクオートフィールドが閉じられる前にここに来た場合
// -フィールド内の改行なので、後続の1行を継ぎ足してParseを再帰呼び出し
var next = Parse(token + Environment.NewLine
+ followingLineSelector(), delimiters, followingLineSelector);
foreach (var s in next)
{
yield return s;
}
}
else if (token != string.Empty || state == TokenState.AfterSeparator)
{
// -残っていたtoken内の文字を出力
// -あるいは、区切り文字で行が終了した場合の空文字フィールドを出力
yield return (state == TokenState.EndQuote)
? token.TrimEnd().Substring(1, token.Length - 2)
.Replace("\"\"", "\"")
: token.TrimEnd();
}
}
enum TokenState
{
//フィールド読み取り前
Empty,
//区切り文字を読み取った直後
AfterSeparator,
//通常のフィールドを読み取り中
NormalField,
//ダブルクオートフィールドを読み取り中
QuotedField,
//ダブルクオート(閉じ)かフィールド内のダブルクオートかを判定
EndQuote,
//ダブルクオートフィールド読み取り後
AfterQuote
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment