2014-06-05 9 views
7

Supponiamo che io ho il seguente documento XML:iterare su testo e gli elementi in eTree lxml

<species> 
    Mammals: <dog/> <cat/> 
    Reptiles: <snake/> <turtle/> 
    Birds: <seagull/> <owl/> 
</species> 

allora ottengo l'elemento species in questo modo:

import lxml.etree 
doc = lxml.etree.fromstring(xml) 
species = doc.xpath('/species')[0] 

Ora vorrei stampare un elenco di animali raggruppati per specie. Come potrei farlo usando ElementTree API?

+0

se si guarda oltre alla tua destra ... sembra che il 4 ° uno verso il basso sotto correlata dovrebbe puntare nella giusta direzione ... –

+0

Hai sotto controllo del formato XML?Normalmente, i classificatori come i Mammiferi, ecc., Sono espressi come nomi di elementi xml o attributi (ad es. ) in modo che i selettori di xpath siano facilmente scritti. – tdelaney

+0

No, non posso modificare l'XML. – Alicia

risposta

4

Se si enumerano tutti i nodi, vedrete un nodo di testo con la classe seguito da nodi elemento con la specie:

>>> for node in species.xpath("child::node()"): 
...  print type(node), node 
... 
<class 'lxml.etree._ElementStringResult'> 
    Mammals: 
<type 'lxml.etree._Element'> <Element dog at 0xe0b3c0> 
<class 'lxml.etree._ElementStringResult'> 
<type 'lxml.etree._Element'> <Element cat at 0xe0b410> 
<class 'lxml.etree._ElementStringResult'> 
    Reptiles: 
<type 'lxml.etree._Element'> <Element snake at 0xe0b460> 
<class 'lxml.etree._ElementStringResult'> 
<type 'lxml.etree._Element'> <Element turtle at 0xe0b4b0> 
<class 'lxml.etree._ElementStringResult'> 
    Birds: 
<type 'lxml.etree._Element'> <Element seagull at 0xe0b500> 
<class 'lxml.etree._ElementStringResult'> 
<type 'lxml.etree._Element'> <Element owl at 0xe0b550> 
<class 'lxml.etree._ElementStringResult'> 

modo da poter costruire da lì:

my_species = {} 
current_class = None 
for node in species.xpath("child::node()"): 
    if isinstance(node, lxml.etree._ElementStringResult): 
     text = node.strip(' \n\t:') 
     if text: 
      current_class = my_species.setdefault(text, []) 
    elif isinstance(node, lxml.etree._Element): 
     if current_class is not None: 
      current_class.append(node.tag) 
print my_species 

risultati in

{'Mammals': ['dog', 'cat'], 'Reptiles': ['snake', 'turtle'], 'Birds': ['seagull', 'owl']} 

si tratta di tutti fragili ... piccoli cambiamenti nel modo in cui i nodi di testo sono disposti può rovinare l'analisi.

+0

Mi piace questo, semplice XPath :) – Alicia

+0

@alecxe - si elabora un numero sempre crescente di nodi di testo precedenti e si scartano tutti tranne l'ultimo ogni volta ... Penso che la mia soluzione sia più semplice. – tdelaney

+0

In Python 3, il tipo di nodo di testo è 'lxml.etree._ElementUnicodeResult'. – saaj

2

nota design

La risposta da @tdelaney è fondamentalmente giusto, ma voglio puntare a una sfumatura di Python elemento albero API. Ecco una citazione da the lxml tutorial:

Gli elementi possono contenere testo:

<root>TEXT</root> 

In molti documenti XML (documenti data-centric), questo è l'unico posto in cui il testo può essere trovato. È incapsulato da un tag foglia in fondo alla gerarchia dell'albero.

Tuttavia, se XML è utilizzato per i documenti di testo con tag come HTML (X), il testo può apparire anche tra elementi diversi, proprio nel bel mezzo di un albero:

<html><body>Hello<br/>World</body></html> 

Qui, il tag <br/> è circondato da testo. Questo è spesso definito come stile documento o XML a contenuto misto. Gli elementi supportano questo attraverso la loro proprietà tail. Contiene il testo che segue direttamente l'elemento, fino all'elemento successivo nell'albero XML.

Le due proprietà text e tail sono sufficienti per rappresentare qualsiasi contenuto di testo in un documento XML. In questo modo, l'API ElementTree non richiede alcun nodo di testo speciale oltre alla classe Element, che tende a intromettersi abbastanza spesso (come potreste sapere dalle classiche API DOM).

Attuazione

Prendendo queste proprietà in considerazione è possibile recuperare il testo del documento senza forzare l'albero di nodi di testo in uscita.

#!/usr/bin/env python3.3 


import itertools 
from pprint import pprint 

try: 
    from lxml import etree 
except ImportError: 
    from xml.etree import cElementTree as etree 


def textAndElement(node): 
    '''In py33+ recursive generators are easy''' 

    yield node 

    text = node.text.strip() if node.text else None 
    if text: 
    yield text 

    for child in node: 
    yield from textAndElement(child) 

    tail = node.tail.strip() if node.tail else None 
    if tail: 
    yield tail 


if __name__ == '__main__': 
    xml = ''' 
    <species> 
     Mammals: <dog/> <cat/> 
     Reptiles: <snake/> <turtle/> 
     Birds: <seagull/> <owl/> 
    </species> 
    ''' 
    doc = etree.fromstring(xml) 

    pprint(list(textAndElement(doc))) 
    #[<Element species at 0x7f2c538727d0>, 
    #'Mammals:', 
    #<Element dog at 0x7f2c538728c0>, 
    #<Element cat at 0x7f2c53872910>, 
    #'Reptiles:', 
    #<Element snake at 0x7f2c53872960>, 
    #<Element turtle at 0x7f2c538729b0>, 
    #'Birds:', 
    #<Element seagull at 0x7f2c53872a00>, 
    #<Element owl at 0x7f2c53872a50>] 

    gen = textAndElement(doc) 
    next(gen) # skip root 
    groups = [] 
    for _, g in itertools.groupby(gen, type): 
    groups.append(tuple(g)) 

    pprint(dict(zip(*[iter(groups)] * 2))) 
    #{('Birds:',): (<Element seagull at 0x7fc37f38aaa0>, 
    #    <Element owl at 0x7fc37f38a820>), 
    #('Mammals:',): (<Element dog at 0x7fc37f38a960>, 
    #    <Element cat at 0x7fc37f38a9b0>), 
    #('Reptiles:',): (<Element snake at 0x7fc37f38aa00>, 
    #    <Element turtle at 0x7fc37f38aa50>)} 
Problemi correlati