Jak podzielić plik CSV, którego kolumny mogą zawierać,

105

Dany

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

Jak używać C # do podzielenia powyższych informacji na ciągi w następujący sposób:

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

Jak widać, jedna z kolumn zawiera, <= (Corvallis, OR)

// aktualizacja // Na podstawie C # Regex Split - przecinki poza cudzysłowami

string[] result = Regex.Split(samplestring, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
q0987
źródło
1
Chociaż w Javie, podobne pytanie: stackoverflow.com/questions/1757065/ ...
sgokhales
1
Używanie wyrażenia regularnego do tego jest złą radą. .NET Framework ma już wbudowaną obsługę analizowania plików CSV. Zobacz tę odpowiedź, którą powinieneś zaakceptować. W przeciwnym razie zamknę to jako duplikat stackoverflow.com/questions/3147836/…, co jest równie błędne.
Kev
Czy możesz wyjaśnić, jakie jest wbudowane wsparcie .NET dla analizowania plików CSV z osadzonymi przecinkami? Czy odnosisz się do klasy Microsoft.VisualBasic.FileIO.TextFieldParser?
AllSolutions

Odpowiedzi:

182

Skorzystaj z Microsoft.VisualBasic.FileIO.TextFieldParserklasy. Spowoduje to przeanalizowanie rozdzielanego pliku TextReaderlub sytuacji, w Streamktórych niektóre pola są zawarte w cudzysłowach, a inne nie.

Na przykład:

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();

Powinno to dać następujący wynik:

2
1016
31.07.2008 14:22
Geoff Dalgas
05.06.2011 22:21
http://stackoverflow.com
Corvallis, OR
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

Aby uzyskać więcej informacji, zobacz Microsoft.VisualBasic.FileIO.TextFieldParser .

Należy dodać odwołanie do Microsoft.VisualBasicna karcie Dodaj odwołania .NET.

Tim
źródło
9
Koleś, bardzo dziękuję za to rozwiązanie, mam około 500K + wierszy danych CSV, które muszę załadować do tabeli i są ładowane przecinkami zawartymi w cudzysłowach. Jestem ci winien wybrany przez ciebie napój dla dorosłych, jeśli nasze ścieżki kiedykolwiek się skrzyżują.
Mark Kram
@tim użyłem tego i zauważa, że ​​pomija wszystkie parzyste numery wierszy, przetwarzając tylko nieparzyste numery wierszy w pliku, który ma 1050 wierszy. jakieś pomysły?
Smith
@Smith - Nie widząc kodu ani przykładowych danych wejściowych, nie mam pojęcia. Proponuję zadać nowe pytanie. Może w pliku brakuje znaku powrotu karetki lub innego znacznika końca linii w parzystych wierszach?
Tim
Nie wiedziałem nawet o tej bibliotece, dopóki tego nie zobaczyłem - dzięki! Jeśli ktoś inny chce mieć przykład analizujący cały plik CSV, zobacz tę odpowiedź SO: stackoverflow.com/a/3508572/3105807
Amy Barrett
2
Czy możemy zlinczować Microsoft za brak konstruktora, który pobiera ciąg, więc musimy najpierw przeskoczyć przez obręcz, aby przekształcić go w strumień? W przeciwnym razie miła odpowiedź.
Loren Pechtel,
43

Jest już bardzo późno, ale może to być pomocne dla kogoś. Możemy użyć RegEx jak poniżej.

Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = CSVParser.Split(Test);
Husen
źródło
4
To jest doskonałe. Wolałby to wykorzystać niż importować całą inną bibliotekę. Brawo.
TheGeekYouNeed
1
Pasuje do asdf, „”, „as ,\„ df ”,
To rozwiązanie nie działa poprawnie - nie uwzględnia znaków mowy, co oznacza, że ​​podczas czytania będzie dużo znaków mowy w niewłaściwych miejscach.
AidanH
Co się stanie, jeśli w jakimś wierszu brakuje kończącego cudzysłowu: asd, „”, „as, \„ df ”,„ asd asd ”,„ as
MarmiK
1
To zadziałało dla mnie i uwzględniło cytowane znaki mowy. 30 milionów ich rzędów. Bardzo dobra i minimalna ilość kodu.
GBGOLC
4

Widzę, że jeśli wkleisz tekst rozdzielany CSV w programie Excel i wykonasz polecenie „Tekst do kolumn”, zostanie wyświetlony monit o podanie „kwalifikatora tekstu”. Domyślnie jest to podwójny cudzysłów, więc traktuje tekst w cudzysłowie jako dosłowny. Wyobrażam sobie, że Excel implementuje to, przechodząc po jednym znaku na raz, jeśli napotka „kwalifikator tekstu”, przechodzi do następnego „kwalifikatora”. Prawdopodobnie możesz zaimplementować to samodzielnie za pomocą pętli for i wartości logicznej, aby wskazać, czy znajdujesz się w dosłownym tekście.

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();
}
Roly
źródło
3

Skorzystaj z biblioteki, takiej jak LumenWorks, do czytania CSV. Będzie obsługiwał pola z cudzysłowami i prawdopodobnie będzie ogólnie bardziej niezawodny niż Twoje niestandardowe rozwiązanie, ponieważ istnieje od dłuższego czasu.

Adam Lear
źródło
2

Analiza plików .csv jest trudna, gdy plik .csv może składać się z ciągów oddzielonych przecinkami, ciągów znaków w cudzysłowie lub chaotycznej kombinacji tych dwóch. Rozwiązanie, które wymyśliłem, pozwala na dowolną z trzech możliwości.

Stworzyłem metodę ParseCsvRow (), która zwraca tablicę z łańcucha csv. Najpierw zajmuję się podwójnymi cudzysłowami w ciągu, dzieląc ciąg w podwójnych cudzysłowach na tablicę o nazwie quotesArray. Pliki .csv z cytatami są poprawne tylko wtedy, gdy występuje parzysta liczba podwójnych cudzysłowów. Podwójne cudzysłowy w wartości kolumny należy zastąpić parą podwójnych cudzysłowów (takie jest podejście programu Excel). Dopóki plik .csv spełnia te wymagania, można oczekiwać, że przecinki ograniczające będą pojawiać się tylko poza parami podwójnych cudzysłowów. Przecinki w parach podwójnych cudzysłowów są częścią wartości kolumny i należy je zignorować podczas dzielenia pliku .csv na tablicę.

Moja metoda sprawdzi, czy przecinki nie znajdują się w podwójnych cudzysłowach, patrząc tylko na parzyste indeksy tablicy cudzysłowu. Usuwa również podwójne cudzysłowy z początku i końca wartości kolumn.

    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;
    }

Jedną z wad mojego podejścia jest sposób, w jaki tymczasowo zastępuję przecinki separatora niejasnym znakiem Unicode. Ten znak musi być tak niejasny, aby nigdy nie pojawił się w twoim pliku .csv. Możesz zająć się tym problemem.

Jason Williams
źródło
1

Miałem problem z plikiem CSV, który zawiera pola ze znakiem cudzysłowu, więc używając TextFieldParser, wymyśliłem:

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 jest nadal używany do odczytywania CSV wiersz po wierszu, w następujący sposób:

using(StreamReader SR = new StreamReader(FileName))
{
  while (SR.Peek() >-1)
    myStringArray = parseCSVLine(SR.ReadLine());
}
RooiWillie
źródło
1

Dzięki Cinchoo ETL - bibliotece open source, może automatycznie obsługiwać wartości kolumn zawierające separatory.

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());
}

Wynik:

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

Więcej informacji można znaleźć w artykule codeproject.

Mam nadzieję, że to pomoże.

RajN
źródło