Pour faciliter l’analyse de fichiers .csv
, j’ai écrit un petit ensemble de classes (loin d’être parfaites, mais qui ont le mérite de plutôt bien fonctionner pour mon utilisation).
Cet analyseur ne traite cependant pas les cas où un délimiteur se trouverait perdu au beau milieu d’un champ (ce qui est quand même la base, je sais bien), il faudrait donc améliorer la partie lecture du fichier pour qu’elle retourne un tableau de chaînes de caractères et ce sera bon (genre ici ou là).
Les interfaces de construction #
L’interface IBuilder
constitue juste l’interface dont dépendent les classes de représentation pour chaque type d’enregistrements. Si un fichier représente des personnes, on définira ensuite une classe People
qui implémente l’interface ci-dessous, et en particulier la méthode Build(string[] content);
, qui spécifiera comment chaque ligne devra être traitée.
public interface IBuilder<T>
{
T Build(string[] content);
}
public class BuilderException : Exception
{
public BuilderException(string message) : base(message) { }
}
Ensuite, on a une interface IManager
, pour définir la manière dont doit être géré l’ensemble des enregistrements d’un même type. Plusieurs lignes du fichier lu en entrée peuvent par exemple représenter une et une seule personne (et plusieurs de ses contrats, par exemple).
Ceci permet d’avoir un gestionnaire qui va stocker chaque personne dans une clé, et qui renverra la liste des éléments connus au travers de la méthode Items()
.
public interface IManager<T> where T : new()
{
/// <summary>
/// Ajoute un élément à la liste interne.
/// </summary>
/// <param name="item">L'élément à ajouter.</param>
void Add(T item);
/// <summary>
/// Retourne tous les éléments de la liste.
/// </summary>
/// <returns>Une instance de liste générique de type T.</returns>
List<T> Items();
}
Lecture d’un fichier #
La méthode Read()
s’occupe de la lecture du fichier passé en paramètre (plutôt un tableau contenant les lignes, en fait) et retourne la liste des éléments de type T
au travers du gestionnaire (implémentant l’interface IManager
), s’il existe.
public List<T> Read<T>(
string[] lines,
char[] delimiters = null,
bool processFirstLine = false,
Managers.IManager<T> manager = null
) where T : Builders.IBuilder<T>, new() {
if (delimiters == null || delimiters.Length == 0)
delimiters = new char[2] { ';', '\t' };
List<T> list = new List<T>();
for (int i = 0; i < lines.Count(); i++)
{
if (!processFirstLine && i == 0) // avoid first line, as it contains titles
continue;
string line = lines[i];
try
{
string[] array = line.Split(delimiters, StringSplitOptions.None);
// checks at least one item is not empty or white spaces
if (array.All(c => String.IsNullOrWhiteSpace(c)))
continue;
// init element with default ctor
T elem = new T();
// call Builder method with string array
elem.Build(array);
if (manager != null)
{
manager.Add(elem);
}
else
{
// add item to returned list
list.Add(elem);
}
}
catch (Builders.BuilderException buildex)
{
System.Diagnostics.Debug.WriteLine(buildex.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
if (manager != null)
return manager.Items();
return list;
}
A revoir #
Les parties à revoir seraient:
- Le
split
sur la ligne, qui n’est franchement pas top, - La gestion des exceptions, puisque dans l’exemple ci-dessus, on ne log strictement rien.
- La gestion de la première ligne et des intitulés de colonnes, dans la mesure où on ne traite pas la première ligne, alors qu’elle contient sans doute toutes les informations nécessaires pour gérer correctement un dictionnaire, et fiabiliser les indices de position des colonnes et différentes informations - ce qu’un
DictReader
fait parfaitement en Python.