Самый Pythonic способ найти родственного элемента в XML - PullRequest
2 голосов
/ 05 июля 2019

Проблема: У меня есть следующий фрагмент XML:

...snip...
<p class="p_cat_heading">DEFINITION</p>
<p class="p_numberedbullet"><span class="calibre10">This</span>, <span class="calibre10">these</span>. </p>
<p class="p_cat_heading">PRONUNCIATION </p>
..snip...

Мне нужно выполнить поиск по всему XML, найти заголовок с текстом DEFINITION и распечатать соответствующие определения. Формат определений не согласован и может изменять атрибуты / элементы, поэтому единственный надежный способ захвата всего этого - читать до следующего элемента с атрибутом p_cat_heading.

Сейчас я использую следующий код, чтобы найти все заголовки:

for heading in root.findall(".//*[@class='p_cat_heading']"):
    if heading.text == "DEFINITION":
        <WE FOUND THE CORRECT HEADER - TAKE ACTION HERE>

То, что я пробовал:

  • Использование метода getnext lxml. Это получает следующий брат, который имеет атрибут "p_cat_heading", что не то, что я хочу.
  • follow_sibling - предполагается, что lxml поддерживает это, но выдает «follow-sibling не найден в prefix-map»

Мое решение:

Я еще не закончил, но из-за того, что мой XML короткий, я просто собирался получить список всех элементов, итерировать до элемента с атрибутом DEFINITION, а затем до следующего элемента с атрибутом p_cat_heading. Это ужасное и уродливое решение, но я не могу найти чистую альтернативу.

Что я ищу:

Более Pythonic способ печати определения, которое "это, это" в нашем случае. Решение может использовать либо xpath, либо какую-то альтернативу Предпочтительны Python-нативные решения, но все подойдет.

Ответы [ 2 ]

1 голос
/ 05 июля 2019

Есть несколько способов сделать это, но, полагаясь на xpath для выполнения большей части работы, это выражение

//*[@class='p_cat_heading'][contains(text(),'DEFINITION')]/following-sibling::*[1]

должно работать.

Использование lxml:

from lxml import html

data = [your snippet above]
exp = "//*[@class='p_cat_heading'][contains(text(),'DEFINITION')]/following-sibling::*[1]"

tree = html.fromstring(data) 
target = tree.xpath(exp)

for i in target:
    print(i.text_content())

Вывод:

Это, эти.

1 голос
/ 05 июля 2019

Вы можете использовать BeatifulSoup с CSS-селекторами для этой задачи.Селектор .p_cat_heading:contains("DEFINITION") ~ .p_cat_heading выберет все элементы с классом p_cat_heading, которым предшествует элемент с классом p_cat_heading, содержащий строку "DEFINITION":

data = '''
<p class="p_cat_heading">THIS YOU DONT WANT</p>
<p class="p_numberedbullet"><span class="calibre10">This</span>, <span class="calibre10">these</span>. </p>
<p class="p_cat_heading">DEFINITION</p>
<p class="p_numberedbullet"><span class="calibre10">This</span>, <span class="calibre10">these</span>. </p>
<p class="p_cat_heading">PRONUNCIATION </p>'''

from bs4 import BeautifulSoup

soup = BeautifulSoup(data, 'lxml')

for heading in soup.select('.p_cat_heading:contains("DEFINITION") ~ .p_cat_heading'):
    print(heading.text)

Печать:

PRONUNCIATION 

Дальнейшее чтение

Руководство по выбору CSS

РЕДАКТИРОВАТЬ:

Чтобы выбрать прямой брат после ОПРЕДЕЛЕНИЯ:

data = '''
<p class="p_cat_heading">THIS YOU DONT WANT</p>
<p class="p_numberedbullet"><span class="calibre10">This</span>, <span class="calibre10">these</span>. </p>
<p class="p_cat_heading">DEFINITION</p>
<p class="p_numberedbullet"><span class="calibre10">This is after DEFINITION</span>, <span class="calibre10">these</span>. </p>
<p class="p_cat_heading">PRONUNCIATION </p>
<p class="p_numberedbullet"><span class="calibre10">This is after PRONUNCIATION</span>, <span class="calibre10">these</span>. </p>'''

from bs4 import BeautifulSoup

soup = BeautifulSoup(data, 'lxml')

s = soup.select_one('.p_cat_heading:contains("DEFINITION") + :not(.p_cat_heading)')
print(s.text)

Отпечатки:

This is after DEFINITION, these. 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...