6.3. Analizzare XML

Come stavo dicendo, analizzare un documento XML è molto semplice: una riga di codice. Dove andare poi, dipende da voi.

Esempio 6.8. Caricare un documento XML (questa volta per davvero)

>>> from xml.dom import minidom                                          1
>>> xmldoc = minidom.parse('~/diveintopython/common/py/kgp/binary.xml')  2
>>> xmldoc                                                               3
<xml.dom.minidom.Document instance at 010BE87C>
>>> print xmldoc.toxml()                                                 4
<?xml version="1.0" ?>
<grammar>
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar>
1 Come abbiamo visto nella sezione precedente, questo importa il modulo minidom dal package xml.dom.
2 Questa è la riga di codice che fa tutto il lavoro: minidom.parse prende un argomento e ritorna la rappresentazione analizzata del documento XML. L'argomento può essere di vario tipo; in questo caso, è semplicemente un nome di file di un documento XML nel mio disco (per seguirmi, occorre che cambiate il percorso del file in modo che punti alla directory dove avete scaricato gli esempi). Ma potete anche passare un oggetto di tipo file o anche un oggetto che si comporta come un file. Ci avvantaggeremo di questa flessibilità più avanti nel capitolo.
3 L'oggetto ritornato da minidom.parse è un oggetto di tipo Document, un discendente della classe Node. Questo oggetto Document è la radice di una struttura ad albero piuttosto complessa di oggetti Python interconnessi, che rappresenta per intero il documento XML che abbiamo passato a minidom.parse.
4 toxml è un metodo della classe Node (ed è dunque disponibile nell'oggetto Document ottenuto da minidom.parse). toxml stampa l'XML rappresentato da questo Node. Per il nodo Document, viene stampato l'intero documento XML.

Ora che abbiamo un documento XML in memoria, possiamo iniziare ad elaborarlo.

Esempio 6.9. Ottenere i nodi figli

>>> xmldoc.childNodes    1
[<DOM Element: grammar at 17538908>]
>>> xmldoc.childNodes[0] 2
<DOM Element: grammar at 17538908>
>>> xmldoc.firstChild    3
<DOM Element: grammar at 17538908>
1 Ogni Node ha un attributo childNodes, che è una lista di oggetti Node. Un Document ha sempre solo un nodo figlio, l'elemento radice del documento XML (in questo caso, l'elemento grammar).
2 Per ottenere il primo (ed in questo caso, il solo) nodo figlio, usate la solita sintassi vista per le liste. Ricordate, non c'è nulla di speciale qui; è soltanto una normale lista Python di normali oggetti Python.
3 Siccome ottenere il primo nodo figlio di un nodo è un'attività utile e comune, la classe Node ha un attributo firstChild, che è sinonimo di childNodes[0]. C'è anche l'attributo lastChild, che è sinonimo di childNodes[-1].

Esempio 6.10. toxml funziona su ogni nodo

>>> grammarNode = xmldoc.firstChild
>>> print grammarNode.toxml() 1
<grammar>
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar>
1 Siccome il metodo toxml è definito nella classe Node, è disponibile in ogni nodo XML, non solo nell'elemento Document.

Esempio 6.11. I nodi figli possono essere di testo

>>> grammarNode.childNodes                  1
[<DOM Text node "\n">, <DOM Element: ref at 17533332>, \
<DOM Text node "\n">, <DOM Element: ref at 17549660>, <DOM Text node "\n">]
>>> print grammarNode.firstChild.toxml()    2



>>> print grammarNode.childNodes[1].toxml() 3
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
>>> print grammarNode.childNodes[3].toxml() 4
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
>>> print grammarNode.lastChild.toxml()     5


1 Guardando l'XML in binary.xml, potreste pensare che il nodo grammar ha solo due nodi figli, i due elementi ref. Ma vi state perdendo qualcosa: le andate a capo! Dopo il '<grammar>' e prima del primo '<ref>' c'è un'andata a capo e questo testo viene considerato come nodo figlio dell'elemento grammar. Analogamente, ci sono delle andate a capo dopo ogni '</ref>'; anche queste vengono considerate nodi. Dunque grammar.childNodes è una lista di 5 oggetti: 3 oggetti di tipo Text e 2 oggetti di tipo Element.
2 Il primo figlio è un oggetto di tipo Text rappresentante l'andata a capo dopo il tag '<grammar>' e prima del primo tag '<ref>'.
3 Il secondo figlio è un oggetto di tipo Element rappresentante il primo elemento ref.
4 Il quarto figlio è un oggetto di tipo Element rappresentante il secondo elemento ref.
5 L'ultimo figlio è un oggetto di tipo Text rappresentante l'andata a capo dopo il tag di chiusura '</ref>' e prima del tag di chiusura '</grammar>'.

Esempio 6.12. Tirare fuori tutti i nodi di testo

>>> grammarNode
<DOM Element: grammar at 19167148>
>>> refNode = grammarNode.childNodes[1] 1
>>> refNode
<DOM Element: ref at 17987740>
>>> refNode.childNodes                  2
[<DOM Text node "\n">, <DOM Text node "  ">, <DOM Element: p at 19315844>, \
<DOM Text node "\n">, <DOM Text node "  ">, \
<DOM Element: p at 19462036>, <DOM Text node "\n">]
>>> pNode = refNode.childNodes[2]
>>> pNode
<DOM Element: p at 19315844>
>>> print pNode.toxml()                 3
<p>0</p>
>>> pNode.firstChild                    4
<DOM Text node "0">
>>> pNode.firstChild.data               5
u'0'
1 Come abbiamo visto nell'esempio precedente, il primo elemento ref è grammarNode.childNodes[1], in quanto childNodes[0] è un nodo di tipo Text per l'andata a capo.
2 L'elemento ref ha il suo insieme di nodi figli, uno per l'andata a capo, un altro per gli spazi, uno per l'elemento p, e così via.
3 Puoi sempre usare il metodo toxml qui, profondamente annidato nel documento.
4 L'elemento p ha solo un nodo figlio (non potete dirlo da questo esempio, ma guardate pNode.childNodes se non mi credete) ed è un nodo di tipo Text contenente il solo carattere '0'.
5 L'attributo .data di un nodo di tipo Text restituisce la stringa rappresentata dal nodo. Ma che cos'è la 'u' davanti la stringa? La risposta merita di avere una propria sezione.