Il n'y a pas de jour sur SO qui passe sans une question sur l'analyse syntaxique (X) HTML ou XML avec des expressions régulières posées.
Bien qu'il soit relativement facile de trouver exemples qui démontrent la non-viabilité des expressions rationnelles pour cette tâche ou avec un collection d'expressions pour représenter le concept, je n'ai toujours pas pu trouver on SO a explication formelle des raisons pour lesquelles cela n'est pas possible en termes simples).
Les seules explications formelles que j'ai pu trouver jusqu'à présent sur ce site sont probablement extrêmement précises, mais aussi assez cryptiques pour le programmeur autodidacte:
la faille ici est que HTML est une grammaire Chomsky Type 2 (grammaire sans contexte) et RegEx est une grammaire Chomsky Type 3 (expression régulière)
ou:
Les expressions régulières ne peuvent correspondre qu'à des langues normales, mais HTML est un langage sans contexte.
ou:
Un automate fini (qui est la structure de données sous-jacente à une expression régulière) n'a pas de mémoire en dehors de l'état dans lequel il se trouve, et si vous avez une imbrication arbitrairement profonde, vous avez besoin d'un automate arbitrairement grand, qui entre en collision avec la notion d'automate fini.
ou:
Le lemme de pompage pour les langues régulières est la raison pour laquelle vous ne pouvez pas faire cela.
[Pour être juste: la majorité des explications ci-dessus renvoient vers des pages wikipedia, mais elles ne sont pas beaucoup plus faciles à comprendre que les réponses elles-mêmes].
Donc ma question est: quelqu'un pourrait-il s'il vous plaît fournir une traduction en termes simples des explications formelles données ci-dessus pour expliquer pourquoi il n'est pas possible d'utiliser des expressions rationnelles pour analyser (X) HTML/XML?
EDIT: Après avoir lu la première réponse, j'ai pensé que je devais clarifier: je recherche une "traduction" qui aussi brièvement explique les concepts qu'il essaie de traduire: à la fin d'une réponse, le lecteur devrait avoir une idée approximative - par exemple - de ce que "langage régulier" et "grammaire sans contexte" signifient ...
Concentrez-vous sur celui-ci:
Un automate fini (qui est la structure de données sous-jacente à une expression régulière) n'a pas de mémoire en dehors de l'état dans lequel il se trouve, et si vous avez une imbrication arbitrairement profonde, vous avez besoin d'un automate arbitrairement grand, qui entre en collision avec la notion d'automate fini.
La définition des expressions régulières équivaut au fait qu'un test de conformité d'une chaîne au modèle peut être effectué par un automate fini (un automate différent pour chaque modèle). Un automate fini n'a pas de mémoire - pas de pile, pas de tas, pas de bande infinie sur laquelle griffonner. Tout ce qu'il a, c'est un nombre fini d'états internes, chacun pouvant lire une unité d'entrée de la chaîne testée, et l'utiliser pour décider de l'état dans lequel passer. Comme cas particulier, il a deux états de terminaison: "oui, cela correspond" et "non, cela ne correspond pas".
Le HTML, en revanche, possède des structures qui peuvent s'imbriquer de façon arbitraire en profondeur. Pour déterminer si un fichier est HTML valide ou non, vous devez vérifier que toutes les balises de fermeture correspondent à une balise d'ouverture précédente. Pour le comprendre, vous devez savoir quel élément est fermé. Sans aucun moyen de "se souvenir" des balises d'ouverture que vous avez vues, aucune chance.
Notez cependant que la plupart des bibliothèques "regex" autorisent en fait plus que la simple définition stricte des expressions régulières. S'ils peuvent correspondre à des références arrières, alors ils sont allés au-delà d'un langage normal. Donc, la raison pour laquelle vous ne devriez pas utiliser une bibliothèque regex sur HTML est un peu plus complexe que le simple fait que HTML n'est pas régulier.
Le fait que le HTML ne représente pas une langue régulière est un hareng rouge. Expression régulière et langues régulières une sorte de son similaire, mais ne le sont pas - elles partagent la même origine, mais il existe une distance notable entre les "langues régulières" académiques et la puissance de correspondance actuelle des moteurs. En fait, presque tous les moteurs d'expression régulière modernes prennent en charge des fonctionnalités non régulières - un exemple simple est (.*)\1
. qui utilise la référence arrière pour faire correspondre une séquence répétée de caractères - par exemple 123123
ou bonbon
. L'association de structures récursives/équilibrées les rend encore plus amusantes.
Wikipedia le dit bien, dans une citation de Larry Wall :
Les "expressions régulières" [...] ne sont que marginalement liées aux expressions régulières réelles. Néanmoins, le terme a grandi avec les capacités de nos moteurs de correspondance de motifs, donc je ne vais pas essayer de lutter contre la nécessité linguistique ici. Je les appellerai cependant généralement "regexes" (ou "regexen", quand je suis d'humeur anglo-saxonne).
"L'expression régulière ne peut correspondre qu'à des langues régulières", comme vous pouvez le voir, n'est rien de plus qu'un sophisme communément déclaré.
Une bonne raison de ne pas faire correspondre HTML à une expression régulière est que "ce n'est pas parce que vous pouvez" que vous devez. Bien que cela soit possible - , il existe simplement de meilleurs outils pour le travail . Considérant:
Très souvent, il est impossible de faire correspondre une partie des données sans les analyser dans leur ensemble. Par exemple, vous pouvez rechercher tous les titres et finir par correspondre dans un commentaire ou un littéral de chaîne. <h1>.*?</h1>
peut être une tentative audacieuse pour trouver le titre principal, mais il peut trouver:
<!-- <h1>not the title!</h1> -->
Ou même:
<script>
var s = "Certainly <h1>not the title!</h1>";
</script>
Le dernier point est le plus important:
Un bon résumé du sujet, et un commentaire important sur le moment où mélanger Regex et HTML peut être approprié, peuvent être trouvés dans le blog de Jeff Atwood: Parsing Html The Cthulhu Way .
Dans la plupart des cas, il est préférable d'utiliser XPath sur la structure DOM qu'une bibliothèque peut vous donner. Pourtant, contre l'opinion populaire, il y a quelques cas où je recommanderais fortement d'utiliser une regex et non une bibliothèque d'analyseur:
Compte tenu de quelques-unes de ces conditions:
Parce que HTML peut avoir une imbrication illimitée de <tags><inside><tags and="<things><that><look></like></tags>"></inside></each></other>
et regex ne peuvent pas vraiment faire face à cela car ils ne peuvent pas suivre l'historique de ce dans quoi ils sont descendus et sortis.
Une construction simple qui illustre la difficulté:
<body><div id="foo">Hi there! <div id="bar">Bye!</div></div></body>
99,9% des routines d'extraction basées sur des expressions rationnelles généralisées ne seront pas en mesure de me donner tout correctement à l'intérieur du div
avec l'ID foo
, car elles ne peuvent pas dire la balise de fermeture pour ce div de la fermeture pour la balise bar
div. C'est parce qu'ils n'ont aucun moyen de dire "d'accord, je suis maintenant descendu dans la deuxième des deux divisions, donc la prochaine fermeture de division que je vois me ramène à une, et celle qui suit est la balise de fermeture pour la première" . Les programmeurs réagissent généralement en concevant des expressions rationnelles de cas spécial pour la situation spécifique, qui se cassent ensuite dès que plus de balises sont introduites dans foo
et doivent être débrouillées à un coût énorme en temps et en frustration. C'est pourquoi les gens deviennent fous de tout cela.
Un langage normal est un langage auquel peut correspondre une machine à états finis.
(Comprendre les machines à états finis, les machines à enfoncer et les machines de Turing est fondamentalement le programme d'un cours CS de quatrième année.)
Considérez la machine suivante, qui reconnaît la chaîne "hi".
(Start) --Read h-->(A)--Read i-->(Succeed)
\ \
\ -- read any other value-->(Fail)
-- read any other value-->(Fail)
Il s'agit d'une machine simple pour reconnaître une langue régulière; Chaque expression entre parenthèses est un état et chaque flèche est une transition. Construire une machine comme celle-ci vous permettra de tester n'importe quelle chaîne d'entrée par rapport à un langage normal - donc une expression régulière.
Le HTML exige que vous sachiez plus que simplement dans quel état vous êtes - il nécessite un historique de ce que vous avez vu auparavant, pour correspondre à l'imbrication des balises. Vous pouvez accomplir cela si vous ajoutez une pile à la machine, mais alors elle n'est plus "régulière". C'est ce qu'on appelle une machine déroulante et reconnaît une grammaire.
Une expression régulière est une machine avec un nombre fini (et généralement assez petit) d'états discrets.
Pour analyser XML, C ou tout autre langage avec une imbrication arbitraire d'éléments de langage, vous devez vous souvenir de votre profondeur. Autrement dit, vous devez pouvoir compter les accolades/crochets/balises.
Vous ne pouvez pas compter avec une mémoire finie. Il peut y avoir plus de niveaux d'accolades que vous n'en avez! Vous pourriez être en mesure d'analyser un sous-ensemble de votre langue qui restreint le nombre de niveaux d'imbrication, mais ce serait très fastidieux.
Une grammaire est une définition formelle de l'endroit où les mots peuvent aller. Par exemple, les adjectifs précèdent les noms in English grammar
, Mais suivent les noms en la gramática española
. Sans contexte signifie que la grammaire est universelle dans tous les contextes. Sensible au contexte signifie qu'il existe des règles supplémentaires dans certains contextes.
En C #, par exemple, using
signifie quelque chose de différent dans using System;
En haut des fichiers, que using (var sw = new StringWriter (...))
. Un exemple plus pertinent est le code suivant dans le code:
void Start ()
{
string myCode = @"
void Start()
{
Console.WriteLine (""x"");
}
";
}
Il y a une autre raison pratique de ne pas utiliser d'expressions régulières pour analyser XML et HTML qui n'a rien à voir avec la théorie informatique: votre expression régulière sera soit affreusement compliquée, soit erronée.
Par exemple, il est très bien d'écrire une expression régulière pour correspondre
<price>10.65</price>
Mais si votre code doit être correct, alors:
Il doit autoriser les espaces après le nom de l'élément dans les balises de début et de fin
Si le document se trouve dans un espace de noms, il doit autoriser l'utilisation de tout préfixe d'espace de noms
Il devrait probablement autoriser et ignorer tous les attributs inconnus apparaissant dans la balise de début (selon la sémantique du vocabulaire particulier)
Il peut être nécessaire d'autoriser les espaces avant et après la valeur décimale (là encore, en fonction des règles détaillées du vocabulaire XML particulier).
Il ne doit pas correspondre à quelque chose qui ressemble à un élément, mais se trouve en fait dans un commentaire ou une section CDATA (cela devient particulièrement important s'il y a une possibilité de données malveillantes essayant de tromper votre analyseur).
Il peut être nécessaire de fournir des diagnostics si l'entrée n'est pas valide.
Bien sûr, cela dépend en partie des normes de qualité que vous appliquez. Nous voyons beaucoup de problèmes sur StackOverflow avec des personnes devant générer du XML d'une manière particulière (par exemple, sans espace dans les balises) car il est lu par une application qui nécessite qu'il soit écrit d'une manière particulière. Si votre code a une longévité quelconque, il est important qu'il puisse traiter le XML entrant écrit de la manière autorisée par la norme XML, et pas seulement le seul exemple de document d'entrée sur lequel vous testez votre code.
Dans un sens purement théorique, il est impossible pour les expressions régulières d'analyser XML. Ils sont définis de manière à ne laisser aucune mémoire d'aucun état antérieur, empêchant ainsi la correspondance correcte d'une balise arbitraire, et ils ne peuvent pas pénétrer à une profondeur arbitraire d'imbrication, car l'imbrication devrait être intégrée à l'expression régulière.
Les analyseurs d'expressions rationnelles modernes, cependant, sont construits pour leur utilité pour le développeur, plutôt que pour leur adhérence à une définition précise. En tant que tel, nous avons des choses comme les références arrières et la récursivité qui utilisent la connaissance des états précédents. À l'aide de ceux-ci, il est remarquablement simple de créer une expression régulière qui peut explorer, valider ou analyser XML.
Considérez par exemple,
(?:
<!\-\-[\S\s]*?\-\->
|
<([\w\-\.]+)[^>]*?
(?:
\/>
|
>
(?:
[^<]
|
(?R)
)*
<\/\1>
)
)
Cela trouvera la prochaine balise ou commentaire XML correctement formé, et il ne le trouvera que si tout son contenu est correctement formé. (Cette expression a été testée à l'aide de Notepad ++, qui utilise la bibliothèque regex de Boost C++, qui se rapproche étroitement de PCRE.)
Voici comment cela fonctionne:
/>
, complétant ainsi la balise, ou elle se terminera par un >
, auquel cas il continuera en examinant le contenu de la balise.<
, auquel cas il reviendra au début de l'expression, lui permettant de traiter un commentaire ou une nouvelle balise.<
qu'il ne peut pas analyser. Le fait de ne pas égaler entraînera bien sûr le redémarrage du processus. Sinon, le <
est probablement le début de la balise de fermeture pour cette itération. Utilisation de la référence arrière dans une balise de fermeture <\/\1>
, il correspondra à la balise d'ouverture de l'itération actuelle (profondeur). Il n'y a qu'un seul groupe de capture, donc ce match est simple. Cela le rend indépendant des noms des balises utilisées, bien que vous puissiez modifier le groupe de capture pour capturer uniquement des balises spécifiques, si vous en avez besoin.Cet exemple résout les problèmes liés aux espaces blancs ou à l'identification du contenu pertinent grâce à l'utilisation de groupes de caractères qui annulent simplement <
ou >
, ou dans le cas des commentaires, en utilisant [\S\s]
, qui correspondra à tout, y compris les retours chariot et les nouvelles lignes, même en mode unifilaire, jusqu'à ce qu'il atteigne -->
. Par conséquent, il traite simplement tout comme valide jusqu'à ce qu'il atteigne quelque chose de significatif.
Dans la plupart des cas, une expression régulière comme celle-ci n'est pas particulièrement utile. Il validera que XML est correctement formé, mais c'est tout ce qu'il fera vraiment, et il ne tient pas compte des propriétés (bien que ce soit un ajout facile). C'est aussi simple que cela car il laisse de côté les problèmes du monde réel comme celui-ci, ainsi que les définitions des noms de balises. L'adapter à une utilisation réelle en ferait beaucoup plus une bête. En général, un véritable analyseur XML serait bien supérieur. Celui-ci est probablement le mieux adapté pour enseigner le fonctionnement de la récursivité.
Pour faire court: utilisez un analyseur XML pour un travail réel, et utilisez-le si vous voulez jouer avec des expressions rationnelles.
Ne pas analyser XML/HTML avec regex, utilisez un analyseur XML/HTML approprié et une puissante requête xpath .
Selon la théorie de la compilation, XML/HTML ne peut pas être analysé en utilisant l'expression régulière basée sur machine à états finis . En raison de la construction hiérarchique de XML/HTML, vous devez utiliser un automate déroulant et manipuler LALR grammaire en utilisant un outil comme YACC .
Vous pouvez utiliser l'une des options suivantes:
xmllint souvent installé par défaut avec libxml2
, xpath1 (cochez mon wrapper pour avoir une sortie délimitée par des sauts de ligne
xmlstarlet peut éditer, sélectionner, transformer ... Non installé par défaut, xpath1
xpath installé via le module Perl XML :: XPath, xpath1
xidel xpath3
saxon-lint mon propre projet, envelopper sur Saxon-HE de @Michael Kay Java, xpath3
python 's lxml
(from lxml import etree
)
Perl s XML::LibXML
, XML::XPath
, XML::Twig::XPath
, HTML::TreeBuilder::XPath
Rubynokogiri , vérifiez cet exemple
phpDOMXpath
, vérifiez cet exemple
Vérifiez: en utilisant des expressions régulières avec des balises HTML