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 (xhtm
l) 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.
3 réponses
HTML
lxml
s 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.
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>
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_tags
keep_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.