37 votes

Comment filtrer toutes les balises HTML à l'exception d'une certaine liste blanche ?

Ceci est pour .NET. IgnoreCase est activé et MultiLine n'est PAS activé.

D'habitude, je suis bon en regex, peut-être que je manque de caféine...

Les utilisateurs sont autorisés à saisir des entités codées en HTML (<lt ;, <amp ;, etc.), et à utiliser les balises HTML suivantes :

u, i, b, h3, h4, br, a, img

Les <br/> et <img/> à fermeture automatique sont autorisés, avec ou sans l'espace supplémentaire, mais ne sont pas obligatoires.

Je le veux :

  1. Supprimez toutes les balises HTML initiales et finales autres que celles énumérées ci-dessus.
  2. Supprimez les attributs des balises restantes, sauf Les ancres peuvent avoir un href.

Mon modèle de recherche (remplacé par une chaîne vide) jusqu'à présent :

<(?!i|b|h3|h4|a|img|/i|/b|/h3|/h4|/a|/img)[^>]+>

Ce site semble pour enlever tout sauf les balises de début et de fin que je veux, mais il y a trois problèmes :

  1. Devoir inclure la version de la balise finale de chaque balise autorisée est laid.
  2. Les attributs survivent. Cela peut-il se produire en un seul remplacement ?
  3. Tags en commençant par les noms de balises autorisés passent à travers. Par exemple, "<abbrev>" et "<iframe>".

Le modèle suggéré ci-dessous ne supprime pas les balises qui n'ont pas d'attributs.

</?(?!i|b|h3|h4|a|img)\b[^>]*>

Comme mentionné ci-dessous, ">" est légal dans une valeur d'attribut, mais on peut dire que je ne le supporterai pas. De plus, il n'y aura pas de blocs CDATA, etc. dont il faudra s'inquiéter. Juste un peu de HTML.

La réponse de Loophole est la meilleure jusqu'à présent, merci ! Voici son modèle (en espérant que le PRE fonctionne mieux pour moi) :

static string SanitizeHtml(string html)
{
    string acceptable = "script|link|title";
    string stringPattern = @"</?(?(?=" + acceptable + @")notag|[a-zA-Z0-9]+)(?:\s[a-zA-Z0-9\-]+=?(?:([""']?).*?\1?)?)*\s*/?>";
    return Regex.Replace(html, stringPattern, "sausage");
}

Je pense que quelques petites améliorations pourraient encore être apportées à cette réponse :

  1. Je pense qu'il est possible de modifier ce système pour capturer les commentaires HTML simples (ceux qui ne contiennent pas de balises) en ajoutant "!--" à la variable "acceptable" et en apportant une petite modification à la fin de l'expression pour permettre l'ajout d'un "" de fin facultatif. \s --".

  2. Je pense que cela ne fonctionnerait pas s'il y a plusieurs caractères d'espacement entre les attributs (exemple : HTML fortement formaté avec des sauts de ligne et des tabulations entre les attributs).

Edit 2009-07-23 : Voici la solution finale que j'ai retenue (en VB.NET) :

 Dim AcceptableTags As String = "i|b|u|sup|sub|ol|ul|li|br|h2|h3|h4|h5|span|div|p|a|img|blockquote"
 Dim WhiteListPattern As String = "</?(?(?=" & AcceptableTags & _
      ")notag|[a-zA-Z0-9]+)(?:\s[a-zA-Z0-9\-]+=?(?:([""']?).*?\1?)?)*\s*/?>"
 html = Regex.Replace(html, WhiteListPattern, "", RegExOptions.Compiled)

L'inconvénient est que l'attribut HREF des balises A est toujours supprimé, ce qui n'est pas idéal.

0 votes

Veuillez supprimer la balise [regular] inutile

0 votes

Avez-vous réussi à supprimer les attributs ? La réponse de Loophole ne semble pas le faire ?

31voto

Loophole Points 351

Voici une fonction que j'ai écrite pour cette tâche :

static string SanitizeHtml(string html)
{
    string acceptable = "script|link|title";
    string stringPattern = @"</?(?(?=" + acceptable + @")notag|[a-zA-Z0-9]+)(?:\s[a-zA-Z0-9\-]+=?(?:(["",']?).*?\1?)?)*\s*/?>";
    return Regex.Replace(html, stringPattern, "sausage");
}

Edit : Pour une raison quelconque, j'ai posté une correction à ma réponse précédente comme une réponse séparée, donc je les consolide ici.

Je vais expliquer un peu la regex, car elle est un peu longue.

La première partie correspond à une parenthèse ouverte et à 0 ou 1 barre oblique (au cas où il s'agirait d'une balise fermée).

Ensuite, vous voyez une construction if-then avec un regard vers l'avant. ( ?(?=SomeTag)then|else) Je vérifie si la partie suivante de la chaîne est l'une des balises acceptables. Vous pouvez voir que je concatène la chaîne de l'expression rationnelle avec la variable acceptable, qui est constituée des noms de balises acceptables séparés par une barre verticale, de sorte que n'importe lequel de ces termes puisse correspondre. S'il y a une correspondance, vous pouvez voir que j'ai mis le mot "notag" parce qu'aucune balise ne correspondrait à cela et si c'est acceptable, je veux le laisser tranquille. Sinon, je passe à la partie "else", où je fais correspondre tout nom de tag [a-z,A-Z,0-9]+.

Ensuite, je veux faire correspondre 0 ou plusieurs attributs, qui, je suppose, sont sous la forme attribut="valeur". Donc maintenant, je groupe cette partie représentant un attribut mais j'utilise le ? : pour empêcher ce groupe d'être capturé pour la vitesse : (? : \s [a-z,A-Z,0-9,-]+=?(?:(["",']?). ? \1 ?))

Ici, je commence par le caractère d'espacement qui se trouverait entre le nom de la balise et celui de l'attribut, puis je fais correspondre un nom d'attribut : [a-z,A-Z,0-9,-]+.

Ensuite, je fais correspondre un signe égal, et ensuite une citation. Je groupe le guillemet pour qu'il soit capturé, et je peux faire une référence arrière plus tard. \1 pour correspondre au même type de citation. Entre ces deux guillemets, vous pouvez voir que j'utilise le point pour correspondre à n'importe quoi, mais j'utilise la version paresseuse * ? au lieu de la version gourmande * afin de ne correspondre qu'au prochain guillemet qui terminerait cette valeur.

Ensuite, nous mettons un * après avoir fermé les groupes avec des parenthèses, de sorte qu'il correspondra à plusieurs combinaisons d'attributs/valeurs (ou à aucune). Enfin, nous faisons correspondre des espaces avec \s et 0 ou 1 barre oblique finale dans la balise pour les balises autofermantes de style xml.

Vous pouvez voir que je remplace les balises par des saucisses, parce que j'ai faim, mais vous pourriez aussi les remplacer par des chaînes vides pour simplement les effacer.

0 votes

Lol... il y a toujours une virgule dans la dernière plage de caractères. Merci pour la mise à jour ! J'ai ajusté le code dans l'OP.

0 votes

Merci pour le code ! Ce code a été mis à jour ou la virgule doit être enlevée de l'expression ?

0 votes

Juste pour ajouter une note de prudence, j'ai eu mon entrée html pour cela venir d'une source externe, il avait une balise br invalide "<br<", ce qui a causé le regex à aller dans une boucle infinie. Assurez-vous donc de valider votre html avant de le passer.

10voto

CMS Points 315406

Il s'agit d'un bon exemple de travail sur le filtrage des balises html :

Assainir le HTML

0 votes

Le site RefactorMyCode est en panne depuis un certain temps. Je crois qu'il n'est plus en service.

0 votes

@sohtimsso1970, oui, je n'avais pas remarqué jusqu'à maintenant, voici la page web archivée de septembre 2010 : web.archive.org/web/20100901160940/http://refactormycode.com/

0 votes

En regardant le code, c'est la réponse la plus stricte et la meilleure des regex que j'ai vue ici. Je n'y vois pas de défaut immédiat, même si je vous déconseille d'essayer d'assainir le HTML avec regex.

5voto

Loophole Points 351

Voici une fonction que j'ai écrite pour accomplir ceci :

static string SanitizeHtml(string html)
{
    string acceptable = "script|link|title";
    string stringPattern = @"</?(?(?=" + acceptable + @")notag|[a-z,A-Z,0-9]+)(?:\s[a-z,A-Z,0-9,\-]+=?(?:(["",']?).*?\1?))*\s*/?>";
    return Regex.Replace(html, stringPattern, "sausage");
}

Je vais expliquer un peu la regex, car elle est un peu longue.

La première partie correspond à une parenthèse ouverte et à 0 ou 1 barre oblique (au cas où il s'agirait d'une balise fermée).

Ensuite, vous voyez une construction if-then avec un regard en avant. ( ?(?=SomeTag)then|else) Je vérifie si la partie suivante de la chaîne est l'une des balises acceptables. Vous pouvez voir que je concatène la chaîne de l'expression rationnelle avec la variable acceptable, qui est constituée des noms de balises acceptables séparés par une barre verticale, de sorte que n'importe lequel de ces termes puisse correspondre. S'il y a une correspondance, vous pouvez voir que j'ai mis le mot "notag" parce qu'aucune balise ne correspondrait à cela et si c'est acceptable, je veux le laisser tranquille. Sinon, je passe à l'étape suivante, où je fais correspondre n'importe quel nom de balise. [a-z,A-Z,0-9]+

Ensuite, je veux faire correspondre 0 ou plusieurs attributs, qui, je suppose, sont sous la forme attribut="valeur". Je regroupe donc maintenant cette partie représentant un attribut, mais j'utilise le ? : pour éviter que ce groupe ne soit capturé pour la vitesse : (?:\s[a-z,A-Z,0-9,\-]+=?(?:(["",']?).*?\1?))*

Ici, je commence par le caractère d'espacement qui se trouverait entre le nom de la balise et celui de l'attribut, puis je fais correspondre un nom d'attribut : [a-z,A-Z,0-9,\-]+

Ensuite, je fais correspondre un signe égal, et ensuite une citation. Je groupe le guillemet pour qu'il soit capturé, et je peux faire une référence arrière plus tard. \1 pour correspondre au même type de devis. Entre ces deux guillemets, vous pouvez voir que j'utilise le point pour correspondre à n'importe quoi, mais j'utilise la version paresseuse. *? au lieu de la version gourmande * de sorte qu'il ne correspondra qu'à la prochaine citation qui terminera cette valeur.

Ensuite, nous mettons un * après avoir fermé les groupes avec des parenthèses, de manière à ce qu'il corresponde à plusieurs combinaisons attirbute/valeur (ou à aucune). Enfin, nous faisons correspondre quelques espaces avec \s et 0 ou 1 barre oblique finale dans la balise pour les balises autofermantes de style xml.

Vous pouvez voir que je remplace les balises par des saucisses, parce que j'ai faim, mais vous pourriez aussi les remplacer par des chaînes vides pour simplement les effacer.

2voto

Sherm Pendley Points 10822

Les attributs sont le problème majeur de l'utilisation des regex pour essayer de travailler avec le HTML. Pensez au nombre d'attributs potentiels, au fait que la plupart d'entre eux sont facultatifs, au fait qu'ils peuvent apparaître dans n'importe quel ordre et au fait que ">" est un caractère légal dans les valeurs d'attribut citées. Lorsque vous commencez à essayer de prendre tout cela en compte, la regex dont vous auriez besoin pour gérer tout cela devient rapidement ingérable.

Ce que je ferais plutôt, c'est utiliser un analyseur HTML basé sur des événements, ou un analyseur qui vous donne un arbre DOM que vous pouvez parcourir.

1voto

Jan Goyvaerts Points 10402

La raison pour laquelle l'ajout du mot "frontière \b n'a pas fonctionné est que vous ne l'avez pas mis dans le lookahead. Ainsi, \b sera tentée après < où elle correspondra toujours si le < commence une balise HTML.

Mettez-le dans le lookahead comme ceci :

<(?!/?(i|b|h3|h4|a|img)\b)[^>]+>

Cela montre également comment vous pouvez mettre le / avant la liste des balises, plutôt qu'avec chaque balise.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X