Как я могу сослаться на родителя и удалить родительский элемент в RSS XML через LXML в Python?

У меня возникли проблемы со взломом этого. У меня есть RSS-канал в виде файла XML. Упрощенно это выглядит так:

<rss version="2.0">
    <channel>
        <title>My RSS Feed</title>
        <link href="https://www.examplefeedurl.com">Feed</link>
        <description></description>
        <item>...</item>
        <item>...</item>
        <item>...</item>
        <item>
            <guid></guid>
            <pubDate></pubDate>
            <author/>
            <title>Title of the item</title>
            <link href="https://example.com" rel="alternate" type="text/html"/>
            <description>
            <![CDATA[<a href="https://example.com" target="_blank" rel="noopener noreferrer">View Example</a>]]>
            </description>
            <description>
            <![CDATA[<p>This actually contains a bunch of text I want to work with. If this text contains certain strings, I want to get rid of the whole item.</p>]]>
            </description>
        </item>
        <item>...</item>
    </channel>
</rss>

Моя цель — проверить, содержит ли второй тег описания определенные строки. Если он содержит эту строку, я бы хотел полностью удалить ее. В настоящее время в моем коде у меня есть это:

doc = lxml.etree.fromstring(testString)
found = doc.findall('channel/item/description')


for desc in found:
    if "FORBIDDENSTRING" in desc.text:
        desc.getparent().remove(desc)

И он удаляет только второй тег описания, что имеет смысл, но я хочу, чтобы весь элемент исчез. Я не знаю, как я могу получить элемент «item», если у меня есть только ссылка «desc».

Я пробовал гуглить, а также искать здесь, но ситуации, которые я вижу, просто хотят удалить тег, как я делаю сейчас, странно, но я не наткнулся на пример кода, который хочет избавиться от всего родительского объекта. Любые указатели на документацию/учебники или помощь очень приветствуются.


person Bono Vanderpoorten    schedule 17.05.2018    source источник


Ответы (2)


Я большой поклонник XSLT, но другой вариант — просто выбрать item вместо description (выберите элемент, который хотите удалить, а не его дочерний элемент).

Также, если вы используете xpath(), вы можете поставить проверку на запрещенную строку прямо в предикате xpath.

Пример...

from lxml import etree

testString = """
<rss version="2.0">
    <channel>
        <title>My RSS Feed</title>
        <link href="https://www.examplefeedurl.com">Feed</link>
        <description></description>
        <item>...</item>
        <item>...</item>
        <item>...</item>
        <item>
            <guid></guid>
            <pubDate></pubDate>
            <author/>
            <title>Title of the item</title>
            <link href="https://example.com" rel="alternate" type="text/html"/>
            <description>
            <![CDATA[<a href="https://example.com" target="_blank" rel="noopener noreferrer">View Example</a>]]>
            </description>
            <description>
            <![CDATA[<p>This actually contains a bunch of text I want to work with. If this text contains certain strings, I want to get rid of the whole item.</p>]]>
            </description>
        </item>
        <item>...</item>
    </channel>
</rss>
"""

forbidden_string = "I want to get rid of the whole item"

parser = etree.XMLParser(strip_cdata=False)
doc = etree.fromstring(testString, parser=parser)
found = doc.xpath('.//channel/item[description[contains(.,"{}")]]'.format(forbidden_string))

for item in found:
    item.getparent().remove(item)

print(etree.tostring(doc, encoding="unicode", pretty_print=True))

это печатает...

<rss version="2.0">
    <channel>
        <title>My RSS Feed</title>
        <link href="https://www.examplefeedurl.com">Feed</link>
        <description/>
        <item>...</item>
        <item>...</item>
        <item>...</item>
        <item>...</item>
    </channel>
</rss>
person Daniel Haley    schedule 17.05.2018
comment
У меня была дополнительная логика с текстом внутри тега описания, за исключением проверки запрещенной строки. Но ваш совет использовать элемент элемента привел меня на правильный путь, я использовал элемент элемента, получил ChildElementIterator, использовал свою логику, и я мог вернуться к элементу, чтобы удалить его, как в вашем примере! Большое спасибо! - person Bono Vanderpoorten; 22.05.2018

Рассмотрим XSLT, язык специального назначения, предназначенный для преобразования XML-файлов, например условного удаления узлов путем ценность. Python lxml может запускать сценарии XSLT 1.0 и даже передавать параметр из сценария Python в XSLT (мало чем отличается от передачи параметров в SQL!). Таким образом, вы избегаете циклов for или логики if или перестроения дерева на прикладном уровне.

XSLT (сохранить как файл .xsl, специальный файл .xml)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" cdata-section-elements="description"/>
  <xsl:strip-space elements="*"/>

  <!-- VALUE TO BE PASSED INTO FROM PYTHON -->
  <xsl:param name="search_string" />       

  <!-- IDENTITY TRANSFORM -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- KEEP ONLY item NODES THAT DO NOT CONTAIN $search_string -->
  <xsl:template match="channel">
    <xsl:copy>
      <xsl:apply-templates select="item[not(contains(description[2], $search_string))]"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Python (для демонстрации ниже выполняется два поиска с использованием опубликованного образца)

import lxml.etree as et

# LOAD XML AND XSL
doc = et.parse('Input.xml')
xsl = et.parse('XSLT_String.xsl')

# CONFIGURE TRANSFORMER
transform = et.XSLT(xsl)    

# RUN TRANSFORMATION WITH PARAM
n = et.XSLT.strparam('FORBIDDENSTRING')
result = transform(doc, search_string=n)

print(result)
# <?xml version="1.0"?>
# <rss version="2.0">
#   <channel>
#     <item>...</item>
#     <item>...</item>
#     <item>...</item>
#     <item>
#       <guid/>
#       <pubDate/>
#       <author/>
#       <title>Title of the item</title>
#       <link href="https://example.com" rel="alternate" type="text/html"/>
#       <description><![CDATA[<a href="https://example.com" target="_blank" rel="noopener noreferrer">View Example</a>]]></description>
#       <description><![CDATA[<p>This actually contains a bunch of text I want to work with. If this text contains certain strings, I want to get rid of the whole item.</p>]]></description>
#     </item>
#     <item>...</item>
#   </channel>
# </rss>

# RUN TRANSFORMATION WITH PARAM
n = et.XSLT.strparam('bunch of text')
result = transform(doc, search_string=n)

print(result)    
# <?xml version="1.0"?>
# <rss version="2.0">
#   <channel>
#     <item>...</item>
#     <item>...</item>
#     <item>...</item>
#     <item>...</item>
#   </channel>
# </rss>

# SAVE TO FILE
with open('Output.xml', 'wb') as f:
    f.write(result)
person Parfait    schedule 17.05.2018
comment
Поскольку вы применяете шаблоны только к item в контексте channel, вы теряете всех других дочерних элементов channel (например, title, description и link). Я бы удалил канал, соответствующий шаблону, и добавил шаблон, соответствующий item. Поскольку вы не можете ссылаться на параметр/переменную в шаблоне соответствия в XSLT 1.0, я бы добавил тест xsl:if с not(description[contains(.,$search_string)]) (без проверки положения description), и если это правда, выведите item (xsl:copy w /xsl:apply-templates, чтобы сохранить стиль push). - person Daniel Haley; 17.05.2018