Commmint Posté 15 Janvier 2008 Posté 15 Janvier 2008 Bonjour à tous, J'espère trouver ici quelques pros pouvant m'aiguiller sur la rédaction d'une expression régulière en PHP somme toute assez simple. Voilà j'ai donc une chaine de HTML en entrée, dont le texte est exclusivement structuré par des "P", des "UL/OL" des "H1,H2,H3" et des "A". Certaines balises sont assorties d'une classe CSS, et les liens contiennent bien entendu leur href. J'ai à disposition une liste de mots clés que je souhaiterais détecter dans cette chaine HTML (toutes les occurrences) et convertir le mot en question en lien avec les restrictions suivantes : Ne pas convertir les mots en lien dans les balises H Ne pas convertir les mots déjà en lien, et exclure le remplacement dans les attributs du liens existant (sinon ça fout le HTML en l'air) Ne pas convertir une portion de mot (pas de lien sur "aero" dans le mot "aeronautique" par exemple), seulement un mot entier (défini par (espace)mot(espace) ou tout autre ponctuation (point, point virgule,guillemets collés ou pas, etc...) Ce qui revient à dire que je ne souhaite le remplacement que dans les paragraphes et les autres balises de formatage, hors titres et liens existants. Je cherche donc un motif polyvalent pour ce traitement. Je tacherai de développer le motif si mon HTML devait se complexifier avec des scripts ou d'autres balises. Ci dessous un cas pratique, qui rend compte du dispositif actuellement en place (traitement du tableau avant preg) <?php//$HTML = '<h1>le gros titre</h1><h2>le moyen titre</h2><p>un paragraphe contenant des gros mots et aussi un lien <a href="protection.html">protection</a> qui ne doit pas etre modifié</p><p>mais ce mot protection doit être converti</p>';//function make_pattern_word($n) { return '//'; // ICI la REGEX}$mots = array('gros','mots','protection', 'doit');$mots_def = array_map("make_pattern_word", $mots);echo preg_replace($mots_def,'\\1<a href="?word=\\2">\\2</a>\\3',$HTML);//?> Merci d'avance pour votre aide et votre temps !!!
tisha_carpenter Posté 16 Janvier 2008 Posté 16 Janvier 2008 simple? <?php// la fonctionfunction ajouter_liens($html,$mots=array()) { // les éléments html à exclure static $balises_exclues=array('head','script','h[1-6]','a'); // format du remplacement static $remplacement='<a href="?word=%s">%s</a>'; // le traitement regex if(!is_array($html)) { if(!is_string($html)||!is_array($mots)||count($mots)==0) return $html; $balises=implode('|',$balises_exclues); $mots=implode('|',array_map('preg_quote',$mots)); $regex='#<('.$balises.')[\s>].+?</\1>|<[^>]+>|\b('.$mots.')\b#si'; return preg_replace_callback($regex,__FUNCTION__,$html); } if(isset($html[2])) return sprintf($remplacement,rawurlencode($html[0]),$html[0]); return $html[0];} // ajouter_liens// tester la fonction$HTML = '<h1>le gros titre</h1><h2>le moyen titre</h2><p>un paragraphe contenant des gros mots et aussi un lien <a href="protection.html">protection</a> qui ne doit pas etre modifié</p><p>mais ce mot protection doit être converti</p>';$nouveau_html=ajouter_liens($HTML,array('gros','mots','protection','doit'));echo '<pre>',htmlentities($nouveau_html),'</pre>';?>
Commmint Posté 16 Janvier 2008 Auteur Posté 16 Janvier 2008 (modifié) Merci tisha_carpenter, Je découvre à l'instant ta solution. J'aurais pas cru qu'elle serait si compliquée. En tout cas elle est jolie. Je la teste tout de suite dans différentes configurations. Merci par avance ! **** ça marche merveilleusement bien. Dommage que je pige toujours rien aux REGEX et au sprintf. Je vais me plonger assidument dans ton code ! Merci encore ! Modifié 16 Janvier 2008 par Commmint
cdpdf Posté 17 Mars 2008 Posté 17 Mars 2008 Merci tisha_carpenter pour ce code, Mais j'ai juste un problème pour pouvoir l'appliquer à un de mes sites. Le problème est que chacun de mes mots est attaché à un id et qu'il faut que l'id se trouve dans l'url Christophe
tisha_carpenter Posté 19 Mars 2008 Posté 19 Mars 2008 Le problème est que chacun de mes mots est attaché à un id et qu'il faut que l'id se trouve dans l'url Même si je ne suis pas certaine de la forme de ton array, j'ai ajouté le paramètre $cle_url qui, à true, fait la transposition sur un array de la forme id=>mot. En espérant que c'est la forme de ton array. Désolée, je ne peux pas éditer le message#2 CODE <?php // La fonction *** ajouter_liens *** // ********************************* // 1) $cle_url à false rend le mot en minuscule (url encodé) dans l'url // 2) $cle_url à true rend la clé du mot en minuscule (url encodé) dans l'url // Le array $mots peut avoir une relation id=>mot textuelle // $cle_url est à false par défaut function ajouter_liens($html,$mots=array(),$cle_url=false) { // Les éléments html à exclure static $balises_exclues=array('head','script','h[1-6]','a'); // Format du remplacement static $remplacement='<a href="?word=%s">%s</a>'; // Pour le traitement interne id=>mot static $mots_id=array(); // Le traitement regex, appel d'origine if(!is_array($html)) { if(!is_string($html)||!is_array($mots)||count($mots)==0) return $html; $mots_id=array(); // S'assurer que la liste statique est vidée if($cle_url) { // Générer $mots_id si id=>mot est désiré $mots_id=array_map('strtolower',$mots); } $balises=implode('|',$balises_exclues); $mots=implode('|',array_map('preg_quote',$mots)); $regex='#<('.$balises.')[\s>].+?</\1>|<[^>]+>|\b('.$mots.')\b#si'; return preg_replace_callback($regex,__FUNCTION__,$html); // Appel récursif } // Traitement php de l'appel récursif sur preg_replace_callback if(isset($html[2])) { // un mot a été trouvé pour remplacement $mot_texte=$html[0]; $mot_url=strtolower($mot_texte); if(count($mots_id)) { // S'il y a une relation id=>mot alors transposer $mot_url=array_search($mot_url,$mots_id,true); if(!$mot_url) return $html[0]; // Couvrir un échec (peu probable) $mot_url=strtolower($mot_url); } return sprintf($remplacement,rawurlencode($mot_url),$mot_texte); } return $html[0]; // Cas où aucun remplacement } // ajouter_liens // Tester la fonction $ancien_html = '<h1>le gros titre</h1> <h2>le moyen titre</h2> <p>un paragraphe contenant des gros mots et aussi un lien <a href="protection.html">protection</a> qui ne doit pas être modifié</p> <p>mais ce mot protection doit être converti</p>'; // Test avec valeur par défaut $cle_url=false echo '<hr><b>$cle_url=false</b> Valeur par défaut<br>'; $liste_mots=array('gros','mots','protection','doit'); $nouveau_html=ajouter_liens($ancien_html,$liste_mots); echo '<pre>',htmlentities($nouveau_html),'</pre>'; // Test avec valeur $cle_url=true echo '<hr><b>$cle_url=true</b> clé=>valeur Transposé<br>'; $liste_mots=array(121=>'gros',617=>'mots',22=>'protection','devoir'=>'doit'); $nouveau_html=ajouter_liens($ancien_html,$liste_mots,true); echo '<pre>',htmlentities($nouveau_html),'</pre>'; ?> Par contre dû à l'id, je me demande si tu as une grande quatité de mots pouvant originer d'une base de données ou de RSS/XML? Si c'est le cas, la fonction suivante t'inspirera peut-être, elle construit les mots uniques possibles d'un document HTML, son résultat peut-être utilisé directement dans la clause IN d'une requête SQL sur une base en ISO (latin1), car tout ce qui est posté ici est en ISO. CODE <?php // La fonction function extraire_mots($html) { $balises=implode('|',array('head','script','h[1-6]','a')); // les exclusions $regex='#<('.$balises.')[\s>].+?</\1>|</?[^>]+>|[^\w<]+#si'; $mots=preg_split($regex,$html,-1,PREG_SPLIT_NO_EMPTY); return '\''.implode('\',\'',array_unique(array_map('strtolower',$mots))).'\''; } // Tester la fonction $html = '<h1>le gros titre</h1> <h2>le moyen titre</h2> <p>Un paragraphe contenant des gros mots et aussi un lien <a href="protection.html">protection</a> qui ne doit pas être modifié</p> <p>mais ce mot protection doit être converti</p>'; $mots=extraire_mots($html); $query="SELECT id_mot,mot FROM mots WHERE mot IN ($mots);"; echo "<pre><b>Exemple de query:</b>\n$query</pre>"; ?> Si utilisée, alors une classe serait la bienvenue dans ton cas pour au moins uniformiser la définition des balises exclues. Tisha
cdpdf Posté 21 Mars 2008 Posté 21 Mars 2008 Merci beaucoup tisha_carpenter pour cette réponse, J'ai cependant encore différents problèmes. Je ne comprend pas pourquoi avec la fonction suivante, les accents des mots sont considérés comme inexistants. // La fonctionfunction extraire_mots($html) {$balises=implode('|',array('head','script','h[1-6]','a')); // les exclusions$regex='#<('.$balises.')[\s>].+?</\1>|</?[^>]+>|[^\w<]+#si';$mots=preg_split($regex,$html,-1,PREG_SPLIT_NO_EMPTY);return '\''.implode('\',\'',array_unique(array_map('strtolower',$mots))).'\'';} De plus, j'ai dans ma base de donnée des mots avec des espaces genre "webmaster hub" ou encore des mots aves des tirets genre "tisha-carpenter" et parfois avec des apostrophes "l'apéro"tout ces mots ne ressortent pas avec la requête WHERE mot IN ('webmaster','hub','un webmaster hub','des','gros','mots','et','aussi','lien','qui','ne','doit','pas','tre','modifi','mais','ce','mot','protection','converti'). je n'arrive pas non plus à utiliser la fonction ajouter_liens à mon cas. Pour le moment mon code est function parser($text) { $sortie = array(); $mot = array(); $sortie = array(); $sqlquery=$xoopsDB->query("SELECT id, name from mabase ORDER LENGTH(name) DESC"); //la requête while ($sqlfetch=$xoopsDB->fetchArray($sqlquery)) { $id = $sqlfetch['id']; $name = $myts->sanitizeForDisplay($sqlfetch['name']); if (stristr($text,$name) ) { // si le mot se trouve dans le texte if (stristr($name,' ') ) {// si le mot est un mot composé $topictexturl = urlencode($name); $nameb = str_replace(" ","-", $name); //j'ai été obligé de supprimer les espaces des mots composés car j'ai aussi bien des définitions pour "webmaster" que pour "webmaster hub" (c'est un exemple) $mot[] = ('`\b(('.$name.')s?)\b`si'); $sortie[] = '<a href="id='.$id.'&mot='.$topictexturl.'">'.$nameb.'</a>'; } else {// si le mot n'est pas un mot composé $topictexturl = urlencode($name); $mot[] = ('`[^->abcdeéèfghijklmnopqrstuvwxyz](('.$name.')s?)\b`si'); $sortie[] = '<a href="id='.$id.'&mot='.$topictexturl.'">$1</a>'; } } } $text = preg_replace($mot,$sortie,$text,2); return $text;} Pour moi mon code ne fonctionne pas bien du tout car * je suis obligé de tester tous les mots de ma base de donnée (plus de 2000) ça fait mal à ma base à chaque fois. * obligé de rajouter des tirets pour tous les mots composés * je ne sais pas évité de faire des changement dans les balises (un point très important) * et surement encore d'autres problème que je ne vois pas. Merci d'avance Christophe
tisha_carpenter Posté 22 Mars 2008 Posté 22 Mars 2008 Je ne comprend pas pourquoi avec la fonction suivante, les accents des mots sont considérés comme inexistants. Tu es dans un environnement configuré en autre chose que ISO-8859-(1 ou 15). PHP récupère les facilités du système d'exploitation sous le nom de locale et la fonction PHP setlocale() peut changer cette configuration le temps de l'exécution du script si la "locale" voulue est accessible par le système d'exploitation (et au moins PHP 4.3). Toutes les fonctions string de PHP sont soumises à cette configuration. Il y a toujours moyen de contourner ou de s'adapter si un changement de configuration est impossible et que setlocale() ne donne aucun résultat. Mais à la base, tu es en présence d'un problème de configuration. La ligne suivante devrait te dire dans quel encodage ton système est: <?phpecho setlocale(LC_CTYPE,null);?> De plus, j'ai dans ma base de donnée des mots avec des espaces genre "webmaster hub" ou encore des mots aves des tirets genre "tisha-carpenter" et parfois avec des apostrophes "l'apéro"tout ces mots ne ressortent pas avec la requête Ce que j'avais posté ici concernait le mot et non pas la phrase. La classe \b est à proscrire car \b est très très gourmant et refusera tout caractère non-mot à l'intérieur de l'énumération, \b doit être utilisé uniquement pour délimiter un mot car il donne un gain en temps d'exécution (cf.: test rapidement l'échec). Par contre dans la fonction ajouter_liens, la ligne: $regex='#<('.$balises.')[\s>].+?</\1>|<[^>]+>|\b('.$mots.')\b#si'; peut-être remplacer par: $regex='#<('.$balises.')[\s>].+?</\1>|<[^>]+>|(?<=\W)('.$mots.')(?=\W)#si'; Et ça devrait fonctionner aussi pour la phrase comme pour le mot sur un tri par longueur comme dans ton code. Surtout pour array('la très belle'=>'tisha carpenter'); * je suis obligé de tester tous les mots de ma base de donnée (plus de 2000) ça fait mal à ma base à chaque fois. Je vois pas comment soulager ta base, sauf par une mise en cache de la requête. Car il devient impossible de savoir si un espace doit être conservé ou éliminé, à moins de faire aussi toutes les combinaisons de 2 mots par proximité, ce qui serait un tout autre genre de bestiaux. * obligé de rajouter des tirets pour tous les mots composés Ne devrait plus être nécessaire après la correction regex proposée un peu plus haut. Dans un bon encodage de caractère, bien entendu. Ta base est encodé en quoi? Tes documents HTML sont encodés en quoi? * je ne sais pas évité de faire des changement dans les balises (un point très important) OK. Je vais essayer de faire ma prof aucune garantie de satisfaction. Le méta-caractère | permet d'énumérer des possibilités sous la forme de OU logique. L'ordre d'énumération est important puisqu'il doit aller du plus important au moins important. J'utilise ce | pour énumérer 3 hypothèses Hypothèse des balises exclues <('.$balises.')[\s>].+?</\1>Revient à isoler de la balise ouvrante à la balise fermante inclusivement selon l'énumération contenu dans $balises. Par exemple, le HTML "<h1>une texte de titre</h1>" sera complètement capturer en un bloc parce que h1 fait partie de $balises. Hypothèse des attributs des autres balises <[^>]+>Permet d'isoler tous les attributs des autres balises car le mot pourrait être présent dans ces attributs. Hypothèse des phrases/mots recherchés (?<=\W)('.$mots.')(?=\W)Isolera une phase/mot de l'énumération contenu dans $mots, qui est précédé et suivi par un caractère \W, donc précédé ou suivi par un caractère non-mot tel que défini par locale (ref.: setlocale). Voilà donc pourquoi $regex='#<('.$balises.')[\s>].+?</\1>|<[^>]+>|(?<=\W)('.$mots.')(?=\W)#si'; Le preg_replace_callback($regex,__FUNCTION__,$html); permet de valider quelle partie de la chaine est isolée, si l'expression régulière est satisfaite avant d'arriver à la seconde parenthèse qui énumère les phases/mots alors la capture numéro 2 sera indéfini... donc aucun changement. Par contre, si la capture de la seconde parenthèse est définie, alors un mot/phrase à remplacer a été trouvé. Le code PHP exécuter par ce callback est: // Traitement php de l'appel récursif sur preg_replace_callback if(isset($html[2])) { // un mot a été trouvé pour remplacement $mot_texte=$html[0]; $mot_url=strtolower($mot_texte); if(count($mots_id)) { // S'il y a une relation id=>mot alors transposer $mot_url=array_search($mot_url,$mots_id,true); if(!$mot_url) return $html[0]; // Couvrir un échec (peu probable) $mot_url=strtolower($mot_url); } return sprintf($remplacement,rawurlencode($mot_url),$mot_texte); } return $html[0]; // Cas où aucun remplacement Qui est la logique expliquée plus haut Le tout en parcourant une seule fois le document HTML. En espérant que mon message t'aidera à progresser vers ton objectif. Tisha
Sujets conseillés
Veuillez vous connecter pour commenter
Vous pourrez laisser un commentaire après vous êtes connecté.
Connectez-vous maintenant