Last active
August 29, 2015 13:57
-
-
Save pierre3/9427491 to your computer and use it in GitHub Desktop.
Parse a csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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