Lire un fichier de texte ligne par ligne avec Node.js

Comment accéder aux lignes d'un document quand on ne peut lire que des blocs de données?

La bibliothèque File System de Node offre de nombreuses fonctions pour lire et écrire un fichier. On peut charger un fichier entier dans une chaîne de caractère. On peut stocker des lignes de texte dans un fichier l'une après l'autre. Mais ce qu'on ne peut pas faire, c'est lire le fichier ligne par ligne. Quand il est très grand ou quand on veut limiter le lecture à un certain nombre de lignes, cette lacune devient embêtante.

Plusieurs solutions ont été proposées à ce problème, avec ou sans module supplémentaire, mais aucune n'est satisfaisante si on veut reproduire les fonctions de PHP ou C ou tout autre langage: ouvrir le fichier, lire une ligne en boucle, fermer le fichier.

Voici un algorithme assez simple qui aboutit à ce résultat, qui ne requiert aucun module externe.

  1. On crée un tableau associatif fileBuffer avec en clé la ressource pour un fichier et en valeur, un tableau contenant les lignes lues.
  2. Le tableau filePtr a pour clé également la ressource d'un fichier et pour valeur la position dans le fichier.
  3. On teste si le tableau a un contenu. Si c'est le cas, on lit le premièr élément qui est supprimé par shift.
  4. Sinon on lit un bloc de données dans le fichier, à partir de la position pos et d'une taille de 4096 octets.
  5. Le nombre effectif d'octets lus est retourné dans br.
  6. Si ce nombre est inférieur à 4096, il n'y aura pas d'autre lecture, on supprime l'entrée du fichier dans filePtr (cela sera utilisé par la fonction eof).
  7. On convertit le buffer en chaîne et on découpe cette chaîne en un tableau qui est assigné à fileBuffer[handle].
  8. Le dernier item du tableau est effacé car la ligne est tronquée dans la plupart des cas.
  9. On détermine la position suivante dans le fichier en ajoutant le nombre d'octets lus, moins la taille du dernier élément.
  10. Quand le tableau est vide, on recommence en 4, sauf si la fin de fichier est détectée.

Code source du module:

var fs = require('fs');
var filePtr = {}
var fileBuffer = {}
var buffer = new Buffer(4096)

exports.fopen = function(path, mode) {
  var handle = fs.openSync(path, mode)
  filePtr[handle] = 0
  fileBuffer[handle]= []
  return handle
}

exports.fclose = function(handle) {
  fs.closeSync(handle)
  if (handle in filePtr) {
    delete filePtr[handle]
    delete fileBuffer[handle]
  } 
  return
}

exports.fgets = function(handle) { 
  if(fileBuffer[handle].length == 0)
  {
    var pos = filePtr[handle]
    var br = fs.readSync(handle, buffer, 0, 4096, pos)
    if(br < 4096) {
      delete filePtr[handle]
      if(br == 0)  return false
    }
    var lst = buffer.slice(0, br).toString().split("\n")
    var minus = 0
    if(lst.length > 1) {
      var x = lst.pop()
      minus = x.length
    } 
    fileBuffer[handle] = lst 
    filePtr[handle] = pos + br - minus
  }
  return fileBuffer[handle].shift()
}

exports.eof = function(handle) {
  return (handle in filePtr) == false && (fileBuffer[handle].length == 0) 
}

On peut importer le module comme dans l'exemple ci-dessous ou intégrer directement les fonctions dans son projet en supprimant le préfixe exports.

Code source de la démonstration

Dans cet exemple, on lit un fichier ligne par ligne et on le recopie dans un nouveau fichier ligne par ligne aussi mais grâce aux fonctions de File System.

var fs = require('fs')
var readline = require("/scripts/node-readline/node-readline.js")

var source="/scripts/node-readline/demosrc.htm"
var target="/scripts/node-readline/demotgt.htm"

var r=readline.fopen(source,"r")
if(r===false)
{
   console.log("Error, can't open ", source)
   process.exit(1)
} 

var w = fs.openSync(target,"w")
var count=0
do
{
   var line=readline.fgets(r)
   console.log(line)
   fs.writeSync(w, line + "\n", null, 'utf8')
   count+=1
}
while (!readline.eof(r))
readline.fclose(r)
fs.closeSync(w)

console.log(count, " lines read.")

Remplacez le nom du fichier source et du fichier cible par ce que vous voulez. Vous pouvez aussi adapter la taille du buffer à vos besoins. Si un fichier contient des lignes plus longues que 4096 octets (c'est plutôt rare quand on doit le lire ligne par ligne), le buffer doit être augmenté en proportion.

Télécharger le code source complet.