2014-06-05 108 views
7

假設我有下面的XML文檔:遍歷文本和元素LXML etree

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

然後我得到了species元素是這樣的:

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

現在我想打印按物種分組的動物列表。我怎麼能使用ElementTree API來做到這一點?

+0

如果你看看你的權利...它看起來像第4個下相關應該指向你在正確的方向... –

+0

你有控制的XML格式?通常,分類器(如Mammals等)表示爲xml元素名稱或屬性(例如),以便xpath選擇器很容易編寫。 – tdelaney

+0

不,我不能更改XML。 – Alicia

回答

4

如果你列舉的所有節點,你會看到與類接着元素節點與種文本節點:

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

所以你可以從那裏建造它:

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 

結果

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

這是文本節點是如何排列的所有脆弱...小的變化可以搞砸解析。

+0

我喜歡這個,簡單的XPath :) – Alicia

+0

@alecxe - 你處理越來越多的以前的文本節點,每次都丟棄最後一個,但我認爲我的解決方案更簡單。 – tdelaney

+0

在Python 3中,文本節點的類型是'lxml.etree._ElementUnicodeResult'。 – saaj

2

設計筆記

通過@tdelaney答案基本上是正確的,但我要指出的Python元素樹API的一個細微差別。下面是從the lxml tutorial報價:

元素可以包含文本:

<root>TEXT</root> 

在許多XML文檔(數據爲中心的文檔),這是文本的地方可以找到的唯一地方。它由葉子標籤封裝在樹層次結構的最底部。

但是,如果用於標記的文本文件,如(X)HTML XML,文本,也可能出現不同元素之間,就在樹的中間:

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

這裏,<br/>標籤包圍文本。這通常被稱爲文檔樣式或混合內容XML。元素通過它們的tail屬性支持這一點。它包含直接跟隨元素的文本,直到XML樹中的下一個元素。

這兩個屬性texttail足以表示XML文檔中的任何文本內容。這樣,ElementTree API 除Element元素類之外不需要任何特殊的文本節點,這些節點往往會相當頻繁地進行(正如您從傳統DOM API中所瞭解的那樣)。

實施

考慮這些特性考慮在內,可以不強制樹輸出的文本節點檢索文檔中的文本。

#!/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>)}