Les balises ouvertes RegEx correspondent aux balises autonomes XHTML

1475

J'ai besoin de faire correspondre toutes ces balises d'ouverture:

<p>
<a href="foo">

Mais pas ceux-ci:

<br />
<hr class="foo" />

Je suis venu avec cela et je voulais m'assurer d'avoir bien compris. Je ne fais que capturer le a-z.

<([a-z]+) *[^/]*?>

Je crois que cela dit:

  • Trouvez un moins que, alors
  • Recherchez (et capturez) az une ou plusieurs fois, puis
  • Trouvez zéro ou plusieurs espaces, puis
  • Trouver n'importe quel caractère zéro ou plusieurs fois, gourmand, sauf / , puis
  • Trouver un plus grand que

Ai-je ce droit? Et surtout, qu'en pensez-vous?

Jeff
la source

Réponses:

4417

Vous ne pouvez pas analyser [X] HTML avec regex. Parce que HTML ne peut pas être analysé par regex. Regex n'est pas un outil qui peut être utilisé pour analyser correctement le HTML. Comme j'ai déjà répondu à de nombreuses questions sur HTML et regex ici, l'utilisation de regex ne vous permettra pas de consommer du HTML. Les expressions régulières sont un outil qui n'est pas suffisamment sophistiqué pour comprendre les constructions employées par HTML. Le HTML n'est pas un langage régulier et ne peut donc pas être analysé par des expressions régulières. Les requêtes Regex ne sont pas équipées pour décomposer le HTML en ses parties significatives. tant de fois mais ça ne m'arrive pas. Même les expressions régulières irrégulières améliorées utilisées par Perl ne sont pas à la hauteur de l'analyse syntaxique de HTML. Tu ne me feras jamais craquer. Le HTML est un langage d'une complexité suffisante pour qu'il ne puisse pas être analysé par des expressions régulières. Même Jon Skeet ne peut pas analyser le HTML à l'aide d'expressions régulières. Chaque fois que vous essayez d'analyser le HTML avec des expressions régulières, l'enfant impie pleure le sang des vierges et les pirates russes pwn votre webapp. Analyser le HTML avec des expressions régulières invoque des âmes entachées dans le domaine des vivants. HTML et regex vont de pair comme l'amour, le mariage et l'infanticide rituel. Le <center> ne peut pas le retenir, il est trop tard. La force de l'expression rationnelle et du HTML ensemble dans le même espace conceptuel détruira votre esprit comme autant de mastic aqueux. Si vous analysez HTML avec des expressions rationnelles, vous les cédez et leurs manières blasphématoires qui nous condamnent tous à un travail inhumain pour Celui dont le nom ne peut pas être exprimé dans le plan multilingue de base, il vient. HTML-plus-regexp liquéfiera les nerfs du sensible pendant que vous observez, votre psyché se flétrir dans l'assaut de l'horreur.il est trop tard, il est trop tard, nous ne pouvons pas être sauvés la transition d'un enfant garantit que regex consommera tous les tissus vivants (à l'exception du HTML qu'il ne peut pas, comme précédemment prophétisé) cher seigneur nous aider comment quelqu'un peut-il survivre à ce fléau en utilisant regex pour analyser Le HTML a condamné l'humanité à une éternité de torture et de trous de sécurité effrayants en utilisant rege x comme un outil pour traiter HTML établit une rupture entre ce monde et le royaume de l'effroi d'entités c͒ͪo͛ͫrrupt (comme les entités SGML, mais plus corrompu) un simple aperçu de le monde de reg ex parseurs pour HTML ins tantly transports ap conscience de rogrammer i nto aw orl d de cris incessants, il vient, le pestilentielle slITHY regex-infection wil l dévorent votre HT analyseur ML, l' application et l' existence de tous les temps comme Visual Basic ne fait qu'empirer viendra , il com es ne pas fi ght h e VIENT, salut s Unholy Radiance de stro҉ying toute lumière, HTML balises fuite fr̶ǫm areil Liq EYES comme uid p ain, la chanson de RÉGULIER exp re analyse syntaxique ssion va ExtJ nguish les voix de mor homme tal de la sp ici , je peux le voir peut vous le voyez , il est beau t - il f inal snufFing o f le mensonge de l' homme TOUT EST PERDU A LL I SL e PONY il vientOST e s il cơm es il co me s t - il ich ou Permeat es al l MON FAC de E MON VISAGE dieu n o NO Noo O ON Θ arrêt t - il un * ̶͑̾̾ Gl ÉS ͎a̧͈͖r̽̾̈́͒͑en ot RÉAL ZA̡͊͠͝LGΌ ISͮ҉̯͈͕̹̘ T O͇̹̺ͅƝ̴ȳ̳ TH̘ Ë͖́̉ ͠P̯͍̭O̚ N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝ S̨̥̫͎̭ͯ̿̔̀ͅ


Avez-vous essayé d'utiliser un analyseur XML à la place?


Note du modérateur

Ce message est verrouillé pour empêcher toute modification inappropriée de son contenu. La publication ressemble exactement à ce qu'elle est censée ressembler - il n'y a aucun problème avec son contenu. Veuillez ne pas le signaler à notre attention.

bobince
la source
179
Kobi: Je pense qu'il est temps pour moi de quitter le poste d'Assistant Don't Parse HTML with Regex Officer. Peu importe combien de fois nous le disons, ils n'arrêteront pas de venir tous les jours ... toutes les heures même. C'est une cause perdue que quelqu'un d'autre peut combattre un peu. Alors continuez, analysez HTML avec regex, si vous le devez. Ce n'est qu'un code cassé, pas la vie ni la mort.
bobince
27
Est-il possible d'utiliser RegEx pour analyser cette réponse?
Chris Porter
2
Si vous ne pouvez pas voir ce post, voici une capture d'écran de celui-ci dans toute sa splendeur: imgur.com/gOPS2.png
Andrew Keeton
3251

Bien qu'un HTML arbitraire avec seulement une expression régulière soit impossible, il est parfois approprié de les utiliser pour analyser un ensemble limité et connu de HTML.

Si vous disposez d'un petit ensemble de pages HTML à partir desquelles vous souhaitez extraire des données, puis les insérer dans une base de données, les expressions régulières peuvent fonctionner correctement. Par exemple, j'ai récemment voulu obtenir les noms, les partis et les districts des représentants fédéraux australiens, que j'ai retirés du site Web du Parlement. Il s'agissait d'un travail limité et ponctuel.

Les regex fonctionnaient très bien pour moi et étaient très rapides à mettre en place.

Kaitlin Duck Sherwood
la source
131
En outre, le grattage de données formatées assez régulièrement à partir de documents volumineux sera beaucoup plus rapide avec une utilisation judicieuse de scan & regex que tout analyseur générique. Et si vous êtes à l'aise avec le codage des expressions régulières, bien plus rapide à coder que le codage des xpath. Et certainement moins fragile aux changements dans ce que vous raclez. Alors bah.
Michael Johnston
255
@MichaelJohnston "Moins fragile"? Certainement pas. Les expressions régulières se soucient des détails de mise en forme du texte qu'un analyseur XML peut ignorer en silence. Basculer entre les &foo;encodages et les CDATAsections? Vous utilisez un minifieur HTML pour supprimer tous les espaces de votre document que le navigateur ne rend pas? Un analyseur XML ne s'en souciera pas, pas plus qu'une déclaration XPath bien écrite. Un "analyseur" basé sur les regex, d'autre part ...
Charles Duffy
41
@CharlesDuffy pour un travail ponctuel, c'est ok, et pour les espaces que nous utilisons \ s +
quantum
68
@xiaomao en effet, si je dois connaître tous les pièges et les solutions pour obtenir une solution à 80% qui échoue le reste du temps "fonctionne pour vous", je ne peux pas vous arrêter. Pendant ce temps, je suis de mon côté de la clôture en utilisant des analyseurs qui fonctionnent à 100% avec du XML syntaxiquement valide.
Charles Duffy
374
Une fois, j'ai dû extraire des données de ~ 10 000 pages, toutes avec le même modèle HTML. Ils étaient parsemés d'erreurs HTML qui provoquaient l'étouffement des analyseurs, et tout leur style était en ligne ou avec <font>etc.: aucune classe ou ID pour aider à naviguer dans le DOM. Après avoir combattu toute la journée avec la "bonne" approche, je suis finalement passé à une solution regex et l'ai fait fonctionner en une heure.
Paul A Jungwirth
2039

Je pense que le défaut ici est que HTML est une grammaire Chomsky Type 2 (grammaire sans contexte) et RegEx est une grammaire Chomsky Type 3 (grammaire régulière) . Puisqu'une grammaire de type 2 est fondamentalement plus complexe qu'une grammaire de type 3 (voir la hiérarchie Chomsky ), il est mathématiquement impossible d'analyser XML avec RegEx.

Mais beaucoup essaieront, certains revendiqueront même le succès - mais jusqu'à ce que d'autres trouvent la faute et vous gâchent totalement.

Vlad Gudim
la source
226
L'OP demande d'analyser un sous-ensemble très limité de balises XHTML: start. Ce qui fait de (X) HTML un CFG, c'est son potentiel à avoir des éléments entre les balises de début et de fin d'autres éléments (comme dans une règle de grammaire A -> s A e). (X) HTML n'a pas cette propriété dans une balise de démarrage: une balise de démarrage ne peut pas contenir d'autres balises de démarrage. Le sous-ensemble que l'OP essaie d'analyser n'est pas un CFG.
LarsH
101
Dans la théorie CS, les langages réguliers sont un sous-ensemble strict de langages sans contexte, mais les implémentations d'expressions régulières dans les langages de programmation traditionnels sont plus puissantes. Comme le décrit noulakaz.net/weblog/2007/03/18/… , les soi-disant "expressions régulières" peuvent vérifier les nombres premiers en unaire, ce qui est certainement quelque chose qu'une expression régulière de la théorie CS ne peut pas accomplir.
Adam Mihalcin
11
@eyelidlessness: le même "seulement si" s'applique à tous les CFG, n'est-ce pas? C'est-à-dire si l'entrée (X) HTML n'est pas bien formée, même un analyseur XML à part entière ne fonctionnera pas de manière fiable. Peut-être que si vous donnez des exemples des "erreurs de syntaxe (X) HTML implémentées dans les agents utilisateurs du monde réel" dont vous parlez, je comprendrai mieux ce que vous cherchez.
LarsH
82
@AdamMihalcin a parfaitement raison. La plupart des moteurs regex existants sont plus puissants que les grammaires Chomsky Type 3 (par exemple, correspondance non gourmande, backrefs). Certains moteurs regex (tels que Perl) sont Turing complet. Il est vrai que même ceux-ci sont de mauvais outils pour analyser HTML, mais cet argument souvent cité n'est pas la raison.
dubiousjim
27
C'est la réponse la plus "complète et courte" ici. Cela amène les gens à apprendre les bases des grammaires et des langues formelles et, espérons-le, certains mathématiques afin qu'ils ne perdent pas de temps sur des choses désespérées comme la résolution de tâches NP en temps polynomial
mishmashru
1333

N'écoutez pas ces gars. Vous pouvez totalement analyser des grammaires sans contexte avec regex si vous divisez la tâche en petits morceaux. Vous pouvez générer le modèle correct avec un script qui fait chacun de ces éléments dans l'ordre:

  1. Résolvez le problème de l'arrêt.
  2. Mettez un cercle en carré.
  3. Résolvez le problème du voyageur de commerce en O (log n) ou moins. Si c'est plus que cela, vous manquerez de RAM et le moteur se bloquera.
  4. Le modèle sera assez grand, alors assurez-vous d'avoir un algorithme qui compresse sans perte des données aléatoires.
  5. Presque là - divisez le tout par zéro. Peasy facile.

Je n'ai pas tout à fait fini la dernière partie moi-même, mais je sais que je m'approche. Il continue de lancer des CthulhuRlyehWgahnaglFhtagnExceptions pour une raison quelconque, donc je vais le porter sur VB 6 et l'utiliser On Error Resume Next. Je mettrai à jour le code une fois que j'aurai enquêté sur cette étrange porte qui vient de s'ouvrir dans le mur. Hmm.

Le PS Pierre de Fermat a également compris comment le faire, mais la marge dans laquelle il écrivait n'était pas assez grande pour le code.

Justin Morgan
la source
80
La division par zéro est un problème beaucoup plus facile que les autres que vous mentionnez. Si vous utilisez des intervalles, plutôt qu'une simple arithmétique à virgule flottante (ce que tout le monde devrait être, mais personne ne l'est), vous pouvez heureusement diviser quelque chose par [un intervalle contenant] zéro. Le résultat est simplement un intervalle contenant l'infini plus et moins.
rjmunro
148
Le problème des petites marges de Fermat a été résolu par des marges faibles dans les logiciels modernes d'édition de texte.
kd4ttc
50
Le problème des petites marges de Fermat a été résolu par Randall Munroe en mettant la taille de
police
29
FYI: Le problème de Fermat a en fait été résolu en 1995 , et cela n'a pris que 358 ans aux mathématiciens.
jmiserez
10
J'ai pu contourner cette étape de division par zéro collante en utilisant plutôt des rochets browniens issus de la fusion à froid ... bien que cela ne fonctionne que lorsque j'enlève la constante cosmologique.
Tim Lehner
1073

Avertissement : utilisez un analyseur si vous en avez la possibilité. Cela dit...

Voici l'expression régulière que j'utilise (!) Pour faire correspondre les balises HTML:

<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>

Ce n'est peut-être pas parfait, mais j'ai exécuté ce code à travers beaucoup de code HTML. Notez qu'il capture même des choses étranges comme <a name="badgenerator"">, qui apparaissent sur le Web.

Je suppose que pour qu'il ne corresponde pas aux balises autonomes, vous souhaitez soit utiliser le look- back négatif de Kobi :

<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+(?<!/\s*)>

ou combinez simplement si et sinon.

Pour les downvoters: il s'agit du code de travail d'un produit réel. Je doute que tous ceux qui liront cette page auront l'impression qu'il est socialement acceptable d'utiliser des expressions rationnelles sur HTML.

Avertissement : je dois noter que cette expression régulière se décompose toujours en présence de blocs CDATA, de commentaires et d'éléments de script et de style. La bonne nouvelle est que vous pouvez vous débarrasser de ceux qui utilisent une expression régulière ...

itsadok
la source
95
J'irais
55
Quelqu'un utilise-t-il CDATA dans HTML?
Danubian Sailor
16
donc vous ne résolvez pas réellement le problème d'analyse avec regexp uniquement mais en tant que partie de l'analyseur cela peut fonctionner. PS: un produit fonctionnel ne signifie pas un bon code. Aucune infraction, mais c'est ainsi que la programmation industrielle fonctionne et obtient son argent
mishmashru
32
Vos départs regex échouent sur le très court HTML possible, valide: <!doctype html><title><</title>. Les '<!doctype html><title><</title>'.match(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g)retours simples ["<!doctype html>", "<title>", "<</title>"]tout en devraient ["<title>", "</title>"].
2
si nous essayons simplement de faire correspondre et de ne pas faire correspondre les exemples donnés, /<.(( ^^>> (...) "toto"> <br /> <hr class = "toto" />'.match(/<.([^r>][^>]*)?>/g)
imma
506

Il y a des gens qui vous diront que la Terre est ronde (ou peut-être que la Terre est une sphéroïde oblate s'ils veulent utiliser des mots étranges). Ils mentent.

Il y a des gens qui vous diront que les expressions régulières ne doivent pas être récursives. Ils vous limitent. Ils ont besoin de vous soumettre, et ils le font en vous gardant dans l'ignorance.

Vous pouvez vivre dans leur réalité ou prendre la pilule rouge.

Comme Lord Marshal (est-il un parent de la classe Marshal .NET?), J'ai vu le verset Regex basé sur la pile Underverse et je suis revenu avec des connaissances en pouvoirs que vous ne pouvez pas imaginer. Oui, je pense qu'il y en avait un ou deux qui les protégeaient, mais ils regardaient le football à la télé, donc ce n'était pas difficile.

Je pense que le cas XML est assez simple. Le RegEx (dans la syntaxe .NET), dégonflé et codé en base64 pour le rendre plus facile à comprendre par votre faible esprit, devrait ressembler à ceci:

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
+r/vD34mUADO1P4/AQAA//8=

Les options à définir sont RegexOptions.ExplicitCapture. Le groupe de capture que vous recherchez est ELEMENTNAME. Si le groupe de capture ERRORn'est pas vide, il y a eu une erreur d'analyse et le regex s'est arrêté.

Si vous avez des problèmes pour le reconvertir en expression régulière lisible par l'homme, cela devrait vous aider:

static string FromBase64(string str)
{
    byte[] byteArray = Convert.FromBase64String(str);

    using (var msIn = new MemoryStream(byteArray))
    using (var msOut = new MemoryStream()) {
        using (var ds = new DeflateStream(msIn, CompressionMode.Decompress)) {
            ds.CopyTo(msOut);
        }

        return Encoding.UTF8.GetString(msOut.ToArray());
    }
}

Si vous n'êtes pas sûr, non, je ne plaisante pas (mais peut-être que je mens). Ça va marcher. J'ai construit des tonnes de tests unitaires pour le tester, et j'ai même utilisé (une partie des) tests de conformité . C'est un tokenizer, pas un analyseur complet, il ne divisera que le XML en ses jetons de composant. Il n'analysera / n'intégrera pas les DTD.

Oh ... si vous voulez le code source de l'expression régulière, avec quelques méthodes auxiliaires:

regex pour tokenize un xml ou le regex ordinaire complet

xanatos
la source
68
Seigneur, c'est énorme. Ma plus grande question est pourquoi? Vous vous rendez compte que tous les langages modernes ont des analyseurs XML, non? Vous pouvez faire tout cela en 3 lignes et assurez-vous que cela fonctionnera. De plus, vous réalisez également que regex pur est prouvable incapable de faire certaines choses? Sauf si vous avez créé un analyseur de code hybride regex / impératif, mais il ne vous ressemble pas. Pouvez-vous également compresser des données aléatoires?
Justin Morgan
113
@Justin Je n'ai pas besoin de raison. Cela pourrait être fait (et ce n'était pas illégal / immoral), donc je l'ai fait. Il n'y a pas de limites à l'esprit sauf celles que nous reconnaissons (Napoleon Hill) ... Les langages modernes peuvent analyser XML? Vraiment? Et je pensais que c'était illégal! :-)
xanatos
76
Monsieur, je suis convaincu. Je vais utiliser ce code dans le noyau de ma machine à mouvement perpétuel - pouvez-vous croire que ces imbéciles du bureau des brevets continuent de rejeter ma demande? Eh bien, je vais leur montrer. Je vais tous les montrer!
Justin Morgan
31
@Justin Donc, un analyseur Xml est par définition exempt de bogues, alors qu'un Regex ne l'est pas? Parce que si un analyseur Xml n'est pas exempt de bogues par définition, il peut y avoir un XML qui le fait planter et nous revenons à l'étape 0. Disons ceci: l'analyseur Xml et ce Regex essaient de pouvoir analyser tous les "XML. Ils PEUVENT analyser du XML "illégal". Les bogues pourraient se bloquer tous les deux. C # XmlReader est sûrement plus testé que ce Regex.
xanatos
31
Non, rien n'est exempt de bogues: 1) Tous les programmes contiennent au moins un bogue. 2) Tous les programmes contiennent au moins une ligne de code source inutile. 3) En # 1 et # 2 et en utilisant l'induction logique, il est simple de prouver que n'importe quel programme peut être réduit à une seule ligne de code avec un bogue. (de Learning Perl)
Scott Weaver
299

Dans le shell, vous pouvez analyser HTML en utilisant sed :

  1. Turing.sed
  2. Écrire un analyseur HTML (devoirs)
  3. ???
  4. Profit!

Connexes (pourquoi vous ne devriez pas utiliser la correspondance d'expression régulière):

kenorb
la source
3
J'ai bien peur que vous n'ayez pas compris la blague, @kenorb. Veuillez lire à nouveau la question et la réponse acceptée. Il ne s'agit pas des outils d'analyse HTML en général, ni des outils shell d'analyse HTML, il s'agit d'analyser HTML via des expressions régulières.
Palec
1
Non, @Abdul. C'est complètement, prouvablement (au sens mathématique) impossible.
Palec
3
Oui, cette réponse le résume bien, @Abdul. Notez que, cependant, les implémentations d' expression régulière ne sont pas vraiment des expressions régulières au sens mathématique - elles ont des constructions qui les rendent plus fortes, souvent complètes de Turing (équivalentes aux grammaires de type 0). L'argument rompt avec ce fait, mais est encore quelque peu valable dans le sens où les expressions régulières n'ont jamais été censées être capables de faire un tel travail.
Palec
2
Et d'ailleurs, la blague à laquelle j'ai fait référence était le contenu de cette réponse avant les éditions (radicales) de kenorb, en particulier la révision 4, @Abdul.
Palec
3
Le plus drôle, c'est que OP n'a jamais demandé d'analyser le html en utilisant regex. Il a demandé de faire correspondre le texte (qui se trouve être HTML) en utilisant l'expression régulière. Ce qui est parfaitement raisonnable.
Paralife
274

Je conviens que le bon outil pour analyser XML et en particulier HTML est un analyseur et non un moteur d'expression régulière. Cependant, comme d'autres l'ont souligné, l'utilisation d'une expression régulière est parfois plus rapide, plus facile et fait le travail si vous connaissez le format des données.

Microsoft a en fait une section des meilleures pratiques pour les expressions régulières dans le .NET Framework et parle spécifiquement de considérer [ing] la source d'entrée .

Les expressions régulières ont des limites, mais avez-vous pensé aux points suivants?

Le framework .NET est unique en ce qui concerne les expressions régulières en ce qu'il prend en charge l' équilibrage des définitions de groupe .

Pour cette raison, je crois que vous POUVEZ analyser XML en utilisant des expressions régulières. Notez cependant qu'il doit s'agir de XML valide (les navigateurs sont très indulgents envers HTML et autorisent une mauvaise syntaxe XML dans HTML ). Cela est possible car la "définition du groupe d'équilibrage" permettra au moteur d'expression régulière d'agir comme un PDA.

Citation de l'article 1 cité ci-dessus:

Moteur d'expression régulière .NET

Comme décrit ci-dessus, les constructions correctement équilibrées ne peuvent pas être décrites par une expression régulière. Cependant, le moteur d'expressions régulières .NET fournit quelques constructions qui permettent de reconnaître des constructions équilibrées.

  • (?<group>) - pousse le résultat capturé sur la pile de capture avec le groupe de noms.
  • (?<-group>) - affiche la capture la plus élevée avec le groupe de noms hors de la pile de capture.
  • (?(group)yes|no) - correspond à la partie oui s'il existe un groupe avec le groupe de noms sinon ne correspond à aucune partie.

Ces constructions permettent à une expression régulière .NET d'émuler un PDA restreint en permettant essentiellement des versions simples des opérations de pile: push, pop et empty. Les opérations simples sont à peu près équivalentes à incrémenter, décrémenter et comparer à zéro respectivement. Cela permet au moteur d'expression régulière .NET de reconnaître un sous-ensemble des langages sans contexte, en particulier ceux qui ne nécessitent qu'un simple compteur. Cela permet à son tour aux expressions régulières .NET non traditionnelles de reconnaître les constructions individuelles correctement équilibrées.

Considérez l'expression régulière suivante:

(?=<ul\s+id="matchMe"\s+type="square"\s*>)
(?>
   <!-- .*? -->                  |
   <[^>]*/>                      |
   (?<opentag><(?!/)[^>]*[^/]>)  |
   (?<-opentag></[^>]*[^/]>)     |
   [^<>]*
)*
(?(opentag)(?!))

Utilisez les drapeaux:

  • Une seule ligne
  • IgnorePatternWhitespace (pas nécessaire si vous réduisez l'expression régulière et supprimez tous les espaces)
  • IgnoreCase (pas nécessaire)

Expression régulière expliquée (en ligne)

(?=<ul\s+id="matchMe"\s+type="square"\s*>) # match start with <ul id="matchMe"...
(?>                                        # atomic group / don't backtrack (faster)
   <!-- .*? -->                 |          # match xml / html comment
   <[^>]*/>                     |          # self closing tag
   (?<opentag><(?!/)[^>]*[^/]>) |          # push opening xml tag
   (?<-opentag></[^>]*[^/]>)    |          # pop closing xml tag
   [^<>]*                                  # something between tags
)*                                         # match as many xml tags as possible
(?(opentag)(?!))                           # ensure no 'opentag' groups are on stack

Vous pouvez l'essayer sur A Better .NET Regular Expression Tester .

J'ai utilisé la source d'échantillon de:

<html>
<body>
<div>
   <br />
   <ul id="matchMe" type="square">
      <li>stuff...</li>
      <li>more stuff</li>
      <li>
          <div>
               <span>still more</span>
               <ul>
                    <li>Another &gt;ul&lt;, oh my!</li>
                    <li>...</li>
               </ul>
          </div>
      </li>
   </ul>
</div>
</body>
</html>

Cela a trouvé le match:

   <ul id="matchMe" type="square">
      <li>stuff...</li>
      <li>more stuff</li>
      <li>
          <div>
               <span>still more</span>
               <ul>
                    <li>Another &gt;ul&lt;, oh my!</li>
                    <li>...</li>
               </ul>
          </div>
      </li>
   </ul>

bien qu'il soit sorti comme ceci:

<ul id="matchMe" type="square">           <li>stuff...</li>           <li>more stuff</li>           <li>               <div>                    <span>still more</span>                    <ul>                         <li>Another &gt;ul&lt;, oh my!</li>                         <li>...</li>                    </ul>               </div>           </li>        </ul>

Enfin, j'ai vraiment apprécié l'article de Jeff Atwood: Parsing Html The Cthulhu Way . Assez drôle, il cite la réponse à cette question qui compte actuellement plus de 4k votes.

Sam
la source
18
System.Textne fait pas partie de C #. Cela fait partie de .NET.
John Saunders
8
Dans la première ligne de votre regex ( (?=<ul\s*id="matchMe"\s*type="square"\s*>) # match start with <ul id="matchMe"...), entre "<ul" et "id" devrait être \s+, non \s*, sauf si vous voulez qu'il corresponde à <ulid = ...;)
C0deH4cker
@ C0deH4cker Vous avez raison, l'expression devrait avoir \s+au lieu de \s*.
Sam
4
Non pas que je le comprenne vraiment, mais je pense que votre regex échoue<img src="images/pic.jpg" />
Scheintod
3
@Scheintod Merci pour le commentaire. J'ai mis à jour le code. L'expression précédente a échoué pour les balises à fermeture automatique qui avaient un /quelque part à l'intérieur qui a échoué pour votre <img src="images/pic.jpg" />html.
Sam
258

Je suggère d'utiliser QueryPath pour analyser XML et HTML en PHP. C'est à peu près la même syntaxe que jQuery, seulement c'est du côté serveur.

John Fiala
la source
8
@ Kyle — jQuery n'analyse pas XML, il utilise l'analyseur intégré du client (s'il y en a un). Par conséquent, vous n'avez pas besoin de jQuery pour le faire, mais aussi peu que deux lignes de JavaScript ancien . S'il n'y a pas d'analyseur intégré, jQuery n'aidera pas.
RobG
1
@RobG En fait, jQuery utilise le DOM, pas l'analyseur intégré.
Qix - MONICA A ÉTÉ BRUÉE le
11
@ Qix - vous feriez mieux de dire aux auteurs de la documentation: " jQuery.parseXML utilise la fonction d'analyse native du navigateur… ". Source: jQuery.parseXML ()
RobG
6
Étant venu ici de la question meme ( meta.stackexchange.com/questions/19478/the-many-memes-of-meta/… ), j'adore que l'une des réponses soit 'Use jQuery'
Jorn
221

Bien que les réponses que vous ne pouvez pas analyser HTML avec des expressions rationnelles soient correctes, elles ne s'appliquent pas ici. L'OP veut juste analyser une balise HTML avec des expressions rationnelles, et c'est quelque chose qui peut être fait avec une expression régulière.

Le regex suggéré est faux, cependant:

<([a-z]+) *[^/]*?>

Si vous ajoutez quelque chose au regex, en faisant un retour en arrière, il peut être forcé de faire correspondre des choses idiotes comme <a >>, [^/]est trop permissif. Notez également que <space>*[^/]*c'est redondant, car le [^/]*peut également correspondre à des espaces.

Ma suggestion serait

<([a-z]+)[^>]*(?<!/)>

(?<! ... )est (dans les expressions rationnelles de Perl) le regard négatif derrière. Il lit "un <, puis un mot, puis tout ce qui n'est pas un>, dont le dernier peut ne pas être un /, suivi de>".

Notez que cela permet des choses comme <a/ >(tout comme l'expression régulière originale), donc si vous voulez quelque chose de plus restrictif, vous devez créer une expression régulière pour correspondre aux paires d'attributs séparées par des espaces.

moritz
la source
29
+1 pour avoir noté que la question ne concerne pas l'analyse complète (X) HTML, mais la correspondance des balises ouvertes (X) HTML.
LarsH
10
Autre chose que la plupart des réponses semblent ignorer, c'est qu'un analyseur HTML peut très bien utiliser des expressions régulières dans son implémentation pour des parties de HTML, et je serais surpris si la plupart des analyseurs ne le faisaient pas.
Thayne
@Thayne Exactement. Lors de l'analyse syntaxique de balises individuelles, une expression régulière est le bon outil pour le travail. Il est assez ridicule de devoir faire défiler la moitié de la page pour trouver une réponse raisonnable. La réponse acceptée est incorrecte car elle mélange lexing et analyse.
kasperd
2
La réponse donnée ici échouera lorsqu'une valeur d'attribut contient un caractère '>' ou '/'.
Martin L
Cela ne fonctionnera pas correctement sur HTML contenant des commentaires ou des sections CData. Cela ne fonctionnera pas non plus correctement si un attribut entre guillemets contient un >caractère. Je suis d' accord que OP suggère peut être fait avec une expression régulière, mais celui présenté ici est loin de simpliste.
JacquesB
183

Essayer:

<([^\s]+)(\s[^>]*?)?(?<!/)>

Il est similaire au vôtre, mais le dernier >ne doit pas être après une barre oblique, et accepte également h1.

Kobi
la source
107
<a href="foo" title="5> 3 "> Oups </a>
Gareth
21
C'est très vrai, et j'y ai pensé, mais j'ai supposé que le >symbole était correctement échappé à & gt ;.
Kobi
65
>est valide dans une valeur d'attribut. En effet, dans la sérialisation «XML canonique», vous ne devez pas utiliser &gt;. (Ce qui n'est pas tout à fait pertinent, sauf pour souligner que >dans un attribut, la valeur n'est pas du tout une chose inhabituelle.)
bobince
5
@Kobi: que signifie le point d'exclamation (celui que vous avez placé vers la fin) dans une expression rationnelle?
Marco Demaio
6
@bobince: êtes-vous sûr? Je ne comprends plus, ce HTML est également valide:<div title="this tag is a <div></div>">hello</div>
Marco Demaio
179

Sun Tzu, ancien stratège chinois, général et philosophe, a déclaré:

On dit que si vous connaissez vos ennemis et vous connaissez vous-même, vous pouvez gagner une centaine de batailles sans une seule perte. Si vous ne vous connaissez que, mais pas votre adversaire, vous pouvez gagner ou perdre. Si vous ne vous connaissez ni vous-même ni votre ennemi, vous vous mettrez toujours en danger.

Dans ce cas, votre ennemi est HTML et vous êtes vous-même ou regex. Vous pourriez même être Perl avec une expression régulière irrégulière. Connaissez le HTML. Se connaitre.

J'ai composé un haïku décrivant la nature du HTML.

HTML has
complexity exceeding
regular language.

J'ai également composé un haïku décrivant la nature des regex en Perl.

The regex you seek
is defined within the phrase
<([a-zA-Z]+)(?:[^>]*[^/]*)?>
cytinus
la source
153
<?php
$selfClosing = explode(',', 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed');

$html = '
<p><a href="#">foo</a></p>
<hr/>
<br/>
<div>name</div>';

$dom = new DOMDocument();
$dom->loadHTML($html);
$els = $dom->getElementsByTagName('*');
foreach ( $els as $el ) {
    $nodeName = strtolower($el->nodeName);
    if ( !in_array( $nodeName, $selfClosing ) ) {
        var_dump( $nodeName );
    }
}

Production:

string(4) "html"
string(4) "body"
string(1) "p"
string(1) "a"
string(3) "div"

Fondamentalement, il suffit de définir les noms de nœuds d'éléments qui se ferment automatiquement, de charger toute la chaîne html dans une bibliothèque DOM, de saisir tous les éléments, de les parcourir et de filtrer ceux qui ne se ferment pas automatiquement et de les opérer.

Je suis sûr que vous savez déjà que vous ne devez pas utiliser l'expression régulière à cette fin.

meder
la source
1
Si vous traitez avec du vrai XHTML, ajoutez getElementsByTagName avec NSet spécifiez l'espace de noms.
meder omuraliev
148

Je ne connais pas votre besoin exact pour cela, mais si vous utilisez également .NET, ne pourriez-vous pas utiliser Html Agility Pack ?

Extrait:

Il s'agit d'une bibliothèque de code .NET qui vous permet d'analyser des fichiers HTML "hors du Web". L'analyseur syntaxique est très tolérant avec le HTML «réel» malformé.

GONeale
la source
137

Vous voulez que le premier >ne soit pas précédé d'un /. Regardez ici pour plus de détails sur la façon de procéder. C'est ce qu'on appelle le lookbehind négatif.

Cependant, une implémentation naïve de cela finira par correspondre <bar/></foo>dans cet exemple de document

<foo><bar/></foo>

Pouvez-vous fournir un peu plus d'informations sur le problème que vous essayez de résoudre? Parcourez-vous les balises par programme?

Jherico
la source
1
Oui, bien sûr. Déterminer toutes les balises actuellement ouvertes, puis comparer cela aux balises fermées dans un tableau séparé. RegEx me fait mal au cerveau.
Jeff
122

Le W3C explique l'analyse syntaxique sous une forme pseudo-regexp:
Lien W3C

Suivez les liens var pour QName, Set Attributepour obtenir une image plus claire.
Sur cette base, vous pouvez créer une assez bonne expression rationnelle pour gérer des choses telles que la suppression des balises.

John-David Dalton
la source
5
Ce n'est pas un formulaire regexp psuedo, c'est un formulaire EBNF, comme spécifié ici: spécification XML, annexe 6
Rob G
106

Si vous en avez besoin pour PHP:

Les fonctions PHP DOM ne fonctionneront pas correctement sauf si elles sont correctement formatées en XML. Peu importe à quel point leur utilisation est meilleure pour le reste de l'humanité.

simplehtmldom est bon, mais je l'ai trouvé un peu bogué, et il est assez lourd en mémoire [Va planter sur les grandes pages.]

Je n'ai jamais utilisé querypath , je ne peux donc pas commenter son utilité.

Un autre à essayer est mon DOMParser qui est très léger sur les ressources et que j'utilise avec bonheur depuis un certain temps. Simple à apprendre et puissant.

Pour Python et Java, des liens similaires ont été publiés.

Pour les downvoters - je n'ai écrit ma classe que lorsque les analyseurs XML se sont révélés incapables de résister à une utilisation réelle. La rétrogradation religieuse empêche simplement la publication de réponses utiles - gardez les choses en perspective, s'il vous plaît.

SamGoody
la source
95

Voici la solution:

<?php
// here's the pattern:
$pattern = '/<(\w+)(\s+(\w+)\s*\=\s*(\'|")(.*?)\\4\s*)*\s*(\/>|>)/';

// a string to parse:
$string = 'Hello, try clicking <a href="#paragraph">here</a>
    <br/>and check out.<hr />
    <h2>title</h2>
    <a name ="paragraph" rel= "I\'m an anchor"></a>
    Fine, <span title=\'highlight the "punch"\'>thanks<span>.
    <div class = "clear"></div>
    <br>';

// let's get the occurrences:
preg_match_all($pattern, $string, $matches, PREG_PATTERN_ORDER);

// print the result:
print_r($matches[0]);
?>

Pour le tester en profondeur, j'ai entré dans la chaîne des balises à fermeture automatique comme:

  1. <h />
  2. <br/>
  3. <br>

J'ai également entré des balises avec:

  1. un attribut
  2. plus d'un attribut
  3. attributs dont la valeur est liée soit entre guillemets simples, soit entre guillemets doubles
  4. attributs contenant des guillemets simples lorsque le délimiteur est un guillemet double et vice versa
  5. Attributs "unsetty" avec un espace avant le symbole "=", après et avant et après.

Si vous trouvez quelque chose qui ne fonctionne pas dans la preuve de concept ci-dessus, je suis disponible pour analyser le code afin d'améliorer mes compétences.

<EDIT> J'ai oublié que la question de l'utilisateur était d'éviter l'analyse des balises à fermeture automatique. Dans ce cas, le modèle est plus simple, se transformant en ceci:

$pattern = '/<(\w+)(\s+(\w+)\s*\=\s*(\'|")(.*?)\\4\s*)*\s*>/';

L'utilisateur @ridgerunner a remarqué que le modèle n'autorise pas les attributs non cotés ou les attributs sans valeur . Dans ce cas, un réglage fin nous apporte le modèle suivant:

$pattern = '/<(\w+)(\s+(\w+)(\s*\=\s*(\'|"|)(.*?)\\5\s*)?)*\s*>/';

</EDIT>

Comprendre le modèle

Si quelqu'un est intéressé à en savoir plus sur le modèle, je fournis une ligne:

  1. la première sous-expression (\ w +) correspond au nom de la balise
  2. la deuxième sous-expression contient le modèle d'un attribut. Il est composé de:
    1. un ou plusieurs espaces blancs \ s +
    2. le nom de l'attribut (\ w +)
    3. zéro ou plusieurs espaces blancs \ s * (c'est possible ou non, en laissant des blancs ici)
    4. le symbole "="
    5. encore une fois, zéro ou plusieurs espaces blancs
    6. le délimiteur de la valeur d'attribut, un guillemet simple ou double ('| "). Dans le modèle, le guillemet simple est échappé car il coïncide avec le délimiteur de chaîne PHP. Cette sous-expression est capturée avec les parenthèses pour pouvoir être référencée encore une fois pour analyser la fermeture de l'attribut, c'est pourquoi il est très important.
    7. la valeur de l'attribut, correspondant à presque n'importe quoi: (. *?); dans cette syntaxe spécifique, en utilisant la correspondance gourmande (le point d'interrogation après l'astérisque), le moteur RegExp permet un opérateur de type "anticipation", qui correspond à tout sauf à ce qui suit cette sous-expression
    8. voici le plaisir: la partie \ 4 est un opérateur de référence arrière , qui se réfère à une sous-expression définie précédemment dans le modèle, dans ce cas, je fais référence à la quatrième sous-expression, qui est le premier délimiteur d'attribut trouvé
    9. zéro ou plusieurs espaces blancs \ s *
    10. la sous-expression d'attribut se termine ici, avec la spécification de zéro ou plusieurs occurrences possibles, donnée par l'astérisque.
  3. Puis, comme une balise peut se terminer par un espace avant le symbole ">", zéro ou plusieurs espaces sont mis en correspondance avec le sous-modèle \ s *.
  4. La balise à mettre en correspondance peut se terminer par un simple symbole ">", ou une fermeture XHTML possible, qui utilise la barre oblique qui la précède: (/> |>). La barre oblique est, bien sûr, échappée car elle coïncide avec le délimiteur d'expression régulière.

Petit conseil: pour mieux analyser ce code il faut regarder le code source généré puisque je n'ai pas fourni de caractères HTML spéciaux s'échappant.

Emanuele Del Grande
la source
12
Ne correspond pas aux balises valides ayant des attributs sans valeur, c.-à-d <option selected>. Ne correspond pas non plus aux balises valides avec des valeurs d'attribut non citées, c'est-à-dire <p id=10>.
ridgerunner
1
@ridgerunner: Merci beaucoup pour votre commentaire. Dans ce cas, le modèle doit changer un peu: $ pattern = '/ <(\ w +) (\ s + (\ w +) (\ s * \ = \ s * (\' | "|) (. *?) \\ 5 \ s *)?) * \ S *> / '; je l'ai testé et fonctionne en cas d'attributs non cités ou d'attributs sans valeur.
Emanuele Del Grande
Que diriez-vous d'un espace avant le nom de la balise: < a href="http://wtf.org" >je suis presque sûr que c'est légal, mais vous ne le faites pas.
Floris
7
NON désolé, les espaces avant une variable sont illégaux. Au-delà d'être "assez sûr" pourquoi ne fournissez-vous pas des preuves de votre objection? Voici les miens, w3.org/TR/xml11/#sec-starttags référencés à XML 1.1, et vous pouvez trouver la même chose pour HTML 4, 5 et XHTML, car une validation W3C avertirait également si vous effectuez un test. Comme beaucoup d'autres poètes bla bla bla ici, je n'ai toujours pas reçu d'argumentation intelligente, à part une centaine de moins à mes réponses, pour démontrer où mon code échoue selon les règles contractuelles spécifiées dans la question. Je ne ferais que leur souhaiter la bienvenue.
Emanuele Del Grande
@ridgerunner bien sûr, votre commentaire était intelligent et bienvenu.
Emanuele Del Grande
91

Chaque fois que j'ai besoin d'extraire rapidement quelque chose d'un document HTML, j'utilise Tidy pour le convertir en XML, puis j'utilise XPath ou XSLT pour obtenir ce dont j'ai besoin. Dans votre cas, quelque chose comme ceci:

//p/a[@href='foo']
Amal Murali
la source
89

J'ai utilisé un outil open source appelé HTMLParser auparavant. Il est conçu pour analyser HTML de diverses manières et sert très bien le but. Il peut analyser HTML en tant que treenode différent et vous pouvez facilement utiliser son API pour extraire des attributs du nœud. Vérifiez-le et voyez si cela peut vous aider.

loupe
la source
84

J'aime analyser HTML avec des expressions régulières. Je n'essaie pas d'analyser un idiot HTML qui est délibérément cassé. Ce code est mon analyseur principal (édition Perl):

$_ = join "",<STDIN>; tr/\n\r \t/ /s; s/</\n</g; s/>/>\n/g; s/\n ?\n/\n/g;
s/^ ?\n//s; s/ $//s; print

Cela s'appelle htmlsplit, divise le code HTML en lignes, avec une balise ou un morceau de texte sur chaque ligne. Les lignes peuvent ensuite être traitées avec d'autres outils de texte et scripts, tels que grep , sed , Perl, etc. Je ne plaisante même pas :) Profitez-en.

Il est assez simple de reconstituer mon script Perl slurp-everything-first en une belle chose en streaming, si vous souhaitez traiter d'énormes pages Web. Mais ce n'est pas vraiment nécessaire.

Je parie que je vais voter pour cela.

Fractionnement HTML


Contre mes attentes, cela a eu quelques votes positifs, je vais donc suggérer de meilleures expressions régulières:

/(<.*?>|[^<]+)\s*/g    # get tags and text
/(\w+)="(.*?)"/g       # get attibutes

Ils sont bons pour XML / XHTML.

Avec des variations mineures, il peut faire face au HTML désordonné ... ou convertir le HTML -> XHTML en premier.


La meilleure façon d'écrire des expressions régulières est dans le style Lex / Yacc , pas sous forme de lignes simples opaques ou de monstruosités multilignes commentées. Je n'ai pas encore fait ça ici; ceux-ci en ont à peine besoin.

Sam Watkins
la source
35
"Je n'essaie pas d'analyser un idiot HTML qui est délibérément cassé." Comment votre code connaît-il la différence?
Kevin Panko
Eh bien, peu importe si le HTML est cassé ou non. La chose divisera toujours HTML en balises et texte. La seule chose qui pourrait l'encrasser, c'est si les gens incluent des caractères <ou> non échappés dans le texte ou les attributs. En pratique, mon minuscule séparateur HTML fonctionne bien. Je n'ai pas besoin d'une énorme cale de monstruosité pleine d'heuristiques. Les solutions simples ne sont pas pour tout le monde ...!
Sam Watkins
J'ai ajouté des expressions rationnelles plus simples pour extraire des balises, du texte et des attributs, pour XML / XHTML.
Sam Watkins
(obtenir les attributs bug 1) /(\w+)="(.*?)"/suppose des guillemets doubles. Il manquera des valeurs entre guillemets simples. Dans la version html 4 et les versions antérieures, les valeurs sans guillemets sont autorisées, s'il s'agit d'un simple mot.
David Andersson
(obtenir les attributs bogue 2) /(\w+)="(.*?)"/peut correspondre faussement au texte qui ressemble à un attribut dans un attribut, par exemple <img title="Nope down='up' for aussies" src="..." />. S'il est appliqué globalement, il correspondra également à de telles choses dans du texte ordinaire ou dans des commentaires html.
David Andersson
74

Voici un analyseur basé sur PHP qui analyse le HTML en utilisant une expression rationnelle impie. En tant qu'auteur de ce projet, je peux vous dire qu'il est possible d'analyser HTML avec regex, mais pas efficace. Si vous avez besoin d'une solution côté serveur (comme je l'ai fait pour mon plugin WordPress wp-Typography ), cela fonctionne.

kingjeffrey
la source
1
htmlawed est un autre projet PHP qui analyse le HTML pour filtrer, convertir, etc. A du bon code si vous pouvez le comprendre!
user594694
Non, vous ne pouvez pas analyser HTML avec regex. Mais pour certains sous-ensembles, cela peut fonctionner.
mirabilos
71

Il y a quelques jolies expressions rationnelles pour remplacer HTML par BBCode ici . Pour tous ceux qui ne disent rien, notez qu'il n'essaie pas d'analyser complètement le HTML, juste de le désinfecter. Il peut probablement se permettre de tuer des balises que son simple "analyseur" ne peut pas comprendre.

Par exemple:

$store =~ s/http:/http:\/\//gi;
$store =~ s/https:/https:\/\//gi;
$baseurl = $store;

if (!$query->param("ascii")) {
    $html =~ s/\s\s+/\n/gi;
    $html =~ s/<pre(.*?)>(.*?)<\/pre>/\[code]$2\[\/code]/sgmi;
}

$html =~ s/\n//gi;
$html =~ s/\r\r//gi;
$html =~ s/$baseurl//gi;
$html =~ s/<h[1-7](.*?)>(.*?)<\/h[1-7]>/\n\[b]$2\[\/b]\n/sgmi;
$html =~ s/<p>/\n\n/gi;
$html =~ s/<br(.*?)>/\n/gi;
$html =~ s/<textarea(.*?)>(.*?)<\/textarea>/\[code]$2\[\/code]/sgmi;
$html =~ s/<b>(.*?)<\/b>/\[b]$1\[\/b]/gi;
$html =~ s/<i>(.*?)<\/i>/\[i]$1\[\/i]/gi;
$html =~ s/<u>(.*?)<\/u>/\[u]$1\[\/u]/gi;
$html =~ s/<em>(.*?)<\/em>/\[i]$1\[\/i]/gi;
$html =~ s/<strong>(.*?)<\/strong>/\[b]$1\[\/b]/gi;
$html =~ s/<cite>(.*?)<\/cite>/\[i]$1\[\/i]/gi;
$html =~ s/<font color="(.*?)">(.*?)<\/font>/\[color=$1]$2\[\/color]/sgmi;
$html =~ s/<font color=(.*?)>(.*?)<\/font>/\[color=$1]$2\[\/color]/sgmi;
$html =~ s/<link(.*?)>//gi;
$html =~ s/<li(.*?)>(.*?)<\/li>/\[\*]$2/gi;
$html =~ s/<ul(.*?)>/\[list]/gi;
$html =~ s/<\/ul>/\[\/list]/gi;
$html =~ s/<div>/\n/gi;
$html =~ s/<\/div>/\n/gi;
$html =~ s/<td(.*?)>/ /gi;
$html =~ s/<tr(.*?)>/\n/gi;

$html =~ s/<img(.*?)src="(.*?)"(.*?)>/\[img]$baseurl\/$2\[\/img]/gi;
$html =~ s/<a(.*?)href="(.*?)"(.*?)>(.*?)<\/a>/\[url=$baseurl\/$2]$4\[\/url]/gi;
$html =~ s/\[url=$baseurl\/http:\/\/(.*?)](.*?)\[\/url]/\[url=http:\/\/$1]$2\[\/url]/gi;
$html =~ s/\[img]$baseurl\/http:\/\/(.*?)\[\/img]/\[img]http:\/\/$1\[\/img]/gi;

$html =~ s/<head>(.*?)<\/head>//sgmi;
$html =~ s/<object>(.*?)<\/object>//sgmi;
$html =~ s/<script(.*?)>(.*?)<\/script>//sgmi;
$html =~ s/<style(.*?)>(.*?)<\/style>//sgmi;
$html =~ s/<title>(.*?)<\/title>//sgmi;
$html =~ s/<!--(.*?)-->/\n/sgmi;

$html =~ s/\/\//\//gi;
$html =~ s/http:\//http:\/\//gi;
$html =~ s/https:\//https:\/\//gi;

$html =~ s/<(?:[^>'"]*|(['"]).*?\1)*>//gsi;
$html =~ s/\r\r//gi;
$html =~ s/\[img]\//\[img]/gi;
$html =~ s/\[url=\//\[url=/gi;
kenorb
la source
15
Ne fais pas ça. S'il vous plaît.
maletor
68

À propos de la question des méthodes RegExp pour analyser (x) HTML, la réponse à tous ceux qui ont parlé de certaines limites est: vous n'avez pas été suffisamment formé pour gouverner la force de cette arme puissante, puisque NOBODY ici a parlé de récursivité .

Un collègue RegExp-agnostic m'a informé de cette discussion, qui n'est certainement pas la première sur le Web à propos de ce sujet ancien et brûlant.

Après avoir lu certains articles, la première chose que j'ai faite a été de rechercher la chaîne "? R" dans ce fil. La seconde consistait à rechercher «récursivité».
Non, vache sacrée, aucune correspondance trouvée.
Comme personne n'a mentionné le mécanisme principal sur lequel un analyseur est construit, j'ai vite compris que personne n'avait compris.

Si un analyseur (x) HTML a besoin d'une récursivité, un analyseur RegExp sans récursivité n'est pas suffisant à cet effet. C'est une construction simple.

L' art noir de RegExp est difficile à maîtriser , alors peut-être qu'il y a d'autres possibilités que nous avons laissées en essayant et testant notre solution personnelle pour capturer le Web entier dans une main ... Eh bien, j'en suis sûr :)

Voici le schéma magique:

$pattern = "/<([\w]+)([^>]*?)(([\s]*\/>)|(>((([^<]*?|<\!\-\-.*?\-\->)|(?R))*)<\/\\1[\s]*>))/s";

Essayez-le.
Il est écrit comme une chaîne PHP, donc le modificateur "s" fait que les classes incluent des sauts de ligne.
Voici un exemple de note sur le manuel PHP que j'ai écrit en janvier: Référence

(Attention, dans cette note, j'ai utilisé à tort le modificateur "m"; il doit être effacé, bien qu'il soit rejeté par le moteur RegExp, car aucun ancrage ^ ou $ n'a été utilisé).

Maintenant, nous pourrions parler des limites de cette méthode d'un point de vue plus éclairé:

  1. selon l'implémentation spécifique du moteur RegExp, la récursivité peut avoir une limite dans le nombre de modèles imbriqués analysés , mais cela dépend de la langue utilisée
  2. Bien que le HTML (x) corrompu ne génère pas d'erreurs graves, il n'est pas nettoyé .

Quoi qu'il en soit, ce n'est qu'un modèle RegExp, mais il révèle la possibilité de développer de nombreuses implémentations puissantes.
J'ai écrit ce modèle pour alimenter l' analyseur de descente récursive d'un moteur de modèle que j'ai construit dans mon framework, et les performances sont vraiment excellentes, à la fois en temps d'exécution ou en utilisation de mémoire (rien à voir avec d'autres moteurs de modèle qui utilisent la même syntaxe).

Emanuele Del Grande
la source
35
Je vais mettre cela dans le bac "Regex qui n'autorise pas plus que dans les attributs". Vérifiez-le par rapport à <valeur d'entrée = "est 5> 3?" />
Gareth
68
Si vous mettez quelque chose comme ça dans le code de production, vous serez probablement abattu par le responsable. Un jury ne le condamnerait jamais.
aehiilrs
30
Les expressions régulières ne peuvent pas fonctionner car, par définition, elles ne sont pas récursives. L'ajout d'un opérateur récursif aux expressions régulières rend fondamentalement un CFG uniquement avec une syntaxe plus pauvre. Pourquoi ne pas utiliser quelque chose conçu pour être récursif en premier lieu plutôt que d'insérer violemment la récursivité dans quelque chose qui déborde déjà de fonctionnalités étrangères?
Welbog
16
Mon objection n'est pas une question de fonctionnalité, c'est une question de temps investi. Le problème avec RegEx est qu'au moment où vous postez les doublures cutsey, il semble que vous avez fait quelque chose de plus efficace ("Voir une ligne de code!"). Et bien sûr, personne ne mentionne la demi-heure (ou 3) qu'ils ont passée avec leur aide-mémoire et (espérons-le) tester toutes les permutations possibles des entrées. Et une fois que vous avez dépassé tout cela, lorsque le responsable va découvrir ou valider le code, il ne peut pas simplement le regarder et voir qu'il est correct. Il faut disséquer l'expression et essentiellement la retester à nouveau ...
Oorang
15
... pour savoir que c'est bon. Et cela se produira même avec des gens qui sont bons avec l'expression régulière. Et honnêtement, je soupçonne qu'une écrasante majorité de gens ne le savent pas bien. Donc, vous prenez l'un des cauchemars de maintenance les plus notoires et le combinez avec la récursivité qui est l' autre cauchemar de maintenance et je me dis que ce dont j'ai vraiment besoin dans mon projet est quelqu'un un peu moins intelligent. Le but est d'écrire du code que les mauvais programmeurs peuvent maintenir sans casser la base de code. Je sais que ça crie de coder au plus petit dénominateur commun. Mais embaucher un excellent talent est difficile, et vous souvent ...
Oorang
62

Comme de nombreuses personnes l'ont déjà souligné, le HTML n'est pas un langage standard, ce qui peut le rendre très difficile à analyser. Ma solution à cela est de le transformer en un langage normal à l'aide d'un programme bien rangé, puis d'utiliser un analyseur XML pour consommer les résultats. Il y a beaucoup de bonnes options pour cela. Mon programme est écrit en utilisant Java avec la bibliothèque jtidy pour transformer le HTML en XML puis Jaxen en xpath en résultat.

Corey Sanders
la source
61
<\s*(\w+)[^/>]*>

Les parties ont expliqué:

<: caractère de départ

\s*: il peut avoir des espaces avant le nom du tag (laid mais possible).

(\w+): les balises peuvent contenir des lettres et des chiffres (h1). Eh bien, \wcorrespond également à «_», mais cela ne fait pas de mal, je suppose. Si vous êtes curieux, utilisez plutôt [[a-zA-Z0-9] +).

[^/>]*: tout sauf >et /jusqu'à la fermeture>

>: fermeture >

NON RELIÉ

Et aux boursiers qui sous-estiment les expressions régulières en disant qu'elles ne sont aussi puissantes que les langues régulières:

un n ba n ba n qui n'est pas régulier et même sans contexte, peut être associé à^(a+)b\1b\1$

Référence arrière FTW !

daghan
la source
@GlitchMr, c'était son point. Les expressions régulières modernes ne sont pas techniquement régulières, et il n'y a aucune raison qu'elles le soient.
alanaktion
3
@alanaktion: Les expressions régulières "modernes" (lire: avec les extensions Perl) ne peuvent pas correspondre à l'intérieur O(MN)(M étant la longueur de l'expression régulière, N étant la longueur du texte). Les références arrières sont l'une des causes de cela. L'implémentation dans awk n'a pas de références et correspond à tout dans le O(MN)temps.
Konrad Borowski
56

Si vous essayez simplement de trouver ces balises (sans ambitions d'analyse), essayez cette expression régulière:

/<[^/]*?>/g

Je l'ai écrit en 30 secondes et testé ici: http://gskinner.com/RegExr/

Il correspond aux types de balises que vous avez mentionnés, tout en ignorant les types que vous avez dit que vous vouliez ignorer.

Lonnie Best
la source
2
Je pense que tu veux dire \/>au lieu de \\>.
Justin Morgan
Non, c'est juste \>ce que je voulais dire; Je n'ai jamais voulu modifier l'expression régulière de mon message d'origine.
Lonnie Best
2
Pour info, vous n'avez pas besoin d'échapper aux équerres. Bien sûr, cela ne fait aucun mal de leur échapper de toute façon, mais regardez la confusion que vous auriez pu éviter. ;)
Alan Moore
Je m'échappe parfois inutilement lorsque je ne sais pas si quelque chose est un caractère spécial ou non. J'ai édité la réponse; cela fonctionne de la même façon mais de façon plus concise.
Lonnie Best
En regardant cela maintenant, je ne sais pas pourquoi je pensais que vous vouliez dire \/, car cela ferait exactement le contraire des exigences. Peut-être que je pensais que vous proposiez un modèle de filtre négatif.
Justin Morgan
54

Il me semble que vous essayez de faire correspondre les balises sans "/" à la fin. Essaye ça:

<([a-zA-Z][a-zA-Z0-9]*)[^>]*(?<!/)>
manixrock
la source
8
Cela ne fonctionne pas. Pour l'entrée '<xa = "<b>" /> <y>', les correspondances sont x et y, bien que x soit terminé.
ceving
51

Il est vrai que lors de la programmation, il est généralement préférable d'utiliser des analyseurs et des API dédiés au lieu d'expressions régulières lorsqu'il s'agit de HTML, en particulier si la précision est primordiale (par exemple, si votre traitement peut avoir des implications sur la sécurité). Cependant, je n'attribue pas une vue dogmatique selon laquelle le balisage de style XML ne devrait jamais être traité avec des expressions régulières. Il y a des cas où les expressions régulières sont un excellent outil pour le travail, comme lorsque vous effectuez des modifications ponctuelles dans un éditeur de texte, réparez des fichiers XML cassés ou traitez des formats de fichiers qui ressemblent mais ne sont pas tout à fait XML. Il y a certains problèmes à prendre en compte, mais ils ne sont pas insurmontables ni même nécessairement pertinents.

Un simple regex comme <([^>"']|"[^"]*"|'[^']*')*>est généralement suffisant, dans des cas comme ceux que je viens de mentionner. C'est une solution naïve, tout bien considéré, mais elle autorise correctement les >symboles non codés dans les valeurs d'attribut. Si vous cherchez, par exemple, une tablebalise, vous pouvez l'adapter en tant que </?table\b([^>"']|"[^"]*"|'[^']*')*>.

Juste pour donner une idée de ce à quoi ressemblerait une expression rationnelle HTML plus "avancée", ce qui suit fait un travail assez respectable d'émulation du comportement du navigateur réel et de l'algorithme d'analyse HTML5:

</?([A-Za-z][^\s>/]*)(?:=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)|[^>])*(?:>|$)

Ce qui suit correspond à une définition assez stricte des balises XML (bien qu'elle ne prenne pas en compte l'ensemble complet des caractères Unicode autorisés dans les noms XML):

<(?:([_:A-Z][-.:\w]*)(?:\s+[_:A-Z][-.:\w]*\s*=\s*(?:"[^"]*"|'[^']*'))*\s*/?|/([_:A-Z][-.:\w]*)\s*)>

Certes, ceux-ci ne tiennent pas compte du contexte environnant et de quelques cas marginaux, mais même de telles choses pourraient être traitées si vous le vouliez vraiment (par exemple, en recherchant entre les correspondances d'une autre expression régulière).

À la fin de la journée, utilisez l'outil le plus approprié pour le travail, même dans les cas où cet outil se trouve être une expression régulière.

slevithan
la source
49

Bien qu'il ne soit pas approprié et efficace d'utiliser des expressions régulières à cette fin, les expressions régulières fournissent parfois des solutions rapides aux problèmes de correspondance simples et, à mon avis, ce n'est pas horrible d'utiliser des expressions régulières pour des travaux triviaux.

Il y a un article de blog définitif sur la correspondance des éléments HTML les plus internes écrits par Steven Levithan.

Emre Yazici
la source