4.0 KiB
Title | Date | Slug | Tags |
---|---|---|---|
Un p'tit parser de fichiers en C# | 2015-10-01 | a-little-csharp-files-parser | dev, dotnet, csv, fichier, parser |
Pour faciliter l'analyse de fichiers .csv
, j'ai écrit un petit ensemble de classes (loin d'être parfait, mais qui a le mérite de plutôt bien fonctionner pour mon utilisation). Cet analyseur ne traite pas les cas où un délimiteur se trouve 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 crache un tableau de chaînes de caractères et ce sera bon (genre ici ou là).
Les interfaces de construction
L'interface IBuilder
est juste l'interface dont dépendent les classes de représentation des records. 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);
).
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 sont gérés les records. Plusieurs lignes du fichier lu peuvent par exemple représenter une et une seule personne (et plusieurs de ses contrats, par exemple). Cela 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();
}
Et finalement, la méthode Read()
, qui lit le fichier passé en paramètre (plutôt un tableau contenant les lignes, en fait) et qui 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() // implements a default ctor and IBuilder
{
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 line zero, 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;
}
Du coup, les parties à revoir seraient le split
sur la ligne, qui n'est franchement pas top, et la gestion des exceptions, puisque dans l'exemple ci-dessus, on ne log strictement rien.