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.
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 :
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 :
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 :
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 :
- Un TLabel qui affichera le mot cherché ; propriété Font, ligne Size : remplacer 0 par 14 ;
- Un TEdit qui recevra la requête(4) ;
- Un TButton qui déclenchera la requête, propriété Caption modifiée en Chercher.
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 :
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 :
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.
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.
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.
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 :
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 :
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.