Conversion du contenu d’un fichier CSV

Comment lire le contenu d'un fichier CSV (le point-virgule étant le séparateur) et le convertir sous une forme exploitable en JavaScript, en l'occurrence un tableau (une entrée par ligne) de tableaux (une entrée par colonne). Une cellule peut contenir une ou plusieurs valeurs (dans ce dernier cas, le contenu de la cellule est sur plusieurs lignes) et une valeur peut contenir le caractère d'échappement (le guillemet). Par exemple :
Contenu d'un fichier CSV à convertir

La solution

La solution s'appuie sur les expressions régulières pour découper le texte contenu dans le fichier CSV en fragments correspondant à chaque ligne, dans chaque ligne à chaque cellule, dans chaque cellule à chaque valeur.

Le code JavaScript

En JavaScript, la solution se traduit par le code suivant :
/*------------------------------------------------------------------------------
Retourne le texte correspondant au contenu d'un fichier CSV sous la forme d'un
tableau de tableaux reproduisant la structure de la feuille de calcul. Une
cellule de cette feuille de calcul peut contenir plusieurs valeurs (sur autant
de lignes). Une valeur peut contenir le caractère d'échappement guillemet.

ENTREE :
	nbCells	Nombre de colonnes de la feuille de calcul
	csv		Contenu du fichier CSV

SORTIE :
	Tableau (lignes) de tableaux (cellules)
------------------------------------------------------------------------------*/

function convertCSV (nbCells, csv) {
	var s, reRow, reCell, rowMatches, cellMatches, i, j, rows;

	s = "(";
	for (i = 0; i != (nbCells - 1); i ++)
		s += "[^;]*?;";
	s += "[^;]*)\\n";
	reRow = new RegExp (s, "g");
	s = "";
	for (i = 0; i != (nbCells - 1); i ++)
		s += "\"?([^;]*?)\"?;";
	s += "\"?([\\s\\S]*?)\"?$";
	reCell = new RegExp (s); // Pas globale !
	i = 0;
	rows = new Array ();
	while (true) {
		rowMatches = reRow.exec (csv);
		if (!rowMatches)
			break;
		cellMatches = reCell.exec (rowMatches[1].replace (/""/g, "\""));
		if (!cellMatches) {
			alert ("Error while parsing cells of row " + (i + 1));
			return (rows);
		}
		rows[i] = new Array ();
		for (j = 1; j != cellMatches.length; j ++)
			rows[i].push (cellMatches[j]);
		i ++;
	}
	return (rows);
}

L'exemple

Cliquez ici pour accéder à une page de test minimaliste. Vous pourrez visualiser le code et le récupérer pour travailler avec.

La logique

Une expression régulière est l'expression des règles qui doivent régir le comportement d'un automate qui parcourt du texte caractère par caractère. Elle se formule dans langage particulièrement abscons pour le profane, où l'auteur décrit des séquences de caractères à détecter (des motifs). Pas plus qu'il ne peut ignorer comment afficher un pixel à l'écran, un programmeur digne de ce nom ne peut ignorer comment traiter du texte avec une expression régulière.
Il n'est pas question de faire un cours sur les expressions régulières, mais simplement de donner le détail de celles qui sont utilisées dans le code :
  • Une expression régulière pour extraire une ligne :
    ([^;]*?;[^;]*?;[^;]*)\n
    
    Dans cette expression, le motif pour chaque cellule d'une ligne est :
    [^;]*?;
    
    Ce qui se lit comme "isoler une séquence de N (éventuellement 0) caractères différents du point-virgule jusqu'à rencontrer un point-virgule". Ce motif est répété jusqu'à la dernière cellule, où il doit être différent, puisqu'il n'y a pas de point-virgule pour terminer la ligne, mais un retour à ligne :
    [^;]*\n
    
  • Une expression régulière pour extraire les cellules :
    "?([^;]*?)"?;"?([^;]*?)"?;"?([\s\S]*?)"?$
    
    Dans cette expression, le motif pour chaque valeur d'une cellule est :
    "?([^;]*?)"?;
    
    Ce qui se lit comme "isoler une séquence de caractères débutant éventuellement par un guillemet, suivi de N (éventuellement 0) caractères différents du point-virgule, suivi éventuellement d'un guillemet". Ce motif est répété jusqu'à la dernière cellule, où il doit être différent, puisqu'il n'y a pas de point-virgule pour terminer la séquence de valeurs, mais un caractère de fin de ligne :
    "?([\s\S]*?)"?$
    
    Ce qui se lit comme "isoler une séquence de caractères débutant éventuellement par un guillemet, suivi de N (éventuellement 0) caractères quelconque (dont le retour à la ligne "\n", et c'est pourquoi on utilise "[\s\S]" et non ".", ce dernier n'absorbant pas "\n"), suivi éventuellement d'un guillemet, suivi d'une fin de ligne".
    L'expression régulière ne fonctionne que si le texte a été au préalable retraité pour éliminer les échappements, c'est-à-dire pour convertir les doubles guillemets (les guillemets échappés par Excel) en simples guillemets. Cela explique ce code :
    rowMatches[1].replace (/""/g, "\"")
    
Noter la maîtrise essentielle du point d'interrogation en tant que modificateur pour spécifier qu'un motif ne doit pas être "greedy".
Conversion du contenu d’un fichier CSV