Utiliser Python et lxml pour supprimer seulement les tags qui ont certains attributs / valeurs

je suis familier avec le programme etree strip_tags et strip_elements méthodes, mais je suis à la recherche d'un moyen simple de retirer les étiquettes (et de laisser leur contenu) qui ne contiennent que des attributs/valeurs spécifiques.

par exemple: je voudrais tout enlever span ou div les balises (ou autres éléments) d'un arbre (xhtml) qui ont un class='myclass' attribut / valeur (préserver le contenu de l'élément comme strip_tags le ferait). En attendant, ces mêmes éléments qui ne pas class='myclass' doit rester intacte.

a L'inverse: je voudrais un moyen de me déshabiller tout "nu"spans ou divs à partir d'un arbre. Sens que ceux spans/divs (ou autres éléments) qui n'ont absolument aucun attributs. En laissant ces mêmes éléments attributs (tous) intacts.

j'ai l'impression de rater quelque chose d'évident, mais je cherche sans succès depuis un certain temps.

10
demandé sur Dan Hoerst 2014-02-10 23:08:58

3 réponses

HTML

lxmls les éléments HTML ont une méthode drop_tag() lequel vous pouvez appeler sur n'importe quel élément dans un arbre analysé par lxml.html.

Il s'agit du type strip_tags en ce qu'il supprime l'élément, mais conserve le texte, et il peut être appelé l'élément-ce qui signifie que vous pouvez facilement sélectionner les éléments qui ne vous intéressent pas avec un XPath expression, puis boucle - les et supprime eux:

doc.html

<html>
    <body>
        <div>This is some <span attr="foo">Text</span>.</div>
        <div>Some <span>more</span> text.</div>
        <div>Yet another line <span attr="bar">of</span> text.</div>
        <div>This span will get <span attr="foo">removed</span> as well.</div>
        <div>Nested elements <span attr="foo">will <b>be</b> left</span> alone.</div>
        <div>Unless <span attr="foo">they <span attr="foo">also</span> match</span>.</div>
    </body>
</html>

strip.py

from lxml import etree
from lxml import html

doc = html.parse(open('doc.html'))
spans_with_attrs = doc.xpath("//span[@attr='foo']")

for span in spans_with_attrs:
    span.drop_tag()

print etree.tostring(doc)

Sortie:

<html>
    <body>
        <div>This is some Text.</div>
        <div>Some <span>more</span> text.</div>
        <div>Yet another line <span attr="bar">of</span> text.</div>
        <div>This span will get removed as well.</div>
        <div>Nested elements will <b>be</b> left alone.</div>
        <div>Unless they also match.</div>
    </body>
</html>

dans ce cas, l'expression XPath //span[@attr='foo'] sélectionne tous les span avec un attribut attr valeur foo. Voir cette tutorial XPath pour plus de détails sur la construction des expressions XPath.

XML / XHTML

Modifier: je viens de remarquer que vous mentionnez spécifiquement XHTML dans votre question, qui selon les documents est mieux comprise comme XML. Malheureusement, le drop_tag() la méthode n'est vraiment disponible que pour les éléments D'un document HTML.

alors pour XML c'est un peu plus compliqué:

doc.xml

<document>
    <node>This is <span>some</span> text.</node>
    <node>Only this <span attr="foo">first <b>span</b></span> should <span>be</span> removed.</node>
</document>

strip.py

from lxml import etree


def strip_nodes(nodes):
    for node in nodes:
        text_content = node.xpath('string()')

        # Include tail in full_text because it will be removed with the node
        full_text = text_content + (node.tail or '')

        parent = node.getparent()
        prev = node.getprevious()
        if prev:
            # There is a previous node, append text to its tail
            prev.tail += full_text
        else:
            # It's the first node in <parent/>, append to parent's text
            parent.text = (parent.text or '') + full_text
        parent.remove(node)


doc = etree.parse(open('doc.xml'))
nodes = doc.xpath("//span[@attr='foo']")
strip_nodes(nodes)

print etree.tostring(doc)

Sortie:

<document>
    <node>This is <span>some</span> text.</node>
    <node>Only this first span should <span>be</span> removed.</node>
</document>

Comme vous pouvez le voir, cette remplacera nœud et tous ses enfants au contenu textuel récursif. J'espère vraiment que ce que vous voulez, sinon les choses deviennent encore plus compliquées ;-)

NOTE Dernière édition ont changé le code en question.

10
répondu Lukas Graf 2016-08-05 15:01:10

j'ai juste eu le même problème, et après quelques réflexions j'ai eu cette idée plutôt hacky, qui est empruntée à regex-ing Markup in Perl onliners: Que diriez-vous d'abord attraper tous les éléments indésirables avec toute la puissance qui element.iterfind apporte, renommer ces éléments en quelque chose d'improbable, et puis enlever tous ces éléments?

Oui,ce n'est pas tout à fait propre et robuste, comme toujours vous pourriez avoir un document qui utilise en fait le "peu probable" nom de la balise que vous avez choisi, mais le résultat de l' le code EST plutôt propre et facilement maintenable. Si vous avez vraiment besoin d'être sûr que le nom "improbable" que vous avez choisi n'existe pas déjà dans le document, vous pouvez toujours vérifier qu'il existe en premier, et faire le renommage seulement si vous ne pouvez pas trouver des étiquettes préexistantes de ce nom.

doc.xml

<document>
    <node>This is <span>some</span> text.</node>
    <node>Only this <span attr="foo">first <b>span</b></span> should <span>be</span> removed.</node>
</document>

strip.py

from lxml import etree
xml = etree.parse("doc.xml")
deltag ="xxyyzzdelme"
for el in xml.iterfind("//span[@attr='foo']"):
    el.tag = deltag
etree.strip_tag(xml, deltag)
print(etree.tostring(xml, encoding="unicode", pretty_print=True))

Sortie

<document>
     <node>This is <span>some</span> text.</node>
     <node>Only this first <b>span</b> should <span>be</span> removed.</node>
</document>
1
répondu Thor 2016-02-13 13:49:53

j'ai le même problème. Mais dans mon cas le scénario un peu plus facile, j'ai une option - pas supprimer des tags, juste le clarifier, nos utilisateurs voient rendu html et si j'ai par exemple

<div>Hello <strong>awesome</strong> World!</div>

je veux effacer strong balise par sélecteur css div > strong et sauvegarder le contexte tail, dans lxml vous ne pouvez pas utiliser strip_tagskeep_tail par sélecteur, vous pouvez enlever seulement par tag, ça me rend fou. Et de plus si vous venez de supprimer <strong>awesome</strong> noeud, vous supprimez aussi ce monde de queue -"!", texte qui enveloppait strong balise. La sortie sera comme:

<div>Hello</div>

Pour moi, ok ceci:

<div>Hello <strong></strong> World!</div>

Aucun génial pour l'utilisateur plus.

doc = lxml.html.fromstring(markup)
selector = lxml.cssselect.CSSSelector('div > strong')
for el in list(selector(doc)):
    if el.tail:
        tail = el.tail
        el.clear()
        el.tail = tail
    else:
        #if no tail, we can safety just remove node
        el.getparent().remove(el)

vous pouvez adapter le code avec suppression physique strong balise avec l'appel element.remove(child) et l'attacher à la queue pour le parent, mais pour mon cas, c'était frais généraux.

0
répondu theodor 2016-01-13 17:13:31