Regexp compliquée.... transformation d'url qui ne sont pas dans un <a>

ViPHP
ViPHP | 3607 Messages

08 juin 2009, 09:43

Bonjour à tous,
C'est rare que je demande de l'aide pour des regexp, mais là, après moultes essais, je n'y arrives plus...
Tout qu'est-ce que je souhaite?
Transformer les urls (avec ou sans protocole, d'où le callback), en lien cliquable Si et Seulement Si (SSI pour les intimes) le lien n'est pas déjà créer (donc hors balises <a...></a>).
Le code ci-dessous fait tout sauf le coup d'éviter les liens déjà transformés... (y a peut-être d'autres coquilles mais je pense pouvoir les régler, ça me parait moins difficile)
Voilà je sais pas si c'est faisable en une seule regexp ou s'il faut imbriquer les callback...
Tout vos avis sont les bienvenus!
<?php

$txt='bla bla  www.machin.com

bla http://machin.com

machin.com

http://www.machin.com

<a href="http://www.truc.com">http://www.truc.com</a>

<a href="">truc.2com</a>';

$replace=preg_replace_callback(

"'
  (
    ((http|ftp|https)://)?            #différents protocoles
    (
      ([a-zA-Z0-9-]+\.)?              #Sous-domaines
      [a-zA-Z0-9-]+                   #nom de domaine
      \.[a-z]{2,4}                    #tld
      (/[a-zA-Z0-9_.-]+)*             #dossiers
      ([a-zA-Z0-9_.-]\.[a-z]{2,5})?   #fichier + extension
    )
  )
'",

create_function(

'$matches',

'$deb=strpos($matches[0],"//")!==false ? "": "http://";

return "<a href=\"".$deb.$matches[0]."\" title=\"".$matches[0]."\">".$matches[0]."</a>$matches[0]";'

),

$txt

);

Merci d'avance!!!
Modifié en dernier par jojolapine le 08 juin 2009, 17:28, modifié 1 fois.

Eléphant du PHP | 94 Messages

08 juin 2009, 17:15

Comme je suis une trompette en regex, j'ai essayé d'y arriver pour m'entraîner un peu.
J'arrive à trouver les urls, mais pas encore à éviter les liens déjà balisés.

Je poste le code :
<?php
#	Fonction de callback
function traitement($matches)
{
	$url = "";
	foreach($matches as $i=>$j)
	{
		if ($i!==0 && $i!==1)
		{
			$url .= $matches[$i];
		}
	}
	$deb = "";	
	if ($matches[1]=="")
	{
		$deb = "http://";
	}
	else
	{
		$deb = $matches[1];
	}
	return ' <a href="'.$deb.$url.'" title="'.$deb.$url.'">'.$deb.$url.'</a> ';
}

#	Texte à traiter
$txt = 'bla bla  www.machin.com
bla http://machin.com
machin.com
machin.fr/yott/file.rar
http://www.machin.com
<a href="http://www.truc.com">http://www.truc.com</a>
<a href="">truc.2com</a>';

#	Traitement du texte selon des expressions régulières
$replace = preg_replace_callback
("/(http:\/\/|ftp:\/\/|https:\/\/)?(www\.)?([a-zA-Z0-9-.]*)(\.[a-z]{2,4})(\/[a-zA-Z0-9-._]*\/)?([a-zA-Z0-9]*\.[a-z]{2,5})?/", "traitement", $txt
);


#	Affichage du retour
echo "<br/><br/>".$txt."<br/><br/>";
echo $replace;

?>
Peut-être qu'en cherchant les espaces et saut à la ligne on peut éviter les '<a href="">...</a> '.
J'ai essayé de rajouter un ' [\S\n] ' au début et à la fin mais sans succès.

EDIT : j'avais pa vu que le tien arrivait déjà au même résultat, donc désolé je ne sers à rien^^

ViPHP
ViPHP | 3607 Messages

08 juin 2009, 17:24

Merci pour l'effort fournit ;)
Et ça ne sert jamais à rien de se creuser la tête :)

Pour infos j'ai essayer au début de rajouter un

Code : Tout sélectionner

[^<a href="]
pour éviter de transformer les url à l'intèrieur des balises, mais déjà ça ne fonctionne pas, et quand bien même ça ne règle pas le pb de l'url qui reste au mileu des balises <a></a>...

Le coup des espaces, j'ai déjà testé, mais je ne veux pas me restreindre à ajouter des espaces de chaque côté des urls "non-transformées"...
A suivre!

Eléphant du PHP | 94 Messages

08 juin 2009, 17:25

En fait, si :D .
J'ai confondu [\S] qui capte tout caractère "non-whitespace",
avec [ \t\r\n\v\f], qui fait l'inverse.

Donc, avec en remplaçant par cette Regexp, normalement ça devrait marcher.

Code : Tout sélectionner

/(http:\/\/|ftp:\/\/|https:\/\/)?(www\.)?([a-zA-Z0-9-.]*)(\.[a-z]{2,4})(\/[a-zA-Z0-9-._]*\/)?([a-zA-Z0-9]*\.[a-z]{2,5})?[ \t\r\n\v\f]/

Eléphant du PHP | 94 Messages

08 juin 2009, 17:34

Effectivement, si tu ne veux pas te servir des espaces, ma technique n'aide pas.

Tu veux dire que ta regexp doit pouvoir transformer :
"www.machin.com-Le site à machin" ?

Donc faut plutôt essayer d'exclure les apostrophes du href="" et les >, < ?

ViPHP
ViPHP | 3607 Messages

09 juin 2009, 11:24

Tient une idée me vient, est-ce qu'il est possible de dire

Code : Tout sélectionner

"Début de chaine" OU "1 caractère blanc" PUIS "url" PUIS "caractère blanc"
ça donnerai à peu près ça:

Code : Tout sélectionner

#($|\s)(url)(\s)#
Mais ça ne fonctionne pas... une idée?

Eléphant du PHP | 94 Messages

09 juin 2009, 14:13

C'est pas plutôt ^ pour le début et $ pour la fin ? :)

Code : Tout sélectionner

(^|[\s]) (url) ($|[\s])
Mais c'est vrai que ça prendrait pas les urls comme :
"www.machin.com-Le site de machin" ou
"www.truc.com-Le site de truc".

J'ai esssayé d'exlure les apostrophes etc comme ça, mais j'ai du mal :roll: :

Code : Tout sélectionner

[^">] (url) [^"<]

ViPHP
ViPHP | 3607 Messages

09 juin 2009, 14:19

Ben en fait je me dit que l'espace de fin est pas très génant..
Disons que rajotuer un espace à l'enregistrement des posts utilisateurs c'est pas trop grave...
En ajouter un au début ça me gène un peu plus...
et puis louper des urls comme ça:

Code : Tout sélectionner

www.machin.com-Le site de machin
c'est pas trop grave, y ont qu'a mettre des espaces :p
Je vais refaire qq tests je reviens...


Edit: me revoilà avec une version qui me semble pas trop mal....
Sans plus attendre le code:
<?php

$txt='<p>www.test.com</p>
<p>&nbsp;</p>
<p>http://www.testtructeetzejzlojrzelkrzelkrjzeklrjzerlkzejrlkzejrzlkrjzlkjrzelkrjzelkrjzerlkjzerlkzjrzeprzelkr-rzerzlkjrzlkerjzelrjze-zerlkzerlkzj.com j hkijb</p>';

$replace=preg_replace_callback(

"'(^|\s|<[^a]>)(((http|ftp|https)://)?(([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-]+\.[a-z]{2,4}(/[a-zA-Z0-9_-]+)*([a-zA-Z0-9_.-]\.[a-z]{2,5})?))($|\s|</[^a]>)'",

'urls',

$txt

);

function urls($matches){
  
  $deb=strpos($matches[2],"//")!==false ? "": "http://";
  
  $value=strlen($matches[2]) > 30 ? substr($matches[2],0,15).'[...]'.substr($matches[2],-15) : $matches[2];
  

  return $matches[1]."<a href=\"".$deb.$matches[2]."\" title=\"".$matches[2]."\">".$value."</a>".$matches[9];
}


print_r($txt);
echo '<hr />';
print_r($replace);
(y a en plus le tronquage d'urls)
Donc là je prend les urls qui soit sont tout au début ou tout à la fin, soit entourées d'espaces, soit avec des balsies autres que <a> (genre <p>, <i>, etc...)
Et ça me parait fonctionner :)

ViPHP
ViPHP | 4039 Messages

09 juin 2009, 15:28

Beaucoup d'idées, mais malheureusement rien de bien efficace.. :wink:

La solution en Regex pur serait très complexe, puisqu'il faudrait tenir compte des balises autres que a, de tous les caractères possibles dans une URL, le placement dans le texte et le conflit avec la ponctuation...

Enfin soit, la solution que je propose repose sur les parenthèses capturantes (voir doc preg_match_all() ). Il capture tous les liens sur la page, et après coup il suffit de filtrer sur la présence ou non des deux parentèses capturantes extérieures pour voir ce qui est bon.

Code : Tout sélectionner

(<a[^>]+>)?(http://[\w\.-/\?=+&%_]+)(</a>)?
Et on peut facilement l'intégrer dans un preg_replace_callback()
Mais qu'importe. (je suis ici - dernier petit projet)
Berze going social.

ViPHP
ViPHP | 3607 Messages

09 juin 2009, 16:11

Bon alors j'ai essayé cette façon de voir (en changeant pas mal de choses, parceque je souhaite pouvoir rendre le protocole facultatif... je sais c'est un peu pénible... donc si on rend le protocole facultatif, la partie url de ta regex est trop simple...)
Donc ça donne ceci:
<?php

$txt='<p>www.test.com</p>
<p>&nbsp;</p>
<p>http://www.testtructeetzejzlojrzelkrzelkrjzeklrjzerlkzejrlkzejrzlkrjzlkjrzelkrjzelkrjzerlkjzerlkzjrzeprzelkr-rzerzlkjrzlkerjzelrjze-zerlkzerlkzj.com j hkijb

<a href="http://blabla.com">http://blabla.com</a></p>

<a href="http://bli.fr">bli.fr</a>

blabla.com';

$replace=preg_replace_callback(

"#(<a[^>]+>)?(((http|ftp|https)://)?(([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-]+\.[a-z]{2,4}(/[a-zA-Z0-9_-]+)*([a-zA-Z0-9_.-]\.[a-z]{2,5})?)+)(</a>)?#",

'urls',

$txt

);

function urls($matches){
  

  // Si on est déjà dans un <a>
  if(
    (isset($matches[1]) && strpos($matches[1],'<a')!==false) || 
    (isset($matches[9]) && strpos($matches[9],'a>')!==false)
    )
  {
    return $matches[0];
  }
  // Sinon on traite
  else {
  
    $deb=strpos($matches[2],"//")!==false ? "": "http://";
    
    $value=strlen($matches[2]) > 30 ? substr($matches[2],0,15).'[...]'.substr($matches[2],-15) : $matches[2];
    

    return "<a href=\"".$deb.$matches[2]."\" title=\"".$matches[2]."\">".$value."</a>";
  }
}


print_r($txt);
echo '<hr />';
print_r($replace);
Seulement je ne suis pas à l'abri de ce genre de choses:
Je suis une phrase avec une fin.un point et pas d'espaces... et paf je vais me retrouver dans un <a> sans le vouloir...
C'est assez pénible :(


EDIT: Une petite question...; je n'arrive pas à transformer le

Code : Tout sélectionner

[^>]
en

Code : Tout sélectionner

[^>]
ça ne fonctionne pas...
Modifié en dernier par jojolapine le 09 juin 2009, 17:09, modifié 1 fois.

ViPHP
ViPHP | 4039 Messages

09 juin 2009, 17:08

Mert, j'ai oublié de préciser que la partie url n'était qu'un jet sans réelle validité ? Ben voilà, pourtant, je me suis répété 15 fois qu'il fallait que je le dise dans le post, et patraf, j'ai oublié.
Oui bien sur, tu fais bien de le noter, ma partie URL était juste la pour ne pas donner un regex vide, mais je ne pourrais le conseiller. C'était surtout l'avant et l'après qui étaient importants.

Pour ce qui est du point et de la gestion de fin d'url, pourquoi ne pas utiliser une liste avec des caractères autorisés en fin d'url ?

Ici appliqué à mon Regex, pour que ce soit clair:

(<a[^>]+>)?(http://[\w\.-/\?=+&%_]+[\w/])(</a>)?

Donc j'autorise ici, en fin d'url, toutes les lettres et le slash.
Et la je rebondis sur une erreur que tu as faite:

[^<a href="]

Ne veut pas du tout dire ce que tu penses. En fait, ce petit bout de regex valide tout caractère qui n'est ni un <, ni un a, ni un h, ni un r (etc..). Entre crochets [], les caractères sont considérés un à un, jamais en groupe. Dés que tu as quelque chose entre [], tu es dans un mode spécial ou les symboles ont une autre signification.

Donc il suffit d'ajouter en fin de ton regex qui valide les URL une simple liste de caractères que tu acceptes en fin d'url.

EDITH:

Ben non, j'ai mal lu ta question. Que je suis bête, j'imaginais la problématique d'une URL dans une phrase avec un point derrière, ce qui est aussi un problème. Pour ton souci, oui, c'est vrai, et c'est inévitable. Alors tu devrait travailler avec des filtres, et tester l'existence des domaines que tu trouves.
Mais qu'importe. (je suis ici - dernier petit projet)
Berze going social.

ViPHP
ViPHP | 3607 Messages

09 juin 2009, 17:16

Bon finallement le coup des urls sans espaces, on oubli...
Je commence à avoir quelque chose de pas mal avec ta solution de tester si on a un <a> ou pas, mais il arrive que j'ai des coup de htmlspecialchars() par ci par là, et je doit faire avec...
Donc si tu n'as pas vu mon dernier edit... ;) je le repointe du doigt...

Eléphant du PHP | 185 Messages

09 juin 2009, 17:57

A mon tour de proposer :
<?php

// remplacement hors tag html moddé
function html_replace($txt, $func)
{
    $pos = strpos($txt, '<');
    if (FALSE !== $pos) {
        $txt1 = substr($txt, 0, $pos);
        $txt2 = substr($txt, $pos);
        $txt1 = $func($txt1, $txt2);
    }
    else {
        $txt1 = '';
        $txt2 = $txt;
    }
    $rplc = $func.'("\\';
    $txt2 = preg_replace('`<([^>]*)>([^<]*)(.*)`ie', '"<\1>".'.$rplc.'2", "\\3")', $txt2);
    return $txt1.$txt2;
}

// http://www.expreg.com/lire-URL-source
// Moddé pour l'occasion (paramètre $suite)
function clicklien($url, $suite){
    if (substr($suite,0,4) == '</a>') {
        return $url.'</a>';
    }
    $in=array( 
    '`((?:https?|ftp)://\S+[[:alnum:]]/?)`si', 
    '`((?<!//)(www\.\S+[[:alnum:]]/?))`si'
    ); 
    $out=array( 
    '<a href="$1">$1</a>', 
    '<a href="http://$1">$1</a>'
    ); 
    return preg_replace($in,$out,$url); 
}

$txt='bla bla  www.machin.com 

bla http://machin.com 

machin.com 

http://www.machin.com 

<a href="http://www.truc.com">http://www.truc.com</a> 

<a href="">truc.2com</a>';

echo nl2br(htmlspecialchars(html_replace($txt, 'clicklien')));

ViPHP
ViPHP | 4039 Messages

09 juin 2009, 19:15

EDIT: Une petite question...; je n'arrive pas à transformer le

Code : Tout sélectionner

[^>]
en

Code : Tout sélectionner

[^>]
ça ne fonctionne pas...
Puisque tu fais de gros efforts et que je trouve ça bien, je vais te dire pourquoi :wink:

[^>] en français correspond à "Je valide tout caractère qui n'est pas un >"

[^>] correspond donc à "Je valide tout caractère qui n'est pas un & et qui n'est pas un g et qui n'est pas un t et qui n'est pas un ;"

Une possibilité serait d'utiliser un lookahead, mais j'ai la flemme et ça apporte d'autres soucis.

L'autre, un peu problématique aussi, est de transformer [^>] en [^>;], mais du coup si dans le texte un url apparait derrière un point virgule, c'est foutu (ce qui n'arrive pas très fréquemment non plus).

Et la dernière est de remplacer toutes les occurrences de > et < par les symboles correspondants. C'est plus propre, et il n'y aura plus d'erreurs.
Mais qu'importe. (je suis ici - dernier petit projet)
Berze going social.

ViPHP
ViPHP | 3607 Messages

10 juin 2009, 09:21

Pour répondre à savageman ça fonctionne pas trop mal, sauf si on a du texte comme ça:

Code : Tout sélectionner

<p><a href="http://www.lien1.com">lien1.com</a></p> bla <p><a href="http://www.lien2.com">lien2.com</a></p> bla <p>http://lien3.com</p>
(généré par fckeditor)
Et vu que je 'nai pas tout saisi le coup de la première fonction... :s
en gros voilà un texte:

Code : Tout sélectionner

<p><a href="http://www.lien1.com">lien1.com</a> et du bla bla bla</p> <p><a href="http://www.lien2.com">lien2.com déjà htmlisé</a> et du texte</p> <p>http://lien3.com lien à changer</p>
Dans lequel seul le lien3 devrait être modifié...