6.11. Create gestori separati per tipo di nodo

Il terzo suggerimento utile per processare gli XML comporta la separazione del vostro codice in funzioni logiche, basate sui tipi di nodi e nomi di elementi. I documenti XML analizzati sono fatti di vari tipi di nodi, di cui ognuno rappresenta un oggetto Python. Il livello base del documento stesso è rappresentato da un oggetto Document. Document contiene uno o più oggetti Element (per i tags XML reali), ognuno dei quali può contenere altri oggetti Element, oggetti Text (per parti di testo), od oggetti Comment (per commenti incorporati). Python rende semplice scrivere uno smistatore per separare la logica per ciascun tipo di nodo.

Esempio 6.40. Nomi di classi di oggetti XML analizzati

>>> 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 Pensate per un momento che kant.xml si trovi nella directory corrente.
2 Come abbiamo visto nella sezione Package, l'oggetto ritornato dall'analisi di un documento XML è un oggetto Document, come definito in minidom.py del package xml.dom. Come abbiamo visto nella sezione Istanziare classi, __class__ è un attributo built-in di ogni oggetto Python.
3 Inoltre, __name__ è un attributo built-in di ogni classe Python, ed è una stringa. Questa stringa non è misteriosa; è la stessa del nome della classe che inserite quando definite una classe da voi. (Ritornate eventualmente alla sezione Definire classi.)

Bene, così adesso possiamo ottenere il nome della classe di ogni particolare nodo XML (dato che ogni nodo XML viene rappresentato come un oggetto Python). Come possiamo utilizzare ciò a nostro vantaggio per separare la logica dell'analisi di ogni tipo di nodo? La risposta è getattr, che abbiamo visto precedentemente nella sezione Ottenere riferimenti agli oggetti usando getattr.

Esempio 6.41. analizzare, un generico smistatore di nodi XML

    def parse(self, node):         
        parseMethod = getattr(self, "parse_%s" % node.__class__.__name__) 1 2
        parseMethod(node) 3
1 Prima cosa, notate che stiamo costruendo una larga stringa basata sul nome della classe del nodo che abbiamo passato (nell'argomento node). Così, se passiamo un nodo Document, stiamo costruendo la stringa 'parse_Document', e così via.
2 Adesso possiamo trattare la stringa come un nome di funzione ed ottenere un riferimento alla funzione stessa usando getattr.
3 Infine, possiamo chiamare quella funzione e passare il nodo stesso come un argomento. Il prossimo esempio mostra le definizioni di ognuna di queste funzioni.

Esempio 6.42. Funzioni chiamate dall'analizzatore di smistamento

    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 è chiamato solo una volta, dato che c'è solo un nodo Document in un documento XML e solo un oggetto Document nella rappresentazione XML analizzata. Semplicemente il programma gira intorno ed analizza l'elemento radice del file grammar.
2 parse_Text è chiamato sui nodi che rappresentano parti di testo. La funzione stessa fa alcune speciali operazioni per gestire automaticamente la conversione in maiuscolo delle prime lettere di una frase, ed aggiunge il testo rappresentato ad una lista.
3 parse_Comment è solo un pass, dato che non teniamo conto dei commenti incorporati nei nostri file grammar. Notate, comunque, che necessitiamo ancora di definire la funzione e non fargli fare esplicitamente nulla. Se la funzione non esistesse, la nostra generica funzione parse fallirebbe nel momento in cui giungerebbe su un commento, perché cercherebbe di trovare la funzione inesistente parse_Comment. Definire una funzione separata per ogni tipo di nodo, anche quelli che non usiamo, permette alla funzione generica parse di essere semplice e muta.
4 L'elemento parse_Element è realmente esso stesso uno smistatore, basato sul nome dell'etichetta dell'elemento. L'idea di base è la stessa: prendere ciò che distingue gli elementi uno dall'altro (i nomi delle loro etichette) e smistare in funzioni separate per ognuno di loro. Costruire una stringa come 'do_xref' (per una etichetta <xref>), trovare una funzione di quel nome e chiamarla. E così via per ognuno degli altri nomi delle etichette che potrebbero essere trovate nel corso dell'analisi di un file grammar (etichetta <p>, etichetta <choice>).

In questo esempio, le funzioni di smistamento parse e parse_Element trovano semplicemente altri metodi nella stessa classe. Se il vostro processo è molto complesso (o avete nomi di etichette differenti), potreste spezzare il vostro codice in moduli separati ed usare l'importazione dinamica per importare ogni modulo e chiamare qualsiasi funzione di cui abbiate bisogno. L'import dinamico sarà discusso nel capitolo sulla Programmazione orientata ai dati.