| You are here: Inicio > Inmersión en Python > Procesamiento de HTML > Presentación de dialect.py | << >> | ||||
Inmersión en PythonPython de novato a experto |
|||||
Dialectizer es una descendiente sencilla (y tonta) de BaseHTMLProcessor. Hace una serie de sustituciones sobre bloques de texto, pero se asegura de que cualquier cosa dentro de un bloque <pre>...</pre> pase sin alteraciones.
Para manejar los bloques <pre> definimos dos métodos en Dialectizer: start_pre y end_pre.
def start_pre(self, attrs):
self.verbatim += 1
self.unknown_starttag("pre", attrs)
def end_pre(self):
self.unknown_endtag("pre")
self.verbatim -= 1 
| Se invoca start_pre cada vez que SGMLParser encuentra una etiqueta <pre> en la fuente HTML (en breve, veremos exactamente cómo sucede esto). El método toma un único parámetro, attrs que contiene los atributos de la etiqueta (si los hay). attrs es una lista de tuplas clave/valor, igual que la que toma unknown_starttag. | |
| En el método reset, inicializamos un atributo de datos que sirve como contador para las etiquetas <pre>. Cada vez que encontramos una etiqueta <pre>, incrementamos el contador; cada vez que encontramos una etiqueta </pre>, decrementamos el contador. Podríamos usar esto como indicador y ponerlo a 1 y reiniciarlo a 0 pero es igual de sencillo de esta manera, y además previene el caso extraño (pero posible) de etiquetas <pre> anidadas. En un minuto, veremos cómo darle uso a este contador. | |
| Esto es el único procesamiento especial que hacemos para las etiquetas <pre>. Ahora pasamos la lista de atributos a unknown_starttag de manera que pueda hacer el procesamiento por omisión. | |
| Se invoca end_pre cada vezq ue SGMLParser encuentra una etiqueta </pre>. Dado que las etiquetas de final no pueden contener atributos, el método no admite parámetros. | |
| Primero querrá hacer el procesamiento por omisión, igual que con cualquier otra etiqueta de final. | |
| En segundo luegar, decrementamos el contador para avisar que se ha cerrado este bloque <pre>. |
Llegados aquí, merece la pena ahondar un poco más en SGMLParser. He asegurado repetidamente (y por ahora lo ha tomado a fe) que SGMLParser busca e invoca métodos específicos para cada etiqueta, si existen. Por ejemplo, acaba de ver la definición de start_pre y end_pre para manejar <pre> y </pre>. ¿Pero cómo sucede esto? Bueno, no es magia, es sólo buena programación en Python.
def finish_starttag(self, tag, attrs):
try:
method = getattr(self, 'start_' + tag)
except AttributeError:
try:
method = getattr(self, 'do_' + tag)
except AttributeError:
self.unknown_starttag(tag, attrs)
return -1
else:
self.handle_starttag(tag, method, attrs)
return 0
else:
self.stack.append(tag)
self.handle_starttag(tag, method, attrs)
return 1
def handle_starttag(self, tag, method, attrs):
method(attrs) 
| En este lugar, SGMLParser ya ha encontrado una etiqueta de inicio y ha obtenido la lista de atributos. La única cosa que le queda por hacer es averiguar si hay algún método manejador especial para esta etiqueta, o si debería acudir al método por omisión (unknown_starttag). | |
| La “magia” de SGMLParser no es nada más que su vieja amiga, getattr. Lo que puede que no haya advertido hasta ahora es que getattr buscará los métodos definidos en los descendientes de un objeto, aparte de en los del propio objeto. Aquí el objeto es self, la implementación en sí. De manera que si tag es 'pre', esta llamada a getattr buscará un método start_pre en la instancia actual, que es una instancia de la clase Dialectizer. | |
| getattr lanza una AttributeError si el método que busca no existe en el objeto (o en alguno de sus descendientes), pero esto no es malo, ya que encerró la llamada a getattr dentro de un bloque try...except y se captura de forma explícita la AttributeError. | |
| Dado que no se encontró un método start_xxx, también buscaremos el método do_xxx antes de rendirnos. Este esquema alternativo de nombres se usa generalmente para etiquetas autocontenidas, como <br>, que no tienen etiqueta de cierre correspondiente. Pero puede utilizar cualquiera de los dos sistemas de nombres. Como puede ver, SGMLParser prueba ambos por cada etiqueta (sin embargo no debería definir ambos métodos manejadores, start_xxx y do_xxx; sólo se llamará al método start_xxx). | |
| Otra AttributeError, lo que significa que la llamada a getattr para buscar do_xxx falló. Como no hemos encontrado un método start_xxx ni do_xxx para esta etiqueta, capturamos la excepción y recurrimos al método por omisión, unknown_starttag. | |
| Recuerde, los bloques try...except pueden tener una cláusula else, que se invoca si no se lanza ninguna excepción dentro del bloque try...except. Lógicamente, esto significa que encontramos un método do_xxx para esta etiqueta, así que vamos a invocarla. | |
| Por cierto, no se preocupe por los diferentes valores de retorno; en teoría significan algo, pero nunca se utilizan. No se preocupe tampoco por el self.stack.append(tag); SGMLParser lleva un seguimiento interno de si las etiquetas de inicio están equilibradas con las etiquetas de cierre adecuadas, pero no hace nada con esta información. En teoría, podría utilizar este módulo para validar si las etiquetas están equilibradas, pero probablemente no merece la pena, y es algo que se sale del ámbito de este capítulo. Tiene mejores cosas de qué preocuparse ahora mismo. | |
| No se invoca directamente a los métodos start_xxx y do_xxx; se pasan la etiqueta, método y atributos a esta función, handle_starttag, de manera que los descendientes puedan reemplazarla y cambiar la manera en que se despachan todas las etiquetas de inicio. No necesita ese nivel de control, así que vamos a limitarnos a usar este método para hacer eso, o sea llamar al método (start_xxx o do_xxx) con la lista de atributos. Recuerde, method es una función devuelta por getattr y las funciones son objetos (sé que se está cansando de oír esto, y prometo que dejaré de hacerlo en cuanto se me acaben las ocasiones de usarlo a mi favor). Aquí se pasa el objeto de la función como argumento al método de despacho, y a su vez este método llama a la función. En este momento, no hace falta saber qué función es, cómo se llama o dónde se define; sólo hace falta saber que la función se invoca con un argumento, attrs. |
Volvamos ahora a nuestro programa estipulado: Dialectizer. Cuando lo dejamos, estábamos en proceso de definir métodos manejadores específicos para las etiquetas <pre> y </pre>. Sólo queda una cosa por hacer, y es procesar bloques de texto con las sustituciones predefinidas. Para hacer esto, necesita reemplazar el método handle_data.
def handle_data(self, text):
self.pieces.append(self.verbatim and text or self.process(text)) 
| Se invoca a handle_data con un único argumento, el texto a procesar. | |
| En el ancestro BaseHTMLProcessor el método simplemente agregaba el texto al búfer de salida, self.pieces. Aquí la lógica es sólo un poco más complicada. Si estamos en mitad de un bloque <pre>...</pre>, self.verbatim tendrá un valor mayor que 0, y querremos poner el texto sin alterar en el búfer de salida. En otro caso, querremos invocar a otro método para procesar las sustituciones, y luego poner el resultado de eso en el búfer de salida. En Python esto se hace en una sola línea utilizando el truco and-or. |
Está cerca de comprender completamente Dialectizer. El único eslabón perdido es la naturaleza de las propias sustituciones de texto. Si sabe algo sobre Perl, sabrá que cuando se precisan sustituciones complicadas de texto, la única solución es usar expresiones regulares. Las clases de dialect.py definen una serie de expresiones regulares que operan sobre el texto entre las etiquetas HTML. Pero usted acaba de leer un capítulo entero sobre expresiones regulares. No querrá volver a pelearse con las expresiones regulares, ¿verdad? Dios sabe que no. Pienso que ya ha aprendido bastante en este capítulo.
<< Poner comillas a los valores de los atributos |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
Todo junto >> |