GENÈSE D'UN DICTIONNAIRE

Construction d'un lexique interactif avec Lazarus


précédentsommairesuivant

Navigation dans le fichier

Introduction

En premier lieu, nous avons accédé à un fichier de mots d'une taille exceptionnelle, l'étape suivante étant de le charger sous Lazarus(3).

Le présent chapitre va examiner comment il est possible de naviguer dans cette liste immense et comment effectuer des recherches simples.

À cette occasion, des problèmes liés aux majuscules et aux accents vont apparaître, et il faudra prendre des options pour la suite de notre projet.

Espace de travail

Pour commencer, nous allons organiser notre espace de travail sous Lazarus. Dans le répertoire Lexique, nous créons un nouveau répertoire Lex2 qui recevra le fichier de mots et tous les fichiers de Lazarus.

Ouvrez Lazarus/Fichier/Nouveau/Application : nous sauvegardons la nouvelle unité dans ce répertoire sous le nom de uLex2. Nous sauvegardons le projet sous le nom de pLex2.

Unité uDisque

Tous les accès au disque (lecture/enregistrement de la base de mots) seront réservés à une nouvelle unité que nous appellerons uDisque. Dans le menu Fichier/Nouveau, nous choisissons Nouvelle unité ; elle apparaît dans l'Éditeur de source ; enregistrement sous le nom de uDisque.

Nous revenons à l'unité principale, uLex2, et nous ajoutons la nouvelle unité dans la liste existante sous la clause uses.

 
Sélectionnez

unit uLex2;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ComCtrls, ExtCtrls, uDisque;
 
type
 
  { TForm1 }

Pour la lecture des données, nous avons remarqué l'efficacité de l'instruction LoadFromFile. Nous allons la reprendre ici, en deux étapes :

1 - dans l'unité uDisque, sous la ligne des unités et avant la commande implementation, nous écrivons la ligne suivante :

procedure LireFichier(listeMots : TStringList);

et nous appuyons simultanément sur les touches Ctrl, Maj et C : la procédure est créée immédiatement sous la ligne implementation :

 
Sélectionnez

unit uDisque;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils; 
procedure LireFichier(listeMots : TStringList);
implementation
 
procedure LireFichier(listeMots: TStringList);
begin
 
end;
 
end.

2 - le curseur clignote entre les mots begin et end ; nous écrivons l'instruction

listeMots.LoadFromFile('liste.de.mots.francais.frgut.txt');

Unité uLex2

C'est l'unité principale du projet. Nous reprenons rapidement les étapes déjà connues :

- la fenêtre Form1 est renommée en Lex2 ;

- on ajoute un composant bouton et un composant memo ;

- dans la procédure onClick du bouton, on remplit l'espace entre les mots begin et end comme suit :

 
Sélectionnez

procedure TForm1.Button1Click(Sender: TObject);
begin
  listeMots := TStringList.Create;
  LireFichier(listeMots);
  Memo1.Append('Premier mot : '+listeMots[0]);
  Memo1.Append('Dernier mot : '+listeMots[listeMots.Count-1]);
end;   

Il faudra enfin déclarer la variable listeMots en ajoutant, sous la ligne Form1 : Tform1, la déclaration :

listeMots: TstringList;

Vérification

Un clic sur le petit triangle vert lance l'exécution. Si tout se passe bien, après un clic sur le bouton de l'interface Lex2, la liste est chargée et le composant Memo indique presque instantanément quels sont les premier et dernier mots de la liste :

Image non disponible

Cliquez sur le petit carré rouge du menu Lazarus pour arrêter l'exécution.

Il est aussi possible de cliquer sur la petite croix en haut et à droite de Lex2.

Une fenêtre indique « Exécution interrompue » ; cliquez sur Ok pour reprendre l'édition du projet.

Lancement et arrêt du programme

Il serait plus rationnel que la mise en route du programme déclenche automatiquement le chargement de la liste de mots. Pour cela, on utilise la propriété onCreate de l'interface.

Dans l'inspecteur d'objets, cliquez sur Form1 pour sélectionner l'objet. Au-dessous, dans l'onglet Évènements, cliquez sur le mot OnCreate, puis sur les trois points qui apparaissent à droite. Automatiquement, la procédure est créée. On y place toutes les instructions qui figuraient jusqu'à présent dans la procédure Button1Click.

Pour arrêter le programme, il est logique d'utiliser un bouton spécialisé. Pour cela, on rebaptise button1 en Arrêt (dans la propriété Caption) et dans le code, on insère les instructions suivantes :

listeMots.Free; Application.Terminate;

La première libère proprement la mémoire, et la seconde termine l'application.

Recherche et affichage d'un mot

Peu à peu, nous allons enrichir notre programme avec des fonctions de balayage, d'édition et de recherche. Il suffit pour cela d'un peu d'imagination, de plonger sur l'interface graphique et compléter le code en fonction de ce que nous désirons.

Interface graphique

Dans la fenêtre Lex2 nous glissons trois nouveaux composants :

  1. Un TLabel qui affichera le mot cherché ; propriété Font, ligne Size : remplacer 0 par 14 ;
  2. Un TEdit qui recevra la requête(4) ;
  3. Un TButton qui déclenchera la requête, propriété Caption modifiée en Chercher.

Image non disponible

La logique est simple : un clic sur le bouton Chercher devra déclencher la lecture du texte saisi dans le TEdit. Ce texte est comparé à la liste en mémoire ; s'il existe, il est affiché dans le TLabel.

Code

L'unité uLex2 comprend maintenant quatre procédures :

 
Sélectionnez

implementation
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
begin
   listeMots.Free;
   Application.Terminate;
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  Recherche(Edit2.Caption);
end;
 
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  listeMots := TStringList.Create;
  iMot := 0;
  LireFichier(listeMots);
  Memo1.Append('Premier mot : '+listeMots[0]);
  Memo1.Append('Dernier mot : '+listeMots[listeMots.Count-1]);
  MAJAffichage;
  //ListBox1.Items.Create;
end;
 
procedure TForm1.Recherche(nMot: string);
var iMot : integer;
begin
   iMot := listeMots.IndexOf(nMot);
   if iMot >= 0 then Label1.Caption:= listeMots[iMot]
                else Label1.Caption:= 'échec';
   Memo1.Append('Index '+IntToStr(iMot));
end;
 
end.  

Un clic sur le bouton2 (Chercher) envoie le contenu du composant Edit2 à la procédure Recherche.

Si le mot n'est pas trouvé, la variable iMot est négative. Dans ce cas, l'afficheur indique « échec ».

Pour permettre de suivre l'activité de recherche, le Memo reçoit la valeur de l'index.

Une variante consisterait à entrer un numéro à la place d'un mot, mais son intérêt pratique est moins évident.

Les procédures Button ou FormCreate sont créées par le système, donc reconnues sans problème au moment de la compilation. Par contre, la procédure Recherche est ignorée si elle n'est pas déclarée dans la même rubrique que les autres, sous la ligne Tform1 = class(TForm).

Exécution

Un clic sur le petit triangle vert…

Le mémo affiche les premier et dernier mots.

Dans la zone d'édition, on entre le mot à chercher, par exemple « bonjour » : le résultat s'affiche. Si on cherche « bonjoux », on lit la mention « échec » ; le memo affiche un index de 33608(5) dans le premier cas, -1 dans le second.

Options de base

La liste de mots que nous utilisons pour l'instant constitue un trésor de référence : toute modification risque d'être dommageable avec l'omission d'un mot, par exemple, ou l'introduction d'une faute d'orthographe, ce qui serait consternant.

Pourtant, il est nécessaire de la modifier pour l'enrichir en fonction des besoins. Des options sont alors à prendre.

Les accents

Tout va bien, ou presque… Si on cherche « a », la recherche aboutit (index 0, qui correspond au premier mot de la liste). Si on cherche « à », on obtient un échec (index -1 alors que la liste contient bien ce mot à l'index 1). D'une façon générale, une erreur se présente pour tous les mots qui contiennent des lettres accentuées(6).

Dans le chapitre 1, nous avions pourtant constaté qu'un programme basique comme WordPad affichait correctement les lettres accentuées. Il y a donc un problème avec Lazarus…

Ce logiciel de programmation est en effet conçu pour s'adapter à toutes les langues (en tout cas à un maximum), et l'option retenue pour le traitement des chaînes de caractères est la norme UTF8. Les lettres accentuées sont traitées non comme un caractère unique, mais comme une séquence formée de plusieurs octets. Encore un effet de la mondialisation…

Au contraire, la norme ANSI, beaucoup plus ancienne, utilisée pour constituer la liste de mots que nous essayons maintenant de maîtriser, code tous les caractères par un unique octet. Avantage : fichiers plus courts, recherches plus rapides… Inconvénient : les procédures de traitement de texte ne s'appliquent plus automatiquement aux autres langues…

Heureusement, des passerelles existent. Passer d'un codage à un autre ne présente aucune difficulté. Il faut donc simplement choisir une option et adopter les passerelles disponibles pour qu'une chaîne de caractères ANSI soit convertie en UTF8 quand c'est nécessaire, et inversement.

Option 1 - Par souci d'homogénéité avec le fichier de mots, nous adopterons le codage ANSI pour notre projet de dictionnaire.
L'interface graphique de Lazarus sera donc alimentée par des chaînes à convertir en UTF8.
Mais la conversion du fichier de mots au standard UTF8 aurait eu également sa logique ! Elle pourrait se révéler nécessaire pour d'autres langues.

Majuscules

Contrairement aux accents, la recherche de mots contenant une ou des majuscules n'offre aucune difficulté : les essais avec « bonjour », « Bonjour » ou « bonJour » donnent le même résultat.

Si on se rappelle que les grilles de mots croisés utilisent exclusivement des lettres majuscules et sans accent, on peut conclure que tout va bien ainsi.

Mais si on a l'ambition d'introduire un peu de logique dans la recherche, la présence ou non de majuscules peut se révéler déterminante. Un pari, des paris, la ville de Paris... les concepts sont vraiment différents. Les mots corse, Corse, corsé s'écrivent de la même façon dans une grille de mots croisés, mais relèvent de définitions différentes… Ignorer les majuscules conduirait, comme pour les accents, à réduire fortement la qualité des informations que nous voulons traiter.

Et pourtant, la liste que nous consultons ne contient actuellement aucune majuscule ! Oui, mais nous devons l'ouvrir aux noms propres et étrangers, ainsi qu'aux abréviations, pour la rendre compatible avec les usages des mots croisés.

Option 2 - Les majuscules et les minuscules seront distinguées de façon à conserver le maximum de sens aux mots stockés.

Lettres accolées

Il existe une troisième convention, pas évidente mais adoptée implicitement, c'est celle des lettres accolées comme dans « cœur », ou « nævus »  : notre dictionnaire les considérera comme des lettres distinctes, c'est-à-dire que œ sera écrit et traité comme o suivi d'un e.

Option 3 - Les mots comme « cœur », « œdème », « sœur », etc. seront enregistrés et traités avec des lettres séparées.

Autres signes

Les définitions des mots croisés introduisent fréquemment des valeurs numériques, par exemple le code postal, la longueur d'un fleuve, l'âge du capitaine, etc. Pour autoriser ultérieurement une recherche, il est donc nécessaire d'autoriser ces caractères.

Par ailleurs, les traits-d'union sont escamotés dans les grilles : « Notre-Dame » s'écrira « NOTREDAME ». De même pour les espaces : « urbi et orbi » s'écrira « URBIETORBI ». Dans les deux cas, la première graphie est correcte, et la seconde réservée aux mots croisés, ce qui complique un peu les opérations d'enregistrement, de recherche et d'affichage. Pour autant, il reste nécessaire de conserver l'orthographe originale : c'est précisément celle qui est utilisée dans les définitions, point de départ de toute recherche. Il restera à soigner les résultats pour que leur format soit compatible avec celui d'une grille.

Option 4 - Les chiffres, les espaces et les traits-d'union seront acceptés dans notre dictionnaire.

Notre projet n'a pas l'ambition de concurrencer l'Académie. Il veut s'ouvrir à toutes les graphies, et esquiver toutes les querelles de chapelles. Les noms propres, étrangers, sigles et index numériques seront acceptés comme c'est l'usage dans les définitions de mots croisés.

Mais cette ouverture ne signifie pas laxisme, et une attention particulière sera portée à l'introduction de mots nouveaux.

Balayage mot par mot

Dans le menu de Lazarus, onglet Common Controls, on trouve le composant TUpDown qui est représenté par une double flèche haut-bas. Cliquez dessus, puis cliquez sur la fenêtre Lex2 pour le coller. Dans l'inspecteur d'objets, onglet Propriétés, ligne Orientation, choisissez le paramètre udHorizontal. Dans l'onglet Événements, ligne OnClick, cliquez sur les trois points pour provoquer la création de la procédure UpDown1Click.

Dans Lex2, élargissez le composant pour lui donner un aspect de navigateur : un clic sur la flèche droite devra afficher le mot suivant, et inversement.

Entre les mots begin et end de la procédure UpDown1Click entrez les quatre lignes suivantes :

 
Sélectionnez

if Button = btNext 
   then Inc(iMot) else Dec(iMot);
iMot := (iMot + listeMots.Count) mod (listeMots.Count);
MAJAffichage; 

Dans la procédure FormCreate, il faut ajouter les instructions :

iMot:= 0 ;MAJAffichage ;

La procédure MAJAffichage se présente comme indiqué ci-dessous.

Enfin, la variable globale iMot doit être déclarée avant implementation, juste après listeMots, avec l'instruction

iMot : integer ;

Voici l'ensemble du code.

 
Sélectionnez

unit uLex2;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ComCtrls, ExtCtrls, uDisque;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit2: TEdit;
    Label1: TLabel;
    Memo1: TMemo;
    UpDown1: TUpDown;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Recherche(nMot : string);
    procedure UpDown1Click(Sender: TObject; Button: TUDBtnType);
    procedure MAJAffichage;
  private
    { private declarations }
  public
    { public declarations }
  end; 
 
var
  Form1: TForm1; 
  listeMots: TStringList;
  iMot : integer;
 
implementation
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
begin
   listeMots.Free;
   Application.Terminate;
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  Recherche(Edit2.Caption);
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  listeMots := TStringList.Create;
  iMot := 0;
  LireFichier(listeMots);
  Memo1.Append('Premier mot : '+listeMots[0]);
  Memo1.Append('Dernier mot : '+listeMots[listeMots.Count-1]);
  MAJAffichage;
end;
 
procedure TForm1.Recherche(nMot: string);
var iMot : integer;
begin
   iMot := listeMots.IndexOf(nMot);
   if iMot >= 0 then Label1.Caption:= listeMots[iMot]
                else Label1.Caption:= 'échec';
   Memo1.Append('Index '+IntToStr(iMot));
end;
 
procedure TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);
begin
   if Button=btNext then Inc(iMot)
                    else Dec(iMot);
   iMot := (iMot + listeMots.Count) mod  listeMots.Count;
   MAJAffichage;
end;
 
procedure TForm1.MAJAffichage;
begin
  Label1.Caption:=listeMots[iMot];
  Edit2.Caption:= '';
  Memo1.Append('Index '+IntToStr(iMot));
end;
 
end.

Exécutez le programme : un clic sur l'une des flèches fait apparaître au choix le mot suivant ou le mot précédent. Rudimentaire, mais efficace.

Extension de l'espace graphique

Le tableau de commande est pour l'instant limité à cinq composants regroupés sur notre interface graphique. Que souhaitons-nous faire, maintenant que la liste de mots est entièrement accessible ? Nous pourrions par exemple, en vrac :

  • ajouter un mot nouveau ;
  • le placer au bon endroit ;
  • le modifier ;
  • le supprimer, avec dans les deux cas un contrôle du classement ;
  • ajouter une information spécifique à ce mot ;
  • éditer à volonté cette information : la modifier ou même la supprimer ;
  • regrouper plusieurs mots ensemble, si un lien logique (ou culturel…) existe entre eux ;
  • éditer ces liens (modification, suppression…) ;
  • permettre l'introduction de plusieurs mots dans une requête et analyser les liens éventuels ;
  • enregistrer les modifications et, progressivement, enrichir la base de connaissances ;
  • lier un fichier rassemblant notes et rappels divers…

Ouf, cela fait déjà un beau programme, mais il est bon de se rappeler que ses limites sont celles que l'on voudra lui donner…

Pour l'instant, nous allons agrandir l'espace graphique affecté à notre programme.

III.7. Introduction d'onglets

Au lieu de partir de zéro(7), nous allons utiliser la fenêtre existante (Lex2) en ajoutant un composant TPageControl (dans le menu Lazarus, cliquez sur l'onglet(8) Common Controls).

Faites un clic droit dans sur ce composant pour faire apparaître le menu contextuel, et choisissez la première ligne :

Ajouter une page

Renommez (Caption) cette page en Page1.

Image non disponible

Les transferts des composants déjà installés peuvent maintenant commencer.

Label1 : cliquez dessus pour le sélectionner, clic droit, choisissez Couper ; cliquez sur la page 1 et collez le label.

Image non disponible Image non disponible

Procédez de même pour les autres composants, en déplaçant si nécessaire le composant PageControl.

Lorsque la fenêtre d'origine est vide, il est possible d'agrandir le composant PageControl pour qu'il occupe la totalité de la surface disponible. L'ensemble peut être ajusté pour que la fenêtre ressemble plus ou moins à celle d'origine… mais avec un avantage certain puisque, à la page 1, il est possible d'ajouter autant de pages que nécessaire.

En principe, le programme reste totalement opérationnel. Pour le vérifier, cliquez sur le petit triangle vert ; notez les erreurs éventuelles, corrigez et relancez… Cliquez sur le carré rouge pour revenir en position d'édition.

Conclusion

Nous disposons maintenant de la matière (la liste de mots), d'un outil (Lazarus), d'une interface graphique modulable et… de quelques idées directrices pouvant servir de feuille de route : le prochain chapitre s'intéressera à différentes techniques d'affichage.

Diamantaire : suit le cours des rivières.
Tristan Bernard

Rappelons avant de terminer le code de l'unité uDisque :

L'unité uDisque
Sélectionnez

unit uDisque;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils; 
 
  procedure LireFichier(listeMots : TstringList);
 
implementation
 
procedure LireFichier(listeMots: TStringList);
begin
  listeMots.LoadFromFile('liste.de.mots.francais.frgut.txt');
end;
 
end.

L'unité principale uLex2 se présente maintenant comme suit :

L'unité uLex2
Sélectionnez

unit uLex2;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls,
  Graphics, Dialogs, StdCtrls, ComCtrls, ExtCtrls, uDisque;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit2: TEdit;
    Label1: TLabel;
    Memo1: TMemo;
    PageControl1: TPageControl;
    Page1: TTabSheet;
    UpDown1: TUpDown;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Recherche(rechMot: string);
    procedure UpDown1Click(Sender: TObject; Button: TUDBtnType);
    procedure MAJAffichage;
  private
    { private declarations }
  public
    { public declarations }
  end;
 
var
  Form1: TForm1;
  listeMots: TstringList;
  iMot : integer;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
begin
   listeMots.Free;
   Application.Terminate; 
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  Recherche(Edit2.Caption);
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  listeMots := TStringList.Create;
  LireFichier(listeMots);
  Memo1.Append('Premier mot : '+listeMots[0]);
  Memo1.Append('Dernier mot : '+listeMots[listeMots.Count-1]);
  iMot := 0;
  MAJAffichage;
end;
 
procedure TForm1.Recherche(rechMot: string);
var iMot : integer;
begin
   iMot := listeMots.IndexOf(rechMot);
   if iMot >= 0 then Label1.Caption:= listeMots[iMot]
                else Label1.Caption:= 'échec';
   Memo1.Append('Index '+IntToStr(iMot));
end;
 
procedure TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);
begin
   if Button=btNext then Inc(iMot)
                       else Dec(iMot);
   iMot := (iMot + listeMots.Count)   mod  (listeMots.Count);
   MAJAffichage;
end;
 
procedure TForm1.MAJAffichage;
begin
   Label1.Caption:=listeMots[iMot];
   Edit2.Caption:= '';
   Memo1.Append('Index '+IntToStr(iMot));
end;
 
end.   

Une lecture attentive permet de déceler, dans la deuxième procédure, la fonction UTF8ToANSI qui convertit la saisie dans le TEdit (standard UTF8) au standard ANSI adopté pour la liste de mots. Cette passerelle permet de lancer des recherches sur des mots incluant des lettres accentuées. L'opération inverse est réalisée simplement par la fonction ANSIToUTF8.


précédentsommairesuivant
La version 1.0.2 présente le composant ExtendedNoteBook en remplacement du composant NoteBook disponible auparavant. Le code pourra donc changer légèrement selon la version utilisée.
Attention au nom de ce composant, ici Edit2, que l'on peut renommer sans problème.
C'est la position du mot bonjour dans la liste, le premier mot a se trouvant en tête de liste, qui commence par convention à la position 0.
Même observation pour la lettre ç.
Il semble plus rationnel de repartir à zéro, mais l'exercice qui suit n'est pas sans intérêt.
On peut également utiliser, dans les éditions plus anciennes de Lazarus, le composant TNoteBook se trouvant dans l'onglet Additional, entre TPaintBox et TLabeledEdit).

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 dimanche2003. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.