10.5. Creación de manejadores diferentes por tipo de nodo

El tercer consejo útil para procesamiento de XML implica separar el código en funciones lógicas, basándose en tipos de nodo y nombres de elemento. Los documentos XML analizados se componen de varios tipos de nodos que representa cada uno un objeto de Python. El nivel raíz del documento en sí lo representa un objeto Document. El Document contiene uno o más objetos Element (por las etiquetas XML), cada uno de los cuales contiene otros objetos Element, Text (para zonas de texto) o Comment (para los comentarios). Python hace sencillo escribir algo que separe la lógica por cada tipo de nodo.

Ejemplo 10.17. Nombres de clases de objetos XML analizados

>>> from xml.dom import minidom
>>> xmldoc = minidom.parse('kant.xml') 1
>>> xmldoc
<xml.dom.minidom.Document instance at 0x01359DE8>
>>> xmldoc.__class__                   2
<class xml.dom.minidom.Document at 0x01105D40>
>>> xmldoc.__class__.__name__          3
'Document'
1 Asuma por un momento que kant.xml está en el directorio actual.
2 Como vio en Sección 9.2, “Paquetes”, el objeto devuelto al analizar un documento XML es un Document, tal como se define en minidom.py en el paquete xml.dom. Como vio en Sección 5.4, “Instanciación de clases”, __class__ es un atributo que incorpora todo objeto de Python.
3 Es más, __name__ es un atributo que incorpora toda clase de Python, y ésta es su cadena. Esta cadena no tiene nada de misterioso; es el mismo nombre de la clase que escribe cuando define una clase usted mismo (vea Sección 5.3, “Definición de clases”).

Bien, ahora podemos obtener el nombre de la clase de cualquier nodo XML (ya que cada nodo se representa con un objeto de Python). ¿Cómo puede aprovechar esto para separar la lógica al analizar cada tipo de nodo? La respuesta es getattr, que ya vio en Sección 4.4, “Obtención de referencias a objetos con getattr”.

Ejemplo 10.18. parse, un despachador XML genérico

    def parse(self, node):          
        parseMethod = getattr(self, "parse_%s" % node.__class__.__name__) 1 2
        parseMethod(node) 3
1 Antes de nada, advierta que está creando una cadena más grande basándose en el nombre de la clase del nodo que le pasaron (en el argumento node). Así que si le pasan un nodo Document estará creando la cadena 'parse_Document', etc..
2 Ahora puede tratar la cadena como el nombre de una función y obtener una referencia a la función en sí usando getattr
3 Por último, podemos invocar la función y pasarle el nodo como argumento. El siguiente ejemplo muestra las definiciones de cada una de estas funciones.

Ejemplo 10.19. Funciones invocadas por parse

    def parse_Document(self, node): 1
        self.parse(node.documentElement)

    def parse_Text(self, node):    2
        text = node.data
        if self.capitalizeNextWord:
            self.pieces.append(text[0].upper())
            self.pieces.append(text[1:])
            self.capitalizeNextWord = 0
        else:
            self.pieces.append(text)

    def parse_Comment(self, node): 3
        pass

    def parse_Element(self, node): 4
        handlerMethod = getattr(self, "do_%s" % node.tagName)
        handlerMethod(node)
1 parse_Document sólo se invoca una vez, ya que sólo hay un nodo Document en un documento XML, y sólo un objeto Document en la representación analizada del XML. Simplemente analiza el elemento raíz del fichero de gramática.
2 parse_Text se invoca sobre los nodos que representan texto. La función en sí hace algo de procesamiento especial para poner en mayúsculas de forma automática la primera palabra de una frase, pero aparte de eso sólo añade el texto representado a la lista.
3 parse_Comment es un simple pass, ya que no nos interesan los comentarios que hay en los ficheros de gramáticas. Observe sin embargo que aún debemos definir la función e indicar explícitamente que no haga nada. Si la función no existiese la parse genérica fallaría tan pronto como se topase con un comentario, porque intentaría encontrar la inexistente función parse_Comment. Definir una función por cada tipo de nodo, incluso los que no usamos, permite que la función genérica parse sea sencilla y tonta.
4 El método parse_Element es en sí mismo un despachador, que se basa en el nombre de la etiqueta del elemento. La idea básica es la misma: tomar lo que distingue a los elementos unos de otros (el nombre de sus etiquetas) y despacharlos a funciones diferentes. Construimos una cadena como 'do_xref' (para una etiqueta <xref>), encontramos una función con ese nombre, y la invocamos. Y así con cada uno de los otros nombres de etiquetas que podrían encontrarse durante el análisis de un fichero de gramática (etiquetas <p>, <choice>).

En este ejemplo, las funciones despachadoras parse y parse_Element encuentras métodos en su misma clase. Si el procesamiento se vuelve complejo (o tenemos muchos tipos diferentes de etiquetas), podríamos dividir el código en módulos separados y usar importación dinámica para llamar a las funciones necesarias dentro de sus módulos. La importación dinámica la expondremos en Capítulo 16, Programación Funcional.