Dialectizer est un descendant simple (et
humoristique) de BaseHTMLProcessor. Il procède
à une série de substitutions dans un bloc de text, mais il s’assure
que tout ce qui est contenu dans un bloc
<pre>...</pre>
passe sans altération.
Pour traiter les blocs <pre>, nous
définissons deux méthodes dans Dialectizer:
start_pre et end_pre.
start_pre est appelé à chaque fois que
SGMLParser trouve une balise
<pre> dans le source
HTML. (Nous verrons bientôt comment cela se
fait.) La méthode prend un paramètre unique,
attrs, qui contient les attributs de la balise
(s’il y en a). attrs est une liste de tuples
clé/valeur, exactement le paramètre que prend unknown_starttag.
Dans la méthode reset, nous
initialisons un attribut qui sert de compteur de balises
<pre>. Chaque fois que nous rencontrons
une balise <pre>, nous incrémentons le
compteur ; chaque fois que nous rencontrons une balise fermante
</pre>, nous décrémentons le compteur.
(Nous pourrions l’utiliser simplement comme un drapeau en le
mettant à 1 puis à 0,
mais c’est aussi facile de cette manière et permet de traiter le
cas improbable (mais possible) de balises
<pre> imbriquées.) Dans une minute, nous
verrons comment ce compteur est mis à profit.
C’est tout, c’est le seul traitement particulier que nous
faisons pour la balise <pre>. Maintenant,
nous passons la liste des attributs à
unknown_starttag pour qu’il puisse faire le
traitement par défaut.
end_pre est appelé chaque fois que
SGMLParser trouve une balise fermante
</pre>. Comme les balises fermantes ne
peuvent pas contenir d’attributs, la méthode ne prend pas de
paramètre.
D’abord nous voulons effectuer le traitement par défaut,
comme pour toute balise fermante.
Ensuite, nous décrémentons notre compteur pour signaler que
ce bloc <pre> a été fermé.
Arrivé à ce point, il est temps d’examiner plus en détail
SGMLParser. J’ai prétendu jusqu’à maintenant (et
vous avez dû me croire sur parole) que SGMLParser
cherche et appelle des méthodes spécifiques pour chaque balise, si elles
existent. Par exemple, nous venons juste de voir la définition de
start_pre et end_pre pour
traiter <pre> et
</pre>. Mais comment est-ce que cela se produit
? Et bien, ce n’est pas de la magie, simplement de la bonne
programmation Python.
A ce niveau, SGMLParser a déjà trouvé
une balise ouvrante et lu la liste d’attributs. La seule chose
restant à faire est de trouver s’il existe un méthode spécifique
pour cette balise ou si il faut la traiter avec la méthode par
défaut (unknown_starttag).
La «magie» de SGMLParser
n’est rien de plus que notre vieille connaissance getattr.
Ce que vous n’aviez peut-être pas réalisé auparavant, c’est que
getattr peut trouver des méthodes définies
dans les descendants d’un objet aussi bien que dans l’objet
lui-même. Ici, l’objet est self, l’instance de
la méthode qui l’appelle. Donc si tag est
'pre', cet appel à getattr
cherchera une méthode start_pre dans
l’instance, qui est une instance de la classe
Dialectizer.
getattr déclenche une exception
AttributeError si la méthode qu’il cherche
n’existe pas dans l’objet (ni dans aucun de ses descendants), mais
cela n’est pas un problème puisque nous avons encadré l’appel à
getattr dans un bloc try...except
et explicitement intercepté l’exception
AttributeError.
Comme nous n’avons pas trouvé de méthode
start_xxx, nous cherchons également une
méthode do_xxx avant d’abandonner. Cette
désignation alternative est généralement utilisée pour les balises
isolées, telles que <br>, qui n’ont pas
de balise fermante correspondante. Vous pouvez utiliser l’une
comme l’autre, comme vous le voyez
SGMLParser essaie les deux pour chaque
balise (vous ne devez pas définir à la fois une méthode
start_xxx et une méthode
do_xxx pour la même balise, seule la méthode
start_xxx serait appelée).
Encore une exception AttributeError,
ce qui veut dire que l’appel à getattr a
échoué pour do_xxx. Comme nous n’avons trouvé
ni un méthode start_xxx ni une méthode
do_xxx pour cette balise, nous interceptons
l’exception et nous rabattons sur la méthode par défaut,
unknown_starttag.
Rappelez-vous que les blocs try...except
peuvent avoir une clause else, qui est appelée
si aucune exception n’est
déclenchée au cours du bloc
try...except. Logiquement, cela signifie que
nous avons trouvé une méthode
do_xxx pour la balise, donc nous allons
l’appeler.
Au fait, ne vous inquiétez pas pour ces valeurs de retour
différentes. En théorie elles signifient quelque chose mais elles
ne sont jamais réellement utilisées. Ne vous inquiétez pas non
plus de self.stack.append(tag),
SGMLParser vérifie trace en interne si vos
balises ouvrantes correspondent à des balises fermantes, mais il
ne fait rien non plus de cette information. En théorie, vous
pourriez utiliser ce module pour vérifier que vos balises sont
équilibrée, mais cela n’en vaut sans doute pas la peine et cela
dépasse le cadre de ce chapitre. Nous avons des choses bien plus
importantes auxquelles penser maintenant.
Les méthodes start_xxx et
do_xxx ne sont pas appelées directement. La
balise, la méthode et les attributs sont passés à cette fonction,
handle_starttag, de manière à ce que des
classes dérivées puissent la redéfinir pour changer la manière
dont toutes les balises ouvrantes sont
traitées. Nous n’avons pas besoin d’un tel niveau de contrôle,
donc nous laissons simplement cette méthode faire ce qu’elle doit
faire, c’est à dire appeler la méthode
(start_xxx ou do_xxx)
avec la liste des attributs. Rappelez-vous que
method est une fonction, retournée par
getattr et que les fonctions sont des objets
(je sais que vous en avez assez de l’entendre et je promets
d’arrêter de le dire dès que nous aurons cessé de trouver de
nouvelles manières de l’utiliser à notre avantage). Ici, l’objet
fonction est passé à cette méthode d’appel en argument et cette
méthode appelle la fonction. Arrivés là, nous n’avons pas à savoir
quelle est la fonction, quel est son nom ni l’endroit où elle a
été définie. La seule chose que nous devons savoir est qu’elle est
appelée avec un argument, attrs.
Revenons à nos moutons : Dialectizer. Nous
l’avons laissé au moment de définir des méthodes spéciales pour le
traitement des balises <pre> et
</pre>. Il n’y a plus qu’une chose à faire et
c’est de traiter les blocs de texte avec nos substitutions prédéfinies.
Pour cela nous devons redéfinir la méthode
handle_data.
Exemple 8.19. Redéfinition de la méthode handle_data
def handle_data(self, text):
self.pieces.append(self.verbatim and text or self.process(text))
handle_data est appelée avec un seul
argument, le texte à traiter.
Dans la classe parente BaseHTMLProcessor,
la méthode handle_data ne fait qu’ajouter le
texte au tampon de sortie, self.pieces. Ici, la
logique n’est qu’un petit peu plus compliquée. Si nous sommes au
milieu d’un bloc
<pre>...</pre>,
self.verbatim sera une valeur quelconque
supérieure à 0, nous voulons alors ajouter le
texte au tampon de sortie sans modification. Sinon, nous
appellerons une méthode spécifique pour appliquer les
substitutions, puis ajouterons le résultat de ce traitement dans
le tampon de sortie. En Python, cela se
fait en une ligne, en utilisant l’astuce
and-or.
Nous sommes près de comprendre complètement
Dialectizer. Le seul chaînon manquant concerne la
nature des substitutions de texte elle-mêmes. Si vous connaissez un peu
de Perl, vous savez que lorsque des
substitutions de texte complexes sont nécessaires, la seule vrai
solution est d’utiliser les expressions régulières. Les classes suivantes de dialect.py définissent une série d'expressions régulières qui opèrent sur le texte entre les balises HTML. Mais nous venons d'avoir un chapitre entier sur les expressions régulières. Je pense que nous en avons assez appris pour un chapitre.