Comment tronquer une chaîne en PHP au mot le plus proche d'un certain nombre de caractères?

183

J'ai un extrait de code écrit en PHP qui extrait un bloc de texte d'une base de données et l'envoie à un widget sur une page Web. Le bloc de texte original peut être un long article ou une courte phrase ou deux; mais pour ce widget, je ne peux pas afficher plus de, disons, 200 caractères. Je pourrais utiliser substr () pour couper le texte à 200 caractères, mais le résultat serait de couper au milieu des mots - ce que je veux vraiment, c'est couper le texte à la fin du dernier mot avant 200 caractères.

Brian
la source
2
La question vise à dire que le texte tronqué tiendra dans un nombre fixe de pixels sur une page Web. Dans ce cas, selon la police choisie, l'espace requis par caractère n'est pas constant. Et par conséquent, nous ne pouvons pas supposer que 200 caractères conviendront le mieux aux pixels disponibles. Jusqu'à présent (jusqu'au 02 mars 2011), toutes les réponses ci-dessous manquent ce point et par conséquent aucune d'entre elles ne fournit une solution fiable. - :(
LionHeart
1
Non, pas vraiment. Vous pouvez définir la police de manière fiable, puis mesurer le pire des cas, c'est-à-dire le nombre de caractères les plus larges qui pourraient entrer. Et si vous avez besoin d'être sûr à 100% de la façon dont le navigateur l'a rendu, ce n'est plus un problème PHP de toute façon.
Mołot
Essayez ce lien, peut vous aider stackoverflow.com/a/26098951/3944217
edCoder
Vous pourriez trouver s($str)->truncateSafely(200)utile, comme trouvé dans cette bibliothèque autonome .
caw

Réponses:

221

En utilisant la fonction Wordwrap . Il divise les textes en plusieurs lignes de telle sorte que la largeur maximale soit celle que vous avez spécifiée, en se coupant aux limites des mots. Après le fractionnement, vous prenez simplement la première ligne:

substr($string, 0, strpos(wordwrap($string, $your_desired_width), "\n"));

Une chose que cet oneliner ne gère pas est le cas où le texte lui-même est plus court que la largeur souhaitée. Pour gérer ce cas de bord, on devrait faire quelque chose comme:

if (strlen($string) > $your_desired_width) 
{
    $string = wordwrap($string, $your_desired_width);
    $string = substr($string, 0, strpos($string, "\n"));
}

La solution ci-dessus pose le problème de couper prématurément le texte s'il contient une nouvelle ligne avant le point de coupure réel. Voici une version qui résout ce problème:

function tokenTruncate($string, $your_desired_width) {
  $parts = preg_split('/([\s\n\r]+)/', $string, null, PREG_SPLIT_DELIM_CAPTURE);
  $parts_count = count($parts);

  $length = 0;
  $last_part = 0;
  for (; $last_part < $parts_count; ++$last_part) {
    $length += strlen($parts[$last_part]);
    if ($length > $your_desired_width) { break; }
  }

  return implode(array_slice($parts, 0, $last_part));
}

Aussi, voici la classe de test PHPUnit utilisée pour tester l'implémentation:

class TokenTruncateTest extends PHPUnit_Framework_TestCase {
  public function testBasic() {
    $this->assertEquals("1 3 5 7 9 ",
      tokenTruncate("1 3 5 7 9 11 14", 10));
  }

  public function testEmptyString() {
    $this->assertEquals("",
      tokenTruncate("", 10));
  }

  public function testShortString() {
    $this->assertEquals("1 3",
      tokenTruncate("1 3", 10));
  }

  public function testStringTooLong() {
    $this->assertEquals("",
      tokenTruncate("toooooooooooolooooong", 10));
  }

  public function testContainingNewline() {
    $this->assertEquals("1 3\n5 7 9 ",
      tokenTruncate("1 3\n5 7 9 11 14", 10));
  }
}

ÉDITER :

Les caractères UTF8 spéciaux tels que «à» ne sont pas gérés. Ajoutez 'u' à la fin du REGEX pour le gérer:

$parts = preg_split('/([\s\n\r]+)/u', $string, null, PREG_SPLIT_DELIM_CAPTURE);

Panthère grise
la source
1
Il semble que cela couperait prématurément le texte s'il y a un \navant la largeur souhaitée.
Kendall Hopkins
@KendallHopkins: c'est vrai, il y a effectivement un problème. J'ai mis à jour la réponse avec une implémentation alternative qui résout le problème donné.
Grey Panther
Cet exemple fonctionnerait-il pour une chaîne contenant des balises HTML comme des balises de paragraphe?
limitlessloop
c'est vraiment utile pour moi, mon mal de tête était de longues Arabiclettres et il est réduit aux mots corrects maintenant avec l'aide de la tokenTruncatefonction .. tnx un million :)
Aditya P Bhatt
1
Pourquoi ne pas ajouter: if (strlen ($ string) <= $ your_desired_width) return $ string; comme première déclaration?
Darko Romanov
139

Cela renverra les 200 premiers caractères des mots:

preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, 201));
mattmac
la source
7
Presque. Il semble que cela supprime le dernier mot de la phrase pour moi quoi qu'il arrive.
ReX357
fonctionne très bien mais j'ai trouvé la même erreur que ReX357. Lorsqu'il y a plus d'un mot, il supprime le dernier.
Andres SK
25
Enveloppez-le simplement dans un chèque pour vous assurer que la chaîne est plus longue que ce que vous testez (même que la réponse acceptée)if (strlen($string) > $your_desired_width) { preg_replace(...); }
Blair McMillan
J'ai modifié la réponse pour inclure le conseil @BlairMcMillan
Kim Stacks
2
Petite amélioration de l'expression régulière: les parenthèses rendent le \ S + final optionnel pour la correspondance, mais elles capturent également ces caractères. Puisque nous n'avons pas besoin de capturer ces caractères, faites en sorte que les parenthèses ne capturent pas comme ceci:/\s+?(?:\S+)?$/
pcronin
45
$WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' '));

Et là vous l'avez - une méthode fiable pour tronquer n'importe quelle chaîne au mot entier le plus proche, tout en restant sous la longueur de chaîne maximale.

J'ai essayé les autres exemples ci-dessus et ils n'ont pas produit les résultats escomptés.

Dave
la source
11
Si la longueur de la chaîne donnée est inférieure à la longueur maximale, cela couperait tout jusqu'au dernier espace. Pour éviter cela, enveloppez ceci dans une ifdéclaration:if (strlen($str) > 200) { ... }
Amal Murali
Simple et probablement beaucoup plus rapide que les autres solutions.
Vladan
1
Un problème avec ceci est qu'il renvoie une chaîne vide si la chaîne ne contient pas d'espace.
1er
Peut être simplifié en:$WidgetText = substr($string, 0, strpos($string, ' ', 200));
wranvaud
36

La solution suivante est née lorsque j'ai remarqué un paramètre $ break de la fonction wordwrap :

string wordwrap (string $ str [, int $ width = 75 [, string $ break = "\ n" [, bool $ cut = false]]])

Voici la solution :

/**
 * Truncates the given string at the specified length.
 *
 * @param string $str The input string.
 * @param int $width The number of chars at which the string will be truncated.
 * @return string
 */
function truncate($str, $width) {
    return strtok(wordwrap($str, $width, "...\n"), "\n");
}

Exemple 1.

print truncate("This is very long string with many chars.", 25);

L'exemple ci-dessus affichera:

This is very long string...

Exemple n ° 2.

print truncate("This is short string.", 25);

L'exemple ci-dessus affichera:

This is short string.
Sergiy Sokolenko
la source
2
cela ne fonctionne pas si la chaîne a déjà un caractère de nouvelle ligne (par exemple si vous essayez d'extraire un article descriptionde blog)
supersan
1
@supersan Peut toujours prétraiter avec preg_replace('/\s+/', ' ', $description)pour remplacer tous les caractères d'espacement par un seul espace;)
Mavelo
9

Gardez à l'esprit lorsque vous divisez par "mot" n'importe où que certaines langues telles que le chinois et le japonais n'utilisent pas de caractère espace pour séparer les mots. En outre, un utilisateur malveillant pourrait simplement entrer du texte sans espaces, ou utiliser un aspect Unicode similaire au caractère d'espace standard, auquel cas toute solution que vous utilisez peut finir par afficher le texte entier de toute façon. Une façon de contourner cela peut être de vérifier la longueur de la chaîne après l'avoir divisée sur des espaces comme d'habitude, puis, si la chaîne est toujours au-dessus d'une limite anormale - peut-être 225 caractères dans ce cas - continuez et divisez-la bêtement à cette limite.

Une autre mise en garde avec des choses comme celle-ci lorsqu'il s'agit de caractères non ASCII; les chaînes les contenant peuvent être interprétées par strlen () standard de PHP comme étant plus longues qu'elles ne le sont réellement, car un seul caractère peut prendre deux octets ou plus au lieu d'un seul. Si vous utilisez simplement les fonctions strlen () / substr () pour fractionner des chaînes, vous pouvez fractionner une chaîne au milieu d'un caractère! En cas de doute, mb_strlen () / mb_substr () sont un peu plus infaillibles.

Garrett Albright
la source
8

Utilisez strpos et substr:

<?php

$longString = "I have a code snippet written in PHP that pulls a block of text.";
$truncated = substr($longString,0,strpos($longString,' ',30));

echo $truncated;

Cela vous donnera une chaîne tronquée au premier espace après 30 caractères.

Lucas Oman
la source
1
Salut, si la longueur de la chaîne sans espace sera inférieure à 30, alors ce sera une erreur de retour. et Ici le résultat sera de 31 premiers caractères et non de 30 ..
Er. Anurag Jain
5

Voici:

function neat_trim($str, $n, $delim='…') {
   $len = strlen($str);
   if ($len > $n) {
       preg_match('/(.{' . $n . '}.*?)\b/', $str, $matches);
       return rtrim($matches[1]) . $delim;
   }
   else {
       return $str;
   }
}
UnkwnTech
la source
Merci, j'ai trouvé la vôtre la fonction la plus utile et la plus fiable de toutes ces réponses pour mes besoins. Cependant, comment puis-je lui faire prendre en charge les chaînes multi-octets?
ctrlbrk
5

Voici ma fonction basée sur l'approche de @ Cd-MaN.

function shorten($string, $width) {
  if(strlen($string) > $width) {
    $string = wordwrap($string, $width);
    $string = substr($string, 0, strpos($string, "\n"));
  }

  return $string;
}
Camsoft
la source
4
$shorttext = preg_replace('/^([\s\S]{1,200})[\s]+?[\s\S]+/', '$1', $fulltext);

La description:

  • ^ - commencer au début de la chaîne
  • ([\s\S]{1,200}) - obtenez de 1 à 200 de n'importe quel caractère
  • [\s]+?- ne pas inclure d'espaces à la fin du texte court afin que nous puissions éviter word ...au lieu deword...
  • [\s\S]+ - correspond à tous les autres contenus

Tests:

  1. regex101.comajoutons à orquelques autresr
  2. regex101.com orrrr exactement 200 caractères.
  3. regex101.comaprès cinquième r orrrrrexclu.

Prendre plaisir.

hlcs
la source
je ne comprends pas la documentation PHP. je sais que le $1est un "remplacement", mais dans ce contexte spécifique à quoi fait-il référence ?? une variable vide?
oldboy
1
@Anthony se $1référant à la correspondance entre crochets ([\s\S]{1,200}). $2fera référence à deux secondes paires de crochets s'il y en a dans le modèle.
hlcs
3

Il est surprenant de voir à quel point il est difficile de trouver la solution parfaite à ce problème. Je n'ai pas encore trouvé de réponse sur cette page qui n'échoue pas dans au moins certaines situations (surtout si la chaîne contient des retours à la ligne ou des tabulations, ou si le mot break est autre chose qu'un espace, ou si la chaîne a UTF- 8 caractères multi-octets).

Voici une solution simple qui fonctionne dans tous les cas. Il y avait des réponses similaires ici, mais le modificateur "s" est important si vous voulez qu'il fonctionne avec une entrée multiligne, et le modificateur "u" lui permet d'évaluer correctement les caractères multioctets UTF-8.

function wholeWordTruncate($s, $characterCount) 
{
    if (preg_match("/^.{1,$characterCount}\b/su", $s, $match)) return $match[0];
    return $s;
}

Un cas de bord possible avec ceci ... si la chaîne ne contient aucun espace dans les premiers caractères $ characterCount, elle renverra la chaîne entière. Si vous préférez qu'il force une rupture à $ characterCount même s'il ne s'agit pas d'une limite de mot, vous pouvez utiliser ceci:

function wholeWordTruncate($s, $characterCount) 
{
    if (preg_match("/^.{1,$characterCount}\b/su", $s, $match)) return $match[0];
    return mb_substr($return, 0, $characterCount);
}

Une dernière option, si vous voulez qu'elle ajoute des points de suspension si elle tronque la chaîne ...

function wholeWordTruncate($s, $characterCount, $addEllipsis = ' …') 
{
    $return = $s;
    if (preg_match("/^.{1,$characterCount}\b/su", $s, $match)) 
        $return = $match[0];
    else
        $return = mb_substr($return, 0, $characterCount);
    if (strlen($s) > strlen($return)) $return .= $addEllipsis;
    return $return;
}
Orrd
la source
2

J'utiliserais la fonction preg_match pour ce faire, car ce que vous voulez est une expression assez simple.

$matches = array();
$result = preg_match("/^(.{1,199})[\s]/i", $text, $matches);

L'expression signifie "correspond à toute sous-chaîne commençant au début de la longueur 1-200 qui se termine par un espace." Le résultat est dans $ result et la correspondance est dans $ matches. Cela prend en charge votre question initiale, qui se termine spécifiquement sur n'importe quel espace. Si vous voulez que cela se termine sur les nouvelles lignes, remplacez l'expression régulière par:

$result = preg_match("/^(.{1,199})[\n]/i", $text, $matches);
Justin Poliey
la source
2

Ok donc j'ai une autre version de ceci basée sur les réponses ci-dessus mais en tenant compte de plus de choses (utf-8, \ n et & nbsp;), aussi une ligne dépouillant les shortcodes wordpress commentés s'ils sont utilisés avec wp.

function neatest_trim($content, $chars) 
  if (strlen($content) > $chars) 
  {
    $content = str_replace('&nbsp;', ' ', $content);
    $content = str_replace("\n", '', $content);
    // use with wordpress    
    //$content = strip_tags(strip_shortcodes(trim($content)));
    $content = strip_tags(trim($content));
    $content = preg_replace('/\s+?(\S+)?$/', '', mb_substr($content, 0, $chars));

    $content = trim($content) . '...';
    return $content;
  }
Yo-L
la source
2

Voici une petite solution pour la réponse de mattmac:

preg_replace('/\s+?(\S+)?$/', '', substr($string . ' ', 0, 201));

La seule différence est d'ajouter un espace à la fin de $ string. Cela garantit que le dernier mot n'est pas coupé selon le commentaire de ReX357.

Je n'ai pas assez de points de répétition pour ajouter ceci en commentaire.

tanc
la source
2
/*
Cut the string without breaking any words, UTF-8 aware 
* param string $str The text string to split
* param integer $start The start position, defaults to 0
* param integer $words The number of words to extract, defaults to 15
*/
function wordCutString($str, $start = 0, $words = 15 ) {
    $arr = preg_split("/[\s]+/",  $str, $words+1);
    $arr = array_slice($arr, $start, $words);
    return join(' ', $arr);
}

Usage:

$input = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna liqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.';
echo wordCutString($input, 0, 10); 

Cela produira les 10 premiers mots.

La preg_splitfonction est utilisée pour diviser une chaîne en sous-chaînes. Les limites le long desquelles la chaîne doit être divisée sont spécifiées à l'aide d'un modèle d'expressions régulières.

preg_split La fonction prend 4 paramètres, mais seuls les 3 premiers sont pertinents pour nous en ce moment.

Premier paramètre - Modèle Le premier paramètre est le modèle d'expressions régulières le long duquel la chaîne doit être divisée. Dans notre cas, nous voulons diviser la chaîne à travers les limites des mots. Par conséquent, nous utilisons une classe de caractères prédéfinie\s qui correspond aux caractères d'espace blanc tels que l'espace, la tabulation, le retour chariot et le saut de ligne.

Deuxième paramètre - Chaîne d'entrée Le deuxième paramètre est la longue chaîne de texte que nous voulons diviser.

Troisième paramètre - Limite Le troisième paramètre spécifie le nombre de sous-chaînes à renvoyer. Si vous définissez la limite sur n, preg_split renverra un tableau de n éléments. Les premiers n-1éléments contiendront les sous-chaînes. Le dernier (n th)élément contiendra le reste de la chaîne.

Bud Damyanov
la source
1

Basé sur l'expression régulière de @Justin Poliey:

// Trim very long text to 120 characters. Add an ellipsis if the text is trimmed.
if(strlen($very_long_text) > 120) {
  $matches = array();
  preg_match("/^(.{1,120})[\s]/i", $very_long_text, $matches);
  $trimmed_text = $matches[0]. '...';
}
barista amateur
la source
1

J'ai une fonction qui fait presque ce que vous voulez, si vous faites quelques modifications, elle s'adaptera exactement:

<?php
function stripByWords($string,$length,$delimiter = '<br>') {
    $words_array = explode(" ",$string);
    $strlen = 0;
    $return = '';
    foreach($words_array as $word) {
        $strlen += mb_strlen($word,'utf8');
        $return .= $word." ";
        if($strlen >= $length) {
            $strlen = 0;
            $return .= $delimiter;
        }
    }
    return $return;
}
?>
Rikudou sennin
la source
1

Voici comment je l'ai fait:

$string = "I appreciate your service & idea to provide the branded toys at a fair rent price. This is really a wonderful to watch the kid not just playing with variety of toys but learning faster compare to the other kids who are not using the BooksandBeyond service. We wish you all the best";

print_r(substr($string, 0, strpos(wordwrap($string, 250), "\n")));
Shashank Saxena
la source
0

Je sais que c'est vieux, mais ...

function _truncate($str, $limit) {
    if(strlen($str) < $limit)
        return $str;
    $uid = uniqid();
    return array_shift(explode($uid, wordwrap($str, $limit, $uid)));
}
gosukiwi
la source
0

Je crée une fonction plus similaire à substr, et en utilisant l'idée de @Dave.

function substr_full_word($str, $start, $end){
    $pos_ini = ($start == 0) ? $start : stripos(substr($str, $start, $end), ' ') + $start;
    if(strlen($str) > $end){ $pos_end = strrpos(substr($str, 0, ($end + 1)), ' '); } // IF STRING SIZE IS LESSER THAN END
    if(empty($pos_end)){ $pos_end = $end; } // FALLBACK
    return substr($str, $pos_ini, $pos_end);
}

Ps.: La longueur totale de la coupe peut être inférieure à substr.

evandro777
la source
0

Ajout d'instructions IF / ELSEIF au code de Dave et AmalMurali pour gérer les chaînes sans espaces

if ((strpos($string, ' ') !== false) && (strlen($string) > 200)) { 
    $WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' ')); 
} 
elseif (strlen($string) > 200) {
    $WidgetText = substr($string, 0, 200);
}
jdorenbush
la source
0

Je trouve que cela fonctionne:

function abbreviate_string_to_whole_word ($ string, $ max_length, $ buffer) {

if (strlen($string)>$max_length) {
    $string_cropped=substr($string,0,$max_length-$buffer);
    $last_space=strrpos($string_cropped, " ");
    if ($last_space>0) {
        $string_cropped=substr($string_cropped,0,$last_space);
    }
    $abbreviated_string=$string_cropped."&nbsp;...";
}
else {
    $abbreviated_string=$string;
}

return $abbreviated_string;

}

Le tampon vous permet d'ajuster la longueur de la chaîne renvoyée.

Mat Barnett
la source
0

Utilisez ceci:

le code suivant supprimera ','. Si vous avez un autre caractère ou sous-chaîne, vous pouvez l'utiliser à la place de ','

substr($string, 0, strrpos(substr($string, 0, $comparingLength), ','))

// si vous avez un autre compte chaîne pour

substr($string, 0, strrpos(substr($string, 0, $comparingLength-strlen($currentString)), ','))
Mahbub Alam
la source
0

Bien que ce soit une question plutôt ancienne, j'ai pensé que je fournirais une alternative, car elle n'était pas mentionnée et valable pour PHP 4.3+.

Vous pouvez utiliser la sprintffamille de fonctions pour tronquer du texte, en utilisant le %.ℕsmodificateur de précision.

Un point .suivi d'un entier dont la signification dépend du spécificateur:

  • Pour les spécificateurs e, E, f et F: il s'agit du nombre de chiffres à imprimer après la virgule décimale (par défaut, il s'agit de 6).
  • Pour les spécificateurs g et G: il s'agit du nombre maximum de chiffres significatifs à imprimer.
  • Pour le spécificateur s: il agit comme un point de coupure, définissant une limite maximale de caractères pour la chaîne

Troncature simple https://3v4l.org/QJDJU

$string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var_dump(sprintf('%.10s', $string));

Résultat

string(10) "0123456789"

Troncature étendue https://3v4l.org/FCD21

Puisque sprintffonctionne de la même manière substret coupera partiellement les mots. L'approche ci-dessous garantira que les mots ne sont pas coupés en utilisantstrpos(wordwrap(..., '[break]'), '[break]') un délimiteur spécial. Cela nous permet de récupérer la position et de nous assurer que nous ne correspondons pas aux structures de phrases standard.

Renvoyer une chaîne sans couper partiellement les mots et qui ne dépasse pas la largeur spécifiée, tout en préservant les sauts de ligne si vous le souhaitez.

function truncate($string, $width, $on = '[break]') {
    if (strlen($string) > $width && false !== ($p = strpos(wordwrap($string, $width, $on), $on))) {
        $string = sprintf('%.'. $p . 's', $string);
    }
    return $string;
}
var_dump(truncate('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', 20));

var_dump(truncate("Lorem Ipsum is simply dummy text of the printing and typesetting industry.", 20));

var_dump(truncate("Lorem Ipsum\nis simply dummy text of the printing and typesetting industry.", 20));

Résultat

/* 
string(36) "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
string(14) "Lorem Ipsum is" 
string(14) "Lorem Ipsum
is" 
*/

Résultats utilisant wordwrap($string, $width)oustrtok(wordwrap($string, $width), "\n")

/*
string(14) "Lorem Ipsum is"
string(11) "Lorem Ipsum"
*/
fyrye
la source
-1

Je l'ai utilisé avant

<?php
    $your_desired_width = 200;
    $string = $var->content;
    if (strlen($string) > $your_desired_width) {
        $string = wordwrap($string, $your_desired_width);
        $string = substr($string, 0, strpos($string, "\n")) . " More...";
    }
    echo $string;
?>
Yousef Altaf
la source
-1

Ici vous pouvez essayer ceci

substr( $str, 0, strpos($str, ' ', 200) ); 
Abhijeet kumar sharma
la source
Cette solution a déjà été mentionnée dans d'autres réponses. Le problème avec cela est qu'il échoue si la chaîne est inférieure à la longueur de 200 caractères ou si elle ne contient aucun espace. Il ne limite pas non plus la chaîne à 200 caractères, mais rompt la chaîne à l'espace après 200 caractères, ce qui n'est généralement pas ce que vous voulez.
orrd
-1

Je pense que c'est le moyen le plus simple de le faire:

$lines = explode('♦♣♠',wordwrap($string, $length, '♦♣♠'));
$newstring = $lines[0] . ' &bull; &bull; &bull;';

J'utilise les caractères spéciaux pour diviser le texte et le couper.

Namida
la source
-2

Peut-être que cela aidera quelqu'un:

<?php

    $string = "Your line of text";
    $spl = preg_match("/([, \.\d\-''\"\"_()]*\w+[, \.\d\-''\"\"_()]*){50}/", $string, $matches);
    if (isset($matches[0])) {
        $matches[0] .= "...";
        echo "<br />" . $matches[0];
    } else {
        echo "<br />" . $string;
    }

?>
slash3b
la source