TUTORIELS 
Programmer un moteur de recherche en Perl
Utiliser Perl et CGI pour la programmation d'un moteur de recherche simple (par mot clé, sans opération booléenne - l'opérateur OR étant implicite -, et sans classement des réponses).  (18 mars 2001)
 

Le langage Perl, aujourd'hui dans sa version 5, est devenu particulièrement populaire auprès des auteurs de scripts CGI (Common Gateway Interface). Gratuit, relativement facile d'accès (surtout pour les familiers du monde Unix), il s'agit d'un langage interprété, qui permet donc, en s'affranchissant d'une étape intermédiaire de compilation, de gagner du temps lors des tests des programmes. Cet article s'adresse à ceux qui, inexpérimentés en Perl, en possèdent toutefois quelques bases, ou, à défaut, ont un peu l'expérience de la programmation shell (ou d'autres langages). Ainsi, comprendre la notion d'« expression régulière » facilitera grandement leur tâche.

Le moteur de recherche viendra lire le contenu des fichiers d'un répertoire, y rechercher un ou plusieurs mots-clés (sans opération booléenne, l'opérateur OR étant implicite), en distinguant les majuscules des minuscules, et sans classer les réponses : le programme Perl devra récupérer les données d'un formulaire HTML et le contenu des fichiers dans lesquels chercher, les traiter, puis renvoyer les résultats sous forme d'une autre page HTML.

Récupération des données du formulaire
Supposons que l'attribut METHOD de la balise <FORM> de votre formulaire HTML ait la valeur "GET", alors le contenu de la variable d'environnement CGI $ENV{'QUERY_STRING'} sera, par exemple, "keywords=journal+du+net&case=oui", si les deux champs du formulaire ont pour noms keywords et case (distinction majuscules/minuscules), et pour valeurs respectives "journal du net" et "oui". Il faut alors extraire de cette variable des informations utilisables :

@paires = split(/&/, $ENV{'QUERY_STRING'});
foreach $paire (@paires) {
  ($nom, $valeur) = split(/=/, $paire);
  $valeur =~ s/\+/ /g;
  $FORM{$nom} = $valeur;
}

L'observation de ces quelques lignes de code Perl permet d'apprendre beaucoup sur la syntaxe du langage : variables, tableaux, appels de fonction, boucles, etc. D'abord, on isole les différentes paires nom/valeur en « découpant » (fonction split) la variable d'environnement $ENV{'QUERY_STRING'} en autant de variables (stockées dans le tableau @paires) qu'elle contient de caractères « & » (+1) ; puis chaque paire ainsi obtenue est à son tour « découpée » en deux variables : $nom et $valeur (le signe « = » servant cette fois de séparateur). Un tableau associatif %FORM (dont l'accès à une entrée s'effectue par $FORM{$nom}), vient enfin stocker ces données de manière liée.

Attardons-nous sur l'instruction :

$valeur =~ s/\+/ /g;

Les familiers de sed ou awk, sous Unix, ne seront pas surpris par cette notation : « $ch =~ s/A/B/ » signifie « remplacer le premier caractère A de la chaîne $ch par le caractère B ». Dans notre programme, A est le caractère « + » (\ est nécessaire car + est un métacaractère), B est le caractère espace : le suffixe g indique que toutes les occurrences de « + », et non plus seulement la première, doivent être remplacées (ici, en l'occurrence, par un espace).

Récupération du contenu des fichiers dans lesquels chercher
Supposons maintenant que vos pages HTML soient situées dans le répertoire $rep : la fonction chdir permet de s'y positionner. Supposons de plus qu'il s'agisse d'un serveur Unix : l'instruction `ls *.html` (à ne pas confondre avec 'ls *.html' !) renverra une variable qui contient tous les fichiers HTML du répertoire, variable à partir de laquelle on récupère (à l'aide, une nouvelle fois, de la fonction split), chaque nom de fichier dans un tableau. Le métacaractère \s+ désignant à la fois les caractères espace, « . » et « , », répétés une ou plusieurs fois.

chdir($rep);
$ls = `ls *.html`;
@fichiers = split(/\s+/, $ls);

Traitement des données
Vient alors l'étape de recherche : il est d'abord nécessaire d'isoler chaque mot clé en « découpant » (fonction split) la variable $FORM{'keywords'} définie plus haut.

@keywords = split(/\s+/, $FORM{'keywords'});

Il reste alors à ouvrir chaque fichier (fonction open), récupérer chaque ligne dans un tableau (@lignes = <fichier>), refermer le fichier (fonction close), concaténer toutes les lignes en une seule chaine (fonction join), éliminer de cette variable ($chaine) les caractères de fin de ligne (métacaractère \n), puis effectuer la recherche proprement dite.

foreach $fichier (@fichiers) {
  open(fichier, "$fichier");
  @lignes = <fichier>;
  close(fichier);
  $chaine = join('.', @lignes);
  $chaine =~ s/\n//g;

  # Recherche
  # …
}

La boucle de recherche est construite sur le modèle suivant :

foreach $keyword (@keywords) {
  If ($chaine =~ /$keyword/i) {
    $INCLURE{$fichier}='oui';
  }
  else {
    $INCLURE{$fichier}='non';
  }
  if ($chaine =~ /<title>(.*)<\title>/i) {
    $TITRE{$fichier} = "$1";
  }
  else {
    $TITRE{$fichier} = "$fichier";
  }
}

La notation « $ch =~ /$A/i » signifiant « comparer la variable $A à la variable $ch sans tenir compte des majuscules ou des minuscules (suffixe i) ». Si $A est contenue dans $ch, l'instruction renverra la valeur logique « vrai ».

Ainsi, après avoir décidé si le fichier doit être inclus ou non dans la liste des résultats, on stocke cette information ainsi que le titre de la page web correspondante dans deux tableaux associatifs. Si la page n'a pas de titre défini entre les balises <TITLE> et </TITLE>, on lui donne comme titre le nom du fichier. A noter que la variable $1 correspond à la chaine de caractère récupérée avec le symbole (métacaractère) (.*).

Il reste à distinguer le cas d'une recherche en faisant la distinction majuscules/minuscules du cas d'une recherche sans distinction : le second correspond à ce qui vient d'être fait, le premier se programme en supprimant le suffixe i à l'instruction de comparaison. La boucle de recherche devient :

foreach $keyword (@keywords) {
  if ($FORM{'case'} eq 'oui') {
    if ($chaine =~ /$keyword/) {                                         
      $INCLURE{$fichier}='oui';
    }
    else {
      $INCLURE{$fichier}='non';
    }
  elsif ($FORM{'case'} eq 'non') {
    If ($chaine =~ /$keyword/i) {
      $INCLURE{$fichier}='oui';
    }
    else {
      $INCLURE{$fichier}='non';
    }
  }
  if ($chaine =~ /<title>(.*)<\title>/i) {
    $TITRE{$fichier} = "$1";
  }
  else {
    $TITRE{$fichier} = "$fichier";
  }
}

Affichage des résultats
L'affichage des résultats sous forme de pages web est la dernière étape : le Perl permet d'écrire du code HTML avec l'instruction print. Ainsi par exemple :

print "<a href=\"http://www.journaldunet.com\">Lien</a>\n";

écrit le code HTML:

<a href="http://www.journaldunet.com">Lien</a>

puis passe à la ligne (caractère \n). Les guillemets étant des caractères réservés, on les fait précéder, dans l'instruction Perl, d'un \ pour éviter la confusion.

Pour afficher les résultats, on écrira en particulier (si $url contient l'URL du répertoire dans lequel la recherche a eu lieu) :

foreach $resultat (%INCLURE) {
  if ($INCLURE{$resultat} eq 'oui') {
     print "<a href=\"$url$resultat\">$TITRE{$resultat}</a>\n";
  }
}

Le code complet (fonctionnant sous serveur Unix) de ce mini-moteur de recherche peut être . De nombreuses autres fonctionnalités peuvent être ajoutées à ce programme, comme la gestion des opérateurs booléens ou la possibilité de rechercher une phrase entière. Une meilleure gestion des fichiers (parcours récursifs dans les répertoires, fichiers cherchés non limités aux fichiers HTML), et un meilleur traitement des données du formulaire seraient également pertinents. L'architecture du programme bénéficiera aussi d'un découpage en sous-routines. Ces ajouts ne sont, individuellement, pas difficiles et constituent un bon moyen de progresser en Perl.
Pour plus de renseignements, la source d'information la plus riche reste la
documentation Perl.

 
[ Jérôme MorlonJDNet
 
Accueil | Haut de page