web-dev-qa-db-fra.com

Comment diviser csv dont les colonnes peuvent contenir,

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 fractionner 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)

// mettre à jour // basé sur C # Regex Split - virgules en dehors des guillemets

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

Utilisez le Microsoft.VisualBasic.FileIO.TextFieldParser classe. Cela gérera l’analyse d’un fichier délimité, TextReader ou Stream où certains champs sont entre guillemets et d’autres 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 générer le résultat suivant:

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

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

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

161
Tim

Il est trop 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);
22
Husen

Vous pouvez séparer toutes les virgules suivies d'un nombre pair de guillemets.

Vous voudriez aussi voir au specf pour le format CSV sur le traitement des virgules.

Lien utile: C# Regex Split - commas outside quotes

5
sgokhales

Je vois que si vous collez du texte délimité par csv dans Excel et effectuez un "Texte en colonnes", il vous demande un "qualificateur de texte". Par défaut, il utilise une citation double pour traiter le texte entre guillemets comme étant littéral. J'imagine qu'Excel l'implémente en exécutant un caractère à la fois. S'il rencontre un "qualificatif de texte", il continue à passer 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 dans 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();
}
4
Roly

Utilisez une bibliothèque telle que LumenWorks pour lire votre fichier CSV. Il gérera les champs contenant des guillemets et sera globalement plus robuste que votre solution personnalisée du fait de son existence depuis longtemps.

3
Adam Lear

Il est délicat d'analyser les fichiers .csv lorsque le fichier .csv peut être une chaîne séparée par des virgules, une chaîne séparée par des virgules ou une combinaison chaotique des deux. La solution que j'ai proposée ne permet aucune 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 avec les guillemets doubles dans la chaîne en scindant la chaîne sur des guillemets doubles dans un tableau appelé quotesArray. Les fichiers .csv de chaînes 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 de délimitation apparaissent uniquement en dehors des paires de guillemets doubles. Les virgules à l'intérieur de paires de guillemets font partie de la valeur de la colonne et doivent être ignorées lors de la scission du fichier .csv dans un tableau.

Ma méthode testera les virgules en dehors des paires de guillemets en regardant uniquement les index pairs de quotesArray. Il supprime également les guillemets des valeurs de début et de fin 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 des inconvénients de mon approche est la façon dont je remplace temporairement les virgules séparateurs par un caractère unicode obscur. Ce personnage doit être si obscur qu'il ne figurerait jamais dans votre fichier .csv. Vous voudrez peut-être vous en occuper davantage.

2
Jason Williams

J'ai eu un problème avec un fichier CSV contenant des champs avec un caractère de citation, alors, 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());
    }
  }
}

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

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

Avec Cinchoo ETL - une bibliothèque open source, elle peut gérer automatiquement les valeurs de 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());
}

Sortie:

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.

1
RajN