SGMLParser ne produit rien de lui même.
Il ne fait qu’analyser et appeler une méthode pour chaque élément
intéressant qu’il trouve, mais les méthodes ne font rien.
SGMLParser est un
consommateur de HTML : il
prend du code HTML et le décompose en petits
éléments structurés. Comme nous l’avons vu dans la section précédente, on peut dériver
SGMLParser pour définir une classe qui trouve
des balises spécifiques et produit quelque chose d’utile, comme une
liste de tous les liens d’une page web. Nous allons maintenant aller
un peu plus loin en définissant une classe qui prends tout ce que
SGMLParser lui envoi et reconstruit
entièrement le document HTML. En termes techniques,
cette classe sera un producteur de
HTML.
BaseHTMLProcessor est dérivé de
SGMLParser et fournit les 8 méthodes de prise en
charge essentielles : unknown_starttag,
unknown_endtag,
handle_charref,
handle_entityref,
handle_comment, handle_pi,
handle_decl et
handle_data.
reset est appelé par
SGMLParser.__init__ initialise
self.pieces en une liste vide avant d’appeler la méthode
ancêtre. self.pieces est une donnée attribut
qui contient les éléments du document HTML que
nous assemblons. Chaque méthode de prise en charge va reconstruire
le code HTML que
SGMLParser a analysé et chaque méthode
ajoutera la chaîne résultante à self.pieces.
Notez que self.pieces est une liste. Vous
pouvez être tenté de la définir comme une chaîne et de lui ajouter
simplement chaque élément. Cela fonctionnerait mais
Python gère les listes de manière bien
plus efficiente.[4]
Comme BaseHTMLProcessor ne définit
aucune méthode pour des balises spécifiques (comme la méthode
start_a de urllister.py),
SGMLParser appelera
unknown_starttag pour chaque balise de début.
Cette méthode prend en paramètre la balise
(tag) et la liste des paires nom/valeurs de ses
attributs (attrs), reconstruit le code
HTML originel et l’ajoute à
self.pieces. Le formatage de
chaîne ici est un peu étrange, nous l’expliquerons (ainsi que la fonction locals à l'air étrange) dans la prochaine section.
Reconstruire les balises de fin est beaucoup plus simple, il
suffit de prendre le nom de la balise est de l’encadrer de
</...>.
Lorsque SGMLParser trouve une
référence de caractère, il appelle
handle_charref avec la référence. Si le
document HTML contient la référence
 , ref vaudra
160. La reconstruction de la référence de
caractère originelle ne demande que d’encadrer
ref par &#...;.
Les références d’entité sont semblables aux références de
caractères, mais sans le signe dièse. La reconstruction de la
référence d’entité originelle demande d’encadrer
ref par &...;. (En fait,
comme un lecteur savant me l’a fait remarquer, c’est un peu plus
compliqué que ça. Seulement certaines entités standard du
HTML finissent par un point-virgule.
Heureusement pour nous, l’ensemble des entités standards est
défini dans un dictionnaire dans un module
Python appelé htmlentitydefs. C’est l’explication
de l’instruction if supplémentaire.)
Les blocs de texte sont simplement ajouté à
self.pieces sans modification.
Les commentaires HTML sont encadrés par
<!--...-->.
Les instructions de traitement sont encadrés par
<?...>.
La spécification HTML exige que tous les
éléments non-HTML (comme le
JavaScript côté client) soient compris dans
des commentaires HTML, mais toutes les pages web ne
le font pas (et les navigateurs web récents ne l’exigent pas).
BaseHTMLProcessor, lui, l’exige, si le script
n’est correctement encadré dans un commentaire, il sera analysé comme
s’il était du code HTML. Par exemple, si le script
contient des signes inférieurs à ou égal,
SGMLParser peut considérer à tort qu’il a
trouvé des balises et des attributs. SGMLParser
convertit toujours les noms de balises et d’attributs en minuscules,
ce qui peut empêcher la bonne exécution du script et
BaseHTMLProcessor entoure toujours les valeurs
d’attributs entre guillemets (même si le document
HTML n’en utilisait pas ou utilisait des guillemets
simples), ce qui empêchera certainement l’exécution du script.
Protégez toujours vos script côté client par des commentaires
HTML.
Exemple 8.9. Sortie de BaseHTMLProcessor
def output(self): """Return processed HTML as a single string"""return"".join(self.pieces)
Voici l’unique méthode de
BaseHTMLProcessor qui n’est jamais appelée
par son ancêtre, SGMLParser. Comme les
méthodes de prise en charge stockent le HTML
reconstitué dans self.pieces, cette fonction
est nécessaire pour assembler toutes ces pièces en une chaîne
unique. Comme noté précédemment, Python
est bon pour gérer les listes et moyens pour les chaînes, nous ne
créons donc la chaîne seulement quand un utilisateur la réclame
explicitement.
Si vous préférez, vous pouvez plutôt utiliser la méthode
join du module string :
string.join(self.pieces, "")
[4] La raison pour laquelle
Python gère mieux les listes que
les chaînes est que les listes sont modifiables et que les
chaînes sont non-modifiables. Cela signifie qu’ajouter à une
liste ne fait qu’ajouter l’élément et mettre à jour l’index.
Mais comme les chaînes ne peuvent pas être changées après
avoir été créées, du code tel que s = s +
newpiece créera une nouvelle chaîne à partir de la
concaténation de l’original et du nouvel élément, puis jettera
la chaîne originelle. Cela implique une gestion mémoire
coûteuse et le coût augmente avec la taille de la chaîne, donc
faire s = s + newpiece à l’intérieur d’une
boucle est fatal. En termes techniques, ajouter
n éléments à une liste est
O(n), alors qu’ajouter n
éléments à une chaîne items est
O(n2).