| Scopo | Genera ed analizza documenti XML |
| Versione Python | 2.5 e successive |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per le versioni 3.x di Python
La libreria ElementTree è un contributo alla libreria standard di Fredrick Lundh. Comprende gli strumenti per analizzare XML usando API basate su eventi e documenti, per cercare nei documenti analizzati tramite espressioni XPath e per creare o modificare documenti.
Tutti gli esempi in questa sezione usano l'implementazione Python di ElementTree per semplicità, ma esiste anche una implementazione C in
xml.etree.cElementTree
.
I documenti XML analizzati sono rappresentati in memoria da da oggetti ElementTree ed Element connessi in una struttura ad albero basata sul modo nel quale sono annidati i nodi del documento XML
Quando si analizza un intero documento con
parse()
viene restituita una istanza di ElementTree. La struttura ad albero si riferisce a tutti i dati del documento di input ed i nodi dell'albero possono essere cercati o manipolati sul posto. Sebbene questa flessibilità possa facilitare il lavoro con il documento analizzato, in genere richiede più memoria rispetto ad un approccio di analisi basato sugli eventi, visto che l'intero documento deve essere caricato in una volta.
L'ammontare di memoria richiesto per piccoli, semplici documenti tipo questo elenco di podcast rappresentati come uno schema OPML non è significativo.
<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.0">
<head>
<title>My Podcasts</title>
<dateCreated>Sun, 07 Mar 2010 15:53:26 GMT</dateCreated>
<dateModified>Sun, 07 Mar 2010 15:53:26 GMT</dateModified>
</head>
<body>
<outline text="Science and Tech">
<outline text="APM: Future Tense" type="rss"
xmlUrl="http://www.publicradio.org/columns/futuretense/podcast.xml"
htmlUrl="http://www.publicradio.org/columns/futuretense/" />
<outline text="Engines Of Our Ingenuity Podcast" type="rss"
xmlUrl="http://www.npr.org/rss/podcast.php?id=510030"
htmlUrl="http://www.uh.edu/engines/engines.htm" />
<outline text="Science & the City" type="rss"
xmlUrl="http://www.nyas.org/Podcasts/Atom.axd"
htmlUrl="http://www.nyas.org/WhatWeDo/SciencetheCity.aspx" />
</outline>
<outline text="Books and Fiction">
<outline text="Podiobooker" type="rss"
xmlUrl="http://feeds.feedburner.com/podiobooks"
htmlUrl="http://www.podiobooks.com/blog" />
<outline text="The Drabblecast" type="rss"
xmlUrl="http://web.me.com/normsherman/Site/Podcast/rss.xml"
htmlUrl="http://web.me.com/normsherman/Site/Podcast/Podcast.html" />
<outline text="tor.com / category / tordotstories" type="rss"
xmlUrl="http://www.tor.com/rss/category/TorDotStories"
htmlUrl="http://www.tor.com/" />
</outline>
<outline text="Computers and Programming">
<outline text="MacBreak Weekly" type="rss"
xmlUrl="http://leo.am/podcasts/mbw"
htmlUrl="http://twit.tv/mbw" />
<outline text="FLOSS Weekly" type="rss"
xmlUrl="http://leo.am/podcasts/floss"
htmlUrl="http://twit.tv" />
<outline text="Core Intuition" type="rss"
xmlUrl="http://www.coreint.org/podcast.xml"
htmlUrl="http://www.coreint.org/" />
</outline>
<outline text="Python">
<outline text="PyCon Podcast" type="rss"
xmlUrl="http://advocacy.python.org/podcasts/pycon.rss"
htmlUrl="http://advocacy.python.org/podcasts/" />
<outline text="A Little Bit of Python" type="rss"
xmlUrl="http://advocacy.python.org/podcasts/littlebit.rss"
htmlUrl="http://advocacy.python.org/podcasts/" />
<outline text="Django Dose Everything Feed" type="rss"
xmlUrl="http://djangodose.com/everything/feed/" />
</outline>
<outline text="Miscelaneous">
<outline text="dhellmann's CastSampler Feed" type="rss"
xmlUrl="http://www.castsampler.com/cast/feed/rss/dhellmann/"
htmlUrl="http://www.castsampler.com/users/dhellmann/" />
</outline>
</body>
</opml>
Per analizzare il file si passa un handle di file aperto a
parse()
, che legge i dati, elabora l'XML, e restituisce un oggetto ElementTree.
from xml.etree import ElementTree
with open('podcasts.opml', 'rt') as f:
tree = ElementTree.parse(f)
print tree
$ python ElementTree_parse_opml.py <xml.etree.ElementTree.ElementTree instance at 0xb76dc24c>
Ora che si è ottenuto un albero XML analizzato, si possono eseguire iterazioni su di esso, visitando tutti i figli per esaminare i loro attributi e contenuto.
from xml.etree import ElementTree
with open('podcasts.opml', 'rt') as f:
tree = ElementTree.parse(f)
for node in tree.getiterator():
print node.tag, node.attrib
In questo caso viene stampato l'intero albero, un tag alla volta
$ python ElementTree_dump_opml.py
opml {'version': '1.0'}
head {}
title {}
dateCreated {}
dateModified {}
body {}
outline {'text': 'Science and Tech'}
outline {'xmlUrl': 'http://www.publicradio.org/columns/futuretense/podcast.xml', 'text': 'APM: Future Tense', 'type': 'rss', 'htmlUrl': 'http://www.publicradio.org/columns/futuretense/'}
outline {'xmlUrl': 'http://www.npr.org/rss/podcast.php?id=510030', 'text': 'Engines Of Our Ingenuity Podcast', 'type': 'rss', 'htmlUrl': 'http://www.uh.edu/engines/engines.htm'}
outline {'xmlUrl': 'http://www.nyas.org/Podcasts/Atom.axd', 'text': 'Science & the City', 'type': 'rss', 'htmlUrl': 'http://www.nyas.org/WhatWeDo/SciencetheCity.aspx'}
outline {'text': 'Books and Fiction'}
outline {'xmlUrl': 'http://feeds.feedburner.com/podiobooks', 'text': 'Podiobooker', 'type': 'rss', 'htmlUrl': 'http://www.podiobooks.com/blog'}
outline {'xmlUrl': 'http://web.me.com/normsherman/Site/Podcast/rss.xml', 'text': 'The Drabblecast', 'type': 'rss', 'htmlUrl': 'http://web.me.com/normsherman/Site/Podcast/Podcast.html'}
outline {'xmlUrl': 'http://www.tor.com/rss/category/TorDotStories', 'text': 'tor.com / category / tordotstories', 'type': 'rss', 'htmlUrl': 'http://www.tor.com/'}
outline {'text': 'Computers and Programming'}
outline {'xmlUrl': 'http://leo.am/podcasts/mbw', 'text': 'MacBreak Weekly', 'type': 'rss', 'htmlUrl': 'http://twit.tv/mbw'}
outline {'xmlUrl': 'http://leo.am/podcasts/floss', 'text': 'FLOSS Weekly', 'type': 'rss', 'htmlUrl': 'http://twit.tv'}
outline {'xmlUrl': 'http://www.coreint.org/podcast.xml', 'text': 'Core Intuition', 'type': 'rss', 'htmlUrl': 'http://www.coreint.org/'}
outline {'text': 'Python'}
outline {'xmlUrl': 'http://advocacy.python.org/podcasts/pycon.rss', 'text': 'PyCon Podcast', 'type': 'rss', 'htmlUrl': 'http://advocacy.python.org/podcasts/'}
outline {'xmlUrl': 'http://advocacy.python.org/podcasts/littlebit.rss', 'text': 'A Little Bit of Python', 'type': 'rss', 'htmlUrl': 'http://advocacy.python.org/podcasts/'}
outline {'xmlUrl': 'http://djangodose.com/everything/feed/', 'text': 'Django Dose Everything Feed', 'type': 'rss'}
outline {'text': 'Miscelaneous'}
outline {'xmlUrl': 'http://www.castsampler.com/cast/feed/rss/dhellmann/', 'text': "dhellmann's CastSampler Feed", 'type': 'rss', 'htmlUrl': 'http://www.castsampler.com/users/dhellmann/'}
Se si vuole stampare solo i gruppi di nomi e gli URL dei feed per i podcast, escludendo tutti i dati nella sezione di intestazione, si può iterare solo attraverso i nodi
outline
e stampare gli attributi
text
e
xmlUrl
.
from xml.etree import ElementTree
with open('podcasts.opml', 'rt') as f:
tree = ElementTree.parse(f)
for node in tree.getiterator('outline'):
name = node.attrib.get('text')
url = node.attrib.get('xmlUrl')
if name and url:
print ' %s :: %s' % (name, url)
else:
print name
Visto che viene passato
'outline'
a
tree.getiterator()
l'elaborazione viene limitata ai soli nodi con il tag
'outline'
$ python ElementTree_show_feed_urls.py Science and Tech APM: Future Tense :: http://www.publicradio.org/columns/futuretense/podcast.xml Engines Of Our Ingenuity Podcast :: http://www.npr.org/rss/podcast.php?id=510030 Science & the City :: http://www.nyas.org/Podcasts/Atom.axd Books and Fiction Podiobooker :: http://feeds.feedburner.com/podiobooks The Drabblecast :: http://web.me.com/normsherman/Site/Podcast/rss.xml tor.com / category / tordotstories :: http://www.tor.com/rss/category/TorDotStories Computers and Programming MacBreak Weekly :: http://leo.am/podcasts/mbw FLOSS Weekly :: http://leo.am/podcasts/floss Core Intuition :: http://www.coreint.org/podcast.xml Python PyCon Podcast :: http://advocacy.python.org/podcasts/pycon.rss A Little Bit of Python :: http://advocacy.python.org/podcasts/littlebit.rss Django Dose Everything Feed :: http://djangodose.com/everything/feed/ Miscelaneous dhellmann's CastSampler Feed :: http://www.castsampler.com/cast/feed/rss/dhellmann/
Il metodo di attraversare l'intero albero in questo modo per cercare i nodi che interessano può essre incline ad errori. Nell'esempio di cui sopra, si è dovuto cercare in ogni nodo 'outline' per determinare se si trattava di un gruppo (nodi che hanno solo un attributo "text") o di un podcast (che ha sia "text" che "xmlUrl"). Se si volesse scrivere un programma che esegue il download di podcast ed occorre produrre un semplice elanco di URL dei feed dei podcast, senza nomi o gruppi, si potrebbe semplificare la logica usando
findall()
per trovare i nodi con caratteristiche di ricerca più descrittiva.
Un primo passo per convertire l'esempio di cui sopra potrebbe essere la costruzione di un parametro XPath per trovare tutti i nodi 'outline'
from xml.etree import ElementTree
with open('podcasts.opml', 'rt') as f:
tree = ElementTree.parse(f)
for node in tree.findall('.//outline'):
url = node.attrib.get('xmlUrl')
if url:
print url
La logica in questa versione non è sostanzialmente diversa da quella che usa
getiterator()
. Occorre ancora verificare la presenta dell'URL, ad eccezione del fatto che non viene stampato il nome del gruppo quando l'URL non viene trovato.
$ python ElementTree_find_feeds_by_tag.py http://www.publicradio.org/columns/futuretense/podcast.xml http://www.npr.org/rss/podcast.php?id=510030 http://www.nyas.org/Podcasts/Atom.axd http://feeds.feedburner.com/podiobooks http://web.me.com/normsherman/Site/Podcast/rss.xml http://www.tor.com/rss/category/TorDotStories http://leo.am/podcasts/mbw http://leo.am/podcasts/floss http://www.coreint.org/podcast.xml http://advocacy.python.org/podcasts/pycon.rss http://advocacy.python.org/podcasts/littlebit.rss http://djangodose.com/everything/feed/ http://www.castsampler.com/cast/feed/rss/dhellmann/
Un'altra versione potrebbe trarre vantaggio dal fatto che si è a conoscenza che i nodi 'outline' hanno solo due livelli di nidificazione. Se si modifica il percorso di ricerca in
.//outline/outline
verrà esaminato solo il secondo livello dei nodi 'outline'.
from xml.etree import ElementTree
with open('podcasts.opml', 'rt') as f:
tree = ElementTree.parse(f)
for node in tree.findall('.//outline/outline'):
url = node.attrib.get('xmlUrl')
print url
Ci si aspetta che tutti i nodi 'outline' nidificati due livelli in profondità nell'input abbiano l'attributo xmlURL che si riferisce al feed del podcast, così, se si è sufficientemente coraggiosi si può evitare di verificare la presenza dell'attributo prima dell'uso.
$ python ElementTree_find_feeds_by_structure.py http://www.publicradio.org/columns/futuretense/podcast.xml http://www.npr.org/rss/podcast.php?id=510030 http://www.nyas.org/Podcasts/Atom.axd http://feeds.feedburner.com/podiobooks http://web.me.com/normsherman/Site/Podcast/rss.xml http://www.tor.com/rss/category/TorDotStories http://leo.am/podcasts/mbw http://leo.am/podcasts/floss http://www.coreint.org/podcast.xml http://advocacy.python.org/podcasts/pycon.rss http://advocacy.python.org/podcasts/littlebit.rss http://djangodose.com/everything/feed/ http://www.castsampler.com/cast/feed/rss/dhellmann/
Questa versione è limitata alla struttura esistente per questo esempio, quindi se i nodi 'outline' sono in un qualche modo riorganizzati in una struttura ad albero più profonda, lo script non funzionerà più.
Gli elementi restituiti da
findall()
e
getiterator()
sono oggetti
Element
, ognuno dei quali rappresenta un nodo nell'albero XML elaborato. Ogni
Element
possiede attributi per accedere ai dati estratti dall'XML. La cosa può essere illustrata da un file di input di esempio in qualche modo più stringato:
data.xml
:
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="UTF-8"?>
<top>
<child>Questo figlio contiene testo.</child>
<child_with_tail>Questo figlio contiene testo normale</child_with_tail>e testo in "coda".
<with_attributes name="value" foo="bar" />
<entity_expansion attribute="Questo & Quello">Quello & Questo</entity_expansion>
</top>
|
Gli 'attributi' di un nodo sono disponibili tramite la proprietà
attrib
, che si comporta come un dizionario.
from xml.etree import ElementTree
with open('data.xml', 'rt') as f:
tree = ElementTree.parse(f)
node = tree.find('./with_attributes')
print node.tag
for name, value in sorted(node.attrib.items()):
print ' %-4s = "%s"' % (name, value)
Il nodo nella riga 5 del file in input ha due attributi,
name
e
foo
.
$ python ElementTree_node_attributes.py with_attributes foo = "bar" name = "value"
Il testo contenuto nei nodi è disponibile, assieme al testo "coda" che segue la fine del tag di chiusura.
from xml.etree import ElementTree
with open('data.xml', 'rt') as f:
tree = ElementTree.parse(f)
for path in [ './child', './child_with_tail' ]:
node = tree.find(path)
print node.tag
print ' testo del nodo figlio:', node.text
print ' e testo di coda :', node.tail
Il nodo
child
della riga 3 contiene testo incorporato, ed il nodo nella riga 4 contiene testo in coda (inclusi spazi).
$ python ElementTree_node_text.py child testo del nodo figlio: Questo figlio contiene testo. e testo di coda : child_with_tail testo del nodo figlio: Questo figlio contiene testo normale e testo di coda : e testo in "coda".
Convenientemente, i riferimenti alle entità XML incorporati nel documenti sono convertiti nei caratteri appropriati prima che i valori vengano restituiti.
from xml.etree import ElementTree
with open('data.xml', 'rt') as f:
tree = ElementTree.parse(f)
node = tree.find('entity_expansion')
print node.tag
print " nell'attributo:", node.attrib['attribute']
print ' nel testo :', node.text
La conversione risparmia al programmatore la preoccupazione di dovere implementare la rappresentazione di alcuni caratteri in un documento XML.
$ python ElementTree_entity_references.py entity_expansion nell'attributo: Questo & Quello nel testo : Quello & Questo
L'altra API utile per l'elaborazione dei documenti XML è basata sugli eventi. Il parser genera eventi
start
per i tag di apertura ed eventi
end
per quelli di chiusura. Iterare attraverso il flusso di eventi consente di estrarre i dati dal documento mentre lo si analizza, che è conveniente se non si deve manipolare l'intero documento successivamente e si vuole evitare di mantenere l'intero documento analizzato in memoria.
iterparse()
restituisce un iterabile che produce delle tuple che contengono il nome dell'evento ed il nodo che ha scatenato l'evento. Gli eventi possono essere uno tra:
from xml.etree.ElementTree import iterparse
depth = 0
prefix_width = 8
prefix_dots = '.' * prefix_width
line_template = '{prefix:<0.{prefix_len}}{event:<8}{suffix:<{suffix_len}} {node.tag:<12} {node_id}'
for (event, node) in iterparse('podcasts.opml', ['start', 'end', 'start-ns', 'end-ns']):
if event == 'end':
depth -= 1
prefix_len = depth * 2
print line_template.format(prefix=prefix_dots,
prefix_len=prefix_len,
suffix='',
suffix_len=(prefix_width - prefix_len),
node=node,
node_id=id(node),
event=event,
)
if event == 'start':
depth += 1
Nella modalità predefinita, vengono generati solo gli eventi
end
. Per vedere altri eventi, si passa la lista dei nomi di evento che si vuole ricevere ad
iterparse()
, come in questo esempio.
$ python ElementTree_show_all_events.py start opml 3077771180 ..start head 3077788012 ....start title 3077788044 ....end title 3077788044 ....start dateCreated 3077788332 ....end dateCreated 3077788332 ....start dateModified 3077788396 ....end dateModified 3077788396 ..end head 3077788012 ..start body 3077788492 ....start outline 3077788620 ......start outline 3077788844 ......end outline 3077788844 ......start outline 3077788908 ......end outline 3077788908 ......start outline 3077789100 ......end outline 3077789100 ....end outline 3077788620 ....start outline 3077789292 ......start outline 3077789356 ......end outline 3077789356 ......start outline 3077789196 ......end outline 3077789196 ......start outline 3077789484 ......end outline 3077789484 ....end outline 3077789292 ....start outline 3077789676 ......start outline 3077789740 ......end outline 3077789740 ......start outline 3077789580 ......end outline 3077789580 ......start outline 3077789868 ......end outline 3077789868 ....end outline 3077789676 ....start outline 3077790092 ......start outline 3077790188 ......end outline 3077790188 ......start outline 3077790252 ......end outline 3077790252 ......start outline 3077790444 ......end outline 3077790444 ....end outline 3077790092 ....start outline 3077790636 ......start outline 3077790700 ......end outline 3077790700 ....end outline 3077790636 ..end body 3077788492 end opml 3077771180
Lo stile di elaborazione ad eventi potrebbe essere più naturale per alcune operazioni, tipo convertire un input XML in qualche altro formato. Ad esempio si supponga di volere convertire l'elenco di podcast sul quale si sta lavorando da un file XML ad un file di dati, che si possa caricare in un foglio elettronico o database. Non occorre mantenere tutto l'insieme di dati in memoria, visto che si sta semplicemente cambiando il formato.
import csv
from xml.etree.ElementTree import iterparse
import sys
writer = csv.writer(sys.stdout, quoting=csv.QUOTE_NONNUMERIC)
group_name = ''
for (event, node) in iterparse('podcasts.opml', events=['start']):
if node.tag != 'outline':
# Ignora qualsiasi parte al di fuori di outline
continue
if not node.attrib.get('xmlUrl'):
# Ricorda il gruppo corrente
group_name = node.attrib['text']
else:
# Scrive una voce di podcast
writer.writerow( (group_name, node.attrib['text'],
node.attrib['xmlUrl'],
node.attrib.get('htmlUrl', ''),
)
)
Questo programma di esempio converte l'elenco di podcast in un file CSV, pronto per essere importato da un'altra applicazione.
xml.etree.ElementTree - Api per la Manipolazione XML $ python ElementTree_write_podcast_csv.py "Science and Tech","APM: Future Tense","http://www.publicradio.org/columns/futuretense/podcast.xml","http://www.publicradio.org/columns/futuretense/" "Science and Tech","Engines Of Our Ingenuity Podcast","http://www.npr.org/rss/podcast.php?id=510030","http://www.uh.edu/engines/engines.htm" "Science and Tech","Science & the City","http://www.nyas.org/Podcasts/Atom.axd","http://www.nyas.org/WhatWeDo/SciencetheCity.aspx" "Books and Fiction","Podiobooker","http://feeds.feedburner.com/podiobooks","http://www.podiobooks.com/blog" "Books and Fiction","The Drabblecast","http://web.me.com/normsherman/Site/Podcast/rss.xml","http://web.me.com/normsherman/Site/Podcast/Podcast.html" "Books and Fiction","tor.com / category / tordotstories","http://www.tor.com/rss/category/TorDotStories","http://www.tor.com/" "Computers and Programming","MacBreak Weekly","http://leo.am/podcasts/mbw","http://twit.tv/mbw" "Computers and Programming","FLOSS Weekly","http://leo.am/podcasts/floss","http://twit.tv" "Computers and Programming","Core Intuition","http://www.coreint.org/podcast.xml","http://www.coreint.org/" "Python","PyCon Podcast","http://advocacy.python.org/podcasts/pycon.rss","http://advocacy.python.org/podcasts/" "Python","A Little Bit of Python","http://advocacy.python.org/podcasts/littlebit.rss","http://advocacy.python.org/podcasts/" "Python","Django Dose Everything Feed","http://djangodose.com/everything/feed/","" "Miscelaneous","dhellmann's CastSampler Feed","http://www.castsampler.com/cast/feed/rss/dhellmann/","http://www.castsampler.com/users/dhellmann/"
Un metodo potenzialmente più efficiente per gestire gli eventi di elaborazione è rimpiazzare il comportamento del costruttore di albero standard con il proprio. Il parser di ElementTree usa uno XMLTreeBuilder per elaborare l'XML e chiamare i metodi su una classe destinazione per salvare i risultati. Il risultato usuale è una istanza di ElementTree creata in modo predefinito dalla classe TreeBuilder. Rimpiazzando TreeBuilder con la propria classe, si possono ricevere gli eventi prima che i nodi Element vengano istanziati, risparmiando quella porzione di sovraccarico.
L'applicazione Da-XML-a-CSV di cui sopra può essere trasposta verso un tree builder.
import csv
from xml.etree.ElementTree import XMLTreeBuilder
import sys
class PodcastListToCSV(object):
def __init__(self, outputFile):
self.writer = csv.writer(outputFile, quoting=csv.QUOTE_NONNUMERIC)
self.group_name = ''
return
def start(self, tag, attrib):
if tag != 'outline':
# Ignora qualsiasi cosa al di fuori di outline
return
if not attrib.get('xmlUrl'):
# Ricorda il gruppo corrente
self.group_name = attrib['text']
else:
# Scrive una voce di podcast
self.writer.writerow( (self.group_name, attrib['text'],
attrib['xmlUrl'],
attrib.get('htmlUrl', ''),
)
)
def end(self, tag):
# Ignora i tag di chiusura
pass
def data(self, data):
# Ignora i dati all'interno dei nodi
pass
def close(self):
# Nulla di speciale da fare qui
return
target = PodcastListToCSV(sys.stdout)
parser = XMLTreeBuilder(target=target)
with open('podcasts.opml', 'rt') as f:
for line in f:
parser.feed(line)
parser.close()
PodcastListToCSV
implementa il protocollo TreeBuilder. Ogni volta che si incontra un nuovo tag XML, viene chiamato
start()
con il nome del tag e gli attributi. Quando viene visto un tag di chiusura viene chiamato
end()
con il nome. In mezzo viene chiamato
data()
quando un nodo ha un contenuto (il costruttore dell'albero tiene traccia del nodo 'corrente'). Quando tutto l'input è stato processato viene chiamato
close()
. E' possibile la restituzione di un valore, il quale può essere ritornato all'utilizzatore di XMLTreeBuilder.
$ python ElementTree_podcast_csv_treebuilder.py "Science and Tech","APM: Future Tense","http://www.publicradio.org/columns/futuretense/podcast.xml","http://www.publicradio.org/columns/futuretense/" "Science and Tech","Engines Of Our Ingenuity Podcast","http://www.npr.org/rss/podcast.php?id=510030","http://www.uh.edu/engines/engines.htm" "Science and Tech","Science & the City","http://www.nyas.org/Podcasts/Atom.axd","http://www.nyas.org/WhatWeDo/SciencetheCity.aspx" "Books and Fiction","Podiobooker","http://feeds.feedburner.com/podiobooks","http://www.podiobooks.com/blog" "Books and Fiction","The Drabblecast","http://web.me.com/normsherman/Site/Podcast/rss.xml","http://web.me.com/normsherman/Site/Podcast/Podcast.html" "Books and Fiction","tor.com / category / tordotstories","http://www.tor.com/rss/category/TorDotStories","http://www.tor.com/" "Computers and Programming","MacBreak Weekly","http://leo.am/podcasts/mbw","http://twit.tv/mbw" "Computers and Programming","FLOSS Weekly","http://leo.am/podcasts/floss","http://twit.tv" "Computers and Programming","Core Intuition","http://www.coreint.org/podcast.xml","http://www.coreint.org/" "Python","PyCon Podcast","http://advocacy.python.org/podcasts/pycon.rss","http://advocacy.python.org/podcasts/" "Python","A Little Bit of Python","http://advocacy.python.org/podcasts/littlebit.rss","http://advocacy.python.org/podcasts/" "Python","Django Dose Everything Feed","http://djangodose.com/everything/feed/","" "Miscelaneous","dhellmann's CastSampler Feed","http://www.castsampler.com/cast/feed/rss/dhellmann/","http://www.castsampler.com/users/dhellmann/"
Per lavorare con porzioni più piccole di testo XML, specialmente stringhe letterali come potrebbero essere incorporate nel sorgente di un programma, si usa xml.etree.ElementTree.XML e si passa un singolo parametro, la stringa che contiene l'XML che deve essere analizzato.
from xml.etree.ElementTree import XML
parsed = XML('''
.<root>
<group>
<child id="a">Questo è il figlio "a".</child>
<child id="b">Questo è il figlio "b".</child>
</group>
<group>
<child id="c">Questo è il figlio "c".</child>
</group>
.</root>
''')
print 'parsed =', parsed
for elem in parsed.getiterator():
print elem.tagxml.etree.ElementTree - Api per la Manipolazione XML
if elem.text is not None and elem.text.strip():
print ' text: "%s"' % elem.text
if elem.tail is not None and elem.tail.strip():
print ' tail: "%s"' % elem.tail
for name, value in sorted(elem.attrib.items()):
print ' %-4s = "%s"' % (name, value)
print
Si noti che al contrario di
parse()
, il valore restituito è una istanza di Element invece che di ElementTree.
$ python ElementTree_XML.py parsed =root group child text: "Questo è il figlio "a"." id = "a" child text: "Questo è il figlio "b"." id = "b" group child text: "Questo è il figlio "c"." id = "c"
In caso di XML strutturato che usi l'attributo "id" per identificare in modo univoco i nodi di interesse,
XMLID()
rappresenta un modo conveniente di accedere ai risultati analizzati.
from xml.etree.ElementTree import XMLID
tree, id_map = XMLID('''
<root>
<group>
<child id="a">Questo è il figlio "a".</child>
<child id="b">Questo è il figlio "b".</child>
</group>
<group>
<child id="c">Questo è il figlio "c".</child>
</group>
</root>
''')
for key, value in sorted(id_map.items()):
print '%s = %s' % (key, value)
XMLID(
restituisce l'albero analizzato come un oggetto Element, assieme ad un dizionario che mapppa le stringhe dell'attributo id ai singoli nodi nell'albero
$ python ElementTree_XMLID.py a =b = c =
Oltre alla capacità di analisi, ElementTree supporta anche la creazione di documenti XML well-formed da oggetti Element costruiti nella propria applicazione. La classe Element usata quando viene analizzato un documento è anche in grado di generare una forma serializzata del suo contenuto, che poi può essere scritta in un file od altro flusso di dati.
Ci sono tre funzioni utili alla creazione di una gerarchia di nodi Element.
Element()
crea un nodo standard,
SubElement()
attacca il nuovo nodo al nodo genitore, e
Comment()
crea un nodo che serializza usando la sintassi per i commenti XML.
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
top = Element('top')
comment = Comment('Generato per PyMOTW')
top.append(comment)
child = SubElement(top, 'figlio')
child.text = 'Questo figlio contiene testo.'
child_with_tail = SubElement(top, 'figlio_con_coda')
child_with_tail.text = 'Questo figlio contiene testo normale.'
child_with_tail.tail = 'E testo "in coda".'
child_with_entity_ref = SubElement(top, 'figlio_con_rif_entita')
child_with_entity_ref.text = 'Questo & Quello'
print tostring(top)
L'output contiene solo i nodi XML nell'albero, non la dichiarazione XML con la versione e la codifica.
$ python ElementTree_create.pyQuesto figlio contiene testo. Questo figlio contiene testo normale. E testo "in coda".Questo & Quello
Si noti che il carattere
&
nel testo di
figlio_con_rif_entita
viene convertito nella corrispondente entità
&
automaticamente.
Non viene compiuto alcuno sforzo da ElementTree per stampare l'output prodotto da
tostring()
in modo facilmente leggibile, visto che l'aggiunta di spazi modificherebbe il contenuto del documento. Per rendere il risultato pià facile da seguire all'occhio umano, gli esempi seguenti faranno uso in un
suggerimento trovato in Internet
e rielaboreranno il testo XML con
xml.dom.minidom
, per usare il suo metodo
toprettyxml()
.
from xml.etree import ElementTree
from xml.dom import minidom
def prettify(elem):
"""Ritorna una string XML pretty-printed per Element.
"""
rough_string = ElementTree.tostring(elem, 'utf-8')
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent=" ")
L'esempio aggiornato è diventato:
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
from ElementTree_pretty import prettify
top = Element('top')
comment = Comment('Generato per PyMOTW')
top.append(comment)
child = SubElement(top, 'figlio')
child.text = 'Questo figlio contiene testo.'
child_with_tail = SubElement(top, 'figlio_con_coda')
child_with_tail.text = 'Questo figlio contiene testo normale.'
child_with_tail.tail = 'E testo "in coda".'
child_with_entity_ref = SubElement(top, 'figlio_con_rif_entita')
child_with_entity_ref.text = 'Questo & Quello'
print prettify(top)
ed il risultato è più facile da leggere:
$ python ElementTree_create.py
<?xml version="1.0" ?>
<top>
<!-- Generato per PyMOTW -->
<figlio>
Questo figlio contiene testo.
</figlio>
<figlio_con_coda>
Questo figlio contiene testo normale.
</figlio_con_coda>
E testo "in coda".
<figlio_con_rif_entita>
Questo & Quello
</figlio_con_rif_entita>
</top>
Oltre allo spazio supplementare per la formattazione, il pretty-printer di xml.dom.minidom aggiunge anche una dichiarazione XML nell'output.
L'esempio precedente creava dei nodi con tag e contenuto testo, ma non impostava alcun attributo dei nodi. Molti degli esempi della parte relativa all'elaborazione dei documenti XML lavoravano con un file
OPML
che elencava dei podcast ed i loro feed. I nodi
outline
nell'albero usavano attributi per i nomi del gruppo e le proprietà dei podcast. Si può usare ElementTree_create per costruire un file XML simile usando un file CSV come input, impostando tutti gli attributi degli elementi mano a mano che l'albero viene costruito
import csv
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
import datetime
from ElementTree_pretty import prettify
generated_on = str(datetime.datetime.now())
# Configura un attributo con set()
root = Element('opml')
root.set('version', '1.0')
root.append(Comment('Generato da ElementTree_csv_to_xml.py per PyMOTW-it'))
head = SubElement(root, 'head')
title = SubElement(head, 'title')
title.text = 'My Podcasts'
dc = SubElement(head, 'dateCreated')
dc.text = generated_on
dm = SubElement(head, 'dateModified')
dm.text = generated_on
body = SubElement(root, 'body')
with open('podcasts.csv', 'rt') as f:
current_group = None
reader = csv.reader(f)
for row in reader:
group_name, podcast_name, xml_url, html_url = row
if not current_group or group_name != current_group.text:
# Inizia un nuovo gruppo
current_group = SubElement(body, 'outline', {'text':group_name})
# Aggiunge questo podcast al gruppo
# impostandone gli attibuti tutti in
# una volta.
podcast = SubElement(current_group, 'outline',
{'text':podcast_name,
'xmlUrl':xml_url,
'htmlUrl':html_url,
})
print prettify(root)
I valori degli attributi possono essere configurati uno alla volta con
set()
(come nel nodo
root
), o tutti in una volta passando un dizionario al costruttore di nodi (come con ogni nodo group e podcast).
$ python ElementTree_csv_to_xml.py
<?xml version="1.0" ?>
<opml version="1.0">
<!-- Generato da ElementTree_csv_to_xml.py per PyMOTW-it -->
<head>
<title>
My Podcasts
</title>
<dateCreated>
2010-05-01 09:38:11.182745
</dateCreated>
<dateModified>
2010-05-01 09:38:11.182745
</dateModified>
</head>
<body>
<outline text="Science and Tech">
<outline htmlUrl="http://www.publicradio.org/columns/futuretense/" text="APM: Future Tense" xmlUrl="http://www.publicradio.org/columns/futuretense/podcast.xml"/>
</outline>
<outline text="Science and Tech">
<outline htmlUrl="http://www.uh.edu/engines/engines.htm" text="Engines Of Our Ingenuity Podcast" xmlUrl="http://www.npr.org/rss/podcast.php?id=510030"/>
</outline>
<outline text="Science and Tech">
<outline htmlUrl="http://www.nyas.org/WhatWeDo/SciencetheCity.aspx" text="Science & the City" xmlUrl="http://www.nyas.org/Podcasts/Atom.axd"/>
</outline>
<outline text="Books and Fiction">
<outline htmlUrl="http://www.podiobooks.com/blog" text="Podiobooker" xmlUrl="http://feeds.feedburner.com/podiobooks"/>
</outline>
<outline text="Books and Fiction">
<outline htmlUrl="http://web.me.com/normsherman/Site/Podcast/Podcast.html" text="The Drabblecast" xmlUrl="http://web.me.com/normsherman/Site/Podcast/rss.xml"/>
</outline>
<outline text="Books and Fiction">
<outline htmlUrl="http://www.tor.com/" text="tor.com / category / tordotstories" xmlUrl="http://www.tor.com/rss/category/TorDotStories"/>
</outline>
<outline text="Computers and Programming">
<outline htmlUrl="http://twit.tv/mbw" text="MacBreak Weekly" xmlUrl="http://leo.am/podcasts/mbw"/>
</outline>
<outline text="Computers and Programming">
<outline htmlUrl="http://twit.tv" text="FLOSS Weekly" xmlUrl="http://leo.am/podcasts/floss"/>
</outline>
<outline text="Computers and Programming">
<outline htmlUrl="http://www.coreint.org/" text="Core Intuition" xmlUrl="http://www.coreint.org/podcast.xml"/>
</outline>
<outline text="Python">
<outline htmlUrl="http://advocacy.python.org/podcasts/" text="PyCon Podcast" xmlUrl="http://advocacy.python.org/podcasts/pycon.rss"/>
</outline>
<outline text="Python">
<outline htmlUrl="http://advocacy.python.org/podcasts/" text="A Little Bit of Python" xmlUrl="http://advocacy.python.org/podcasts/littlebit.rss"/>
</outline>
<outline text="Python">
<outline htmlUrl="" text="Django Dose Everything Feed" xmlUrl="http://djangodose.com/everything/feed/"/>
</outline>
<outline text="Miscelaneous">
<outline htmlUrl="http://www.castsampler.com/users/dhellmann/" text="dhellmann's CastSampler Feed" xmlUrl="http://www.castsampler.com/cast/feed/rss/dhellmann/"/>
</outline>
</body>
</opml>
tostring()
in realtà scrive ad un oggetto in memoria tipo file, quindi restituisce la stringa che rappresenta l'intero albero degli elementi. Quando si lavora con un grande numero di dati, occorrerà meno memoria e si avrà un uso più efficiente delle librerie di I/O scrivendo direttamente verso un handle di file usando il metodo
write()
di ElementTree.
import sys
from xml.etree.ElementTree import Element, SubElement, Comment, ElementTree
top = Element('top')
comment = Comment('Generato per PyMOTW')
top.append(comment)
child = SubElement(top, 'figlio')
child.text = 'Questo figlio contiene testo.'
child_with_tail = SubElement(top, 'figlio_con_coda')
child_with_tail.text = 'Questo figlio contiene testo normale.'
child_with_tail.tail = 'E testo "in coda".'
child_with_entity_ref = SubElement(top, 'figlio_con_rif_entita')
child_with_entity_ref.text = 'Questo & Quello'
ElementTree(top).write(sys.stdout)
L'esempio usa
sys.stdout
per scrivere verso la console, ma potrebbe anche scrivere verso un file aperto od un socket.
$ python ElementTree_write.py <top><figlio>Questo figlio contiene testo.</figlio><figlio_con_coda>Questo figlio contiene testo normale.</figlio_con_coda>E testo "in coda".<figlio_con_rif_entita>Questo & Quello</figlio_con_rif_entita></top>
Vedere anche: