Encore un billet technique sur ce blog. Comme j'en ai pris l'habitude, j'aime varier entre mes différentes passions telles que le référencement web, les réseaux sociaux, les CMS ou encore les astuces techniques (notamment en PHP). Ne pensez pas que je n'aime rien d'autre, mais rien qu'avec ces sujets, nous avons de quoi discuter un moment... :-)
Aujourd'hui, je vous présente une même fonction PHP transcrite en PHP procédural et objet (POO) qui permet de générer un extrait de texte à partir d'un mot précis. Le cas d'école pour ce type de fonction est la génération de description dans un moteur de recherche. Si vous souhaitez créer un snippet (extrait de texte) autour d'un mot recherché, ces fonctions et méthodes sont faites pour vous !
Fonctionnalités générales
La fonction est assez modulaire, même si elle peut être encore améliorée. Au-delà de générer un texte, elle permet plusieurs adaptations :
- Accepter un mot précis ou un tableau de mot (dans ce cas, l'extrait est généré autour d'un des mots tirés au hasard). Dans la version en POO, il est possible de définir l'indice du mot que nous souhaiterions prioriser.
- Modifier le nombre de mots à afficher avant et après le mot recherché. Par conséquent, vous pouvez gérer la taille du texte créé dynamiquement.
- Choisir autour de quelle occurrence du mot générer l'extrait. En effet, il peut arriver qu'un mot soit répété dans un texte, il convient alors de choisir quelle occurrence privilégier pour générer le snippet.
- Afficher une chaîne au choix avant et après l'extrait (souvent des points de suspension...).
Tous les paramètres sont détaillés dans les fichiers ou les codes présentés ci-dessous, n'hésitez pas à me contacter si vous avez un problème...
N.B. : si cette fonction vous aide, vous pourrez dire merci à Olivier Andrieu, le "Pape" du référencement, car c'est pour lui que j'ai développé cet outil à l'origine. Comme je n'aime pas jeter mes créations par les fenêtres, je préférais vous en faire profiter... :-)
Génération de texte autour d'un mot : la fonction PHP !
Voici la fonction generateWrapText() en PHP procédural. Elle est relativement complète mais peut encore être améliorée comme nous le verrons à la fin de cet article. Si elle vous convient, vous pouvez la recopier ou la télécharger ci-dessous.
<?php // Fonction de génération d'un texte autour d'un mot précis // Paramètre 1 : $texte correspond au texte dans lequel rechercher et générer un extrait // Paramètre 2 : $mot correspond au mot autour duquel va être dessiné l'extrait // Paramètre 3 : $limite est le nombre de mot à récupérer autour du mot recherché // Paramètre 4 : $numOccurrence équivaut au numéro de l'occurrence du mot trouvé dans le texte ("5" étant la 5e présence du mot dans le texte, pour générer l'extrait plus loin dans le texte) // Paramètre 5 : $strong = true signifie que le mot recherché (central) sera mis en gras (nouveau depuis la version 1.1) // Paramètre 6 : $chainePrec correspond au texte à écrire avant l'extrait // Paramètre 7 : $chaineSuiv correspond au texte à écrire après l'extrait function generateWrapText($texte = '', $mot = '', $limite = 15, $numOccurrence = 5, $strong = true, $chainePrec = "(...)", $chaineSuiv = "...") { // Dans le cas où malgré la suppression des accents $mot reste un tableau if(is_array($mot)) { $indice = rand(0, count($mot)); // On prend un des mots au hasard $mot = $mot[$indice]; } // Coupe la chaîne mot à mot $chaineTexte = mb_split("([[:space:]]|[\(\)\[\]\{\},;:!?<>])+", strip_tags(trim($texte))); // Nettoyage de la chaine (optionnel) foreach($chaineTexte as $cle => $motUnique) { $cleanWord = trim($motUnique); $motUnique = preg_replace("#(\\n|\\r| )#i", " ", $motUnique); // Tableau nettoyé reconstitué $chaineTab[] = $motUnique; } // Récupère la clé du mot recherché $cleMot = array_keys($chaineTab, $mot); // Toutes les occurrences // On compte le nombre d'occurrences pour assurer de générer un snippet autour du mot cherché $nbOcc = count($cleMot, 1); if($numOccurrence > $nbOcc) { $numOccurrence = $nbOcc - 1; // On prend l'avant-dernière occurrence par exemple } // Paramètres par défaut $debutIncomplet = false; $finIncomplete = false; $chaineCreee = ""; $chaine = ""; // Extraction des mots suivants if(isset($cleMot[$numOccurrence])) { foreach($chaineTab as $cle => $valeur) { if($cle == $cleMot[$numOccurrence]) { // "n" Mots précédents for($i = $limite; $i >= 1; $i--) { $posMotPrecedent = $cleMot[$numOccurrence]-$i; if(isset($chaineTab[$posMotPrecedent])) { $chaineCreee.= " ".$chaineTab[$posMotPrecedent]; } else { $debutIncomplet = true; } } // Mot recherché (affiché en gras ou non) if($strong == true) { $chaineCreee.= " <strong>".$chaineTab[$cleMot[$numOccurrence]]."</strong>"; } else { $chaineCreee.= " ".$chaineTab[$cleMot[$numOccurrence]]; } // "n" Mots suivants for($i = 1; $i <= $limite; $i++) { $posMotSuivant = $cleMot[$numOccurrence]+$i; if(isset($chaineTab[$posMotSuivant])) { $chaineCreee.= " ".$chaineTab[$posMotSuivant]; } else { $finIncomplete = true; } } } } // Ajout des caractères de début (... par exemple) si le découpage n'atteint pas le début du texte. if($debutIncomplet == false) { $chaine.= $chainePrec; } $chaine.= $chaineCreee; // Ajout de la chaîne reconstituée // Ajout des caractères de fin (... par exemple) si la phrase n'est pas au bout. if($finIncomplete == false) { $chaine.= $chaineSuiv; } } // Retourne le résultat return $chaine; } ?>
Version plus complète en PHP Objet (POO)
Juste pour le plaisir, j'ai développé une version en PHP Objet de ce générateur de texte autour d'un mot précis. Au fond, rien de bien neuf, la Class PHP peut encore être améliorée mais elle profite désormais de getters (accesseurs) et setters (mutateurs) pour effectuer plus facilement des opérations sur les extraits à générer.
Les méthodes principales de la Class GenerateWrapText sont les suivantes :
- retournerTexte("texte général", "mot recherché") qui retourne le résultat (return).
- afficherTexte("texte général", "mot recherché") qui affiche le résultat (echo).
Vous pouvez la télécharger ci-dessous ou la recopier dans le cadre qui suit.
<?php class GenerateWrapText { private $chaine; public $texte = ''; // Texte dans lequel généré un extrait public $mot = ''; // Mot recherché (peut être un tableau de mots !) public $limite = 15; // Nombre de mots avant et après le mot recherché public $numOccurrence = 5; // Numéro de l'occurrence du mot à récupérer dans le texte public $randOcc = 1; // Si l'occurrence du mot dépasse sa présence, on prend $randOcc en moins public $strong = true; // Mettre le mot recherché en gras public $chainePrec = "(...)"; // Chaine affichée au début public $chaineSuiv = "..."; // Chaine de fin // Fonction principale pour retourner le texte public function retournerTexte($texte = '', $mot = '') { // Récupération des variables if(!empty($texte)) { $this->texte = $texte; } if(!empty($mot)) { $this->setMot($mot); } // Coupe la chaîne mot à mot $chaineTexte = mb_split("([[:space:]]|[\(\)\[\]\{\},;:!?<>])+", strip_tags(trim($this->texte))); // Nettoyage de la chaine (optionnel) foreach($chaineTexte as $cle => $motUnique) { $cleanWord = trim($motUnique); $motUnique = preg_replace("#(\\n|\\r| )#i", " ", $motUnique); // Tableau nettoyé reconstitué $chaineTab[] = $motUnique; } // Récupère la clé du mot recherché $cleMot = array_keys($chaineTab, $this->mot); // On compte le nombre d'occurrences pour assurer de générer un snippet autour du mot cherché $nbOcc = count($cleMot, 1); if($this->numOccurrence > $nbOcc) { $this->numOccurrence = $nbOcc - $this->randOcc; // On prend l'avant-dernière occurrence par exemple } // Paramètres par défaut $debutIncomplet = false; $finIncomplete = false; $chaineCreee = ""; $chaine = ""; // Extraction des mots suivants if(isset($cleMot[$this->numOccurrence])) { foreach($chaineTab as $cle => $valeur) { if($cle == $cleMot[$this->numOccurrence]) { // "n" Mots précédents for($i = $this->limite; $i >= 1; $i--) { $posMotPrecedent = $cleMot[$this->numOccurrence]-$i; if(isset($chaineTab[$posMotPrecedent])) { $chaineCreee.= " ".$chaineTab[$posMotPrecedent]; } else { $debutIncomplet = true; } } // Mot recherché (affiché en gras ou non) if($this->getStrong() == true) { $chaineCreee.= " <strong>".$chaineTab[$cleMot[$this->numOccurrence]]."</strong>"; } else { $chaineCreee.= " ".$chaineTab[$cleMot[$this->numOccurrence]]; } // "n" Mots suivants for($i = 1; $i <= $this->limite; $i++) { $posMotSuivant = $cleMot[$this->numOccurrence]+$i; if(isset($chaineTab[$posMotSuivant])) { $chaineCreee.= " ".$chaineTab[$posMotSuivant]; } else { $finIncomplete = true; } } } } // Ajout des caractères de début (... par exemple) si le découpage n'atteint pas le début du texte. if($debutIncomplet == false) { $chaine.= $this->chainePrec; } $chaine.= $chaineCreee; // Ajout de la chaîne reconstituée // Ajout des caractères de fin (... par exemple) si la phrase n'est pas au bout. if($finIncomplete == false) { $chaine.= $this->chaineSuiv; } } // Retourne le résultat $this->chaine = $chaine; return $chaine; } // Affiche le texte public function afficherTexte($texte = '', $mot = '') { // Récupération des variables if(!empty($texte)) { $this->texte = $texte; } if(!empty($mot)) { $this->mot = $mot; } echo $this->retournerTexte($this->texte, $this->mot); } // Accesseurs (getters) public function getTexte() { return $this->texte; } public function getMot() { return $this->mot; } public function getLimite() { return $this->limite; } public function getNumOccurrence() { return $this->numOccurrence; } public function getRandOcc() { return $this->randOcc; } public function getStrong() { return $this->strong; } public function getChainePrev() { return $this->chainePrev; } public function getChaineSuiv() { return $this->chaineSuiv; } // Mutateurs (setters) public function setTexte($texte) { $this->texte = $texte; } public function setMot($mot, $i = '') { // Si $mot est un tableau if(is_array($mot) && empty($i)) { // On prend un des mots au hasard dans le tableau $indice = rand(0, count($mot)); $mot = $mot[$indice]; } else if(is_array($mot) && !empty($i)) { $this->mot = $mot[$i]; } else { $this->mot = $mot; } } public function setLimite($limite) { if(is_numeric($limite)) { $this->limite = $limite; } else { $this->limite = 15; // Valeur par défaut si $limite n'est pas numérique } } public function setNumOccurrence($numOccurrence) { if(is_numeric($numOccurrence)) { $this->numOccurrence = $numOccurrence; } else { $this->numOccurrence = 15; // Valeur par défaut si $numOccurrence n'est pas numérique } } public function setRandOcc($randOcc) { if(is_numeric($randOcc)) { $this->randOcc = $randOcc; } else { $this->randOcc = 1; // Valeur par défaut si $randOcc n'est pas numérique } } public function setStrong($bool) { if(is_bool($bool)) { $this->strong = $bool; } } public function setChainePrec($chainePrec) { if(is_string($chainePrec)) { $this->chainePrec = $chainePrec; } else { $this->chainePrec = "(...)"; // Défaut si ce n'est pas une "string" } } public function setChaineSuiv($chaineSuiv) { if(is_string($chaineSuiv)) { $this->chaineSuiv = $chaineSuiv; } else { $this->chaineSuiv = "..."; // Défaut si ce n'est pas une "string" } } } ?>
Variante de création d'extrait de texte à la volée
Il existe une variante pour faire le même type de création d'extrait de texte à la volée autour d'un mot précis. Souvent, les développeurs conseillent de chercher la position exacte du mot (avec strpos ou l'inverse strrpos), d'en récupérer la longueur (avec strlen), puis de boucler autour de cette position pour afficher "n" lettres avant ou après.
J'aime moins cette méthode qui me semble plus lourde et plus complexe pour couper précisément des mots, bien qu'il y ait des moyens d'arriver à nos fins. Il faudrait notamment remonter de "n" caractères avant le mot recherché pour trouver un espace ou idem après ce mot. Toutefois, nous n'aurions jamais la précision et le contrôle sur le nombre de mots réels à afficher (sans parler des calculs parfois lourds pour trouver les positions, etc.)
Conclusion sur la génération de snippets en PHP autour d'un mot précis
Voilà, j'espère que cette fonction pourra rendre service à certains d'entre vous, voire donner des idées d'améliorations.
Je vous glisse une piste par exemple. La fonction découpe les mots à chaque espace, mais il arrive que des "mots" n'en soit pas réellement, il faudrait donc filtrer ces "faux mots" tant que possible. Certes, cela pourrait être un travail de titan mais voici quelques exemples simples d'améliorations possibles :
- Si on écrit une phrase avec un trait d'union encadré d'espace, ce tiret est considéré comme un mot, il faudrait donc supprimer les "mots" de ce type lors du découpage du texte en tableau de termes. Beaucoup d'exemples sont similaires comme l'esperluette, le signe euro, le pourcentage (...) qui sont souvent espacés des textes. Il faut également supprimer la ponctuation espacée comme les points d'exclamation, d'interrogation, etc. Tout cela permettrait d'avoir un nombre de mots plus réaliste et précis autour du terme ciblé.
- Si on utilise des encodages variés (fortement déconseillé mais ça arrive...), il pourrait être intéressant de faire un test de détection d'encodage (avec mb_detect_encoding ou mieux) pour adapter l'affichage final.