2014-06-05 16 views
7

Załóżmy, że mam następujący dokument XML:iteracyjnego tekstu i elementów w lxml etree

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

następnie uzyskać element species tak:

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

Teraz chciałbym, aby wydrukować lista zwierząt pogrupowanych według gatunków. Jak mogłem to zrobić za pomocą interfejsu API ElementTree?

+0

jeśli spojrzeć na po prawej ... wygląda na to, 4. jeden antypodów powiązany powinien wskazać we właściwym kierunku ... –

+0

masz kontrolę formacie xml?Zwykle klasyfikatory, takie jak ssaki itp., Są wyrażane jako nazwy lub atrybuty elementu xml (np. ), dzięki czemu selektory xpath są łatwo zapisywane. – tdelaney

+0

Nie, nie mogę zmienić kodu XML. – Alicia

Odpowiedz

4

Jeśli wyliczyć wszystkie węzły, zobaczysz węzeł tekstowy z klasy następnie węzłów elementu z gatunków:

>>> 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'> 

Więc można zbudować go stamtąd:

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 

skutkuje

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

to wszystko kruche ... niewielkie zmiany w sposobie węzły tekstowe są ułożone może zepsuć parsowanie.

+0

Podoba mi się ten jeden, prosty XPath :) – Alicia

+0

@alecxe - przetwarzasz ciągle rosnącą liczbę poprzednich węzłów tekstowych i odrzucasz wszystkie oprócz ostatniej za każdym razem ... Myślę, że moje rozwiązanie jest prostsze. – tdelaney

+0

W języku Python 3 typem węzła tekstowego jest 'lxml.etree._ElementUnicodeResult'. – saaj

2

uwaga Projekt

Odpowiedź przez @tdelaney jest w zasadzie w porządku, ale chcę zwrócić uwagę na jeden niuans Python elementem drzewa API. Oto cytat z the lxml tutorial:

Elementy mogą zawierać tekst:

<root>TEXT</root> 

w wielu dokumentach XML (dokumenty danych-centric), jest to jedyne miejsce, gdzie tekst można znaleźć. Jest enkapsulowany przez znacznik liści na samym dole hierarchii drzewa.

Jednakże, jeśli XML jest używany do oznaczonych dokumentów tekstowych, takich jak (X) HTML, tekst może również pojawić się pomiędzy różnymi elementami, w samym środku drzewa:

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

Tutaj tag <br/> jest otoczony tekstem. Jest to często określane jako XML w stylu dokumentu lub XML o mieszanej zawartości. Elementy obsługują to poprzez ich właściwość tail. Zawiera tekst bezpośrednio podążający za elementem, aż do następnego elementu w drzewie XML.

Obie właściwości: text i tail są wystarczające do reprezentowania dowolnej treści tekstowej w dokumencie XML. W ten sposób ElementTree API nie wymaga żadnych specjalnych węzłów tekstowych oprócz klasy Elementów, które mają tendencję do wchodzenia w drogę dość często (jak można się domyślić z klasycznych API DOM).

Realizacja

Biorąc pod uwagę te właściwości możliwe jest odzyskanie tekstu dokumentu bez wymuszania drzewo do węzłów tekstowych wyjście.

#!/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>)} 
Powiązane problemy