Discussion: Faire un "CSVSplit()"

  1. #1
    Expert confirmé
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    février 2010
    Messages
    3 070
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : février 2010
    Messages : 3 070
    Points : 5 019
    Points
    5 019
    Billets dans le blog
    1

    Par défaut Faire un "CSVSplit()"

    Bonjour,

    Je dois créer un outil permettant de manipuler des fichiers CSV provenant de différents systèmes, donc avec des séparateurs différents.

    Le but de l'outil est de modifier les fichiers CSV afin de remplacer certains codes dans certaines colonnes par d'autres.

    La question que je me pose, c'est comment découper "proprement" et de manière "générique" les colonnes du fichier.

    En effet, mettons le fichier suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    1;TOTO;2.4
    2;TITI;5.3
    Ok : aucun problème, un simple Split(';') sur chaque ligne me permet de retrouver mes trois colonnes.

    Mais je peux aussi avoir :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    1;"TOTO;TATA";2.4
    2;TITI;5.3
    Si je fais un simple Split, la valeur TOTO;TATA va être éclatée en deux : "TOTO et TATA".
    Donc je peux essayer, avec une expression régulière par exemple de remplacer tous les ";" contenus entre "" par un autre caractère, mettons "¤", faire un split, puis remettre le ;.

    Mais je peux aussi avoir ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    1;"TOTO
    TATA";2.4
    2;TITI;5.3
    Là, c'est au moment du découpage des lignes que ça va merder : de la même manière, il faut que je sâche conserver les sauts de lignes à l'intérieur des ""...

    Sans oublier ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    1;"TOTO ""TATA""";2.4
    2;TITI;5.3
    Ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    1;"TOTO \"TATA\"";2.4
    2;TITI;5.3
    Là on a un peu de tout, avec des " à l'intérieur des "... Deux solution, soit les " sont échappés en les doublants, soit en les échappant avec le caractère "\"...

    Avez-vous en tête une solution "miracle" pour m'en sortir ? Un Split un peu plus intelligent qu'un simple éclatement avec un caractère tout bête ?
    On ne jouit bien que de ce qu’on partage.

  2. #2
    Membre chevronné

    Homme Profil pro
    Responsable déploiement (SCCM, AirWatch, AMP)
    Inscrit en
    juillet 2014
    Messages
    1 121
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Responsable déploiement (SCCM, AirWatch, AMP)
    Secteur : Transports

    Informations forums :
    Inscription : juillet 2014
    Messages : 1 121
    Points : 2 080
    Points
    2 080

    Par défaut

    1;"TOTO ""TATA""";2.4
    1;"TOTO \"TATA\"";2.4
    Comment doit être considéré le deuxième item ? Moi je dirais :
    TOTO ""TATA""
    TOTO \"TATA\"
    et non pas
    TOTO "TATA"
    TOTO "TATA"

  3. #3
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    juin 2007
    Messages
    396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : juin 2007
    Messages : 396
    Points : 612
    Points
    612

    Par défaut

    Tu devrais utiliser directement une lib de gestion de CSV, comme celle-ci (avec packages NuGet) ou celle-là (Google est ton ami). Si tu tiens à te faire ta propre syntaxe tu peux utiliser Sprache, présentée par Thomas Levesque, une lib qui permet de créer facilement des parsers.

  4. #4
    Expert confirmé
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    février 2010
    Messages
    3 070
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : février 2010
    Messages : 3 070
    Points : 5 019
    Points
    5 019
    Billets dans le blog
    1

    Par défaut

    Merci pour vos réponses.

    Pour répondre à ericlm128, dans l'idéal la fonction doit pouvoir gérer les deux cas, et choisir l'un ou l'autre des modes d'échappement en fonction d'un paramètre.

    Sinon, je vais regarder les lib CSV. C'est vrai que j'ai pas du tout le réflexe nuget...
    On ne jouit bien que de ce qu’on partage.

  5. #5
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    juin 2007
    Messages
    396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : juin 2007
    Messages : 396
    Points : 612
    Points
    612

    Par défaut

    Citation Envoyé par StringBuilder Voir le message
    Sinon, je vais regarder les lib CSV. C'est vrai que j'ai pas du tout le réflexe nuget...
    C'est typiquement le genre de chose relativement standard qui a été faite et refaite maintes fois alors pourquoi s'embêter à réinventer la roue

  6. #6
    Modérateur
    Avatar de DotNetMatt
    Homme Profil pro
    CTO
    Inscrit en
    février 2010
    Messages
    3 352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : CTO
    Secteur : Finance

    Informations forums :
    Inscription : février 2010
    Messages : 3 352
    Points : 8 855
    Points
    8 855
    Billets dans le blog
    3

    Par défaut

    Citation Envoyé par StringBuilder Voir le message
    Mais je peux aussi avoir ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    1;"TOTO
    TATA";2.4
    2;TITI;5.3
    Là, c'est au moment du découpage des lignes que ça va merder : de la même manière, il faut que je sâche conserver les sauts de lignes à l'intérieur des ""...
    Le RFC 4180 (Common Format and MIME Type for Comma-Separated Values (CSV) Files) le stipule clairement dans la section 2:
    1. Chaque enregistrement est sur une ligne distincte, delimitee par un retour a la ligne (CRLF). Par example :

    aaa,bbb,ccc CRLF
    zzz,yyy,xxx CRLF
    Donc pour ce scenario, ce n'est plus du CSV au sens du standard RFC. A mon avis tu vas devoir coder une solution maison pour gerer ce cas.
    Less Is More
    Pensez à utiliser les boutons , et les balises code
    Desole pour l'absence d'accents, clavier US oblige

  7. #7
    Expert confirmé
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    février 2010
    Messages
    3 070
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : février 2010
    Messages : 3 070
    Points : 5 019
    Points
    5 019
    Billets dans le blog
    1

    Par défaut

    En même temps, mise à part peut-être les américains, qui utilise le standard CSV au sens de la RFC ?

    Même Excel (c'est à dire l'outil de manipulation de données le plus utilisé au monde) ne respecte absolument pas le CSV : si t'es en locale française, il te génère un fichier avec délimiteur ";" et non "," afin de ne pas devoir rendre invariant les nombres.

    Le format CSV en tant que tel est bourré de lacunes, à commencer par celle de ne pas être en mesure d'encapsuler n'importe quelle valeur littérale.

    La plupart des outils qui lisent/produisent des fichiers "CSV" gèrent les sauts de ligne nativement.

    Sinon, pour CsvHelper, ça tombe bien, il a l'air de supporter les sauts de lignes :
    The value used to escape fields that contain a delimiter, quote, or line ending.

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    // Default value
    csv.Configuration.Quote = '"';
    En revanche, je ne vois aucune trace du paramétrage du "line ending".
    Ça c'est pas franchement cool, car un CSV produits sur un système X-like va utiliser "Cr" comme retour à la ligne, un Mac "LfCr" quant à Windows le bon vieux "CrLf"... Quant à certains outils ils permettent d'utiliser n'importe quel caractère, et là ça va être franchement compliqué...

    Idem, ils ne disent pas comment encoder/décoder les "Quote" à l'intérieur d'une valeur (doublage, échappement avec un \, remplacement par une séquence particulière, etc.)
    On ne jouit bien que de ce qu’on partage.

  8. #8
    Expert confirmé
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    février 2010
    Messages
    3 070
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : février 2010
    Messages : 3 070
    Points : 5 019
    Points
    5 019
    Billets dans le blog
    1

    Par défaut

    Bon, ben merci beaucoup !

    En quelques minutes j'ai un prototype fonctionnel de ce que je voulais faire !

    Juste le caractère de fin de ligne et la possibilité d'échapper les délimiteurs avec un \ qui ne sont pas pris en charge... Tant pis, on fera sans

    Fichier en entrée : (args[0])
    Code csv : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    1;2;toto;ORI1
    2;5;"titi
    tata";ORI1
    3;;gna;ORI4
    4;;"gnoki ""inc"".";ORI3

    Fichier de paramétrage : (args[1])
    Code xml : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    <?xml version="1.0"?>
    <transcodification>
    	<field id="0" name="id" output="raw"/>
    	<field id="3" name="code" output="transcode">
    		<code in="ORI1" out="DEST1"/>
    		<code in="ORI2" out="DEST2"/>
    		<code in="ORI3" out="DEST3"/>
    		<code in="ORI4" out="DEST4"/>
    	</field>
    	<field id="1" name="price" output="no"/>
    </transcodification>

    Le "programme" :
    Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
     
    using System;
    using System.IO;
    using System.Xml;
    using CsvHelper;
     
    namespace TestCSV
    {
        class Program
        {
            static void Main(string[] args)
            {
                XmlDocument dom = new XmlDocument();
                dom.Load(args[1]);
     
                TextReader tr = File.OpenText(args[0]);
                TextWriter tw = File.CreateText(args[0] + ".out");
     
                CsvReader reader = new CsvReader(tr);
                CsvWriter writer = new CsvWriter(tw);
     
                reader.Configuration.Delimiter = ";";
                reader.Configuration.Quote = '"';
                reader.Configuration.HasHeaderRecord = false;
     
                writer.Configuration.Delimiter = ";";
                writer.Configuration.Quote = '"';
                writer.Configuration.HasHeaderRecord = false;
     
                string ori;
                while (reader.Read())
                {
                    foreach (XmlNode nd in dom.SelectNodes("/transcodification/field"))
                    {
                        ori = reader.GetField(int.Parse(nd.Attributes["id"].Value));
                        switch (nd.Attributes["output"].Value)
                        {
                            case "no":
                                break;
                            case "raw":
                                writer.WriteField(ori);
                                break;
                            case "transcode":
                                writer.WriteField(nd.SelectSingleNode($"code[@in='{ori}']/@out").InnerText);
                                break;
                            default:
                                throw new NotImplementedException("Méthode d'output non supportée : '" + nd.Attributes["output"].Value + "'");
                        }
                    }
                    writer.NextRecord();
                }
                tr.Close();
                tw.Close();
                Console.WriteLine("terminé");
                Console.ReadKey(true);
            }
        }
    }

    Le résultat :
    Code csv : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    1;DEST1
    2;DEST1
    3;DEST4
    4;DEST3
    On ne jouit bien que de ce qu’on partage.

  9. #9
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    juin 2007
    Messages
    396
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : juin 2007
    Messages : 396
    Points : 612
    Points
    612

    Par défaut

    Citation Envoyé par StringBuilder Voir le message
    Juste le caractère de fin de ligne et la possibilité d'échapper les délimiteurs avec un \ qui ne sont pas pris en charge... Tant pis, on fera sans
    Tu dois pouvoir faire un échappement avec les guillemets, non ?

  10. #10
    Expert confirmé
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    février 2010
    Messages
    3 070
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : février 2010
    Messages : 3 070
    Points : 5 019
    Points
    5 019
    Billets dans le blog
    1

    Par défaut

    Non c'est pas ça.

    Je veux dire que certains outils génèrent des fichiers "CSV" (pas dans le sens RFC du terme) avec un caractère de séparation de record autre que le saut de ligne Windows.

    On peut avoir par exemple tous sur une seule ligne, avec les records séparés par un "|".

    A ce moment, la lib en question ne saura pas gérer.
    Dans mon cas ça ne devrait pas être trop gênant. Le seul cas que je vais avoir, vu que je m'interface avec des outils Linux, et notamment qu'on passe par un serveur hébergé sur un Linux c'est les "CrLf" remplacés par des "Cr" tous courts.
    Si dans ce cas la lib ne sait pas gérer, il me suffit de remplacer en amont tous les "Cr" non suivis d'un "Lf" par "CrLf", c'est pas bien compliqué. Juste domage que ce soit pas géré nativement par la lib qui accepte certaines libertés mais pas toutes.
    On ne jouit bien que de ce qu’on partage.

  11. #11
    Expert confirmé
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    février 2010
    Messages
    3 070
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : février 2010
    Messages : 3 070
    Points : 5 019
    Points
    5 019
    Billets dans le blog
    1

    Par défaut

    Bon, alors je me souvenais avoir eu une mauvaise expérience avec des NuGets... et c'est pour ça que c'est un truc que j'avais soigneusement mis de côté dans les oubliettes de mon cerveau.

    Et voilà que ça recommence !

    Sur mon PC perso, le code ci-dessus fonctionne.

    Sur mon PC du boulot, le même code (partagé sur un répertoire onedrive, donc c'est rigoureusement le même) ne fonctionne pas.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    writer.Configuration.Quote = '"';
    Ça me dit que l'attribut n'a pas de setter public.

    Sur le reader, en revanche pas de souci.

    Sur les deux PC c'est du Windows 10, Visual Studio 2017 Community Edition

    Le tout en dernières versions derniers patches, au détails près que le PC du boulot est en Windows Pro et que j'ai la dernière build insider (mais je vois pas franchement ce que ça pourrait changer à ma lib nuget).

    Quelqu'un peut me dire si chez lui aussi Configuration.Quote est readonly sur un CsvWriter ?


    -- Edit : Hmmmm j'ai trouvé ! Sur le PC du boulot j'avais coché que je voulais aussi les beta NuGet, car à l'époque j'avais bossé avec une API Google qui était buggée en release... Bah là c'est l'inverse, la beta de CsvHelper est bugée !
    On ne jouit bien que de ce qu’on partage.

+ Répondre à la discussion
Cette discussion est résolue.

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo