8792 sujets

Développement web côté serveur, CMS

Bonjour, j'aimerais faire un script qui récupère une liste de mots sur ma base SQL (en l'occurence, des noms+noms de familles); et, qui, si elle les trouve dans le texte, les remplace par un lien.
Pratiquement, on aurait donc
-ID- -NOM+PRENOM
1 Frank Fruk
2 Jack Jouk
3 Robert Berusse

Frank Fruk est l'ami de Jack Jouk, mais tous les deux ils détestent Robert Berusse car Robert a toujours des croutes dans les yeux.

Donnerait donc :

<a href="monlien.php?id=2">Jack Jouk</a> est l'ami de <a href="monlien.php?id=1">Frank Fruk</a>, mais tous les deux ils détestent <a href="monlien.php?id=3">Robert Berusse</a> car Robert a toujours des croutes dans les yeux.

J'ai trouvé des portions de code intéressantes pour remplacer une chaîne par une autre, mais je ne sais pas comment le faire quand le script doit tester plusieurs possibilités ...
Voilà, merci !

<?php

$rechercher = 'Frank Fruk';
$remplacer = '<a href="lien?$id">Frank Fruk</a>';

$nouvelleChaine = str_replace($rechercher, $remplacer, $texte);

echo $nouvelleChaine;
?>
Salut,
Je ne suis pas sûr d'avoir saisi ta question à 100%

$rechercher = array ('Frank Fruk','tom jones','sean connery','etc...');

for ($n=0;$rechercher[$n]!="";$n++){
   $remplacer[$n] = '<a href="lien?$id">'.$rechercher[$n].'</a>';
   $nouvelleChaine = str_replace($rechercher[$n], $remplacer[$n], $texte);
}
echo $nouvelleChaine;

il y surement plus simple et plus élégant, mais bon...
Si tu récupères ça de ta base SQL :


$req = mysql_query("select id, nom, prenom from table");
$rech = array(); $rempl = array();
while ($ligne = mysql_fetch_assoc($req)) {
$nomprenom = "{$ligne['prenom']} {$ligne['nom']}";
$id = $ligne['id'];
array_push($rech, $nomprenom);
array_push($rempl, "<a href=\"page.php?id=$id\"<$nomprenom</a>";
}
$chaine = str_replace($rech, $rempl, $chaine);
ça marche nickel ! Seul soucis : j'aimerais que la chaîne à traiter soit un fichier que j'inclus :
$chaine = include 'afaccast.php';

Mais ça ne marche plus du coup. Je ne comprend pas pourquoi, car quand je fais echo $chaine, il me l'imprime comme il faut.
Administrateur
Dans affcast.php, tu déclares:
$chaine = Array(...);

et tu inclues ce fichier:
$retour = include ...;

(regarde la doc° d'include)
Modifié par Felipe (22 Nov 2006 - 13:09)
Bonjour.

Cette syntaxe :

$chaine = include 'afaccast.php';

... n'est pas du tout correcte, car include interprète (exécute) le code du fichier.
Pour lire le contenu d'un fichier dans une variable, il vaut mieux écrire :

$chaine = file_get_contents('afaccast.php');
...                                // traitement de la chaîne
echo eval($chaine);   // exécution comme si c'était en include


Il faudra peut être consulter le manuel de la fonction eval() pour la mise au point, car il y a quelques finesses que je n'ai pas en tête...
Attention aux remplacements multiples avec str_replace(), ils sont cumulatifs ! si deux utilisateurs "Jean" et "Jean Bombeur" existent on peut se retrouver avec un
<a href="..."><a href="...">Jean</a> Bombeur</a>

Alternativement on peut utiliser strtr() ou encore un preg_replace() où tous les noms sont incorporés dans l'expression régulière par ordre de longueur de façon à capturer les plus longs en premier
$noms = array(123 => 'Jean', 456 => 'Jean Bombeur');
uasort($noms, 'uasort_strlen');

$texte = 'Hello Jean et Jean Bombeur, how r you?';
$texte = preg_replace_callback('*' . implode('|', array_map('preg_quote', $noms)) . '*i', 'mafonction', $texte);

function uasort_strlen($a, $b)
{
	return (isset($a[strlen($b)])) ? -1 : 1;
}

...en imaginant que mafonction() s'occupe de construire le lien à partir du nom de l'utilisateur, en partageant par exemple $noms
function mafonction($m)
{
	static $ids;
	if (!isset($ids))
	{
		// in_array() est super lent, on crée un index des noms à la place
		global $noms;
		$ids = array_flip($noms);
	}

	return '<a href="page.php?id=' . $ids[$m[0]] . '">' . $m[0] . '</a>';
}


Dans tous les cas, le script risque d'être relativement lent, je te conseille de tester avec quelques centaines d'utilisateurs pour voir Smiley decu

PS: j'utilise * comme délimiteur pour l'expression régulière parce que c'est un caractère échappé par preg_quote() et si j'en utilisais un autre il faudrait s'assurer qu'il n'apparait dans aucun nom de membre et donc faire une boucle sur tous les noms de membres et perdre encore plus de temps. Puisqu'on n'utilise pas * dans l'expression régulière on peut s'en servir comme délimiteur
Modifié par Hubert Roksor (23 Nov 2006 - 05:07)
Str_replace est plus rapide. Si on trie les éléments par ordre de longueur comme suggéré dans le dernier post, on peut se passer d'expression régulière.
Mais le problème des remplacements multiple reste entier si on utilise str_replace() Smiley ohwell

Par curiosité, j'ai chronométré quelques tests informels et preg_replace() est systématiquement 10 fois plus lent que strtr() même dans les cas les plus favorables. De plus, à partir d'une cinquantaine de noms strtr() devient plus rapide que str_replace() tout en garantissant des remplacements uniques. Exemple:
$texte = 'Hello Jean et Jean Bombeur, how r you?';
$noms = array(123 => 'Jean', 456 => 'Jean Bombeur');

$trans = array();
foreach ($noms as $id => $nom)
{
	$trans[$nom] = '<a href="page.php?id=' . $id . '">' . $nom . '</a>';
}

die(strtr($texte, $trans));

À noter qu'il n'est pas nécessaire de trier les noms par longueur car strtr() le fait déjà en interne.
Hubert Roksor a écrit :
PS: j'utilise * comme délimiteur pour l'expression régulière parce que c'est un caractère échappé par preg_quote() et si j'en utilisais un autre il faudrait s'assurer qu'il n'apparait dans aucun nom de membre et donc faire une boucle sur tous les noms de membres et perdre encore plus de temps. Puisqu'on n'utilise pas * dans l'expression régulière on peut s'en servir comme délimiteur


Ce n'est pas parce que preg_quote prévoit l'échappement des métacaractères qu'il faut les utiliser comme délimiteur. Smiley ohwell
Tôt ou tard cela conduira à des erreurs.

Se fixer comme règle à n'utiliser qu'un caractère qui n'a aucune chance de se retrouver dans le motif, comme le `par exemple.

C'est une question de cohérence !

Quand à la solu donnée avec strtr, ça ne fonctionne pas !!!!!
Exemple :
$texte = 'Hello Jean, Jean-Pierre et Jean Bombeur, how are you?';


La seule solu viable passe par une regex !
Modifié par Bison (24 Nov 2006 - 08:02)
Bison a écrit :

Quand à la solu donnée avec strtr, ça ne fonctionne pas !!!!!
Exemple :
$texte = 'Hello Jean, Jean-Pierre et Jean Bombeur, how are you?';


Pourquoi dites-vous que ça ne fonctionne pas ?
Avez mous modifié le tableau comme suit ?

$noms = array(123 => 'Jean', 230 => 'Jean-Pierre' ,456 => 'Jean Bombeur');


J'obtiens ça:

Hello <a href="page.php?id=123">Jean</a>, 
<a href="page.php?id=230">Jean-Pierre</a> et <a href="page.php?id=456">Jean Bombeur</a>, 
how are you?


Que devrait-on obtenir ?
Modifié par GeorgesM (24 Nov 2006 - 11:18)
Je dis que ça ne fonctionne pas parce que si Jean-Pierre est contenu dans le texte mais pas dans la base, il n'entre pas dans le tableau !!!!
EDIT :
Autre exemple :
$texte = 'Hello Jean, Jean-Marie, Pierre-Jacques et Jean Bombeur, comment allez-vous? Connaissez-vous les Rolling Stones ,
Savez vous que Rolling Stones signifie "Les Pierres qui roulent" mais aussi "vagabond" !';

$noms = array(123 => 'Jean', 257=>'Pierre', 456 => 'Jean Bombeur');



$trans = array();

foreach ($noms as $id => $nom)

{

	$trans[$nom] = '<a href="page.php?id=' . $id . '">' . $nom . '</a>';

}



die(strtr($texte, $trans)); 

Modifié par Bison (24 Nov 2006 - 11:46)
Bison a écrit :
Je dis que ça ne fonctionne pas parce que si Jean-Pierre est contenu dans le texte mais pas dans la base, il n'entre pas dans le tableau !!!!


D'accord.

Maintenant, la solution regex ?
Bison a écrit :
Se fixer comme règle à n'utiliser qu'un caractère qui n'a aucune chance de se retrouver dans le motif

Euh, dans mon exemple * n'a aucune chance de se retrouver dans le motif, en tout cas pas en tant que méta-caractère. La génération de l'expression régulière en question est très controlée, le jour où gordie a envie de changer l'expression régulière et utiliser des * il n'aura qu'à choisir un autre délimiteur.

Bison a écrit :
Quand à la solu donnée avec strtr, ça ne fonctionne pas !!!!!

Tout dépend du cahier des charges en fait. Si on sait que les noms seront toujours composés d'un prénom et d'un nom de famille alors les chances de faux-positifs sont très faibles et l'avantage d'utiliser une solution rapide défait ses inconvénients. Une solution basée sur une expression régulière sera entre 10 et 100 fois plus lente ! Smiley decu
Je suis sûr que tu es impatient de connaître la solution de Bison, sinon il y a ce que j'ai posté un peu plus haut: http://forum.alsacreations.com/topic-20-20025-1-Rechercher-une-srie-de-chanes-dans-un-texte-et-les-remplacer.html#p150848

Mais là encore on est loin de la perfection (je ne pense pas qu'il existe une solution définitive à ce problème) pour plusieurs raisons. En dehors des performances catastrophiques, il est à noter que l'on est limité dans le nombre de noms car l'expression rationnelle est elle-même limité en longueur (quelques KiB si mes souvenirs sont bons). Il faudrait également la modifier pour éviter que "Jean Martin" ne remplace partiellement "Jean Martine" mais même après ce relooking on pourrait trouver des cas de faux-positifs (même si improbables).
GeorgesM a écrit :
Toujours pas de solution regex ?

La solu regex ne se fera pas en deux coup de cuillères à pot, il faut y consacré du temps et de la réflexion.
Hubert Roksor a écrit :
Je suis sûr que tu es impatient de connaître la solution de Bison,

Non, je n'ai pas la solu toute faites, mais sans regex, il existera toujours des combinaisons qui laisseront la place à l'à-peu-près !

De toutes façons, le jeu n'en vaut pas la chandelle.
Modifié par Bison (28 Nov 2006 - 07:46)
Bison a écrit :
sans regex, il existera toujours des combinaisons qui laisseront la place à l'à-peu-près !

Et avec regex aussi. La seule façon d'avoir un taux d'efficacité à 100% c'est de considérer toutes les possibilités, et je ne suis même pas assez bon en math pour calculer combien de chiffres il faudrait pour exprimer leur nombre (quelque chose me dit que c'est plus de 10).

Explication rapide : pour capturer tous les remplacements possibles avec une expression régulière (ci-après, "regex") je ne vois que deux méthodes. La première c'est indiquer toutes les "cibles" dans la regex, par exemple "(Jean Bombeur|Anne Onyme|Augustin Connu)". Gros problème : pour un forum comme celui-ci ça représente une regex de plusieurs (dizaines de) kilo-octets et on dépasse rapidement la limite fixée par PHP/PCRE. L'autre possibilité est de capturer tout ce qui ressemble à un nom le comparer à la liste, mais là encore c'est infiniment compliqué parce que le nombre de combinaisons possibles peut aisément devenir phénoménal. L'un dans l'autre, on pourrait se contenter de capturer toute série de mots commençant par une majuscule, mais dès lors qu'on essaie de prendre en compte les noms composés (Jean-Pierre) ou les particules (Galouzeau de Villepin) on se retrouve à examiner un grand nombre de faux-positifs (Coca du McDo). Difficulté supplémentaire, lors d'une capture chaque caractère ne peut-être "consommé" qu'une seule fois. Donc pour chaque mot il faut décider s'il appartient à la proposition d'avant ou si c'est une nouvelle proposition mais on ne peut pas examiner les deux possibilités sans une proportion conséquente de PHP additionnel, et je n'ai même pas evoqué les problèmes de performance.

Donc pour en revenir au topic, si on veut le meilleur rapport entre le temps d'exécution et le résultat obtenu (et je ne parle même pas du temps de développement) c'est strtr() qu'il faut utiliser. Si on veut un taux d'efficacité très élevé sans trop compromettre les temps d'exécution alors à mon avis la meilleure solution consiste à décomposer le texte en mots et itérer sur chacun d'eux à la recherche d'une suite connue. Comme le dit Bison, "le jeu n'en vaut pas la chandelle" donc sauf s'il s'agit de faire du "Intellisense" on préfèrera la solution qui offre le meilleur rapport entre la complexité et les résultats. Smiley cligne
Modifié par Hubert Roksor (29 Nov 2006 - 01:07)
Bonjour à tous,
J'ai pour mon site de grands projets Smiley smile et entre autres quasiment le même cas présenté par gordie :
Il s'agit d'un glossaire de mots spécifiques, dans une base de données.
J'aimerai que chacun des textes de définition, s'ils contiennent un nom présent dans le glossaire, apparait sous forme de lien vers ladite définition.
Exemple :
Mon glossaire comprend (entre autres) les définitions 'aiguillon central' et 'aréole'.
Le texte de définition pour 'aiguillon central' est :
a écrit :
Il est principalement situé au centre de l'aréole. Il se distingue nettement des autres aiguillons car il est généralement plus gros, plus long et souvent crochu à l'extrémité et parfois d'une autre couleur.

J'aimerai donc par exemple que le mot 'aréole' se transforme en lien vers la def du mot aiguillon (id=2).
Le glossaire compte moins de 200 mots.
Est ce envisageable ou complètement utopique ?