8731 sujets

Développement web côté serveur, CMS

Modérateur
Bonjour,
Je sais qu'on trouve énormément de ressources sur le Web à ce sujet. Cependant, après avoir opté pour une solution qui me convenait, je me bute à un problème de mise en forme d'expression régulière. Explications...
Voici le code que j'utilise (en m'inspirant donc de la page vers laquelle pointe mon lien plus haut) :

function pxUrlToLink($commenttext) { 
//motifs à repérer
	$in = array(
		'`((?:https?|ftp)://\\S+)`',
		'`;\)`'
		);
//remplacement des chaînes correspondant aux motifs 
	$out = array(  
		'<a href="$1" title="$1">$1</a>',
		'<img src="xmedia/emoticones/clin_d_oeil.gif" alt="Clin d\'oeil" />'
  ); 
 // Exécution de l'expression régulière et envoi du texte formaté 
 $commenttext = preg_replace($in, $out, $commenttext);  
 return $commenttext;
}

Les url sont bien remplacées par le lien mais, lorsqu'elles sont suivies d'une virgule ou d'un point également (puisque le motif repère les chaînes commençant par http, https et ftp et comportant une chaîne sans espaces) ! J'ai donc essayé d'arranger le motif permettant de reconnaître les url en ajoutant le fait qu'il ne faut pas prendre en compte une virgule, un point (éventuellement, j'ajouterai une parenthèse fermante, un crochet fermant, un point-virgule) qui se trouvent à la fin de l'expression capturée. J'ai tenté ça :
((?:https?|ftp)://\\S+[^,.])

Mais bien évidemment, cela ne fonctionne pas (d'ailleurs je ne sais pas trop pourquoi Smiley rolleyes ) ! J'ai passé une semaine à essayer de trouver la solution, mais rien n'y fait, je sèche lamentablement.
Une idée, une piste ou mieux une solution ?


Merci d'avance Smiley smile
Modifié par jojaba (20 Apr 2010 - 21:50)
Essaie ça :
((?:https?|ftp)://\S+?)(?=[]).,;:!?]?(?:\s|\Z)|\Z)

Quelques explications s'imposent :
- Le motif que tu proposes ne matchera jamais rien, car \S+ ne peut jamais être suivi de [.,]. Le quantificateur + absorbe le maximum de caractères possible, donkc \S+ ne peut être suivi qu'au plus de \s, ou la fin de la chaîne. C'est \S+? qu'il aurait fallu utiliser, car +? arrête la recherche le plus tôt possible.
- On a malgré tout un autre problème. Si tu remplaces \S+ par \S+? dans ton motif, tu vas t'apercevoir que tu vas quand même matcher la virgule ou le point. Si tu le mets dans une parenthèse capturante différente, il ne sera plus dans le lien mais il sera supprimé au remplacement ! C'est un détail mais c'est quand même un peu énervant...
- IL faut donc utiliser ce qu'on appelle le look ahead, c'est un moyen d'examiner ce qui se trouve plus loin dans la chaîne sous pour autant l'inclure dans la sous-chaîne effectivement matchée.
- Ici, on tente donc de repérer une ponctuation suivi d'un espace, une ponctuation terminant la chaîne, ou simplement la fin de la chaîne. On est obligé de repérer l'espace après la ponctuation et non pas simplement la ponctuation, sinon l'URL sera coupée prématurément.

S'il y a des experts en regexp qui sont capables de la simplifier, je suis preneur.... il doit y avoir moyen de faire encore mieux.
Modifié par QuentinC (20 Apr 2010 - 08:59)
Modérateur
Waouh ! Merci Quentin !
Merci pour le motif à tester et surtout pour les explications, c'est vraiment chouette de pouvoir comprendre (même si pour l'instant, tout n'est pas tout à fait clair, mais cela n'est pas dû aux explications, ne t'inquiète pas Smiley cligne ) ce qu'on te propos.
J'ai immédiatement et rapidement testé. Ça fonctionne pour les virgules et les points mais pas pour les points-virgules (ceci dit, normalement, en typographie française, il y a un espace à gauche et à droite du ";"), ce sont les 3 ponctuations que j'ai pû rapidement tester. Je me demandais si tu n'avais pas oublié d'échapper la parenthèse dans l'expression suivante (la deuxième) :
(?=[]).,;:!?]?(?:\s|\Z)|\Z)

Il y a peut-être d'autres choses à échapper...
Par contre, si je ne m'abuse, tu as capturé cette deuxième partie de motif, faut-il la réutiliser dans le remplacement ?
Tu peux m'expliquer dans le fragment de motif ci-dessous ce que veut dire "?=[]" ? J'ai compris que quand on a un "?:" en début de parenthèse cela signifie que le contenu de la parenthèse ne doit pas être capturé.
Je n'ai plus le temps de voir ça maintenant, mais dès que je peux, je me replonge là-dedans Smiley ravi

Merci encore

===modification======
Bon ben je me suis trompé, le point-virgule est bien pris en compte (j'avais en fait placé ";)" sans espace après une url, c'est normal que le ";" n'ait pas été pris en compte puisque pas à la fin de la chaîne. J'ai tout de même échappé la parenthèse... Voici ce qui fonctionne donc pour l'instant :
((?:https?|ftp)://\S+?)(?=[]\).,;:!?]?(?:\s|\Z)|\Z)

Je vais aller à la récolte de codes concernant ce sujet pour les proposer ici, histoire de voir si on peut faire plus simple effectivement Smiley cligne
===fin de modification======
Modifié par jojaba (20 Apr 2010 - 14:02)
Modérateur
Voici comme promis le résultat de ma collecte :
$text = ereg_replace("[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]", "<a href=\"\\0\">\\0</a>", $text);

/*** make sure there is an  http://  on all URLs ***/
$string = preg_replace("/([^\w\/])(www\.[a-z0-9\-]+\.[a-z0-9\-]+)/i", "$1http://$2",$string);
/*** make all URLs links ***/
$string = preg_replace("/([\w]+:\/\/[\w-?&;#~=\.\/\@]+[\w\/])/i","<a target=\"_blank\" href=\"$1\">$1</A>",$string);

$chaine = eregi_replace("([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])",
"<A HREF=\"\\1://\\2\\3\" TARGET=\"_blank\">\\1://\\2\\3</A>",$chaine);

$str = ereg_replace("[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]","<a href=\"\\0\">\\0</a>", $str);

$html = preg_replace('/\\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]/i', "<a href=\"#\" onclick=\"open_url('\\0')\";return false;>\\0</a>", $html);

Le motif suivant est prévu pour les adresses commençant par "www" mais l'explication du bonhomme (mattle) est tellement impressionnante de clarté qu'il fallait que je lui rendes hommage !
Voici ce qu'il propose :
$myRegex = "/(www\.[a-z\d-\.]*\.[a-z]{2,4}?\/?(?:[^\s]*[\/a-z\d]))/ig";
preg_replace($myRegex, "<a href='$1'>$1</a>", $text);  

Son message sur les forums de webhostingtalk : http://www.webhostingtalk.com/showpost.php?p=6504695&postcount=14

Je ne sais pas si ces fragments de codes vont pouvoir faire évoluer le schmilblic, mais j'avais dit que je le ferai, voilà qui est fait Smiley langue !
J'ai finalement modifié le motif comme suis :
(?:https?|ftp)://\S+?)(?=[[:punct:]]?(?:\s|\Z)|\Z)

Ce qui donne donc pour l'instant (peut-être que quelqu'un trouvera mieux Smiley ravi ) :
function pxUrlToLink($text) {
	//Motifs permettant de retrouver les éléments à remplacer
	$in = array(
		'`((?:https?|ftp)://\S+?)(?=[[:punct:]]?(?:\s|\Z)|\Z)`',
		'`;\)`'
		);
	//Les chaînes qui vont remplacer les éléments repérés
	$out = array(  
		'<a href="$1" title="$1">$1</a>',
		'<img src="xmedia/emoticones/clin_d_oeil.gif" alt="Clin d\'oeil" />'
		); 
	// on éxécute l'expression régulière et on retourne le texte formaté 
	$text = preg_replace($in, $out, text);  
	return $text;
}

Je n'ai pas encore tout à fait compris l'utilité de certains des points d'interrogations dans ce motif, une petite explication pour le ?=[...]?
Modifié par jojaba (20 Apr 2010 - 21:49)
Non. Dans une classe de caractères, l'échappement est facultatif s'il n'y a pas d'ambiguïté. Une classe se termine nécessairement par un crochet fermant, donc tout ce qui se trouve entre en fait automatiquement partie. Ainsi, il n'y a pas besoin d'échapper ni le point, ni la parenthèse, ni les points d'exclamation et d'interrogation (ni un plus, ni un astérisque, ni un dollar non plus). Pas besoin d'échapper un tiret s'il est en premier ou en dernier (intervalle). Pas besoin d'échapper un circonflexe s'il n'est pas en premier (négation/complément). Cas spécial, une classe ne peut pas être vide donc pas besoin d'échapper un crochet fermant s'il est en premier. Sus aux backslash inutiles !

En ce qui concerne plus généralement cette seconde partie de motif, ?= délimite justement ce que j'ai expliqué dans mon post précédent : c'est un look ahead, et ça permet d'examiner la suite de la chaîne sans pour autant l'inclure dans le motif effectivement matché au final. Donc non, cette partie n'est pas capturée et il n'y a aucun traitement à faire. Tu pourras remarquer que $2 n'existe pas...

Au cas où le sujet t'intéresse, voici un lien vers la doc des regexp en php. Le look ahead est une assertion complexe, il est décrit dans la rubrique assertions.

Je suis un peu mazo, j'aime bien les regexp alors que tout le monde les a en horreur...
Modifié par QuentinC (21 Apr 2010 - 08:46)
Modérateur
QuentinC a écrit :
Non. Dans une classe de caractères, l'échappement est facultatif s'il n'y a pas d'ambiguïté. Une classe se termine nécessairement par un crochet fermant, donc tout ce qui se trouve entre en fait automatiquement partie. Ainsi, il n'y a pas besoin d'échapper ni le point, ni la parenthèse, ni les points d'exclamation et d'interrogation (ni un plus, ni un astérisque, ni un dollar non plus). Pas besoin d'échapper un tiret s'il est en premier ou en dernier (intervalle). Pas besoin d'échapper un circonflexe s'il n'est pas en premier (négation/complément). Cas spécial, une classe ne peut pas être vide donc pas besoin d'échapper un crochet fermant s'il est en premier. Sus aux backslash inutiles !
Ah ben encore appris quelque chose ! merci ! Je dois avouer que la logique (si on peut parler de logique dans le cas des expressions rationnelles) du mécanisme m'échappe parfois, mais j'essaie de comprendre ce que je peux à partir de ce que je lis de-ci de-là. Smiley cligne
QuentinC a écrit :

En ce qui concerne plus généralement cette seconde partie de motif, ?= délimite justement ce que j'ai expliqué dans mon post précédent : c'est un look ahead, et ça permet d'examiner la suite de la chaîne sans pour autant l'inclure dans le motif effectivement matché au final. Donc non, cette partie n'est pas capturée et il n'y a aucun traitement à faire. Tu pourras remarquer que $2 n'existe pas...
D'accord, compris.
QuentinC a écrit :

Au cas où le sujet t'intéresse, voici un lien vers la doc des regexp en php. Le look ahead est une assertion complexe, il est décrit dans la rubrique assertions.
Merci !
QuentinC a écrit :

Je suis un peu mazo, j'aime bien les regexp alors que tout le monde les a en horreur...

Je comprends qu'on puisse devenir "accro", c'est vraiment passionnant et presque enivrant de trouver LA bonne expression qui pourra réaliser ce qui est recherché. Quelle puissance, quelle simplicité (quand on arrive à les utiliser correctement) !!!
Petite question subsidiaire. J'aurais aimé en plus pouvoir raccourcir les liens affichés sur la page après avoir été "linkified" (traduction proposée : "translié" Smiley rolleyes ) puisque souvent on se retrouve avec de longues chaînes qui risquent de "casser" le formatage de la page. Je sais qu'il existe des scripts php réalisés à cet effet sur le Web, je n'ai pas encore fait de recherche là-dessus pour le moment. Ma question (j'avoue que je ne sais pas trop comment commencer, sans avoir vu un script qui se charge de cette transformation et je ne te demande pas de me donner la solution Smiley cligne ) : est-il possible de faire réaliser ces 2 opérations (transformation des url en lien et raccourcissement éventuel si l'url est trop longue) par une seule expression régulière ? Si ce n'est pas possible, je pense qu'on pourra faire un traitement après le preg_replace (je ne sais pas trop si on peut et surtout comment récupérer les chaînes modifiées après le preg_replace juste avant l'affichage)...
Allez, sur ce nouveau challenge, bonne journée Smiley cligne
a écrit :
) : est-il possible de faire réaliser ces 2 opérations (transformation des url en lien et raccourcissement éventuel si l'url est trop longue) par une seule expression régulière ? Si ce n'est pas possible, je pense qu'on pourra faire un traitement après le preg_replace (je ne sais pas trop si on peut et surtout comment récupérer les chaînes modifiées après le preg_replace juste avant l'affichage)...

IL faut alors utiliser preg_replace_callback. Au lieu de faire un remplacement simple, preg_replace_callback envoie chaque sous-chaîne trouvée à une fonction, et c'est cette fonction qui est chargée de retourner le texte de remplacement.
doc de la fonction preg_replace_callback
Modérateur
QuentinC a écrit :

IL faut alors utiliser preg_replace_callback.
doc de la fonction preg_replace_callback

Vraiment génial le php !
J'ai consulté sur la doc, mais ce n'était pas trop clair. J'ai trouvé une autre ressource qui proposait des exemples me permettant de mieux comprendre : Fonction preg_replace_callback() sur expreg.com. Seulement comme je tourne en php 5 (et non 5.3) j'ai été contraint d'utiliser la fonction create_function() à la place d'une fonction anonyme (comme ils proposent sur regexp.com). J'ai pris un peu de temps à comprendre comment définir cette fonction create-function() mais finalement, je pense avoir réussi. En tout cas, ça fonctionne chez moi comme ça.
Je rappelle donc ce que je voulais faire : avant de remplacer les URL par une URL "cliquable", je voulais les raccourcir pour éviter d'avoir de trop longues chaînes de caractères qui "cassent" le formatage de la page HTML. Dans le code ci-dessous, seules les URL sont traitées, les émoticônes je les traite à part en utilisant la fonction str_replace() amplement suffisante pour remplacer les combinaisons de 2 caractères et surtout évitant ainsi d'utiliser des expressions régulières qui, d'après ce que j'ai lu, sont relativement gourmandes en ressources serveur...
Le code donc :
//Motif permettant de retrouver les URL à remplacer
    	    	$pattern = '`((?:https?|ftp)://\S+?)(?=[[:punct:]]?(?:\s|\Z)|\Z)`'; 
	       	//Fonction permettant de raccourcir les longues URL lors du remplacement dans la fonction preg_replace_callback()
	    	$callback_function = create_function(
	    				'$matches',
	    				'$link_displayed = (strlen($matches[0]) > 25) ? substr( $matches[0], 0, 10).\'[&hellip;]\'.substr($matches[0], -10) : $matches[0];
	    				 return \'<a href="\'.$matches[0].\'" title="\'.$matches[0].\'" class="website">\'.$link_displayed.\'</a>\';'
	    				);
	    	$content = preg_replace_callback($pattern, $callback_function, $content);

Pour raccourcir le texte affiché, je ne me suis inspiré d'aucune ressource Web pour la simple raison que je n'ai même pas trouvé d'exemple pour faire ça. On trouve en revanche pas mal de services Web permettant de raccourcir des url... Smiley ravi Cela m'a permis de me replonger un peu dans le traitement de chaînes en php et c'est très bien ainsi !
Voilà, je pense que la boucle est bouclée. Ceci dit, je suis toujours prêt à entendre d'autres conseils, astuces pour éventuellement améliorer ce script.
Je souhaite terminer en te remerciant chaleureusement Quentin. Tu as été un maître patient, pédagogique et disponible. A charge de revanche (si c'est dans mes moyens Smiley langue ) !
Modifié par jojaba (21 Apr 2010 - 21:19)
Petite remarque, le coup du create_function préconisé dans la doc n'est pas une obligation. Personnellement, je trouve que ça n'apporte rien par rapport à une fonction normale, c'est moche, pas forcément très clair, et ça complique inutilement. Les closures de php 5.3 sont par contre toutes indiquées. (Ceci dit, tout comme toi, je suis pour le moment bloqué à php 5.2.6 (debian oblige))

a écrit :
Je souhaite terminer en te remerciant chaleureusement Quentin. Tu as été un maître patient, pédagogique et disponible. A charge de revanche (si c'est dans
mes moyens

Merci à toi, tu es le bienvenu pour une prochaîne fois si je peux encore aider.
Modérateur
QuentinC a écrit :
Petite remarque, le coup du create_function préconisé dans la doc n'est pas une obligation. Personnellement, je trouve que ça n'apporte rien par rapport à une fonction normale, c'est moche, pas forcément très clair, et ça complique inutilement.

Cela veut-il dire qu'il y aune autre solution ? d'après ce que j'ai compris, je n'ai pas trop le choix, mais si tu pouvais confirmer...
QuentinC a écrit :
(Ceci dit, tout comme toi, je suis pour le moment bloqué à php 5.2.6 (debian oblige))

Moi c'est free qui me bloque Smiley ravi : version 5.1.3RC4-dev. de php
Merci pour ta proposition d'aide future, je n'hésiterai pas ! Smiley langue Smiley smile
a écrit :
Cela veut-il dire qu'il y aune autre solution ? d'après ce que j'ai compris, je n'ai pas trop le choix, mais si tu pouvais confirmer...

Tu peux très bien utiliser une fonction normale. Il suffit de passer son nom sous forme de chaîne. p.ex. :

function minuscules ($match) {
return strtolower($match[0]);
}
function balises_en_minuscules ($html) {
return preg_replace_callback('%</?\w+%', 'minuscules', $html);
}

ON peut aussi utiliser des méthodes, y compris privées. Pour plus d'infos, regarde la description du type callback dans la doc.

La doc préconise create_function parce que la fonction de callback n'est bien souvent utiliser qu'une seule fois, et il est inutile de charger l'espace global avec une fonction qui ne sert qu'à un endroit. Un cas tout indiqué pour utiliser une closure.

Par contre non seulement create_function est moche et pas pratique, mais en plus il est potentiellement dangereux et pas optimisé. Parce qu'en réalité, create_function, c'est rien d'autre qu'approximativement ça :

function create_function ($args, $code) {
$name = 'func'.rand(1000000, 9999999);
eval("function $name ($args) { $code }");
return $name;
}

Et eval, c'est le mal... bon dans ce cas je doute que des entrées utilisateur soient réutilisées, mais c'est quand même pas optimisé.
Modifié par QuentinC (22 Apr 2010 - 17:05)
Modérateur
QuentinC a écrit :

Tu peux très bien utiliser une fonction normale. Il suffit de passer son nom sous forme de chaîne. p.ex. :

function minuscules ($match) {
return strtolower($match[0]);
}
function balises_en_minuscules ($html) {
return preg_replace_callback('%</?\w+%', 'minuscules', $html);
}


Ah ben je croyais que c'était ça une fonction anonyme... Est-ce qu'on peut aussi faire comme ça ? :

function minuscules ($match) {
return strtolower($match[0]);
}
$html = preg_replace_callback('%</?\w+%', 'minuscules', $html);

Je suppose que non. Il faut donc placer preg_replace_callback dans une fonction, c'est ça ?

QuentinC a écrit :

Par contre non seulement create_function est moche et pas pratique, mais en plus il est potentiellement dangereux et pas optimisé.

Je vais donc modifier ça... Smiley cligne
Modifié par jojaba (23 Apr 2010 - 07:20)
Modérateur
Ah ben petit souci.
Voici ce que j'ai fait :

//Pattern to retrieve the url in the comment
    	    	$pattern = '`((?:https?|ftp)://\S+?)(?=[[:punct:]]?(?:\s|\Z)|\Z)`'; 
	       	//Function to wrap long URLs into smaller one before replacement in the preg_replace_callback() function
	    	function wrap_url($match) {
	    		$link_displayed = (strlen($matches[0]) > 25) ? substr( $matches[0], 0, 10).'&hellip;'.substr($matches[0], -10) : $matches[0];
	    		return '<a href="'.$matches[0].'" title="'.$matches[0].'" class="website" style="font-style: normal; font-size: 0.7em">'.$link_displayed.'</a>';
	    	}
	    	//Replacement of the pattern
	    	function replace_pattern($content) {
	    		return preg_replace_callback($pattern, 'wrap_url', $content);
	    	}

Message d'erreur :
a écrit :

Fatal error: Cannot redeclare wrap_url() (previously declared in C:\wamp\www\jojaba2010\manager\tools\utils\functions.php:391) in C:\wamp\www\jojaba2010\manager\tools\utils\functions.php on line 391

Ligne 391 :
function wrap_url($match) {

Modifié par jojaba (23 Apr 2010 - 08:13)
a écrit :

Je suppose que non. Il faut donc placer preg_replace_callback dans une fonction, c'est ça ?

Pas du tout. Ce que tu proposes fonctione. Tant que la chaîne envoyée en 2ème paramètre de preg_replace_callback contient le nom d'une fonction existante, tout va bien.

Pour ta fatal error, c'est sûrement un problème d'inclusions multiples. Utilise require_once / include_once à la place de require / include.
Modérateur
QuentinC a écrit :

Pas du tout. Ce que tu proposes fonctionne. Tant que la chaîne envoyée en 2ème paramètre de preg_replace_callback contient le nom d'une fonction existante, tout va bien.

Je confirme, ça marche également sans utiliser de deuxième fonction.

QuentinC a écrit :

Pour ta fatal error, c'est sûrement un problème d'inclusions multiples. Utilise require_once / include_once à la place de require / include.

Non l'inclusion se fait bien par un require_once par contre, dans mon cas, la fonction de remplacement se trouve dans une autre fonction utilisée plusieurs fois dans la page Web d'où le problème. Il a suffit de ressortir la fonction wrap_url() de la fonction dont je parle pour que ça fonctionne... Smiley cligne
Modifié par jojaba (23 Apr 2010 - 10:24)
Modérateur
Ah ben j'ai oublié de conclure avec le bon code. Le voici :

//Function to wrap long URLs into smaller one before replacement in the preg_replace_callback() function
function ShortUrl($matches) {
	 $link_displayed = (strlen($matches[0]) > 25) ? substr( $matches[0], 0, 10).'…'.substr($matches[0], -10) : $matches[0];
	 return '<a href="'.$matches[0].'" title="Se rendre à « '.$matches[0].' »" class="website">'.$link_displayed.'</a>';
}
function UrlToShortLink ($text) {
	//Pattern to retrieve the url in the comment
    	$pattern = '`((?:https?|ftp)://\S+?)(?=[[:punct:]]?(?:\s|\Z)|\Z)`'; 
	//Replacement of the pattern
	$text = preg_replace_callback($pattern, 'ShortUrl', $text);
	return $text;
}

J'aurais aimé mettre la longueur de l'url à partir de laquelle il faut la tronquer en paramètre mais cela ne fonctionne pas. Voici ce que j'ai tenté (variable $limit) :

//Function to wrap long URLs into smaller one before replacement in the preg_replace_callback() function
function ShortUrl($matches) {
	 $link_displayed = (strlen($matches[0]) > $limit) ? substr( $matches[0], 0, 10).'…'.substr($matches[0], -10) : $matches[0];
	 return '<a href="'.$matches[0].'" title="Se rendre à « '.$matches[0].' »" class="website">'.$link_displayed.'</a>';
}
function UrlToShortLink ($text, $limit= 25) {
	//Pattern to retrieve the url in the comment
    	$pattern = '`((?:https?|ftp)://\S+?)(?=[[:punct:]]?(?:\s|\Z)|\Z)`'; 
	//Replacement of the pattern
	$text = preg_replace_callback($pattern, 'ShortUrl', $text);
	return $text;
}

Je comprends qu'un paramètre défini dans une fonction ne puisse pas être utilisé dans une autre fonction, mais comment faire alors ? rendre la variable globale ?