xml.etree.ElementTree - Genera ed elabora documenti XML
Scopo: La libreria ElementTree include strumenti per elaborare XML usando API basate su eventi e documento, cercando i documenti elaborati con espressioni XPath, e creando o modificando documenti esistenti.
Elaborare un Documento XML
I documenti XML elaborati sono rappresentati in memoria da oggetti ElementTree
e Element
connessi in una struttura ad albero basata sul modo nel quale i nodi nel documento XML sono annidati.
L'elaborazione di un intero documento con parse()
ritorna una istanza di ElementTree
. L'alberatura conosce tutto circa i dati del documento in input, e i nodi dell'albero possono essere cercati o manipolati sul posto. Mentre questa flessibilità può rendere il lavoro con i documenti elaborati più conveniente, tipicamente richiede più memoria rispetto a un approccio di elaborazione basato su eventi, visto che l'intero documento deve essere caricato tutto in una volta.
L'impronta di memoria di piccoli, semplici documenti come questo elenco di podcast rappresentati con il formato OPML non è significativo.
<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.0">
<head>
<title>My Podcasts</title>
<dateCreated>Sat, 06 Aug 2016 15:53:26 GMT</dateCreated>
<dateModified>Sat, 06 Aug 2016 15:53:26 GMT</dateModified>
</head>
<body>
<outline text="Non-tech">
<outline
text="99% Invisible" type="rss"
xmlUrl="http://feeds.99percentinvisible.org/99percentinvisible"
htmlUrl="http://99percentinvisible.org" />
</outline>
<outline text="Python">
<outline
text="Talk Python to Me" type="rss"
xmlUrl="https://talkpython.fm/episodes/rss"
htmlUrl="https://talkpython.fm" />
<outline
text="Podcast.__init__" type="rss"
xmlUrl="http://podcastinit.podbean.com/feed/"
htmlUrl="http://podcastinit.com" />
</outline>
</body>
</opml>
Per elaborare il file, si passi un handle di file aperto a parse()
.
# ElementTree_parse_opml.py
from xml.etree import ElementTree
with open('podcasts.opml', 'rt') as f:
tree = ElementTree.parse(f)
print(tree)
Verranno letti i dati, elaborato l'XML, e ritornato un oggetto ElementTree
.
$ python3 ElementTree_parse_opml.py <xml.etree.ElementTree.ElementTree object at 0x7f4fa3092310>
Attraversare l'Albero Elaborato
Per vistare nell'ordine tutti i figli nell'ordine, si usi iter()
per creare un generatore che itera attraverso l'istanza di ElementTree
.
# ElementTree_dump_opml.py
from xml.etree import ElementTree
with open('podcasts.opml', 'rt') as f:
tree = ElementTree.parse(f)
for node in tree.iter():
print(node.tag)
Questo esempio stampa l'intero albero, un tag alla volta.
$ python3 ElementTree_dump_opml.py opml head title dateCreated dateModified body outline outline outline outline outline
Per stampare solo i gruppi di nomi e URL dei feed per i podcast, si tralasciano tutti i dati nella sezione di intestazione iterando solo attraverso i nodi outline
e stampando gli attributi text
e xmlUrl
recuperandone i valori nel dizionario attrib
.
# ElementTree_show_feed_urls.py
from xml.etree import ElementTree
with open('podcasts.opml', 'rt') as f:
tree = ElementTree.parse(f)
for node in tree.iter('outline'):
name = node.attrib.get('text')
url = node.attrib.get('xmlUrl')
if name and url:
print(' %s' % name)
print(' %s' % url)
else:
print(name)
L'argomento di iter()
indica che l'elaborazione viene limitata solo ai nodi con il tag outline
.outline
$ python3 ElementTree_show_feed_urls.py Non-tech 99% Invisible http://feeds.99percentinvisible.org/99percentinvisible Python Talk Python to Me https://talkpython.fm/episodes/rss Podcast.__init__ http://podcastinit.podbean.com/feed/
Trovare Nodi in un Documento
L'attraversare un intero albero in questo modo, cercando i nodi che interessano, può essere incline a errori. L'esempio precedente ha dovuto verificare ogni nodo outline
per determinare se si trattasse di un gruppo (nodi con un solo attributo text
) o podcast (con entrambi gli attributi text
e xmlUrl
). Per produrre una semplice lista di URL di feed di podcast, senza nomi o gruppi, la logica potrebbe essere semplificata usando findall()
per trovare i nodi con caratteristiche di ricerca più descrittive.
Come primo passaggio per convertire la prima versione si può usare un argomento XPath per trovare tutti i nodi outline
.
# ElementTree_find_feeds_by_tag.py
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 presenta differenze sostanziali con la versione che usa getiterator()
. Deve comunque verificare la presenza di'URL
, eccetto che non stampa il nome del gruppo quando URL
non viene trovato.
$ python3 ElementTree_find_feeds_by_tag.py http://feeds.99percentinvisible.org/99percentinvisible https://talkpython.fm/episodes/rss http://podcastinit.podbean.com/feed/
E' possibile trarre vantaggio dal fatto che i nodi outline
sono nidificati solo due livelli più in profondità. Modificando il percorso di ricerca in .//outline/outline
significa che il ciclo elaborerà solo il secondo livello dei nodi outline
.
# ElementTree_find_feeds_by_structure.py
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 attende che tutti i nodi outline
annidati due livelli in profondità nell'input abbiano un attributo xmlURL
che si riferisce al feed del podcast, quindi il ciclo può evitare di cercare l'attributo prima di usarlo.
$ python3 ElementTree_find_feeds_by_structure.py http://feeds.99percentinvisible.org/99percentinvisible https://talkpython.fm/episodes/rss http://podcastinit.podbean.com/feed/
Questa versione è limitata alla struttura esistente, tuttavia, quindi se i nodi outline
sono ristrutturati in un albero più profondo non smetterebbe di funzionare.
Elaborare gli Attributi dei Nodi
Gli elementi ritornati da findall()
e iter()
sono oggetti Element
, ognuno di essi rappresentante un node nell'albero XML. Ogni Element
ha attributi per accedere ai dati estratti dall'XML. La cosa può essere illustrata con un esempio in qualche modo più contenuto del file in input.
1 2 3 4 5 6 7 8 9 | <?xml version="1.0" encoding="UTF-8"?>
<top>
<child>Testo normale.</child>
<child_with_tail>Testo normale.</child_with_tail>testo "in coda".
<with_attributes name="value" foo="bar" />
<entity_expansion attribute="Questo & Quello">
Quello & Questo
</entity_expansion>
</top>
|
Gli attributi XML di un nodo sono disponibili nella proprietà attrib
, che agisce come un dizionario.
# ElementTree_node_attributes.py
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 a riga 5 ha due attributi, name
e foo
.
$ python3 ElementTree_node_attributes.py with_attributes foo = "bar" name = "value"
Il testo contenuto nei nodi è disponibile, assieme al testo in tail
che si trova alla fine di un tag chiuso.
# ElementTree_node_text.py
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
a riga 3 contiene testo incorporato e il nodo a riga 4 ha testo in coda, spazi compresi.
$ python3 ElementTree_node_text.py child testo del nodo figlio: Testo normale. e testo di coda : child_with_tail testo del nodo figlio: Testo normale. e testo di coda : testo "in coda".
I riferimenti a entità XML nel documento sono convertiti negli appropriati caratteri prima che i valori siano ritornati.
# ElementTree_entity_references.py
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 automatica consente di ignorare i dettagli di implementazione della rappresentazione di certi caratteri in un documento XML.
$ python3 ElementTree_entity_references.py entity_expansion nell'attributo: Questo & Quello nel testo : Quello & Questo
Monitorare Eventi Durante l'Elaborazione
L'altra API per elaborare documenti XML è basata sugli eventi. L'elaboratore genera eventi start
per l'apertura ed eventi end
per la chiusura di tag. I dati possono essere estratti dal documento durante la fase di elaborazione iterando sul flusso di eventi, il che è conveniente se non è necessario manipolare l'intero documento successivamente e non serve mantenere l'intero documento elaborato in memoria.
Gli eventi possono essere uno tra:
NOME EVENTO | DESCRIZIONE |
---|---|
start |
E' stato rilevato un nuovo tag. E' stata elaborata la parentesi angolare di chiusura del tag, ma non il suo contenuto |
end |
La parentesi angolare di chiusura di un tag di chiusura è stata elaborata. Tutti i figli sono già stati elaborati. |
start-ns |
Inizia la dichiarazione di uno spazio dei nomi |
end-ns |
Finisce la dichiarazione di uno spazio dei nomi |
iterparse()
ritorna un iterabile che produce tuple contenenti il nome dell'evento e il nodo che ha scatenato l'evento.
# ElementTree_show_all_events.py
from xml.etree.ElementTree import iterparse
depth = 0
prefix_width = 8
prefix_dots = '.' * prefix_width
line_template = ' '.join([
'{prefix:<0.{prefix_len}}',
'{event:<8}',
'{suffix:<{suffix_len}}',
'{node.tag:<12}',
' {node_id}',
])
EVENT_NAMES = ['start', 'end', 'start-ns', 'end-ns']
for (event, node) in iterparse('podcasts.opml', EVENT_NAMES):
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, sono eventi end
sono generati. Per vedere altri eventi si passi l'elenco dei nomi degli eventi desiderati a iterparse()
, come in questo esempio.
$ python3 ElementTree_show_all_events.py start opml 139989621343632 .. start head 139989621343712 .... start title 139989621343792 .... end title 139989621343792 .... start dateCreated 139989621343872 .... end dateCreated 139989621343872 .... start dateModified 139989621344032 .... end dateModified 139989621344032 .. end head 139989621343712 .. start body 139989621344192 .... start outline 139989621344272 ...... start outline 139989621344432 ...... end outline 139989621344432 .... end outline 139989621344272 .... start outline 139989621344512 ...... start outline 139989621344752 ...... end outline 139989621344752 ...... start outline 139989621344992 ...... end outline 139989621344992 .... end outline 139989621344512 .. end body 139989621344192 end opml 139989621343632
Lo stile di elaborazione a eventi è più naturale per talune operazioni, come la conversione di input XML in qualche altro formato. Questa tecnica può essere usata per convertire la lista di podcast degli esempi precedenti da file XML a file CSV, in modo che possano essere caricati in un foglio elettronico o database.
# ElementTree_write_podcast_csv.py
import csv
from xml.etree.ElementTree import iterparse
import sys
writer = csv.writer(sys.stdout, quoting=csv.QUOTE_NONNUMERIC)
group_name = ''
parsing = iterparse('podcasts.opml', events=['start'])
for (event, node) in parsing:
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 conversione non deve mantenere l'intero file di input elaborato in memoria, e l'elaborazione di ciascun nodo mano a mano che viene rilevato è più efficiente.
$ python3 ElementTree_write_podcast_csv.py "Non-tech","99% Invisible","http://feeds.99percentinvisible.org/99percentinvisible","http://99percentinvisible.org" "Python","Talk Python to Me","https://talkpython.fm/episodes/rss","https://talkpython.fm" "Python","Podcast.__init__","http://podcastinit.podbean.com/feed/","http://podcastinit.com"
Creare un Costruttore di Albero Personalizzato
Un modo potenzialmente più efficace di gestire eventi di elaborazione è sostituire il comportamento standard del costruttore di albero con una versione personalizzata. L'elaboratore XMLParser
usa TreeBuilder
per elaborare l'XML e chiamare metodi su una classe destinazione per salvare i risultati. Il risultato usuale è una istanza di ElementTree
creata in modalità predefinita dalla classe TreeBuilder
. La sostituzione di TreeBuilder
con un'altra classe consente di ricevere eventi prima che i nodi Element
vengano istanziati, risparmiando quella porzione di elaborazione.
Questo convertitore XML -> CSV dalla sezione precedente può essere reimplementato come costruttore di albero.
# ElementTree_podcast_csv_treebuilder.py
import csv
import io
from xml.etree.ElementTree import XMLParser
import sys
class PodcastListToCSV(object):
def __init__(self, outputFile):
self.writer = csv.writer(outputFile, quoting=csv.QUOTE_NONNUMERIC)
self.group_name = ''
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"
def data(self, data):
"Ignora i dati all'interno dei nodi"
def close(self):
"Nulla di speciale da fare qui"
target = PodcastListToCSV(sys.stdout)
parser = XMLParser(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 si individua un tag di chiusura viene chiamato end()
con il nome. In mezzo, viene chiamato data()
quando un nodo ha del contenuto (ci si attende che il costruttore di albero mantenga aggiornata l'informazione del nodo "corrente"). Quando viene elaborato tutto l'input, viene chiamato close()
. Può ritornare un valore, che verrà restituito all'utente di TreeBuilder
.
$ python3 ElementTree_podcast_csv_treebuilder.py "Non-tech","99% Invisible","http://feeds.99percentinvisible.org/99percentinvisible","http://99percentinvisible.org" "Python","Talk Python to Me","https://talkpython.fm/episodes/rss","https://talkpython.fm" "Python","Podcast.__init__","http://podcastinit.podbean.com/feed/","http://podcastinit.com"
Elaborare Stringhe
Per lavorare piccole parti di testo XML, specialmente stringhe letterali che potrebbero essere incorporate nel sorgente di un programma, si usi XML()
e la stringa che contiene il frammento XML sarà elaborata quale unico argomento.
# ElementTree_XML.py
from xml.etree.ElementTree import XML
def show_node(node):
print(node.tag)
if node.text is not None and node.text.strip():
print(' text: "%s"' % node.text)
if node.tail is not None and node.tail.strip():
print(' tail: "%s"' % node.tail)
for name, value in sorted(node.attrib.items()):
print(' %-4s = "%s"' % (name, value))
for child in node:
show_node(child)
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:
show_node(elem)
Al contrario di parse()
il valore ritornato è una istanza di Element
invece che di ElementTree
. Element
supporta direttamente il protocollo di iterazione, quindi non serve chiamare getiterator()
.
$ python3 ElementTree_XML.py parsed = <Element 'root' at 0x7fb61d3b9db0> 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"
Per XML strutturato che usa l'attributo id
per identificare nodi univoci, XMLID()
è un modo conveniente per accedere ai risultati elaborati.
# ElementTree_XMLID.py
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()
ritorna l'albero elaborato come oggetto Element
, assieme a un dizionario che mappa le stringhe dell'attributo id
ai singoli nodi dell'albero.
$ python3 ElementTree_XMLID.py a = <Element 'child' at 0x7fa6b33c94f0> b = <Element 'child' at 0x7fa6b3376540> c = <Element 'child' at 0x7fa6b33765e0>
Costruire Documenti con Nodi Element
Oltre alle capacità di elaborazione, xml.etree.ElementTree supporta anche la creazione di documenti XML ben formati partendo da oggetti Element
costruiti in una applicazione. La classe Element
usata quanto si elabora un documento sa anche come generare una forma serializzata del suo contenuto, che poi può essere scritta verso un file o altro canale dati.
Ci sono tre funzioni di aiuto utili alla creazione di una gerarchia di nodi Element
. Element()
crea un nodo standard, SubElement()
attacca un nuovo nodo a un genitore, Comment()
crea un nodo che viene serializzato usando la sintassi XML per i commenti.
# ElementTree_create.py
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
top = Element('top')
comment = Comment('Generato per PyMOTW-it')
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))
Il risultato contiene solo i nodi XML nell'albero, non la dichiarazione con versione e codifica.
$ python3 ElementTree_create.py b'<top><!--Generato per PyMOTW-it--><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>'
Il carattere &
nel testo di child_with_entity_ref
viene convertito automaticamente nella sua entità di riferimento &
.
Stampa Pretty-Printing di XML
ElementTree
non si preoccupa di formattare il risultato di tostring()
per facilitare la lettura in quanto l'aggiunta di spazi, tabulazioni, caratteri di fine riga cambia il contenuto di un documento. Per rendere il risultato più leggibile, gli esempi restanti usano il metodo toprettyxml()
del modulo xml.dom.minidom
per rielaborare l'XML.
# ElementTree_pretty.py
from xml.etree import ElementTree
from xml.dom import minidom
def prettify(elem):
"""Ritorna una stringa XML pretty-print per Element.
"""
rough_string = ElementTree.tostring(elem, 'utf-8')
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent=" ")
L'esempio aggiornato ora risulta così:
# ElementTree_create_pretty.py
from xml.etree.ElementTree import Element, SubElement, Comment
from ElementTree_pretty import prettify
top = Element('top')
comment = Comment('Generato per PyMOTW-it')
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))
e il risultato è più facile da leggere.
$ python3 ElementTree_create_pretty.py <?xml version="1.0" ?> <top> <!--Generato per PyMOTW-it--> <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 alla spaziatura aggiuntiva utilizzata per formattare, toprettyxml()
aggiunge anche la dichiarazione XML nel risultato.
Impostare le Proprietà di un Elemento
L'esempio precedente creava nodi con tag e contenuto testo, ma non impostava attributi per i nodi. Molti degli esempi precedenti lavoravano con un file OPML che elencava dei podcast e i loro feed. I nodi outline
nell'albero usavano attributi per i nodi di gruppo e le proprietà del podcast. ElementTree
può essere usato per costruire un file simile da un input CSV, impostando tutti gli attributi di un elemento in fase di costruzione dell'albero.
# ElementTree_csv_to_xml.py
import csv
from xml.etree.ElementTree import (
Element, SubElement, Comment
)
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 current_group is None 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))
Questo esempio usa due tecniche per impostare i valori di attributo dei nuovi nodi. Il nodo radice viene configurato usando set()
per cambiare un attributo alla volta. Ai nodi podcast vengono assegnati tutti i propri attributi in una sola volta passando un dizionario al codice che costruisce il nodo.
$ python3 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>2021-06-03 09:16:42.132249</dateCreated> <dateModified>2021-06-03 09:16:42.132249</dateModified> </head> <body> <outline text="Non-tech"> <outline text="99% Invisible" xmlUrl="http://feeds.99percentinvisible.org/99percentinvisible" htmlUrl="http://99percentinvisible.org"/> </outline> <outline text="Python"> <outline text="Talk Python to Me" xmlUrl="https://talkpython.fm/episodes/rss" htmlUrl="https://talkpython.fm"/> </outline> <outline text="Python"> <outline text="Podcast.__init__" xmlUrl="http://podcastinit.podbean.com/feed/" htmlUrl="http://podcastinit.com"/> </outline> </body> </opml>
Costruire Alberi da Elenchi di Nodi
Molteplici figli possono essere aggiunti assieme a una istanza di Element
con il metodo extend()
. L'argomento per extend()
è un qualsiasi iterabile, compreso list
o un'altra istanza di Element
.
# ElementTree_extend.py
from xml.etree.ElementTree import Element, tostring
from ElementTree_pretty import prettify
top = Element('top')
children = [
Element('figlio', num=str(i))
for i in range(3)
]
top.extend(children)
print(prettify(top))
Quando si passa un oggetto list
i nodi nella lista sono aggiunti direttamente al nuovo genitore.
$ python3 ElementTree_extend.py <?xml version="1.0" ?> <top> <figlio num="0"/> <figlio num="1"/> <figlio num="2"/> </top>
Quando viene fornita un'altra istanza di Element
, i figli di quel nodo sono aggiunti al nuovo genitore.
# ElementTree_extend_node.py
from xml.etree.ElementTree import (
Element, SubElement, tostring, XML
)
from ElementTree_pretty import prettify
top = Element('top')
parent = SubElement(top, 'genitore')
children = XML(
'<root><figlio num="0" /><figlio num="1" />'
'<figlio num="2" /></root>'
)
parent.extend(children)
print(prettify(top))
In questo caso il nodo con tag root
creato elaborando la stringa XML ha tre figli, i quali sono aggiunti al nodo genitore
. Il nodo root
non è parte dell'albero risultante.
$ python3 ElementTree_extend_node.py <?xml version="1.0" ?> <top> <genitore> <figlio num="0"/> <figlio num="1"/> <figlio num="2"/> </genitore> </top>
E' importante capire che extend()
non modifica alcuna relazione esistente padre-figlio tra i nodi. Se i valori passati a extend()
esistono già da qualche parte nell'albero, essi saranno conservati, e ci saranno ripetizioni nel risultato.
# ElementTree_extend_node_copy.py
from xml.etree.ElementTree import (
Element, SubElement, tostring, XML
)
from ElementTree_pretty import prettify
top = Element('top')
parent_a = SubElement(top, 'genitore', id='A')
parent_b = SubElement(top, 'genitore', id='B')
# Crea figlio
children = XML(
'<root><figlio num="0" /><figlio num="1" />'
'<figlio num="2" /></root>'
)
# Imposta l'id all'id dell'oggetto Python del nodo
# per facilitare l'individuazione dei duplicati.
for c in children:
c.set('id', str(id(c)))
# Aggiunge al primo genitore
parent_a.extend(children)
print('A:')
print(prettify(top))
print()
# Copia nodi al secondo genitore
parent_b.extend(children)
print('B:')
print(prettify(top))
print()
Con l'impostazione dell'attributo id
di questi figli all'identificativo univoco Python di un oggetto si evidenzia il fatto che stessi oggetti nodo appaiono nell'albero risultante più di una volta.
$ python3 ElementTree_extend_node_copy.py A: <?xml version="1.0" ?> <top> <genitore id="A"> <figlio num="0" id="139878292229792"/> <figlio num="1" id="139878292217920"/> <figlio num="2" id="139878292218000"/> </genitore> <genitore id="B"/> </top> B: <?xml version="1.0" ?> <top> <genitore id="A"> <figlio num="0" id="139878292229792"/> <figlio num="1" id="139878292217920"/> <figlio num="2" id="139878292218000"/> </genitore> <genitore id="B"> <figlio num="0" id="139878292229792"/> <figlio num="1" id="139878292217920"/> <figlio num="2" id="139878292218000"/> </genitore> </top>
Serializzare XML Verso un Canale
tostring()
è implementato per scrivere un oggetto di tipo file in-memoria, quindi ritorna una stringa che rappresenta l'intero albero di elementi. Quando si lavora con grandi quantità di dati, scrivere direttamente a un file handle usando il metodo write()
di ElementTree
richiede minor memoria e costituisce un uso più efficiente delle librerie I/O.
# ElementTree_write.py
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'
empty_child = SubElement(top, 'figlio vuoto')
ElementTree(top).write(sys.stdout.buffer)
Questo esempio usa sys.stdout.buffer
per scrivere alla console in luogo di sys.stdout
poichè ElementTree
produce byte codificati invece che una stringa Unicode. Si potrebbe anche scrivere verso un file in modalità binaria oppure un socket.
$ python3 ElementTree_write.py <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><figlio vuoto /></top>
L'ultimo nodo nell'albero non contiene testo o sotto nodi, quindi viene scritto come un tag vuoto, <empty_child />
. write()
riceve un metodo come argomento per controllare la gestione dei nodi vuoti.
# ElementTree_write_method.py
import sys
from xml.etree.ElementTree import (
Element, SubElement, ElementTree,
)
top = Element('top')
child = SubElement(top, 'child')
child.text = 'Contiene testo.'
empty_child = SubElement(top, 'empty_child')
for method in ['xml', 'html', 'text']:
print(method)
sys.stdout.flush()
ElementTree(top).write(sys.stdout.buffer, method=method)
print('\n')
Sono supportati tre metodi.
MODALITA' | DESCRIZIONE |
---|---|
xml |
Il metodo predefinito, produce <empty_child /> |
html |
Produce la coppia di tag, come richiesto nei documenti HTML (<empty_child><empty_child /> ) |
text |
Stampa solo il testo dei nodi, quindi salta completamente tag vuoti. |
$ python3 ElementTree_write_method.py xml <top><child>Contiene testo.</child><empty_child /></top> html <top><child>Contiene testo.</child><empty_child></empty_child></top> text Contiene testo.
Vedere anche:
- xml.etree.ElementTree.html
- La documentazione della libreria standard per questo modulo.
- csv
- Legge e scrive file con valori separati da virgola
- defusedxml
- Un pacchetto con sistemazioni per le varie vulnerabilità di negazione del servizio causate da espansione di entità, utile per lavorare con dati XML non affidabili.
- Pretty print xml with python - indenting xml
- Un suggerimento da Rene Dudfield per la stampa di XML in python in formato gradevole alla vista
- ElementTree overview
- La documentazione originale di Fredrick Lundh e collegamenti alle versioni di sviluppo della libreria ElementTree
- Process XML in Python with ElementTree
- Articolo dello sviluppatore IBM Dave Winer
- XML Path Language (XPath)
- Una sintassi per identificare parti di un documento XML
- XPath Support in ElementTree
- Parte della documentazione originale di Fredrick Lundh per ElementTree