web-dev-qa-db-ja.com

列に含まれる可能性のある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

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)が含まれています

//更新// C#正規表現分割-引用符の外のコンマ に基づいて

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

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 
 7/31/2008 14:22 
 Geoff Dalgas 
 6/5/2011 22:21 
 http://stackoverflow.com 
 Corvallis、OR 
 7679 
 351 
 81 
 b437f461b3fd27387c5d8ab47a293d35 
 34 

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

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

161
Tim

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

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

偶数個の引用符が続くすべてのコンマで分割できます。

また、コンマの処理に関するCSV形式の specf で表示することもできます。

便利なリンク: C# Regex Split - commas outside quotes

5
sgokhales

CSVで区切られたテキストをExcelに貼り付けて「テキストから列へ」を実行すると、「テキスト修飾子」が要求されることがわかります。デフォルトでは二重引用符になっているため、二重引用符内のテキストはリテラルとして扱われます。 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();
}
4
Roly

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

3
Adam Lear

.csvファイルがコンマ区切りの文字列、コンマ区切りの引用符付き文字列、またはこの2つの混chaとした組み合わせのいずれかである可能性がある場合、.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ファイルには表示されません。これをもっと処理したいかもしれません。

2
Jason Williams

引用文字を含むフィールドを含む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());
    }
  }
}

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

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

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の記事をご覧ください。

それが役に立てば幸い。

1
RajN