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

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.

  1. 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".

  2. 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...

  3. 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 :

  1. renommer le fichier Javascript en utilisant votre (vos) nom(s),

  2. télécharger le fichier HTML,

  3. modifier la ligne <script src="./tags-Brown.js"></script> pour refléter le nom de votre fichier Javascript,

  4. modifier uniquement le fichier Javascript,

  5. 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 &lt;.

  1. Modifiez la fonction process_line pour qu'elle remplace les "<" par des &lt;.

    Notes

    • replace est une méthode sur les objets "chaine de caractères". Pour une variable s, on utilise donc s.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...

  2. Ajoutez le code pour remplacer ">" par &gt; et "&" par &amp;.

  3. N'oubliez pas de tester ! Par exemple, sur l'entrée

    if (0xff & (n<<2))

    vous devriez obtenir

    if (0xff &amp; (n&lt;&lt;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.

  1. É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.)

  2. Ajoutez dans la fonction principale, juste avant l'appel à la fonction process_line, un test utilisant is_comment pour ignorer les lignes commentaire.

  3. 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"

Reconnaissance de chaines

Refaites la question précédente en remplaçant l'appel à votre fonction is_comment par un appel à :

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 :

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 :

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.

  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

    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 /)

  2. 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 ./TP2/pieuvre-small.jpg

    Choisissez votre expression régulière avec soin afin de ne repérer que les images avec les extensions standards.

  3. 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 ./TP2/pieuvre-small.jpg et un céphalopode décapode ./TP2/calmar-small.jpg

  4. 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).

  1. 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

  2. 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

  1. 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 !

  2. Ajoutez le code nécessaire à la fonction process_line pour ajouter les balises <b> et </b> propres au style "gras".

  3. 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"

  1. 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é.

  2. Expliquez avec vos propres mots l'origine du problème.

    Indice : on dit que les expressions régulières sont "gourmandes" ("greedy" en anglais).

  3. 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"

  1. 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.

  2. 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...

  3. 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

Par contre :

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é.

  1. 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).

  2. Testez votre code sur des exemples et modifiez vos expressions régulières pour prendre en compte cette nouvelle fonctionnalité.

  3. 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

  1. Ajoutez le style italique avec la syntaxe

    Voici du texte //en italique//...

    Les balises HTML pour l'italique sont <i>...</i>.

  2. 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
  3. 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)

  1. 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 &gt; 0) and (y//2 &gt; 0):
        p = x**y</pre>
    

    pour afficher

    if (x//2 > 0) and (y//2 > 0):
        p = x**y
  2. 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

  3. Ajoutez la gestion des tableaux.

  4. ...