Consignes
Le rendu de TP se fera uniquement à travers l'interface web TPLab. Vous devrez fournir un unique fichier .py (ou .js) contenant le code demandé.
Attention: n'oubliez pas de renommer le fichier avec votre (vos) nom(s).
Liens utiles
-
un site avec de nombreuses informations et exemples sur les expressions régulières http://www.regular-expressions.info
-
encadrant de TP : Pierre.Hyvernat@univ-smb.fr
-
documentation du module re ("regular expression") de Python -
-
fichier fourni (HTML) à utiliser avec le fichier Javascript
-
documentation de la classe RegEx ("Regular Expression") de Javascript
1. Préliminaires
Vous pouvez faire ce TP en utilisant le langage Python ou bien le langage Javascript.
Si vous hésitez, Python est le langage conseillé...
1.1. Lire les lignes sur l'entrée standard en Python
La fonction suivante permet de récupérer les lignes (une par une) sur
l'entrée standard, de leur appliquer une fonction de traitement
(process_line
) et d'afficher le résultat sur la sortie standard.
def main():
for line in sys.stdin:
# suppression des symboles de fin de ligne
line = line.strip("\n\r")
# traitement de la ligne
line = process_line(line)
# affichage de la ligne traitée
print(line)
Par exemple, avec la fonction process_line
suivante
def process_line(line):
line = line.upper()
return line
on obtient l'exécution suivante :
$ ./tags-Brown.py < tags-Brown.py
...
...
DEF MAIN():
FOR LINE IN SYS.STDIN:
# SUPPRESSION DES SYMBOLES DE FIN DE LIGNE
LINE = LINE.STRIP("\N\R")
# TRAITEMENT DE LA LIGNE
LINE = PROCESS_LINE(LINE)
# AFFICHAGE DE LA LIGNE TRAITÉE
PRINT(LINE)
...
...
Le fichier fourni que vous devrez compléter agit comme un filtre : il traite l'entrée standard et affiche le résultat sur la sortie standard.
-
Sans aucun argument, il lit des lignes au clavier et affiche les résultats au fur et à mesure. Ceci est pratique pour tester interactivement votre programme sur de petites entrées.
$ python3 tags-Brown.py J'écris une ligne J'ÉCRIS UNE LIGNE et une deuxième ligne ET UNE DEUXIÈME LIGNE
Pour quitter le programme, il faut utiliser "Control-d".
-
En utilisant les redirections, le programme peut aussi lire des lignes dans un fichier, et sauvegarder le résultat dans un autre fichier.
$ python3 tags-Brown.py < tags-Brown.py > resultat.html
Dans ce cas, les lignes seront lues dans le fichier tags-Brown.py, et le résultat sera sauvé dans le fichier resultat.html.
Ceci est plus approprié pour des tests plus complets, que vous allez faire de nombreuses fois...
-
Pour tester les exemples avancés, redirigez la sortie standard dans un fichier resultat.html que vous ouvrirez dans votre navigateur favori en mode normal, et en mode "visualisation des sources".
1.2. Lire les lignes d'un textarea en Javascript
La fonction suivante permet de récupérer les lignes d'un objet "textarea" dans un document HTML, de leur appliquer
une fonction de traitement (process_line
) et d'afficher le
résultat dans un autre objet "textarea".
function main() {
// élément contenant le texte à traiter
var input_text = document.getElementById("input_text");
// lignes à traiter
var input_lines = input_text.value.split("\n");
var nb_lines = input_lines.length;
// traitement
var output_lines = []; // résultat
var i, line;
for (i=0; i<nb_lines; i++) {
line = input_lines[i];
// traitement de la ligne
line = process_line(line);
// sauvegarde de la ligne traitée
output_lines.push(line);
}
// insertion du résultat dans les éléments correspondants
// résultat brut, dans un "textarea"
var output_text = document.getElementById("output_text");
output_text.value = output_lines.join("\n");
}
En utilisant la fonction process_line
suivante
function process_line(line) {
line = line.toUpperCase();
return line;
}
et en copiant/collant un morceau de code dans la page web fournie, vous obtiendrez par exemple le résultat
FOR (I=0; I<NB_LINES; I++) {
// TRAITEMENT DE LA LIGNE
LINE = PROCESS_LINE(INPUT_LINES[I]);
// SAUVEGARDE DE LA LIGNE TRAITÉE
OUTPUT_LINES.PUSH(LINE);
}
Pour utiliser le fichier fourni, vous devez :
-
renommer le fichier Javascript en utilisant votre (vos) nom(s),
-
télécharger le fichier HTML,
-
modifier la ligne
<script src="./tags-Brown.js"></script>
pour refléter le nom de votre fichier Javascript, -
modifier uniquement le fichier Javascript,
-
lors des test, le résultat du traitement ainsi que le rendu HTML seront affiché automatiquement.
1.3. Une première question
Récupérez les fichiers, renommez les, et ajoutez le code nécessaire pour passer les lignes lues en majuscules.
2. Implantation d'un langage de balisage léger
Introduction
Les langages de balisage léger sont des langages de balisage au même titre que HTML, XML, troff, TeX, etc. L'adjectif léger indique que les balises sont faites pour rester "lisibles". Ceci est particulièrement intéressant lorsqu'on édite les sources d'un document.
Par exemple, ce sujet de TP est généré à partir d'un tels langage de balisage léger...
Les wikis sont des exemples de langages de balisage légers : au lieu d'écrire quelque chose comme
<h1>Classification des fruits et légumes</h1>
<table>
<tr>
<td> carottes </td>
<td> légume </td>
</tr>
<tr>
<td> poireau </td>
<td> légume </td>
</tr>
<tr>
<td> poire </td>
<td> fruit </td>
</tr>
<tr>
<td> tomate </td>
<td> ? </td>
</tr>
<tr>
<td> fraise </td>
<td> ? </td>
</tr>
</table>
il suffit d'écrire
= Classification des fruits et légumes = | carottes | légume | poireau | légume | poire | fruit | tomate | ? | fraise | ?
L'objectif de ce TP est d'implanter un "compilateur" vers HTML pour un langage de balisage léger inspiré de txt2tags.
Les exemples sont donnés de la manière suivante :
Le texte rentré par l'utilisateur est donné **verbatim** dans un cadre avec des tirets.
Le texte renvoyé par
le programme est donné <b>en donnant le code HTML</b> dans
un cadre gris avec <u>coloration syntaxique</u>
pour faciliter la visualisation.
Le résultat tels qu'il doit s'afficher dans un navigateur est donné dans un cadre simple.
2.1. Premières fonctionnalités
Certains caractères ont un sens spécial en HTML : "<", ">" et
"&". Pour afficher un "<", il faut le remplacer par la séquence
<
.
-
Modifiez la fonction
process_line
pour qu'elle remplace les "<" par des<
.-
en Python, vous pouvez utiliser la méthode
str.replace(old, new)
-
en Javascript, vous pouvez utiliser la méthode
str.replace(old, new)
Notes
-
replace
est une méthode sur les objets "chaine de caractères". Pour une variables
, on utilise doncs.replace(...)
. -
replace
ne modifie pas la chaine de caractères, mais renvoie une nouvelle chaine. -
Vous pouvez supprimer le code qui passe chaque ligne en majuscules...
-
Vous n'avez pas besoin d'utiliser d'expression régulière pour cette question...
-
-
Ajoutez le code pour remplacer ">" par
>
et "&" par&
. -
N'oubliez pas de tester ! Par exemple, sur l'entrée
if (0xff & (n<<2))
vous devriez obtenir
if (0xff & (n<<2))
qui sera rendu par le navigateur comme
if (0xff & (n<<2))
Il est pratique de pouvoir mettre des commentaires dans les sources. Pour notre langage, les commentaires auront la forme
%%% Ceci est un commentaire
Formellement, les lignes commentaires peuvent commencer par un nombre arbitraire d'espaces et tabulations, et doivent ensuite avoir trois symboles "%" consécutifs.
-
Écrivez une fonction
is_comment(line)
qui teste si une ligne est un commentaire.Cette fonction n'utilisera pas les expressions régulières mais seulement des opérations standards sur les chaines (taille, etc.)
-
Ajoutez dans la fonction principale, juste avant l'appel à la fonction
process_line
, un test utilisantis_comment
pour ignorer les lignes commentaire. -
N'oubliez pas de tester. Par exemple, sur l'entrée
Cette ligne est normale, % ainsi que celle ci. %%% cette ligne là, est, elle, un commentaire ! Cette ligne contient trois "%": %%% mais ce n'est pas un commentaire %%%%% Remarque : les commentaires peuvent commencer par 4 "%". % % % % Les "%" des commentaires doivent être consécutif...
vous devriez obtenir
Cette ligne est normale, % ainsi que celle ci. Cette ligne contient trois "%": %%% mais ce n'est pas un commentaire % % % % Les "%" des commentaires doivent être consécutif...
2.2. Utilisation simple des expressions régulières
Littéraux "expressions régulières"
-
En Python, une expression régulière est simplement une chaine, interprétée comme une expression régulière. Ainsi, pour vérifier si la chaine
chaine
est reconnue par l'expression régulière ^(aa)*$, on utiliseif re.match("^(aa)*$", chaine): ...
Il est également possible de "compiler" les expressions régulières afin d'obtenir des objets, mais nous ne l'utiliserons pas dans ce TP.
-
En Javascript, une expression régulière est donnée en remplaçant les guillemets (utilisés pour les chaines) par des symboles slash "/". Ainsi, pour vérifier si la chaine
chaine
est reconnue par l'expression régulière ^(aa)*$, on utiliseif (chaine.match(/^(aa)*$/)) { ... }
Reconnaissance de chaines
Refaites la question précédente en remplaçant l'appel à votre fonction
is_comment
par un appel à :
-
en Python, la fonction
re.match(regex, str)
qui permet de tester si une expression régulièreregex
reconnait une chainestr
, -
en Javascript, la fonction
str.match(regex)
qui permet de tester si une expression régulièreregex
reconnait une chainestr
.
Dans les deux cas, vous pouvez pour le moment supposer que la valeur de retour de ces fonctions est un booléen.
Note: ne supprimez pas le code de votre fonction
is_comment
.
En utilisant :
-
en Python, la fonction
re.sub(regex, repl, str)
qui permet de remplacer, dans la chainestr
, chaque sous-chaine reconnue parregex
par la chainerepl
, -
en Javascript, la fonction
str.replace(regex, repl)
qui permet de remplacer, dans la chainestr
, chaque sous-chaine reconnue parregex
par la chainerepl
,
remplacez les adresses emails par la chaine "EMAIL".
L'expression suivante permet de reconnaitre la plupart des adresses email,
tout en restant assez simple :
"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}
".
Pour autoriser les majuscules dans les adresses email, vous pouvez :
-
soit les rajouter explicitement dans la regex,
-
soit ajouter l'option "insensibilité à la case" en utilisant
-
"(?i)regex"
en Python -
/regex/i
en Javascript
-
-
en Javascript, pour préciser qu'il faut remplacer toutes les sous chaine reconnue (et pas simplement la première), il faut également ajouter l'option "globale" :
/regex/g
, ou/regex/gi
si vous souhaitez les deux options en même temps. (Vous pouvez en profiter pour corriger les premiers appels àstr.replace(...)
de la première question.)
Note: n'oubliez pas de tester !
2.3. Utilisation intermédiaire des expressions régulières : les groupes
La question précédente permettait de remplacer des chaines (celles reconnues par une expression régulière) par une chaine constante. En général, on souhaite remplacer des chaines (celles reconnues par une expression régulière) par une chaine qui dépend de la chaine d'origine.
Pour cela, on peut utiliser les groupes : chaque morceau d'une expression régulière entre parenthèses est un groupe, et les morceaux de chaines reconnus par ces groupes sont sauvegardés. On peut y faire référence en utilisant leur numéro, c'est à dire le numéro de la parenthèse ouvrante correspondante.
-
En Python, les groupes sont notés avec
\N
. Pour éviter que le caractère\
ne soit interprété comme un caractère d'échappement de chaine, il faut-
soit échapper le caractère
\
: on écrit alors\\1
pour le premier groupe -
soit donner toute la chaine en mode raw (qui supprime l'interprétation du caractère d'échappement \) en précédent le guillemet ouvrant de la lettre r: on écrit alors
r"...\1..."
-
-
En Javascript, les groupes ont deux notations :
-
dans la regex elle même, les groupes sont notés avec
\1
, -
dans la chaine de remplacement, les groupes sont notés avec
$1
.
-
-
En utilisant la même fonction que précédemment, mais en utilisant un groupe, remplacer chaque url comme
Mon site se trouve là : http://lama.univ-smb.fr/~hyvernat.
par le code HTML pour le lien correspondant:
Mon site se trouve là : <a href="http://lama.univ-smb.fr/~hyvernat">http://lama.univ-smb.fr/~hyvernat</a>.
Le navigateur affichera donc
Mon site se trouve là : http://lama.univ-smb.fr/~hyvernat.
Choisissez votre expression régulière soigneusement : vous devez par exemple reconnaitre
-
https://duckduckgo.com/?q=expressions+r%C3%A9guli%C3%A8res+site%3Alinuxfr.org
-
http://linuxfr.org, qui ne contient surement pas la virgule "," qui suit l'url
mais il ne faut pas reconnaitre
-
www.google.com (absence du http://)
-
htp://www.google.com (htp au lieu de http)
-
http:/www.google.com (il manque un /)
-
En utilisant le même mécanisme, remplacez les images locales, données entre crochets. Par exemple, l'entrée
voici une image de céphalopode [./TP2/pieuvre-small.jpg]
donnera le code HTML suivant:
voici une image de céphalopode <img src="./TP2/pieuvre-small.jpg"/>
qui sera affiché comme
voici une image de céphalopode
Choisissez votre expression régulière avec soin afin de ne repérer que les images avec les extensions standards.
-
Vérifiez que le code suivant
voici un céphalopode octopode [./TP2/pieuvre-small.jpg] et un céphalopode décapode [./TP2/calmar-small.jpg]
donne le résultat attendu, à savoir
voici un céphalopode octopode et un céphalopode décapode
-
Expliquez le résultat obtenu lorsque vous essayez de donner une image distante, comme
les otaries [http://www.lama.univ-smb.fr/~hyvernat/Enseignement/1718/info502/TP2/otarie-small.jpg] ont de petites oreilles
(Note: il n'est pas nécessaire de corriger le problème...)
2.4. Remplacement complexes : arguments fonctionnels
L'utilisation des groupes n'est pas toujours suffisante pour les
remplacements complexes. Au lieu de prendre comme second argument une
chaine (éventuellement avec des groupes), la fonction de remplacement
(re.sub(...)
en Python ou str.replace(...)
en
Javascript) peut également prendre une fonction comme second argument.
Ainsi, re.sub(regex, F, str)
(Python) ou
str.replace(regex, F)
va remplacer chaque sous-chaine (appelée
r
) de str
reconnue par regex
par
F(r)
.
-
En Python,
r
est en fait un match object. La seule méthode que vous aurez à utiliser sur ces objets est la méthoder.group(N)
qui prend renvoie le contenu du groupe numéroN
.F
est donc une fonction à un argument qui renvoie une chaine de caractères. -
En Javascript, la fonction
F
prend plusieurs arguments :-
le premier est la sous-chaine
r
en entier -
les arguments suivants sont le morceaux de
r
correspondant aux groupes de l'expression régulière.
F
est donc une fonction à plusieurs arguments qui renvoie une chaine de caractères. -
-
En utilisant une fonction comme second argument de la fonction de remplacement, passez les lignes qui commencent par "TODO:" (éventuellement précédé de caractères blancs) en majuscules.
Ainsi, la ligne
TODO: finir le sujet du TP3
sera rendu comme
TODO: FINIR LE SUJET DU TP3
-
Remplacez les titres de la forme
= Titre = == Sous-titre == === Sous-sous-titre === ==== Sous-sous-sous-titre ==== ===== Sous-sous-sous-sous-titre ===== ==a== = Preuve que P=NP =
par les balises HTML correspondantes:
<h1> Titre </h1> <h2> Sous-titre </h2> <h3> Sous-sous-titre </h3> <h4> Sous-sous-sous-titre </h4> <h5> Sous-sous-sous-sous-titre </h5> <h2>a</h2> <h1> Preuve que P=NP </h1>
Pour ceci, vous n'utiliserez qu'une seule expression régulière, ainsi que le mécanisme précédent.
-
les titres ne peuvent pas apparaitre au milieu d'une ligne, mais doivent se trouver tout seuls sur leur propre ligne...
-
attention, les lignes
== ceci n'est pas un titre === == ceci n'est pas un titre non plus... =
ne sont pas des titres !
-
2.5. Utilisation avancée des expressions régulières
Nous allons maintenant gérer les différents styles de polices de caractères
-
le gras, indiqué par **le gras**
-
le souligné, indiqué par __le souligné__.
-
Proposez une expression régulière pour reconnaitre les parties de texte en gras.
Attention :
-
le premier caractère après le second * à gauche ne peut pas être un caractère blanc
-
le dernier caractère avant le premier * à droite ne peut pas être un caractère blanc
-
il peut n'y avoir qu'un seul caractère en gras, comme ici.
-
la chaine peut contenir des caractères * simples:
Le nombre 91 n'est pas premier : **97 = 13*7** !
donnera
Le nombre 91 n'est pas premier : 97 = 13*7 !
-
-
Ajoutez le code nécessaire à la fonction
process_line
pour ajouter les balises<b>
et</b>
propres au style "gras". -
Adaptez votre expression régulière pour le souligné, et ajoutez le code nécessaire à la fonction
process_line
. Les balises pour le gras sont<u>...</u>
.
Politique du "longuest match"
-
Testez votre code sur la ligne suivante :
Une __grande ligne__ qui contient du **gras** et du __souligné__.
Le résultat attendu est
Une grande ligne qui contient du gras et du souligné.
-
Expliquez avec vos propres mots l'origine du problème.
Indice : on dit que les expressions régulières sont "gourmandes" ("greedy" en anglais).
-
Recherchez une solution au problème et expliquez sa mise en œuvre.
Autres problèmes
On a parfois envie de donner un nom aux liens. Il est beaucoup plus lisible d'avoir un lien comme "les regex sur le site linuxfr" plutôt qu'un lien comme "https://duckduckgo.com/?q=expressions+r%C3%A9guli%C3%A8res+site%3Alinuxfr.org"
-
Remplacez les liens avec titre
Voici un lien permettant de [retrouver son chemin http://www.perdu.com].
par le code HTML correspondant
Voici un lien permettant de <a href="http://www.perdu.com">retrouver son chemin</a>.
qui sera rendu par le navigateur comme
Voici un lien permettant de retrouver son chemin.
-
Décrivez le problème constaté et proposez une solution.
Note: si vous n'avez pas le temps d'implanter votre solution, décrivez la en commentaires...
-
Est-ce que cela apporte une solution au problème contacté pour l'insertion d'images distantes ?
Proposez une solution pour passer en rouge les dates jj-mm-aaaa invalides. Par exemple
-
12-34-5678
-
11-10-2017
-
31-11-2012 (il n'y a pas de 31 novembre)
-
29-02-1900 (1900 n'est pas une année bissextile)
-
...
Par contre :
-
1024-16-1006 = 2 (Ce n'est assurément pas une date, car le jour est codé sur 4 chiffres...)
Vous pouvez passer du texte en rouge grâce aux balises <font
color="red">...</font>
.
2.6. Gestion des paragraphes et expressions régulières multilignes
Actuellement, le traitement se fait ligne par ligne. Si on écrit
Une __grande phrase soulignée__.
la fonction process_line
ne détectera pas l'utilisation du
souligné.
-
Modifiez la fonction principale pour qu'elle stocke les lignes consécutives, et n'appelle la fonction
process_line
qu'en fin de paragraphe.Note : un paragraphe se termine lorsqu'une ligne ne contenant que des espaces / tabulations est rencontrée (ou lorsqu'on atteint la dernière ligne).
-
Testez votre code sur des exemples et modifiez vos expressions régulières pour prendre en compte cette nouvelle fonctionnalité.
-
N'oubliez pas de commenter votre code...
-
Vous ne devez pas supprimer les sauts de ligne des chaines de caractères. Le résultat de la l'entrée précédente sera
Une <u>grande phrase soulignée</u>.
-
Il faudra probablement se renseigner sur l'option DOTALL des expressions régulières (Python) ou sur une manière alternative d'autoriser les sauts de lignes à être reconnus.
-
2.7. Pour aller plus loin
-
Ajoutez le style italique avec la syntaxe
Voici du texte //en italique//...
Les balises HTML pour l'italique sont
<i>...</i>
. -
Comment expliquez vous le résultat sur le texte
Et voila __quelques__ lignes avec du **gras //italique//** et même des __liens__ vers http://en.wikipedia.org, http://fr.wikipedia.org et http://sv.wikipedia.org
-
Proposez une solution pour corriger l'interaction entre l'italique et les url.
Remarques:
-
il n'est pas forcément nécessaire d'implanter votre solution, vous pouvez la décrire dans un commentaire;
-
votre solution ne doit pas nécessairement utiliser les expressions régulières.
-
(bonus)
-
Ajoutez une gestion des blocs de code. Par exemple, l'entrée
~~~~~ if (x//2 > 0) and (y//2 > 0): p = x**y ~~~~~
devra générer le bloc de code HTML
<pre>if (x//2 > 0) and (y//2 > 0): p = x**y</pre>
pour afficher
if (x//2 > 0) and (y//2 > 0): p = x**y
-
Ajoutez la gestion des listes : l'entrée
Liste de course : - Monoprix : - sacs poubelles - shampoing - allumettes - Castorama : - tournevis **cruciforme** - vernis - colle à bois
devra générer du code comme (la position et présence des sauts de ligne n'est pas spécifiée)
Liste de course : <ul> <li> Monoprix : <ul> <li> sacs poubelles </li> <li> shampoing </li> <li> allumettes </li> </ul> </li> <li> Castorama : <ul> <li> tournevis <b>cruciforme</b> </li> <li> vernis </li> <li> colle à bois </li> </ul> </li> </ul>
et le rendu sera
Liste de course :
-
Monoprix :
-
sacs poubelles
-
shampoing
-
allumettes
-
-
Castorama :
-
tournevis cruciforme
-
vernis
-
colle à bois
-
-
-
Ajoutez la gestion des tableaux.
-
...