2015-08-02 2 views
4

мне нужно сделать некоторые манипуляции с текстом внутри одного из тегов и хочет получить родительский тег для каждого найденного текста узла для этогоПочему getparent() работает не так, как ожидалось?

Код:

import lxml.etree 
import pprint 
s = ''' 
<data> 
    data text 
    <foo>foo - <bar>bar</bar> text</foo> 
    data text 
    <bar> 
     bar text 
     <baz>baz text</baz> 
     <baz>baz text</baz> 
     bar text 
    </bar> 
    data text 
</data> 
''' 
etree = lxml.etree.fromstring(s) 
text = etree.xpath("//text()[normalize-space()]") 
pprint.pprint([(s.getparent().tag, s.strip()) for s in text]) 

Выход:

[('data', 'data text'), 
('foo', 'foo -'), 
('bar', 'bar'), 
('bar', 'text'), 
('foo', 'data text'), 
('bar', 'bar text'), 
('baz', 'baz text'), 
('baz', 'baz text'), 
('baz', 'bar text'), 
('bar', 'data text')] 

I ожидается:

[('data', 'data text'), 
('foo', 'foo -'), 
('bar', 'bar'), 
('foo', 'text'), 
('data', 'data text'), 
('bar', 'bar text'), 
('baz', 'baz text'), 
('baz', 'baz text'), 
('bar', 'bar text'), 
('data', 'data text')] 

Где моя ошибка? Похоже на теги в моем представлении - это не родительский тег для текста в дереве, а просто предыдущий тег.

Редактировать Рабочий код для моих потребностей:

etree = lxml.etree.fromstring(s) 
text = etree.xpath("//text()[normalize-space()]") 
for s in text: 
    if s.is_tail: 
     print(s.getparent().getparent().tag, s.strip()) 
    else: 
     print(s.getparent().tag, s.strip()) 

ответ

2

То, что вы видите, связано с свойством tail (текст сразу после конечного тега), что является особенностью метода ElementTree и lxml r представление XML.

Добавляя is_tail тест (возвращает True если текст "хвост текст") в вашем коде, вы можете увидеть, что происходит:

import lxml.etree 
import pprint 

s = ''' 
<data> 
    data text 
    <foo>foo - <bar>bar</bar> text</foo> 
    data text 
    <bar> 
     bar text 
     <baz>baz text</baz> 
     <baz>baz text</baz> 
     bar text 
    </bar> 
    data text 
</data> 
''' 

etree = lxml.etree.fromstring(s) 
text = etree.xpath("//text()[normalize-space()]") 
pprint.pprint([(s.getparent().tag, s.is_tail, s.strip()) for s in text]) 

Выход:

[('data', False, 'data text'), 
('foo', False, 'foo -'), 
('bar', False, 'bar'),§ 
('bar', True, 'text'), 
('foo', True, 'data text'), 
('bar', False, 'bar text'), 
('baz', False, 'baz text'), 
('baz', False, 'baz text'), 
('baz', True, 'bar text'), 
('bar', True, 'data text')] 
1

Это, насколько я могу судить, это связано с понятием «хвост» в lxml (См: 2. How ElementTree represents XML). Когда содержимое элемента содержит смесь узлов элементов и текстовых узлов, текстовый узел представлен как «хвост» предыдущего элемента или обычно представлен как дочерний элемент родительского элемента , только если он наступает первым.

Вы можете позвонить getparent() дважды, чтобы получить фактическое родителя в случае текстового узла 'хвост' (is_tail=True), например:

pprint.pprint(
    [(s.getparent().getparent().tag if s.is_tail else s.getparent().tag, 
     s.strip()) 
    for s in text] 
    ) 

выход:

[('data', 'data text'), 
('foo', 'foo -'), 
('bar', 'bar'), 
('foo', 'text'), 
('data', 'data text'), 
('bar', 'bar text'), 
('baz', 'baz text'), 
('baz', 'baz text'), 
('bar', 'bar text'), 
('data', 'data text')] 
Смежные вопросы