DLL et CSV

Signaler
Messages postés
18
Date d'inscription
mercredi 18 mars 2020
Statut
Membre
Dernière intervention
24 février 2021
-
Messages postés
15607
Date d'inscription
mardi 11 mars 2003
Statut
Non membre
Dernière intervention
24 février 2021
-
Bonjour à tous !

Je suis entrain d'écrire une dll en C# pour faciliter l'utilisation de ce type de fichier. (Je ne peux pas utiliser une dll déjà faite)

Pour le moment je récupère les données de mon csv dans une list et j'essaye ensuite de récupérer une ligne de ma list à l'aide d'un match d'un string que je mets en paramètre.
A la base je voulais récupérer un index que je traiterais dans la fonction GetValue() mais si quelqu'un a une meilleure idée ...

Mes problèmes sont:
- Mon programme récupère seulement une cellule sur deux (et je ne vois pas pourquoi)
- Le match ne fonctionne pas, il passe à true pour toutes les cellules (ou presque) malgrès le regex
- Je souhaiterais enlever "nbColumn" et récupérer les colonnes jusqu'à ce que'il n'y est plus de valeurs ( essayé avec reader.EndOfData mais ça semble uniquement fonctionner avec les lignes )

Ci dessous le code en question :

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.VisualBasic.FileIO;

namespace CSV
{
    public class CCSV
    {
        public List<List<string>> Tab;

        //Constructeur

        public void Load(string path, string separator)
        {
            Tab = new List<List<string>>(GetCol(path , separator));
        }

        private List<List<string>> GetCol(string path, string separator)   //Recupère le tableau dans une liste
        {

            List<List<string>> RegExValues = new List<List<string>>();
            int nbColumn = 27; //Faire en sorte que nbColumn s'arrete quand il n'y a plus de valeur dans les colonnes!!
            int i;
            string indexLine = "0";
            int count = 0;
            try
            {
               
                TextFieldParser reader = new TextFieldParser(path);
                reader.TextFieldType = FieldType.Delimited;
                reader.SetDelimiters(separator);
               

                while (!reader.EndOfData)
                {
                   
                        //Process row
                        List<string> AllDataLine = new List<string>(reader.ReadFields());
                        List<string> Line = new List<string>();
                        for ( i = 0; i < nbColumn; i++)
                        {
                            Line.Add(AllDataLine[i]);
                            i++;
                        }


                    if ( AllDataLine[0] != "LigneIndesirable") //Evite l'ajout de la ligne LigneIndésirable + ajoute une cellule "index"
                    {
                        indexLine = count.ToString();
                        Line.Add(indexLine);
                        count++;
                        RegExValues.Add(Line);
                    }

                    else
                    {
                        reader.ReadFields();
                    }
                }
                reader.Close();

            }
            catch (Exception ex)
            {
                //Gérer les erreur avec loggers
            }
            return RegExValues;


        }

        public string GetValue(string MLFB, string ValueName)
        {
            string result = "";

            return result;
        }


        public int GetIndex(string value)
        {
            int index = 0;
            int result = 0;

            foreach (List<string> line in Tab)
            {
                foreach (string col in line)
                {
                    Match match = Regex.Match(value, col); 
                    if (match.Success)
                    {
                        result = index;
                    }
                }
                index++;
            }

            return index;
        }

    }
}





Merci d'avance :)

4 réponses

Messages postés
15607
Date d'inscription
mardi 11 mars 2003
Statut
Non membre
Dernière intervention
24 février 2021
665
D'abord, parser un csv ça prend beaucoup moins de lignes que tu en as écrit.
Même si le nombre de lignes n'est pas un critère de qualité de code, écrire des trucs en trop c'est perdre du temps.

Soit ce csv

Nom;Prénom;Sexe;Date de naissance
Sors;Jean;M;01/01/2001
Zétofrais;Mélanie;F;02/02/2002


Et voilà pour le parser
            List<List<string>> lesLignes = (from l in File.ReadAllLines("test.csv")
                                             select l.Split(';').ToList()
                                             ).ToList();


Résultat


Ok, là ça n'exclut pas la ligne d'entêtes, pas de problèmes

Option 1, on part du principe qu'il y a toujours une ligne d'entêtes => on zappe la première ligne
            List<List<string>> lesLignes = (from l in File.ReadAllLines("test.csv").Skip(1)
                                             select l.Split(';').ToList()
                                             ).ToList();


Option 2, on est pas sûr que cette ligne soit systématiquement présente, mais quand elle est là elle correspond à une condition, pour l'exemple, je dis qu'elle commence par Nom;
            List<List<string>> lesLignes = (from l in File.ReadAllLines("test.csv")
                                            where !l.StartsWith("Nom;")
                                             select l.Split(';').ToList()
                                             ).ToList();


Option 3, tu ne sais pas à quoi correspond la ligne d'entête, mais dans les lignes valables, la dernière colonne contient une date
            List<List<string>> lesLignes = (from l in File.ReadAllLines("test.csv")
                                            let split = l.Split(';').ToList()
                                            where DateTime.TryParse(split.Last(), out DateTime d)
                                             select split
                                             ).ToList();


Et j'en passe....
Brefs tu peux imaginer tout ce qu'une simple requête Linq peut t'apporter.

Une fois que tu as ta liste de listes de strings. Pour rechercher une ligne dont un colonne correspond à un critère, ben Linq

            List<string> Annee2001 = (from l in lesLignes
                                      where l.Last().EndsWith("/2001")
                                      select l
                                    ).FirstOrDefault();


j'aurais pu mettre une regex aussi.
Ce qui donne



FirstOrDefaut est ici par fainéantise, j'ai forcé la requête à retourner la première occurence (s'il y en a plusieurs) ou une liste vide s'il n'y en a pas.
On peut facilement gérer plusieurs occurrences, par exemple avec une nouvelle liste de listes de string.

Mais en fait, tout ça ne sert à rien.
En l'état ton code considère qu'un csv c'est un fichier contenant des lignes qui contiennent des colonnes de texte. Et que C# est un langage qui ne traite que du texte.

C'est faux pour la très grande majorité des csv. A de rares exceptions près, un csv contient une donnée par ligne et cette donnée contient des champs.
Et c'est faux pour C#, car c'est un langage pensé pour manipuler des collections d'objets.

Soit donc l'object Contact que voilà
    class Contact
    {
        //Les propriétés qui correspondent aux champs du csv
        public string Nom { get; set; } 

        public string Prenom { get; set; }

        public DateTime DateNaissance { get; set; }

        public Sexe Sexe { get; set; }


        //d'autres propriétés et des méthodes utiles au déroulement du programme
        public string NomComplet { get { return string.Format("{0} {1}", Prenom, Nom); } }

        public int Age { get { return DateTime.Now.Year - DateNaissance.Year; } }

        public override string ToString()
        {
            return string.Format("{0}, {3} de  {1} an{2}", NomComplet, Age, Age > 1 ? "s" : "", Sexe == Sexe.Feminin ? "femme" : Sexe == Sexe.Masculin ? "homme" : "personne" ) ;
        }
    }

    public enum Sexe
    {
        Inconnu,
        Feminin,
        Masculin
    }
}


Comme tu le voies, parser un csv directement dans une collection dont l'objet est adapté à la donnée stockée ne prends que quelques lignes.
Faire des recherches personnalisée et performantes aussi.
Je traite tous les jours des fichiers textes de toutes sortes et ça va tellement vite d'adapter que je n'ai jamais ressenti le besoin d'une dll dédiée.

En plus, créer un code générique dans une dll pour parser n'importe quel csv en n'importe quel objet près définit, c'est probablement possible, mais pas super simple.
Il va falloir écrire des méthodes génériques dans lesquels on transmettrait l'objet à parser et la façon dont on le parse.


On peut le parser comme ça
            List<Contact> lesContacts = (from l in File.ReadAllLines("test.csv").Skip(1)
                                            let split = l.Split(';')
                                            select new Contact
                                            {
                                                 Nom = split[0],
                                                 Prenom = split[1],
                                                 Sexe = split[2] == "F" ? Sexe.Feminin : split[2] == "M" ? Sexe.Masculin : Sexe.Inconnu,
                                                 DateNaissance = DateTime.Parse(split[3])
                                            }
                                             ).ToList();

Ce qui donne


Et on peut faire des recherches sur des "vraies" données, par exmple

            Contact Annee2001 = (from c in lesContacts
                                      where c.DateNaissance.Year == 2001
                                      select c
                                    ).FirstOrDefault();

ou encore
            List<Contact> Majeurs = (from c in lesContacts
                                     where c.Age > 17
                                     select c
                                    ).ToList();


Qui donnent



Messages postés
18
Date d'inscription
mercredi 18 mars 2020
Statut
Membre
Dernière intervention
24 février 2021

Merci énormément pour ton retour très complet.
Effectivement beaucoup plus simple avec linq :D
Cependant je suis obligé de faire une dll , donc me gratter un peu la tête pour l'adapter à un maximum de fichier.
Sachant que un fois les données récupéré, je dois potentiellement retrouver une ligne spécifique à l'aide de la colonne "Donnée_RegEx" et également renvoyer une valeur en passant en paramètre laVariable + leNomDeLaColonne (exemple: GetValue("RegEx", "Temperature", "Lower_Tol");
Ma fonction va alors retrouver la variable "Temperature" , descendre jusqu'à Nominal_Value_1, récupérer le 1, chercher la colonne "Lower_Tol_1" et récupérer la valeur qui match entre la colonne "Lower_Tol_1" et la ligne de la RegEx.. Voilà plus ou moins mon but final



Je vais essayer d'écrire quelque chose dans la semaine, je publierais le résultat ici !
Merci encore,
Messages postés
15607
Date d'inscription
mardi 11 mars 2003
Statut
Non membre
Dernière intervention
24 février 2021
665
J’ai rien compris....

Cette capture, c’est le csv affiché dans Excel ?

Tu parles d’un 1 dans la colonne Nominal_Value_1 mais on ne voit que -5 et -0.5.

Enfin ça doit matcher entre la colonne regex et la colonne Lower_Tol_1 mais les 2 exemples que tu montres dans la colonne regex ne peut pas matcher avec un nombre, et on ne voit que des nombres dans la colonne Lower_Tol_.

Bref, le peu que je crois comprendre ne colle pas avec l’exemple que tu montres...
Messages postés
18
Date d'inscription
mercredi 18 mars 2020
Statut
Membre
Dernière intervention
24 février 2021

Oui c'est le CSV affiché dans un Excel (Plus facile pour expliquer)

Le 1 qui est récupéré est celui de Nominal_Value_1
Ce qui permettra de retrouver les colonnes Lower et Upper même si on change l'ordre

Ce n'est pas RegEx et une valeur qui doivent matcher mais :
- Je recherche une ligne spécifique avec le RegEx
- Dans cette ligne je suis à la recherche de " Lower_Tol_1" Mais dans le code je peux uniquement mettre le nom de la variable, ici "Temperature"
- Je dois pouvoir gérer l'inversion des colonnes donc j'utilise l'indice derrière chaque colonne (_1 ou _2 pour l'exemple)

Je ne sais pas si c'est plus clair ...
Messages postés
15607
Date d'inscription
mardi 11 mars 2003
Statut
Non membre
Dernière intervention
24 février 2021
665
Bonjour

Je n’ai pas le temps de suite pour regarder ton code en détail.
Mais tu ne connais pas linq?

Plus besoin de reader, de boucles, etc...
Messages postés
18
Date d'inscription
mercredi 18 mars 2020
Statut
Membre
Dernière intervention
24 février 2021

Non , malheureusement je ne connais pas linq..
Messages postés
15607
Date d'inscription
mardi 11 mars 2003
Statut
Non membre
Dernière intervention
24 février 2021
665
Maintenant je comprends le 1 mais pas Température.

Peux tu poster sur cjoint ou un service équivalent un fichier représentatif (sans données "confidentielles" bien sûr) et expliquer dans un message ce que tu veux mettre en données d'entrées, et ce que tu veux avoir en sortie.