web-dev-qa-db-fra.com

Traitement des virgules dans un fichier CSV

Je cherche des suggestions sur la façon de gérer un fichier csv en cours de création, puis téléchargé par nos clients, et qui peut comporter une virgule dans une valeur, comme un nom de société.

Certaines des idées que nous examinons sont les suivantes: identificateurs cités (valeur "," valeurs "," etc.) ou utilisation de | au lieu d'une virgule. Le plus gros problème est que nous devons simplifier les choses, sinon le client ne le fera pas. 

432
Bob The Janitor

Comme d'autres l'ont dit, vous devez échapper aux valeurs qui incluent des guillemets. Voici un petit lecteur CSV en C qui prend en charge les valeurs entre guillemets, y compris les guillemets et les retours à la ligne.

À propos, il s'agit d'un code testé par unité. Je l’affiche maintenant parce que cette question semble être très fréquente et que d’autres ne voudront peut-être pas une bibliothèque entière si un simple support CSV suffit.

Vous pouvez l'utiliser comme suit:

using System;
public class test
{
    public static void Main()
    {
        using ( CsvReader reader = new CsvReader( "data.csv" ) )
        {
            foreach( string[] values in reader.RowEnumerator )
            {
                Console.WriteLine( "Row {0} has {1} values.", reader.RowIndex, values.Length );
            }
        }
        Console.ReadLine();
    }
}

Voici les cours. Notez que vous pouvez également utiliser la fonction Csv.Escape pour écrire un fichier CSV valide.

using System.IO;
using System.Text.RegularExpressions;

public sealed class CsvReader : System.IDisposable
{
    public CsvReader( string fileName ) : this( new FileStream( fileName, FileMode.Open, FileAccess.Read ) )
    {
    }

    public CsvReader( Stream stream )
    {
        __reader = new StreamReader( stream );
    }

    public System.Collections.IEnumerable RowEnumerator
    {
        get {
            if ( null == __reader )
                throw new System.ApplicationException( "I can't start reading without CSV input." );

            __rowno = 0;
            string sLine;
            string sNextLine;

            while ( null != ( sLine = __reader.ReadLine() ) )
            {
                while ( rexRunOnLine.IsMatch( sLine ) && null != ( sNextLine = __reader.ReadLine() ) )
                    sLine += "\n" + sNextLine;

                __rowno++;
                string[] values = rexCsvSplitter.Split( sLine );

                for ( int i = 0; i < values.Length; i++ )
                    values[i] = Csv.Unescape( values[i] );

                yield return values;
            }

            __reader.Close();
        }
    }

    public long RowIndex { get { return __rowno; } }

    public void Dispose()
    {
        if ( null != __reader ) __reader.Dispose();
    }

    //============================================


    private long __rowno = 0;
    private TextReader __reader;
    private static Regex rexCsvSplitter = new Regex( @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" );
    private static Regex rexRunOnLine = new Regex( @"^[^""]*(?:""[^""]*""[^""]*)*""[^""]*$" );
}

public static class Csv
{
    public static string Escape( string s )
    {
        if ( s.Contains( QUOTE ) )
            s = s.Replace( QUOTE, ESCAPED_QUOTE );

        if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 )
            s = QUOTE + s + QUOTE;

        return s;
    }

    public static string Unescape( string s )
    {
        if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) )
        {
            s = s.Substring( 1, s.Length - 2 );

            if ( s.Contains( ESCAPED_QUOTE ) )
                s = s.Replace( ESCAPED_QUOTE, QUOTE );
        }

        return s;
    }


    private const string QUOTE = "\"";
    private const string ESCAPED_QUOTE = "\"\"";
    private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
}
212
harpo

Pour 2017, CSV est entièrement spécifié - RFC 4180.

C'est une spécification très courante, et est complètement couverte par de nombreuses bibliothèques ( exemple ).

Utilisez simplement n’importe quelle bibliothèque csv facilement disponible - c’est-à-dire RFC 4180.


Il existe en fait une spécification pour le format CSV et comment gérer les virgules: 

Les champs contenant des sauts de ligne (CRLF), des guillemets doubles et des virgules doivent être placés entre guillemets.

http://tools.ietf.org/html/rfc4180

Donc, pour avoir les valeurs foo et bar,baz, procédez comme suit:

foo,"bar,baz"

Une autre exigence importante à prendre en compte (également de la spécification):

Si des guillemets doubles sont utilisés pour entourer des champs, un guillemet double apparaissant à l'intérieur d'un champ doit être échappé en le précédant de une autre double citation. Par exemple:

"aaa","b""bb","ccc"
375
Corey Trager

Le format CSV utilise des virgules pour séparer les valeurs. Les valeurs contenant des retours à la ligne, des sauts de ligne, des virgules ou des guillemets doubles sont entourées de guillemets doubles. Les valeurs qui contiennent des guillemets doubles sont entre guillemets et chaque guillemet littéral est précédé d'un guillemet précédant immédiatement: Par exemple, les 3 valeurs:

test
list, of, items
"go" he said

serait codé comme:

test
"list, of, items"
"""go"" he said"

N'importe quel champ peut être cité, mais uniquement les champs contenant des virgules, CR/NL ou des guillemets doit être cité.

Il n’existe pas de véritable standard pour le format CSV, mais presque toutes les applications respectent les conventions décrites dans la documentation ici . Le RFC mentionné ailleurs n'est pas un standard pour CSV, c'est un RFC pour utiliser le CSV dans MIME et contient des limitations non conventionnelles et inutiles qui le rendent inutile en dehors de MIME.

Le fait que de nombreux modules CSV puissent être encodés dans un seul champ est un piège qui empêche de supposer que chaque ligne est un enregistrement séparé. Vous devez également ne pas autoriser de nouvelles lignes dans votre données ou être prêt à gérer cela.

71
Robert Gamble

Mettez des guillemets autour des chaînes. C’est généralement ce que fait Excel .

Ala Eli,

vous échappez à une double citation en tant que deux double citation. Par exemple. "test1", "foo" "bar", "test2"

38
Joe Phillips

Vous pouvez mettre des guillemets autour des champs. Je n'aime pas cette approche, car elle ajoute un autre caractère spécial (la double citation). Il suffit de définir un caractère d'échappement (généralement une barre oblique inverse) et de l'utiliser chaque fois que vous avez besoin d'échapper à quelque chose:

données, plus de données, plus de données \, même, encore plus

Vous n'avez pas à essayer de faire correspondre les guillemets et vous avez moins d'exceptions à analyser. Cela simplifie également votre code.

8
Adam Jaskiewicz

Il existe une bibliothèque disponible via nuget pour traiter à peu près n'importe quel CSV bien formé (.net) - CsvHelper

Exemple pour mapper à une classe:

var csv = new CsvReader( textReader );
var records = csv.GetRecords<MyClass>();

Exemple pour lire des champs individuels:

var csv = new CsvReader( textReader );
while( csv.Read() )
{
    var intField = csv.GetField<int>( 0 );
    var stringField = csv.GetField<string>( 1 );
    var boolField = csv.GetField<bool>( "HeaderName" );
}

Laisser le client contrôler le format de fichier:
, est le délimiteur de champ standard, " est la valeur standard utilisée pour échapper aux champs contenant un délimiteur, une citation ou une fin de ligne.

Pour utiliser (par exemple) # pour les champs et ' pour un échappement:

var csv = new CsvReader( textReader );
csv.Configuration.Delimiter = "#";
csv.Configuration.Quote = ''';
// read the file however meets your needs

Plus de documentation

6
NikolaiDante

Si vous êtes sur un * nix-system , accédez à sed et il peut y avoir une ou plusieurs virgules indésirables uniquement dans un champ spécifique de votre CSV, vous pouvez utiliser la ligne suivante pour les inclure dans " comme RFC4180 section 2 propose:

sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile

Selon le champ dans lequel peuvent se trouver les virgules indésirables, vous devez modifier/étendre les groupes de capture de l'expression rationnelle (et la substitution).
L’exemple ci-dessus comprendra le quatrième champ (sur six) entre guillemets.

enter image description here

En combinaison avec l'option --in-place- , vous pouvez appliquer ces modifications directement au fichier.

Afin de "construire" la bonne expression rationnelle, il y a un principe simple à suivre:

  1. Pour chaque champ de votre fichier CSV qui vient before le champ avec la virgule indésirable, vous écrivez un [^,]*, et vous les mettez tous ensemble dans un groupe de capture.
  2. Pour le champ contenant la ou les virgules indésirables, vous écrivez (.*).
  3. Pour chaque champ after le champ avec la ou les virgules non désirées, vous écrivez un ,.* et vous les mettez tous ensemble dans un groupe de capture.

Voici un bref aperçu des différentes expressions rationnelles possibles/substitutions en fonction du champ spécifique. S'il n'est pas donné, la substitution est \1"\2"\3.

([^,]*)(,.*)                     #first field, regex
"\1"\2                           #first field, substitution

(.*,)([^,]*)                     #last field, regex
\1"\2"                           #last field, substitution


([^,]*,)(.*)(,.*,.*,.*)          #second field (out of five fields)
([^,]*,[^,]*,)(.*)(,.*)          #third field (out of four fields)
([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*) #fourth field (out of six fields)

Si vous souhaitez supprimer les virgules indésirables avec sed au lieu de les entourer de guillemets, reportez-vous à cette réponse .

4
Basti M

Ajoutez une référence à Microsoft.VisualBasic (oui, il indique VisualBasic mais cela fonctionne aussi bien en C # - rappelez-vous qu’à la fin, il ne s’agit que d’IL). 

Utilisez la classe Microsoft.VisualBasic.FileIO.TextFieldParser pour analyser le fichier CSV. Voici l'exemple de code:

 Dim parser As TextFieldParser = New TextFieldParser("C:\mar0112.csv")
 parser.TextFieldType = FieldType.Delimited
 parser.SetDelimiters(",")      

   While Not parser.EndOfData         
      'Processing row             
      Dim fields() As String = parser.ReadFields         
      For Each field As String In fields             
         'TODO: Process field                   

      Next      
      parser.Close()
   End While 
3
mvilaskumar

Vous pouvez utiliser des "délimiteurs" alternatifs, comme ";" ou "|" mais le plus simple peut être juste de citer ce qui est supporté par la plupart des bibliothèques CSV (décentes) et des feuilles de calcul les plus décentes.

Pour plus d'informations sur les délimiteurs CSV et une spécification pour un format standard décrivant les délimiteurs et les citations, voir cette page Web

3
Rufus Pollock

En Europe, nous avons ce problème doit plus tôt que cette question. En Europe, nous utilisons tous une virgule pour un point décimal. Voir ces chiffres ci-dessous:

| American      | Europe        |
| ------------- | ------------- |
| 0.5           | 0,5           |
| 3.14159265359 | 3,14159265359 |
| 17.54         | 17,54         |
| 175,186.15    | 175.186,15    |

Il n'est donc pas possible d'utiliser le séparateur de virgule pour les fichiers CSV. Pour cette raison, les fichiers CSV en Europe sont séparés par un point-virgule (;)

Des programmes tels que Microsoft Excel peuvent lire les fichiers avec un point-virgule et il est possible de passer d'un séparateur à l'autre. Vous pouvez même utiliser une tabulation (\t) comme séparateur. Voir cette réponse de Supper User .

2
H. Pauwelyn

Si vous avez envie de réinventer la roue, voici ce qui peut vous convenir:

public static IEnumerable<string> SplitCSV(string line)
{
    var s = new StringBuilder();
    bool escaped = false, inQuotes = false;
    foreach (char c in line)
    {
        if (c == ',' && !inQuotes)
        {
            yield return s.ToString();
            s.Clear();
        }
        else if (c == '\\' && !escaped)
        {
            escaped = true;
        }
        else if (c == '"' && !escaped)
        {
            inQuotes = !inQuotes;
        }
        else
        {
            escaped = false;
            s.Append(c);
        }
    }
    yield return s.ToString();
}
2
Neil

Si vous êtes intéressé par un exercice plus instructif sur la façon d'analyser les fichiers en général (en utilisant CSV par exemple), vous pouvez consulter cet article de Julian Bucknall. J'aime l'article parce qu'il décompose les choses en problèmes beaucoup moins importants et beaucoup moins insurmontables. Vous commencez par créer une grammaire, et une fois que vous avez une bonne grammaire, il est relativement facile et méthodique de convertir cette grammaire en code.

L'article utilise C # et un lien en bas permet de télécharger le code.

1
Phil

En règle générale, je code URL les champs qui peuvent avoir des virgules ou des caractères spéciaux. Et puis décodez-le quand il est utilisé/affiché sur n’importe quel support visuel.

(les virgules deviennent% 2C)

Chaque langue devrait avoir des méthodes pour encoder et décoder les chaînes d'URL.

par exemple, en Java

URLEncoder.encode(myString,"UTF-8"); //to encode
URLDecoder.decode(myEncodedstring, "UTF-8"); //to decode

Je sais que c'est une solution très générale et que cela pourrait ne pas être idéal pour une situation où l'utilisateur veut voir le contenu du fichier csv manuellement.

0
hariszhr

Comme il s’agit de pratiques générales, commençons par les règles du pouce:

  1. N'utilisez pas CSV, utilisez XML avec une bibliothèque pour lire et écrire le fichier XML à la place.

  2. Si vous devez utiliser CSV. Faites-le correctement et utilisez une bibliothèque libre pour analyser et stocker les fichiers CSV.

Pour justifier 1), la plupart des analyseurs au format CSV ne sont pas conscients de l’encodage. Par conséquent, si vous n’utilisez pas l’US-ASCII, vous posez des problèmes ..___ Par exemple, Excel 2002 stocke le fichier CSV dans l’encodage local sans aucune remarque sur l’encodage. . Le standard CSV n'est pas largement adopté: (. Par contre, le standard xml est bien adopté et gère très bien les encodages.

Pour justifier 2), il existe des tonnes d'analyseurs syntaxiques CSV pour presque toutes les langues, il n'est donc pas nécessaire de réinventer la roue, même si les solutions semblent assez simples.

Pour en nommer quelques uns:

  • pour l'utilisation de python construit dans csv module

  • pour Perl vérifier CPAN et Texte :: CSV

  • pour php utiliser les fonctions fgetcsv/fputcsv

  • pour Java check SuperCVS bibliothèque

En réalité, il n'est pas nécessaire de l'implémenter à la main si vous n'allez pas l'analyser sur un périphérique intégré.

0
Piotr Czapla

La solution la plus simple que j'ai trouvée est celle utilisée par LibreOffice:

  1. Remplacer tous les " littéraux par
  2. Mettez des guillemets autour de votre chaîne

Vous pouvez également utiliser celui utilisé par Excel:

  1. Remplacer tous les " littéraux par ""
  2. Mettez des guillemets autour de votre chaîne

Notez que d'autres personnes ont recommandé de ne faire que l'étape 2 ci-dessus, mais cela ne fonctionne pas avec les lignes où un " est suivi d'un ,, comme dans un CSV où vous voulez avoir une seule colonne avec la chaîne hello",world, comme le CSV le lirait. :

"hello",world"

Ce qui est interprété comme une ligne avec deux colonnes: hello et world"

0
MondKin
    public static IEnumerable<string> LineSplitter(this string line, char 
         separator, char skip = '"')
    {
        var fieldStart = 0;
        for (var i = 0; i < line.Length; i++)
        {
            if (line[i] == separator)
            {
                yield return line.Substring(fieldStart, i - fieldStart);
                fieldStart = i + 1;
            }
            else if (i == line.Length - 1)
            {
                yield return line.Substring(fieldStart, i - fieldStart + 1);
                fieldStart = i + 1;
            }

            if (line[i] == '"')
                for (i++; i < line.Length && line[i] != skip; i++) { }
        }

        if (line[line.Length - 1] == separator)
        {
            yield return string.Empty;
        }
    }
0
Rajat26

Vous pouvez lire le fichier csv comme ceci.

cela utilise les scissions et prend soin des espaces.

ArrayList List = new ArrayList();
static ServerSocket Server;
static Socket socket;
static ArrayList<Object> list = new ArrayList<Object>();


public static void ReadFromXcel() throws FileNotFoundException
{   
    File f = new File("Book.csv");
    Scanner in = new Scanner(f);
    int count  =0;
    String[] date;
    String[] name;
    String[] Temp = new String[10];
    String[] Temp2 = new String[10];
    String[] numbers;
    ArrayList<String[]> List = new ArrayList<String[]>();
    HashMap m = new HashMap();

         in.nextLine();
         date = in.nextLine().split(",");
         name = in.nextLine().split(",");
         numbers = in.nextLine().split(",");
         while(in.hasNext())
         {
             String[] one = in.nextLine().split(",");
             List.add(one);
         }
         int xount = 0;
         //Making sure the lines don't start with a blank
         for(int y = 0; y<= date.length-1; y++)
         {
             if(!date[y].equals(""))
             {   
                 Temp[xount] = date[y];
                 Temp2[xount] = name[y];
                 xount++;
             }
         }

         date = Temp;
         name =Temp2;
         int counter = 0;
         while(counter < List.size())
         {
             String[] list = List.get(counter);
             String sNo = list[0];
             String Surname = list[1];
             String Name = list[2];
             for(int x = 3; x < list.length; x++)
             {           
                 m.put(numbers[x], list[x]);
             }
            Object newOne = new newOne(sNo, Name, Surname, m, false);
             StudentList.add(s);
             System.out.println(s.sNo);
             counter++;
         }
0
Eric

J'ai utilisé la bibliothèque papaParse pour analyser le fichier CSV et disposer des paires clé-valeur (clé/en-tête/première ligne du fichier valeur CSV).

voici l'exemple que j'utilise:

https://codesandbox.io/embed/llqmrp96pm

il contient un fichier dummy.csv pour que la démo de l'analyse syntaxique CSV soit utilisée.

Je l'ai utilisé dans reactJS, bien qu'il soit facile et simple de le répliquer dans une application écrite avec n'importe quelle langue.

0
parag patel

J'ai utilisé la bibliothèque Csvreader, mais en l'utilisant, j'ai obtenu des données en décomposant la virgule (,) dans la valeur de la colonne.

Donc, si vous voulez insérer des données de fichier CSV contenant une virgule (,) dans la plupart des valeurs de colonnes, vous pouvez utiliser la fonction ci-dessous . Author link => https://Gist.github.com/jaywilliams/385876

function csv_to_array($filename='', $delimiter=',')
{
    if(!file_exists($filename) || !is_readable($filename))
        return FALSE;

    $header = NULL;
    $data = array();
    if (($handle = fopen($filename, 'r')) !== FALSE)
    {
        while (($row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
        {
            if(!$header)
                $header = $row;
            else
                $data[] = array_combine($header, $row);
        }
        fclose($handle);
    }
    return $data;
}
0
VirenPanchal

Premièrement, demandons-nous: "Pourquoi ressentons-nous le besoin de traiter les virgules différemment pour les fichiers CSV?"

Pour moi, la réponse est: "Parce que lorsque j'exporte des données dans un fichier CSV, les virgules d'un champ disparaissent et mon champ est séparé en plusieurs champs dans lesquels les virgules apparaissent dans les données d'origine." (C'est parce que la virgule est le caractère séparateur de champ CSV.)

Selon votre situation, les points-virgules peuvent également être utilisés comme séparateurs de champ CSV.

Compte tenu de mes besoins, je peux utiliser un caractère, par exemple un guillemet simple à faible 9, qui ressemble à une virgule.

Alors, voici comment vous pouvez le faire dans Go:

// Replace special CSV characters with single low-9 quotation mark
func Scrub(a interface{}) string {
    s := fmt.Sprint(a)
    s = strings.Replace(s, ",", "‚", -1)
    s = strings.Replace(s, ";", "‚", -1)
    return s
}

Le deuxième caractère dans la fonction Remplacer est une décimale 8218.

Sachez que si vous avez des clients pouvant utiliser des lecteurs de texte ASCII uniquement, ce caractère décima 8218 ne ressemblera pas à une virgule. Si tel est votre cas, alors je vous recommande de entourer le champ avec la virgule (ou le point-virgule) avec des guillemets doubles conformément à la RFC 4128: https://tools.ietf.org/html/rfc4180

0
l3x

Je pense que la solution la plus simple à ce problème consiste à faire en sorte que le client ouvre le fichier csv dans Excel, puis ctrl + r pour remplacer toutes les virgules par l’identificateur de votre choix. Ceci est très facile pour le client et nécessite seulement un changement de code pour lire le délimiteur de votre choix.

0
jamesdeath123

Je le fais généralement dans mes routines d'analyse de fichiers CSV. Supposons que la variable 'ligne' soit une ligne dans un fichier CSV et que les valeurs de toutes les colonnes soient entre guillemets. Après l'exécution des deux lignes ci-dessous, vous obtiendrez des colonnes CSV dans la collection 'values'.

// The below two lines will split the columns as well as trim the DBOULE QUOTES around values but NOT within them
    string trimmedLine = line.Trim(new char[] { '\"' });
    List<string> values = trimmedLine.Split(new string[] { "\",\"" }, StringSplitOptions.None).ToList();
0
user1451111