Comment diviser le csv dont les colonnes peuvent contenir,

105

Donné

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

Comment utiliser C # pour diviser les informations ci-dessus en chaînes comme suit:

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

Comme vous pouvez le voir, l'une des colonnes contient, <= (Corvallis, OR)

// mise à jour // Basé sur C # Regex Split - virgules hors guillemets

string[] result = Regex.Split(samplestring, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
q0987
la source
1
Bien qu'en Java, similaire Question: stackoverflow.com/questions/1757065/…
sgokhales
1
Utiliser une regex pour faire cela est un mauvais conseil. Le .NET Framework a déjà une prise en charge intégrée pour analyser CSV. Voyez cette réponse qui est celle que vous devriez accepter. Sinon, je fermerai ceci comme une dupe de stackoverflow.com/questions/3147836/... qui est tout aussi faux.
Kev
Pouvez-vous expliquer quelle est la prise en charge intégrée de .NET pour l'analyse des fichiers CSV avec des virgules incorporées? Faites-vous référence à la classe Microsoft.VisualBasic.FileIO.TextFieldParser?
AllSolutions

Réponses:

182

Utilisez la Microsoft.VisualBasic.FileIO.TextFieldParserclasse. Cela gérera l'analyse d'un fichier délimité, TextReaderou Streamlorsque certains champs sont entre guillemets et certains ne le sont pas.

Par exemple:

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

Cela devrait aboutir à la sortie suivante:

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

Consultez Microsoft.VisualBasic.FileIO.TextFieldParser pour plus d'informations.

Vous devez ajouter une référence à Microsoft.VisualBasicdans l'onglet Ajouter des références .NET.

Tim
la source
9
Mec, merci beaucoup pour cette solution, j'ai environ 500K + lignes de données CSV que je dois charger dans une table et il est chargé avec des virgules contenues à l'intérieur de guillemets. Je vous dois une boisson pour adulte de votre choix si jamais nos chemins se croisent.
Mark Kram
@tim j'ai utilisé ceci et remarque qu'il saute tous les numéros de ligne pairs, ne traitant que les numéros de ligne impairs dans un fichier de 1050 lignes. des idées?
Smith le
@Smith - Sans voir votre code ou votre exemple d'entrée, je n'ai aucune idée. Je suggère de poster une nouvelle question. Peut-être que le fichier manque un retour chariot ou un autre marqueur de fin de ligne sur les lignes paires?
Tim
Je ne connaissais même pas cette bibliothèque avant de voir cela - merci! Si quelqu'un d'autre veut un exemple qui analyse un fichier CSV entier, voir cette réponse SO: stackoverflow.com/a/3508572/3105807
Amy Barrett
2
Pouvons-nous lyncher Microsoft pour ne pas avoir fourni un constructeur qui prend une chaîne, nous devons donc sauter à travers le cercle de la conversion en un flux? Sinon, bonne réponse.
Loren Pechtel
43

Il est tellement tard mais cela peut être utile pour quelqu'un. Nous pouvons utiliser RegEx comme ci-dessous.

Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = CSVParser.Split(Test);
Husen
la source
4
C'est parfait. Je préfère l'utiliser plutôt que d'importer une toute autre bibliothèque. Bravo.
TheGeekYouNeed
1
Correspond à asdf, "", "as ,\" df ",
Cette solution ne fonctionne pas correctement - elle ne tient pas compte des marques de discours, ce qui signifie qu'il y aura beaucoup de marques de parole à des endroits incorrects pendant la lecture.
AidanH
Que faire si le guillemet de fin manque dans une ligne: asd, "", "as, \" df "," asd asd "," as
MarmiK
1
Cela a fonctionné pour moi et a représenté les marques de discours entre guillemets. 30 millions de lignes d'entre eux. Très bon et une quantité minimale de code.
GBGOLC
4

Je vois que si vous collez du texte délimité csv dans Excel et faites un "Text to Columns", il vous demande un "text qualifier". Il est défini par défaut sur un guillemet double afin qu'il traite le texte entre guillemets comme littéral. J'imagine qu'Excel implémente cela en allant un caractère à la fois, s'il rencontre un "qualificatif de texte", il continue d'aller au "qualificatif" suivant. Vous pouvez probablement l'implémenter vous-même avec une boucle for et un booléen pour indiquer si vous êtes à l'intérieur d'un texte littéral.

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
la source
3

Utilisez une bibliothèque comme LumenWorks pour faire votre lecture CSV. Il traitera les champs avec des guillemets et sera probablement dans l'ensemble plus robuste que votre solution personnalisée en raison de son existence depuis longtemps.

Adam Lear
la source
2

Il est difficile d'analyser les fichiers .csv lorsque le fichier .csv peut être soit des chaînes séparées par des virgules, soit des chaînes entre guillemets ou une combinaison chaotique des deux. La solution que j'ai trouvée permet l'une des trois possibilités.

J'ai créé une méthode, ParseCsvRow () qui renvoie un tableau à partir d'une chaîne csv. Je traite d'abord les guillemets doubles dans la chaîne en divisant la chaîne sur des guillemets doubles dans un tableau appelé quotesArray. Les fichiers .csv de chaîne entre guillemets ne sont valides que s'il existe un nombre pair de guillemets doubles. Les guillemets doubles dans une valeur de colonne doivent être remplacés par une paire de guillemets doubles (c'est l'approche d'Excel). Tant que le fichier .csv répond à ces exigences, vous pouvez vous attendre à ce que les virgules du délimiteur apparaissent uniquement en dehors des paires de guillemets doubles. Les virgules à l'intérieur des paires de guillemets doubles font partie de la valeur de la colonne et doivent être ignorées lors de la division du .csv en un tableau.

Ma méthode testera les virgules en dehors des paires de guillemets doubles en regardant uniquement les index pairs de quotesArray. Il supprime également les guillemets doubles du début et de la fin des valeurs de colonne.

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

Un inconvénient de mon approche est la façon dont je remplace temporairement les virgules de délimitation par un caractère unicode obscur. Ce caractère doit être si obscur qu'il n'apparaîtra jamais dans votre fichier .csv. Vous voudrez peut-être mettre plus de manipulation autour de cela.

Jason Williams
la source
1

J'ai eu un problème avec un CSV contenant des champs avec un caractère de citation, donc en utilisant TextFieldParser, j'ai trouvé ce qui suit:

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

Un StreamReader est toujours utilisé pour lire le CSV ligne par ligne, comme suit:

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

Avec Cinchoo ETL - une bibliothèque open source, il peut gérer automatiquement les valeurs des colonnes contenant des séparateurs.

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

Production:

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

Pour plus d'informations, veuillez consulter l'article codeproject.

J'espère que ça aide.

RajN
la source