Avant que Perl ne devienne un langage de programmation générique, il
était PERL : "un Langague Pratique pour
l'Extraction de données et l'écriture de Rapports". On
trouve des vestiges de l'évolution de Perl depuis ses humbles débuts
dans les recoins oubliés du langage. Les formats, par exemple, ont une
syntaxe peu commune dans Perl et ils peuvent en général être émulés
par d'autres routines (printf
en particulier). C'est
pourquoi la plus part des personnes commencent par passer les formats
lors de leurs premiers contacts avec Perl. Mais si vous avez à écrire
de nombreux scripts produisant des rapports d'analyse de gros volumes de
de données, les formats peuvent se révéler être de bons outils.
printf()
, mais quand le j'ai donné à Tom Limoncelli, il
me l'a rendu avec des formats à la place. Ainsi modifié, il était plus
agréable à lire (mais mon compte en banque était déjà géré).
Je voulais que le fichier d'entrée soit le plus facile possible à saisir, son formatage est donc très simple. La première ligne reprend l'état initial du compte, en centièmes (je n'ai ainsi pas besoin de saisir la virgule des décimales ni à travailler en flottant). Chaque ligne suivante représente un mouvement : quatre champs, séparés par une tabulation, contiennent le numéro du chèque ou le code du mouvement, la date, une description et une valeur (toujours en centièmes). Les dépôts et les versements sont représentés par des nombres négatifs (en général je prélève plus souvent sur mon compte que je ne le crédite). Voici un programme simple qui lit ce fichier et génère le compte rendu de l'état du compte :
format STDOUT = @<<<<< @>>>> @<<<<<<<<<<<<<<<<<<< $@######.## $@######.## $code, $date,$descript, $amt, $balance . open(INP, "transactions") || die "Can't read transactions file\n"; chop($penny_balance = <INP>); while (<INP>) { chop; ($code, $date, $descript, $penny_amt) = split(/\t/); $penny_balance -= $penny_amt; $amt = $penny_amt / 100; $balance = $penny_balance / 100; write; } close(INP); format top = . Trans: Date: Description: Montant: Solde: ====== ===== ============ ======= ======== .Les quatres premières lignes de cet exemple sont la déclaration du format. La première définit son nom. Quand la fonction
write()
est appelée pour écrire une ligne de données
formatées, elle utilise le format dont le nom correspond à celui de
la "handle" utilisée pour manipuler le fichier. Dans notre exemple, le
programme écrit les données sur la sortie standard. Notez que si aucun
format n'est spécifié, Perl utilise STDOUT
par défaut,
mais il est toujours préférable de nommer explicitement les formats,
même pour écrire sur STDOUT
.
La seconde ligne donne l'aspect d'une ligne à écrire. Chaque
groupe de lettres commençant par un @
spécifie un champ à
écrire - tout le reste n'est que du texte (ex: les symboles
$
au début de deux montants). Le signe inférieur (<)
signifie que le champ doit être justifié à gauche, le signe
supérieur(>) que le champ doit être justifié à droite; le signe
tube (|) qu'il doit être centré. Les champs numériques sont indiqués
par un (#) et un point décimal optionnel. La taille du champ est
déterminée par le nombre de ces caractères spéciaux y compris le
@
(dans cet exemple, le premier champ fait 6 caractères,
le second 5, etc...). De ce fait le format représente de façon
synthétique l'exact alignement de la sortie.
La troisième ligne associe une variable à chaque champ. Lorsque la
fonction write()
est appelée, la valeur courante de la
variable est imprimée en utilisant le format spécifié. Les choses sont
plus claires à lire si vous écrivez le nom des variables sous leur
champ dans dans le format.
La dernière ligne du format est toujours un point isolé sur la ligne. Il termine le format.
La déclaration d'un format peut intervenir n'importe où dans le code. L'exemple ci-dessous en contient deux, l'un avant le code l'autre après. Ceci pour exposer le principe; dans votre propre code, je vous recommande de toujours grouper vos formats au début du script. Si plusieurs formats portant le même nom sont utilisés, seul celui défini en dernier sera pris en compte.
Le format du nom de "top" est systématiquement imprimé en début de
chaque page. La variable spéciale $=
contient le nombre de
lignes d'une page; elle vaut 60 par défaut mais vous pouvez la
redéfinir à une valeur plus petite si vous le souhaitez. La variable
spéciales $-
contient le nombre lignes restantes dans la
page. Vous pouvez forcer un saut de page en assignant 0 a
$-
. Cependant ne mélangez pas l'utilisation des fonctions
print()
, print()
et write()
sinon $-
ne sera pas correctement décrémentée.
write()
utilise le nom de la
"handle" pour sélectionner le format qu'elle utilise mais il est
possible d'en utiliser un autre en assignant son nom à la variable
$~
. L'astuce consiste donc à tester le nombre de lignes
restant avant la fin de la page et à utiliser un format spécial en
lieu et place du pied de page. Voici l'algorithme pour mettre en
oeuvre cette astuce :
format top = Trans: Date: Description: Amount: Balance: ====== ===== ============ ======= ======== . format STDOUT = @<<<<< @>>>> @<<<<<<<<<<<<<<<<<<< $@######.## $@######.## $code, $date,$descript, $amt, $balance format footer = Page @### $% . $footer_depth = 2; open(INP, "transactions") || die "Can't read transactions file\n"; chop($penny_balance = <INP>); while (<INP>) { chop; ($code, $date, $descript, $penny_amt) = split(/\t/); $penny_balance -= $penny_amt; $amt = $penny_amt / 100; $balance = $penny_balance / 100; write; if ($- == $footer_depth) { $~ = "footer"; write; $~ = "STDOUT"; } } close(INP);D'abord nous définissons un nouveau format pour le pied de page ainsi qu'une constante globale contenant le nombre de lignes occupées par un pied de page. Le format du pied de page de notre exemple utilise une autre variable spéciale,
$%
, qui contient le numéro de la
page courante (en commençant par 1).
Chaque fois que nous écrivons une nouvelle ligne, nous testons le
nombre de lignes restantes dans la page ($-
). Lorsqu'il
nous reste exactement $footer_depth
lignes il est temps
d'afficher le pied de page. Nous affectons simplement le nom du format
du pied de page (footer
) à la variable $~
,
nous appelons write()
puis nous re-affectons le nom du
format normal (STDOUT
) avant lewrite
suivant. Cette ligne sera écrite sur la page suivante après l'en-tête.
Alors que cette méthode fonctionner parfaitement aussi longtemps que
write()
n'écrit qu'une seule ligne à la fois,
l'anticipation de l'arrivée de la fin de la page est plus compliquée
quand on utilise des formats de plusieurs lignes. Il est nécessaire
d'écrire un peu plus de code dans la boucle while()
pour
écrire des lignes blanches et le pied de page. Cette question est
laissée en exercice au lecteur.
Si vous souhaitez changer l'en-tête - pare exemple pour obtenir une
grande entête pour la première page et une plus petite pour les
suivantes - vous pouvez utiliser la variable $^
. Elle se
comporte pour les entêtes de la même façon que se comporte
$~
pour les formats. Ne définissez jamais $-
ou $^
à un format inexistant ou votre programme sortira en
erreur fatale. Si vous ne souhaitez pas d'en tête, de définissez pas
du tout le format top, ou définissez le à un format vide.
Deuxièmement, la déclaration du format porte sur plusieurs lignes. Ceci est également légal et chaque ligne peut contenir zéro, un ou plusieurs champs. En général un format multi-ligne alterne la description des champs avec leur valeur.
Le code suivant est un exemple intéressant de formatage
multiligne. Je présuppose l'existence d'une fonction
mailparse()
qui lit sur le standard input une message à
la fois. Pour chaque message, mailparse()
stocke chaque
information de l'en-tête dans un tableau associatif global,
%header
, indexée par les drapeaux du message
(From
, To
... ) et toutes les lignes du messages
dans une variable scalaire nommée $body
. La sortie du
message est donnée dans l'exemple. A propos, mon éditeur ne m'a jamais
envoyé un tel message: Je l'ai forgé de toutes pièces. Comme tous les
auteurs, je suis toujours en avance sur mes dates de livraison. Hum,
cette dernière partie est mensongère, mais j'ai bien forgé moi même le
message.
format message = Date: @<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $header{`Date'}, $body From: @<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $header{`From'}, $body To : @<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $header{`To'}, $body Subj: ^<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $header{`Subject'}, $body ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $header{`Subject'}, $body . $~ = "message"; while (<STDIN>) { &mailparse(); write; } Date: Tue, 11 Apr 1995 16:39:06 Hal-- What's the status of your Perl From: tmd@iwi.com (Tina M. Darmo article for the upcoming issue of ;login;? To : hal@netmarket.com (Hal Pom Rob needs to review the article before Subj: Your ;login: article is giving it to Carolyn for typesetting. *OVERDUE* Please send email soon-- the fate of the universe is at stake. --TinaIl y a un certain nombre de nouveaux constructeurs dans le format du message de cet exemple. Tout d'abord nous trouvons des champs qui commencent par
^
au lieu de @
. Pour ce type
de champ, Perl consommera dans la variable autant de lettres que
nécessaire pour le remplir. En en utilisant plusieurs de ce type associé
à une variable contenant une longue chaîne, il est possible de
l'afficher dans un texte justifié à gauche, comme elle apparaît dans
notre exemple avec d'un coté l'en-tête du message et de l'autre le
message. Le variable spéciale $:
(je promets que c'est la
dernière fois que je mentionne une variable spéciale dans cet article)
contient l'ensemble des caractères où il est licite de couper un mot;
les valeurs par défaut de $:
sont \n -
(newline, espace et tiret).
Le marqueur spécial ~~
sur la dernière ligne signifie
"Continuer d'écrire jusqu'à épuisement de toute les variables"
($body
et $header{Subject}
dans ce
cas). Ceci est utile dans le cas ou vous ne connaissez pas exactement
la longueur du texte, mais que vous voulez être certain d'imprimer la
totalité de l'information. Vous pouvez placer ~~
n'importe où dans le format, mais il vaut mieux le placer à un endroit
bien visible (le début d'une ligne est presque toujours la meilleure
place).
printf()
qui auraient été plus facile à
écrire et à relire si l'auteur avait utilisé des formats. Si vous avez
à produire rapidement des rapports contenant un nombre important de
données tabulées, les formats sont un outil extrêmement puissant.
Traduction de ;login: Vol. 20 No. 3, June 1995.
Dernière édition: 21 mars 1998 phb Original 11/27/96ah |
|