2014-06-05 73 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>)}