Подтвердить что ты не робот

Как получить весь контент между двумя тегами xml в Python?

Я пытаюсь получить весь контент между открывающим тегом xml и его закрытием.

Получение содержимого в прямых случаях, таких как title ниже, легко, но как я могу получить весь контент между тегами, если используется смешанный контент, и я хочу сохранить внутренние теги?

<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
  <text sometimes="attribute">Some text with <extradata>data</extradata> in it.
  It spans <sometag>multiple lines: <tag>one</tag>, <tag>two</tag> 
  or more</sometag>.</text>
</review>

Я хочу, чтобы содержимое между тегами text, включая теги: Some text with <extradata>data</extradata> in it. It spans <sometag>multiple lines: <tag>one</tag>, <tag>two</tag> or more</sometag>.

В настоящее время я использую регулярные выражения, но это становится бесполезным, и мне не нравится этот подход. Я склоняюсь к решению на основе парсер XML. Я просмотрел minidom, etree, lxml и BeautifulSoup, но не смог найти решение для этого случая (целое содержимое, включая внутренние теги).

4b9b3361

Ответ 1

from lxml import etree
t = etree.XML(
"""<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
  <text>Some text with <extradata>data</extradata> in it.</text>
</review>"""
)
(t.text + ''.join(map(etree.tostring, t))).strip()

Трюк здесь в том, что t является итерируемым, и при повторении он возвращает все дочерние узлы. Поскольку etree избегает текстовых узлов, вам также необходимо восстановить текст до первого дочернего тега, t.text.

In [50]: (t.text + ''.join(map(etree.tostring, t))).strip()
Out[50]: '<title>Some testing stuff</title>\n  <text>Some text with <extradata>data</extradata> in it.</text>'

Или:

In [6]: e = t.xpath('//text')[0]

In [7]: (e.text + ''.join(map(etree.tostring, e))).strip()
Out[7]: 'Some text with <extradata>data</extradata> in it.'

Ответ 2

Здесь что-то работает для меня и вашего образца:

from lxml import etree
doc = etree.XML(
"""<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
  <text>Some text with <extradata>data</extradata> in it.</text>
</review>"""
)

def flatten(seq):
  r = []
  for item in seq:
    if isinstance(item,(str,unicode)):
      r.append(unicode(item))
    elif isinstance(item,(etree._Element,)):
      r.append(etree.tostring(item,with_tail=False))
  return u"".join(r)

print flatten(doc.xpath('/review/text/node()'))

Урожайность:

Some text with <extradata>data</extradata> in it.

xpath выбирает все дочерние узлы элемента <text> и либо выводит их в unicode напрямую, если они являются подклассом string/unicode (<class 'lxml.etree._ElementStringResult'>), либо вызывает на нем etree.tostring, если он Element, with_tail=False избегает дублирования хвоста.

Возможно, вам придется обрабатывать другие типы node, если они присутствуют.

Ответ 3

Это значительно упрощается с lxml *, используя функции parse() и tostring():

from  lxml.etree import parse, tostring

Сначала вы разбираете документ и получаете свой элемент (я использую XPath, но вы можете использовать все, что хотите):

doc = parse('test.xml')
element = doc.xpath('//text')[0]

Функция tostring() возвращает текстовое представление вашего элемента:

>>> tostring(element)
'<text>Some <text>text</text> with <extradata>data</extradata> in it.</text>\n'

Однако вам не нужны внешние элементы, поэтому мы можем удалить их с помощью простого вызова str.replace():

>>> tostring(element).replace('<%s>'%element.tag, '', 1)
'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n'

Обратите внимание, что str.replace() получил 1 в качестве третьего параметра, поэтому он удалит только первое появление открытого тега. Это можно сделать и с закрывающим тегом. Теперь вместо 1 мы передаем -1 для замены:

>>> tostring(element).replace('</%s>'%element.tag, '', -1)
'<text>Some <text>text with <extradata>data</extradata> in it.\n'

Решение, конечно же, должно делать все сразу:

>>> tostring(element).replace('<%s>'%element.tag, '', 1).replace('</%s>'%element.tag, '', -1)
'Some <text>text with <extradata>data</extradata> in it.\n'

EDIT: @Charles сделал хороший момент: этот код является хрупким, поскольку тег может иметь атрибуты. Возможным еще ограниченным решением является разбиение строки на первый >:

>>> tostring(element).split('>', 1)
['<text',
 'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n']

получить вторую результирующую строку:

>>> tostring(element).split('>', 1)[1]
'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n'

затем rsplitting:

>>> tostring(element).split('>', 1)[1].rsplit('</', 1)
['Some <text>text</text> with <extradata>data</extradata> in it.', 'text>\n']

и, наконец, получить первый результат:

>>> tostring(element).split('>', 1)[1].rsplit('</', 1)[0]
'Some <text>text</text> with <extradata>data</extradata> in it.'

Тем не менее, этот код по-прежнему является хрупким, поскольку > является вполне допустимым char в XML, даже внутри атрибутов.

В любом случае, я должен признать, что решение MattH является реальным общим решением.

* На самом деле это решение работает с ElementTree, что отлично, если вы не хотите зависеть от lxml. Единственное различие заключается в том, что вы не сможете использовать XPath.

Ответ 4

Мне нравится решение @Marcin выше, однако я обнаружил, что при использовании его второго варианта (преобразование под node, а не корень дерева) он не обрабатывает сущности.

Его код сверху (изменен для добавления объекта):

from lxml import etree
t = etree.XML("""<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
    <text>this &amp; that.</text>
</review>""")
e = t.xpath('//text')[0]
print (e.text + ''.join(map(etree.tostring, e))).strip()

возвращает:

this & that.

с голой/неэкранированной '&' вместо правильной сущности ('&').

Мое решение состояло в том, чтобы использовать для вызова etree.tostring на уровне node (а не для всех дочерних элементов), затем отмените начальный и конечный теги с помощью регулярного выражения:

import re
from lxml import etree
t = etree.XML("""<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
    <text>this &amp; that.</text>
</review>""")

e = t.xpath('//text')[0]
xml = etree.tostring(e)
inner = re.match('<[^>]*?>(.*)</[^>]*>\s*$', xml, flags=re.DOTALL).group(1)
print inner

дает:

this &amp; that.

Я использовал re.DOTALL, чтобы гарантировать, что это работает для XML, содержащего строки новой строки.

Ответ 5

Просто нашел решение, довольно легко:

In [31]: t = x.find('text')

In [32]: t
Out[32]: <Element text at 0xa87ed74>

In [33]: list(t.itertext())
Out[33]: ['Some text with ', 'data', ' in it.']

In [34]: ''.join(_)
Out[34]: 'Some text with data in it.'

itertext - это, безусловно, путь сюда!

Изменить://Извините, я думал, что вам нужен только текст между детьми, мой плохой