Как получить весь контент между двумя тегами 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>. 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 но не смог найти решение для этого случая (целое содержимое, включая внутренние теги).

  • Извлечение элементов из файла XML с использованием Python
  • Анализ XML-кода из URL-адреса в объект python
  • конвертировать xml в python dict
  • Чтение XML-файла и выбор его атрибутов в Python
  • Как заставить BeautifulSoup 4 уважать самозакрывающийся тег?
  • UnicodeEncodeError: кодек ascii не может кодировать символ u '\ xef' в позиции 0: порядковый номер не в диапазоне (128)
  • получить всех родителей узла xml с помощью python
  • Анализатор Python SVG
  • 5 Solutions collect form web for “Как получить весь контент между двумя тегами xml в Python?”

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

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

     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 избегает дублирования хвоста.

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

    Это значительно облегчает работу с 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.' 

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

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

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

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

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

     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. 

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

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

     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, содержащего новые строки.

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

     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 – это, itertext , путь сюда!

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

    Python - лучший язык программирования в мире.