8768 sujets

Développement web côté serveur, CMS

Bonsoir
Contrairement à JS, il semble que PHP n'interprète pas les expressions régulières "non greedy", c'est à dire que /(<tr.*?>)<td>/ va sélectionner tout ce qui suit jusqu'au premier <td>. S'il y a un <th>...</th> entre <tr> et <td>, il sera inclus dans $1, alors que je ne voudrais prendre en compte que les <tr> directement suivis d'un <td>.
Est-ce exact où est-ce mon grand âge qui me fait délirer?
Modifié par PapyJP (24 Jun 2016 - 22:26)
dew a écrit :
"Normalement" PHP gère le greedy/non greedy, avec les *? Smiley cligne
C'est ce que je croyais mais je suis tombé sur un os.
Ce que j'obtiens avec *? Est la même chose que sans le ?
Il y a aussi une option U que je ne comprends pas bien, mais ça n'a pas l'air de changer grand chose.
Peux tu me donner un exemple qui fonctionne?
Bonsoir,
L'expression s'est bien comportée de manière ungreedy. Avec la chaîne <tr><th>...</th><td>, .*? commence par ne rien sélectionner (on est bien en ungreedy, donc on sélectionne le minimum de caractères possible), ">" sélectionne ">" et ainsi de suite jusqu'au "d" qui ne sélectionne pas "h", échec de l'expression donc backtracking, .*? sélectionne un caractère de plus.
Je ne fais pas le développement complet mais le backtracking va se poursuivre jusqu'à ce que .*? sélectionne "><th>...</th>", et <td> sélectionne <td>, succès de l'expression.

Le problème ici, c'est le sélecteur universel qui en prend trop. Il faut remplacer .*? par [^>]*
Ici, pas besoin d'un quantifier ungreedy, au contraire. La classe négative prendra tous les attributs de la balise <tr>, s'arrêtera bien au ">", et ce du premier coup sans backtracking.
@seventears
Oui, c'est bien comme ça que j'ai traité le problème, reste donc à comprendre pourquoi le *? ne fonctionne pas sous PHP. Je n'ai trouvé aucune doc sur le sujet. Il y a plein de personnes qui apparemment ont le même problème, et les réponses sont toujours "comment le contourner"...
Bonjour !

PapyJP a écrit :

reste donc à comprendre pourquoi le *? ne fonctionne pas sous PHP.


Il a l'air de marcher... quand cela ne concerne pas des balises HTML.

Smiley confus
Bonjour,

Il me semble que les regex est un sujet souvent remanié par Php. N'y-a-t-il pas une différence d'interprétation liée à une différence de version du langage ?

Je ne sais pas trop dans les autres langages mais je trouve casse-bonbons les regex en php.

Comme dirait le schtroumpf Grognon moi j'aime pas les regex, bon courage Papy !
Modifié par Greg_Lumiere (25 Jun 2016 - 09:38)
Greg_Lumiere a écrit :
Bonjour,

Il me semble que les regex est un sujet souvent remanié par Php. N'y-a-t-il pas une différence d'interprétation liée à une différence de version du langage ?

Je ne sais pas trop dans les autres langages mais je trouve casse-bonbons les regex en php.

Comme dirait le schtroumpf Grognon moi j'aime pas les regex, bon courage Papy !

Merci de tes encouragements Smiley cligne
Moi non plus je ne trouve pas ce type de langage particulièrement folichon.
Dans les années 1960, IBM avait inventé un langage "compact" appelé APL (A Programming Language, en toute modestie!!!) et il y avait eu une série de conférences à laquelle j'ai assisté, sur l'utilisation de ce langage dans différents domaines d'activité.
C’était ce genre de langage dans lesquels chaque caractère a une signification qui dépend de sa position par rapport aux autres, un machin impossible à relire et maintenir, pire que le JS dit "minimisé" dont le but prétendu est de faire gagner des performances, mais le but réel est de décourager la rétro ingénierie.
Moyennant quoi, je récupère du vieux code, et si je veux le mettre au goût du jour, j'ai besoin en premier lieu que ce code soit interprétable par loadHTML(). Il faut donc que je fasse des modifications sur le code brut plein de bugs avant même de pouvoir travailler sur la structure du DOMDocument.
Je ne sais pas si mes outils me prendront moins de temps à développer que de faire tout le travail à la main, mais comme il y a plusieurs centaines de pages à prendre en compte, ce doit tout de même être rentable, en tout cas c'est moins barbant que faire manuellement des opérations répétitives...

Edit: A propos de la version: c'est PHP 7, normalement ce genre de problème devrait être absent de cette nouvelle version.
Modifié par PapyJP (25 Jun 2016 - 10:43)
PapyJP a écrit :

Merci de tes encouragements Smiley cligne
Moi non plus je ne trouve pas ce type de langage particulièrement folichon.
Dans les années 1960, IBM avait inventé un langage "compact" appelé APL (A Programming Language, en toute modestie!!!) et il y avait eu une série de conférences à laquelle j'ai assisté, sur l'utilisation de ce langage dans différents domaines d'activité.
C’était ce genre de langage dans lesquels chaque caractère a une signification qui dépend de sa position par rapport aux autres, un machin impossible à relire et maintenir, pire que le JS dit "minimisé" dont le but prétendu est de faire gagner des performances, mais le but réel est de décourager la rétro ingénierie.
Moyennant quoi, je récupère du vieux code, et si je veux le mettre au goût du jour, j'ai besoin en premier lieu que ce code soit interprétable par loadHTML(). Il faut donc que je fasse des modifications sur le code brut plein de bugs avant même de pouvoir travailler sur la structure du DOMDocument.
Je ne sais pas si mes outils me prendront moins de temps à développer que de faire tout le travail à la main, mais comme il y a plusieurs centaines de pages à prendre en compte, ce doit tout de même être rentable, en tout cas c'est moins barbant que faire manuellement des opérations répétitives...

Edit: A propos de la version: c'est PHP 7, normalement ce genre de problème devrait être absent de cette nouvelle version.

Boudiou... APL j'en ai fait quelques années (développement / maintenance d'un logiciel de back office bancaire). Le genre de langage tout à fait ésotérique où tu dois d'abord apprendre le grec ancien avant de produire une ligne de code (le nom de chaque instruction de base étant formé d'un seul caractère / symbole).
Totalement imbuvable, mais nous avions repris un existant, il fallait donc faire avec.
La secte des APListes existe toujours et je connais bien l'un de ses grands gourous français avec qui j'ai eu le plaisir de travailler durant ces années. L'une des périodes les plus sympa de ma vie professionnelle sur un plan humain... mais pas la plus excitante sur le plan langage. APL reste ce qu'il est... Un vieux souvenir.
Pour en revenir aux expressions régulières, cela reste un monde à part plutôt difficile à appréhender dès lors qu'on commence à les pousser un peu plus loin.
Les principes de base restent plus ou moins communs à tous les langages de programmation, mais comme souvent ce sont les détails qui tuent, variations entre langages ou versions d'un même langage.
Si ton stock existant représente une volumétrie importante, et il semble que ce soit le cas, investir dans un développement spécifique peut s'avérer une bonne option. Tant qu'à galérer, autant pouvoir en retirer quelques routines réutilisables par la suite.
Si tu effectues ce travail de reprise de façon non automatique, à la mano, le temps passé risque d'être tout aussi voire plus important sans que tu puisses capitaliser dessus et espérer une quelconque réutilisabilité.
Bon courage en tout cas.
Modifié par sepecat (25 Jun 2016 - 13:17)
sepecat a écrit :

Si tu effectues ce travail de reprise de façon non automatique, à la mano, le temps passé risque d'être tout aussi voire plus important sans que tu puisses capitaliser dessus et espérer une quelconque réutilisabilité.
Bon courage en tout cas.

Oui, c'est ce que je pense, et de toute façon c'est plus fun de réfléchir à comment b... ce f.. langage et faire comprendre à ce f... programme quelle est la structure de ces f... pages, plutôt que de jouer à l’automate pendant des heures, des jours, des semaines, des mois... en me gourant évidemment.
Merci de tes encouragements!
Compte tenu de mon âge, j'espère avoir fini avant de ne plus être de ce monde.
Zelena a écrit :

Il a l'air de marcher... quand cela ne concerne pas des balises HTML. Smiley confus


Bon j'ai dit une bêtise : greedy, lazy finalement ça marche même avec les balises HTML...

Smiley smile
Modifié par Zelena (25 Jun 2016 - 17:52)
Oui, décidément les regex, c'est compliqué (surtout quand la documentation est en anglais...)

Le problème de PapyJP, ce n'était pas que le non-greedy ne s'activait pas, c'était autre chose... que je n'ai toujours pas bien compris et qui a avoir avec ce qu'a dit Seven tears.

J'ai finalement trouvé une formulaire qui fonctionne mais que PapyJP n'adoptera sans doute pas tellement elle est peu évidente :
(?><tr.*?>)(?=<td)


La première parenthèse (?>....) est un "groupe atomique" évalué comme un bloc.
La seconde parenthèse (?=....) est un "lookahead", qui ne capture rien et qui est juste là pour la position.

Smiley smile
Modifié par Zelena (25 Jun 2016 - 18:54)
Zelena a écrit :
Oui, décidément les regex, c'est compliqué (surtout quand la documentation est en anglais...)
si au moins c'était de l'anglais !!!
C'est du jargon technique que seuls peuvent comprendre ceux qui n'ont pas besoin de le lire parce qu'ils savent déjà ce que ça veut dire
Zelena a écrit :

(?><tr.*?>)(?=<td)


La première parenthèse (?>....) est un "groupe atomique" évalué comme un bloc.
La seconde parenthèse (?=....) est un "lookahead", qui ne capture rien et qui est juste là pour la position.

Smiley smile

Whahhh! Je ne l'adopterai sans doute pas, mais ça vaut le coup de regarder ça de près. ?= c'est du nouveau pour moi!
Bonjour !

Alors, je n'ai pas bien compris le truc mais effectivement ça a l'air de ce que Seven tears a voulu expliquer...

@PapyJP
Le phénomène qui vous a embêté s'appelle, sur Internet, le "catastrophic backtracking"...
(Edit : pour être plus exact, il semblerait que le terme "catastrophic" s'applique lorsque le nombre de fois où le backtracking est réalisé devient exponentiel... ce qui est catastrophique.)

Je n'ai pas trouvé d'explications en français...

Peut-être ce serait intéressant pour vous de regarder la classe DOMDocument... Ce n'est pas forcément plus simple mais l'outil me semble plus adapté...

Smiley smile
Modifié par Zelena (26 Jun 2016 - 12:09)
Il n'y a à ma connaissance pas vraiment de traduction pour le terme backtracking. En très résumé, c'est une phase de l'algorithme dans laquelle on revient en arrière sur ce qu'on avait décidé parce qu'on s'aperçoit que ça ne nous permet pas d'aboutir à une solution correcte, et donc on essaie une autre possibilité.

Si vous avez du mal avec les regex, je peux vous conseiller une vieille ressource mais qui est toujours globalement d'actualité: http://expreg.com/
Perso j'adore les regex !

Par contre, effectivement, pour traiter du HTML, mieux vaudrait utiliser un parseur XML du type API DOM plutôt que les regex. Le HTML et plus généralement le XML étant un langage contextuel et non régulier, les regex ne sont pas vraiment adaptées, et donc c'est rapidement très délicat.
Modifié par QuentinC (26 Jun 2016 - 09:15)
Merci pour vos réponses.
J'ai dû mal m'exprimer:
Bien entendu j'utilise la classe DOMDocument, et à haute dose!
J'ai essayé d’utiliser la fonction loadHTMLFile() mais le HTML est tellement pourri que la fonction ne fonctionne pas, ou plus exactement saute des portions entières du fichier qu'elle ne comprend pas.
Notez que les navigateurs sont moins regardants et acceptent ce code pourri
J'ai donc travaillé de la façon suivante:
1) lire le fichier par $htmlText = file_get_contents($filePath);
2) "nettoyer" la variable $htmlText par une série de preg_replace()
3) charger le DOMDocument par la fonction loadHTML($htmlText)

C'est la fonction de nettoyage qui m'a conduit à vous poser ces questions.

En ce qui concerne le backtracking: je crois comprendre que la façon dont c'est implémenté doit consister dans un premier temps à calculer d'abord comme s'il n'y avait pas de ? et ensuite à faire un correctif.
Si c'est bien comme ça que ça fonctionne, on voit bien que ça fonctionne difficilement dans mon contexte, dans lequel on applique la regex sur une très longue chaîne de caractères contenant des foultitudes de ">", et que le programme laisse tomber, ce qui n'est pas en soi une mauvaise option de programmation.

En conclusion la réponse à ma question est :
Si, PHP accepte bien les expressions rationnelles ungreedy, mais "jusqu'à un certain point".

Bien entend, j'avais utilisé [\>]* avant de poser la question, que je n'ai posée que pour y voir plus clair. Je n'aime pas trop les choses qui marchent "parfois" sans qu'on comprenne le pourquoi, le comment, le quand, etc.

Je vous remercie tous sur pour votre contribution à cette discussion.
Modifié par PapyJP (27 Jun 2016 - 10:26)
Bonjour,
Vous avez encore l'air de croire que les lazy quantifiers (ou ungreedy, ou reluctant) ne fonctionnent pas avec PHP... si c'est le cas, c'est que la lib PCRE sur laquelle le module PHP se base a changé. M'étonnerait fort.
En plus, je vous ai fait la démo que ça fonctionnait, en vous expliquant pourquoi ça sélectionnait également les <th>. "Lazy", ça ne veut pas dire que le moteur s'arrête à la première balise (le moteur ne sait même pas qu'il traite du HTML, hein), ça veut dire que le quantifier commence par sélectionner le moins de caractères possible, et si nécessaire (ie en cas d'échec de l'expression) le phénomène de backtracking (machine arrière) le forcera à essayer avec 1 caractère de plus, et ainsi de suite jusqu'au succès de l'expression, ou jusqu'à ce qu'il n'y ait plus rien à sélectionner (entrainant l'échec de l'expression).
Si le quantifier est greedy, c'est l'inverse : le moteur sélectionne le maximum de caractères possible, et en cas d'échec le backtracking le force à abandonner 1 caractère, puis 2, etc.

Si je fais le développement avec la chaîne <tr><th><td> (volontairement raccourci pour l'exemple) et l'expression (<tr.*?>)<td>

1ère étape :
<tr sélectionne <tr
.*? ne sélectionne aucun caractère comme l'y autorise son quantifier * qui est lazy, et le moteur enregistre la position pour un éventuel backtracking
><t sélectionne ><t
d ne sélectionne pas h, échec de l'expression donc backtracking

2è étape (à la position du backtracking, on ne se soucie pas du <tr déjà sélectionné)
.*? sélectionne >
> ne sélectionne pas < , échec donc backtracking

3è étape : .*? sélectionne ><, > ne sélectionne pas t, échec
4è étape : .*? sélectionne ><t, > ne sélectionne pas h, échec
5è étape : .*? sélectionne ><th, > sélectionne >, <td> sélectionne <td>, SUCCES !

On est pourtant en lazy : et alors ? le backtracking continue tant qu'il y a des possibilités. S'il y avait du contenu dans le th, et que l'élément soit bien formé, vous auriez encore plein d'échecs après l'étape 4, jusqu'à arriver au chevron fermant du </th>, ce chevron > validant le > qui suit le .*?
En fait, cette expression recherche un <tr suivi de près ou de loin pas un <td> précédé d'un chevron fermant (caractère "supérieur"). Absolument pas ce que PapyJP recherche.

En greedy, même combat sauf que .* sélectionne ><th><td>, et > échoue car on est en fin de chaîne. .* sélectionne alors ><th><td, > sélectionne > et < échoue car fin de chaîne. Je poursuis ?
Et encore, je vous fais l'exemple avec le <td> en fin de chaîne, rendez-vous compte dans un document HTML, le backtracking se poursuivra jusqu'au dernier <td> du document.

PapyJP a écrit :

En conclusion la réponse à ma question est :
Si, PHP accepte bien les expressions rationnelles ungreedy, mais "jusqu'à un certain point".

Non non, il les accepte tout le temps, il faut juste savoir ce qu'elles font exactement Smiley cligne
@Zelena

Concernant les groupes atomiques : c'est en effet une bonne idée ici, afin d'éviter un backtracking inutile (c'est le principe des groupes atomiques : ils ne conservent pas les positions de backtracking). Avec la classe négative, on n'aura pas de backtracking seulement si le code HTML est bien formé.
Par contre, un groupe atomique ne fait pas de capture. Pour cela, il faut faire un groupe de capture atomique (ouais, je suis comme QuentinC, j'adore les regexp Smiley ravi ) : (?>(<tr.*?>))

Pour le lookahead, ça évite de sélectionner le <td>. Pas convaincu, le fait de le sélectionner ne change pas grand chose, on pourra toujours le remettre dans le process de remplacement. Reste la question des performances, là j'avoue que je n'en sais rien.

Et en effet, le catastrophic backtracking n'est pas en cause ici. Un exemple pris sur http://www.regular-expressions.info (plus précisément http://www.regular-expressions.info/catastrophic.html ) :
(x+x+)+y appliqué à la chaîne "xxxxxxxxxx"
Pour un humain, l'échec est évident à cause du "y", parce que nous avons une vue globale et que nous avons déchiffré le pattern (au moins un "x", puis au moins un autre "x", tout ça au moins une fois, puis un "y") et que l'absence du "y" saute aux yeux. Pour s'en rendre compte, le moteur a besoin de 2558 étapes pour une chaîne de 10 x, 5118 pour 11 x, 10238 pour 12 x, et s'envoie en l'air à 21 x et plus de 2,8 millions d'étapes (avec le logiciel RegexBuddy).
Bonsoir !
Seven tears a écrit :
@Zelena

Par contre, un groupe atomique ne fait pas de capture.


En effet, sauf si on considère l'ensemble du motif, en ce cas, on le récupère avec $0. (D'où l'intérêt du lookahead...)

En plus du site que vous citez qui est très bien, j'ai trouvé aussi un site intéressant et en particulier cette page qui explique la différence "greedy - non greedy" (hélas en anglais aussi) :
http://www.rexegg.com/regex-quantifiers.html

La difficulté est de se mettre à la place du "moteur regex"... ce qui n'est pas toujours très intuitif. Tester ses motifs avec un programme est sans doute une nécessité... Smiley ohwell (Si j'ai bien compris, l'intérêt d'utiliser RegexBuddy, c'est qu'il permet de prévoir le 'catastrophic backtracking'... Quand on sait que les regex sont aussi utilisées pour l'URL Rewriting... Smiley rolleyes )

Smiley smile
Modifié par Zelena (27 Jun 2016 - 19:38)