列にが含まれている可能性のあるcsvを分割する方法


105

与えられた

2,1016,7 / 31/2008 14:22、Geoff Dalgas、6/5/2011 22 : 21、http: //stackoverflow.com、 "Corvallis、OR"、7679,351,81、b437f461b3fd27387c5d8ab47a293d35,34

C#を使用して上記の情報を次のように文字列に分割する方法:

2
1016
7/31/2008 14:22
Geoff Dalgas
6/5/2011 22:21
http://stackoverflow.com
Corvallis, OR
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

ご覧のように、列の1つに、<=(Corvallis、OR)が含まれています

// update // C#Regex Splitに基づく -引用符の外のコンマ

string[] result = Regex.Split(samplestring, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

1
Javaでも、同様の質問:stackoverflow.com/questions/1757065/...は
sgokhales

1
これを行うために正規表現を使用することは悪いアドバイスです。.NET Frameworkには、CSVを解析するためのサポートが組み込まれています。あなたが受け入れるべきであるこの答えを見てください。それ以外の場合は、stackoverflow.com / questions / 3147836 / … と同じように間違ってこれを閉じます。
ケブ

カンマが埋め込まれたCSVファイルを解析するための.NETの組み込みサポートについて詳しく教えてください。Microsoft.VisualBasic.FileIO.TextFieldParserクラスを参照していますか?
AllSolutions 2016

回答:


182

Microsoft.VisualBasic.FileIO.TextFieldParserクラスを使用します。これは、区切られたファイルの解析、TextReaderまたはStream引用符で囲まれたフィールドとそうでないフィールドの解析を処理します。

例えば:

using Microsoft.VisualBasic.FileIO;

string csv = "2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,\"Corvallis, OR\",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

TextFieldParser parser = new TextFieldParser(new StringReader(csv));

// You can also read from a file
// TextFieldParser parser = new TextFieldParser("mycsvfile.csv");

parser.HasFieldsEnclosedInQuotes = true;
parser.SetDelimiters(",");

string[] fields;

while (!parser.EndOfData)
{
    fields = parser.ReadFields();
    foreach (string field in fields)
    {
        Console.WriteLine(field);
    }
} 

parser.Close();

これにより、次の出力が得られます。

2
1016
2008/7/31 14:22
ジェフ・ダルガス
2011年6月5日22:21
http://stackoverflow.com
オレゴン州コーバリス
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

詳細については、Microsoft.VisualBasic.FileIO.TextFieldParserを参照してください。

参照Microsoft.VisualBasicの追加.NETタブで参照を追加する必要があります。


9
おい、このソリューションを本当にありがとう、私はテーブルにロードする必要があるCSVデータの約50万行以上があり、引用符の中に含まれるコンマでロードされました。私たちの道が交差する場合、私はあなたにあなたが選んだ大人の飲み物を借りています。
マーククラム2014

@tim私はこれを使用し、すべての偶数行番号をスキップして、1050行のファイル内の奇数行番号のみを処理していることに気付きました。何か案は?
スミス

@スミス-あなたのコードやサンプル入力を見なければ、私にはわかりません。新しい質問を投稿することをお勧めします。ファイルの偶数行にキャリッジリターンまたはその他の行末マーカーがないのでしょうか?
Tim

これを見るまで、私はこのライブラリについてさえ知りませんでした-ありがとう!CSVファイル全体を解析する例が必要な場合は、次のSO回答を参照してください。stackoverflow.com/ a / 3508572/3105807
Amy Barrett

2
文字列をとるコンストラクタを提供しないことでMicrosoftをリンチできるので、最初にそれをストリームに変換するというフープを飛び越えなければなりませんか?そうでなければ、いい答えです。
Loren Pechtel 2016

43

それはとても遅いですが、これは誰かにとって役に立ちます。RegExを次のように使用できます。

Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = CSVParser.Split(Test);

4
これは完璧です。他のライブラリ全体をインポートするのではなく、これを使用します。ブラボー。
TheGeekYouNeed

1
マッチ"ASDF"、 "として,\" DF」、

このソリューションは正しく機能しません-スピーチマークが考慮されないため、読み取り中に誤った場所に多数のスピーチマークが存在します。
AidanH

一部の行で末尾の引用符が欠落している場合:asd、 ""、 "as、\" df "、" asd asd "、" as
MarmiK

1
これは私にとってはうまくいき、引用されたスピーチマークを説明しました。それらの3000万行。非常に良い、最小限のコード。
GBGOLC 2019年


4

Excelでcsvで区切られたテキストを貼り付けて「テキストから列へ」を実行すると、「テキスト修飾子」を要求することがわかります。デフォルトでは二重引用符が使用されるため、二重引用符内のテキストはリテラルとして扱われます。Excelが一度に1文字ずつ行くことでこれを実装すると思います。「テキスト修飾子」に遭遇すると、次の「修飾子」に進み続けます。おそらく、これをforループとブール値を使用して自分で実装し、リテラルテキストの内部にいるかどうかを示すことができます。

public string[] CsvParser(string csvText)
{
    List<string> tokens = new List<string>();

    int last = -1;
    int current = 0;
    bool inText = false;

    while(current < csvText.Length)
    {
        switch(csvText[current])
        {
            case '"':
                inText = !inText; break;
            case ',':
                if (!inText) 
                {
                    tokens.Add(csvText.Substring(last + 1, (current - last)).Trim(' ', ',')); 
                    last = current;
                }
                break;
            default:
                break;
        }
        current++;
    }

    if (last != csvText.Length - 1) 
    {
        tokens.Add(csvText.Substring(last+1).Trim());
    }

    return tokens.ToArray();
}

3

LumenWorksなどのライブラリを使用してCSVを読み取ります。引用符が含まれているフィールドを処理し、長い間使用されているため、カスタムソリューションよりも全体的に堅牢である可能性があります。


2

.csvファイルがコンマ区切りの文字列、コンマ区切りの引用符付き文字列、または2つの無秩序な組み合わせのいずれかである場合、.csvファイルを解析するのは難しい問題です。私が思いついた解決策は、3つの可能性のいずれかを可能にします。

csv文字列から配列を返すメソッドParseCsvRow()を作成しました。最初に、文字列内の二重引用符を扱います。二重引用符で囲まれた文字列をquotesArrayという配列に分割します。引用符付きの文字列.csvファイルは、二重引用符の数が偶数の場合にのみ有効です。列値の二重引用符は、二重引用符のペアで置き換える必要があります(これはExcelのアプローチです)。.csvファイルがこれらの要件を満たしている限り、区切り記号のコンマは二重引用符のペアの外側にのみ出現することが期待できます。二重引用符のペア内のコンマは列値の一部であり、.csvを配列に分割する場合は無視する必要があります。

私の方法では、quotesArrayのインデックスだけを見て、二重引用符のペアの外側にあるコンマをテストします。また、列の値の最初と最後から二重引用符を削除します。

    public static string[] ParseCsvRow(string csvrow)
    {
        const string obscureCharacter = "ᖳ";
        if (csvrow.Contains(obscureCharacter)) throw new Exception("Error: csv row may not contain the " + obscureCharacter + " character");

        var unicodeSeparatedString = "";

        var quotesArray = csvrow.Split('"');  // Split string on double quote character
        if (quotesArray.Length > 1)
        {
            for (var i = 0; i < quotesArray.Length; i++)
            {
                // CSV must use double quotes to represent a quote inside a quoted cell
                // Quotes must be paired up
                // Test if a comma lays outside a pair of quotes.  If so, replace the comma with an obscure unicode character
                if (Math.Round(Math.Round((decimal) i/2)*2) == i)
                {
                    var s = quotesArray[i].Trim();
                    switch (s)
                    {
                        case ",":
                            quotesArray[i] = obscureCharacter;  // Change quoted comma seperated string to quoted "obscure character" seperated string
                            break;
                    }
                }
                // Build string and Replace quotes where quotes were expected.
                unicodeSeparatedString += (i > 0 ? "\"" : "") + quotesArray[i].Trim();
            }
        }
        else
        {
            // String does not have any pairs of double quotes.  It should be safe to just replace the commas with the obscure character
            unicodeSeparatedString = csvrow.Replace(",", obscureCharacter);
        }

        var csvRowArray = unicodeSeparatedString.Split(obscureCharacter[0]); 

        for (var i = 0; i < csvRowArray.Length; i++)
        {
            var s = csvRowArray[i].Trim();
            if (s.StartsWith("\"") && s.EndsWith("\""))
            {
                csvRowArray[i] = s.Length > 2 ? s.Substring(1, s.Length - 2) : "";  // Remove start and end quotes.
            }
        }

        return csvRowArray;
    }

私のアプローチの1つの欠点は、区切り文字のコンマを一時的に不明瞭なUnicode文字に置き換える方法です。この文字は非常に不明瞭である必要があり、.csvファイルには表示されません。これについては、もっと扱いたいと思うかもしれません。


1

引用符を含むフィールドを含むCSVで問題が発生したため、TextFieldParserを使用して、次のことを思いつきました。

private static string[] parseCSVLine(string csvLine)
{
  using (TextFieldParser TFP = new TextFieldParser(new MemoryStream(Encoding.UTF8.GetBytes(csvLine))))
  {
    TFP.HasFieldsEnclosedInQuotes = true;
    TFP.SetDelimiters(",");

    try 
    {           
      return TFP.ReadFields();
    }
    catch (MalformedLineException)
    {
      StringBuilder m_sbLine = new StringBuilder();

      for (int i = 0; i < TFP.ErrorLine.Length; i++)
      {
        if (i > 0 && TFP.ErrorLine[i]== '"' &&(TFP.ErrorLine[i + 1] != ',' && TFP.ErrorLine[i - 1] != ','))
          m_sbLine.Append("\"\"");
        else
          m_sbLine.Append(TFP.ErrorLine[i]);
      }

      return parseCSVLine(m_sbLine.ToString());
    }
  }
}

StreamReaderは、次のようにCSVを1行ずつ読み取るために引き続き使用されます。

using(StreamReader SR = new StreamReader(FileName))
{
  while (SR.Peek() >-1)
    myStringArray = parseCSVLine(SR.ReadLine());
}

1

Cinchoo ETL -オープンソースライブラリ、それが自動的にセパレータを含む列値を処理することができます。

string csv = @"2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,""Corvallis, OR"",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

using (var p = ChoCSVReader.LoadText(csv)
    )
{
    Console.WriteLine(p.Dump());
}

出力:

Key: Column1 [Type: String]
Value: 2
Key: Column2 [Type: String]
Value: 1016
Key: Column3 [Type: String]
Value: 7/31/2008 14:22
Key: Column4 [Type: String]
Value: Geoff Dalgas
Key: Column5 [Type: String]
Value: 6/5/2011 22:21
Key: Column6 [Type: String]
Value: http://stackoverflow.com
Key: Column7 [Type: String]
Value: Corvallis, OR
Key: Column8 [Type: String]
Value: 7679
Key: Column9 [Type: String]
Value: 351
Key: Column10 [Type: String]
Value: 81
Key: Column11 [Type: String]
Value: b437f461b3fd27387c5d8ab47a293d35
Key: Column12 [Type: String]
Value: 34

詳細については、codeprojectの記事をご覧ください。

それが役に立てば幸い。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.