web-dev-qa-db-fra.com

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

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 situé sur une page Web. Le bloc de texte original peut être un long article, une phrase courte ou deux. mais pour ce widget, je ne peux pas afficher plus de 200 caractères. Je pourrais utiliser substr () pour couper le texte à 200 caractères, mais le résultat serait couper au milieu de mots - ce que je veux vraiment, c'est couper le texte à la fin du dernier. Mot avant 200 caractères.

170
Brian

En utilisant la fonction wordwrap . Il divise les textes en plusieurs lignes de telle sorte que la largeur maximale soit celle spécifiée, en respectant les limites de Word. Après division, vous prenez simplement la première ligne:

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

Ce que l’oneliner ne gère pas, c’est le cas lorsque le texte lui-même est plus court que la largeur souhaitée. Pour gérer ce cas Edge, il faut 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));
}

En outre, voici la classe de test PHPUnit utilisée pour tester la mise en œuvre:

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));
  }
}

EDIT:

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);

214
Grey Panther

Cela renverra les 200 premiers caractères des mots:

preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, 201));
125
mattmac
$WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' '));

Et voilà: une méthode fiable pour tronquer une chaîne au mot entier le plus proche, tout en restant sous la longueur maximale de la chaîne.

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

42
Dave

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

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 # 2.

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

L'exemple ci-dessus affichera:

This is short string.
33
Sergiy Sokolenko

N'oubliez pas, chaque fois que vous séparez par «Word», que des langues telles que le chinois et le japonais n'utilisent pas de caractère d'espacement pour séparer des mots. En outre, un utilisateur malveillant pourrait simplement saisir du texte sans espaces ou utiliser un caractère similaire à Unicode pour remplacer le caractère d’espace standard. Dans ce cas, toute solution que vous utiliserez pourrait quand même afficher le texte en entier. Une solution consiste à vérifier la longueur de la chaîne après l'avoir divisé normalement, puis, si la chaîne dépasse toujours une limite anormale (peut-être 225 caractères dans ce cas), continuez à la diviser bêtement à cette limite.

Une mise en garde supplémentaire avec des choses comme celle-ci en ce qui concerne les caractères non-ASCII; Les chaînes qui les contiennent 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 uniquement les fonctions strlen ()/substr () pour scinder des chaînes, vous pouvez scinder une chaîne au milieu d'un caractère! En cas de doute, mb_strlen () / mb_substr () est un peu plus sûr.

9
Garrett Albright

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.

8
Lucas Oman

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;
}
5
Camsoft

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;
   }
}
4
UnkwnTech

Il est surprenant de constater combien 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 nouvelles lignes ou des tabulations, ou si le saut de mot est autre chose qu'un espace, ou si la chaîne a des caractères 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 que cela fonctionne avec une entrée multiligne, et le modificateur "u" permet d'évaluer correctement les caractères multi-octets UTF-8.

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

Un cas Edge possible avec ceci ... si la chaîne ne comporte aucun espace dans les premiers caractères $ characterCount, elle renverra la chaîne entière. Si vous préférez, cela force un saut dans $ characterCount même s'il ne s'agit pas d'une limite de Word, 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 le faire ajouter Ellipsis s'il 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;
}
3
orrd
$shorttext = preg_replace('/^([\s\S]{1,200})[\s]+?[\s\S]+/', '$1', $fulltext);

La description:

  • ^ - commence au début de la chaîne
  • ([\s\S]{1,200}) - obtenez de 1 à 200 caractères
  • [\s]+? - n'inclut pas d'espaces à la fin du texte court afin d'éviter Word ... au lieu de Word...
  • [\s\S]+ - correspond à tout autre contenu

Tests:

  1. regex101.com ajoutons à or quelques autres r
  2. regex101.comorrrr exactement 200 caractères.
  3. regex101.com après la cinquième rorrrrr exclue.

Prendre plaisir.

3
hlcs

Ok, j’ai eu une autre version de ceci basée sur les réponses ci-dessus mais en prenant en compte plus de choses (utf-8,\n et & nbsp;), également une ligne supprimant les shortcodes wordpress commentés s’ils étaient 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;
  }
2
Yo-L
/*
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 fonction preg_split permet de scinder une chaîne en sous-chaînes. Les limites le long desquelles la chaîne doit être scindée sont spécifiées à l'aide d'un modèle d'expressions régulières.

La fonction preg_split prend 4 paramètres, mais seuls les 3 premiers nous concernent pour le 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 scindée. Dans notre cas, nous voulons diviser la chaîne entre les limites de Word. Par conséquent, nous utilisons une classe de caractères prédéfinie \s qui correspond aux caractères d'espacement, tels que espace, tabulation, retour à la ligne et saut de ligne.

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

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 éléments n-1 contiendront les sous-chaînes. Le dernier élément (n th) contiendra le reste de la chaîne.

2
bodi0

Je voudrais utiliser la fonction preg_match pour faire cela, car ce que vous voulez est une expression assez simple.

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

L'expression signifie "faire correspondre toute chaîne à partir du début de la longueur 1-200 qui se termine par un espace". Le résultat est en $ résultat et la correspondance en $ correspondances. Cela répond à votre question initiale, qui se termine spécifiquement sur n'importe quel espace. Si vous souhaitez que cela se termine sur les nouvelles lignes, modifiez l'expression régulière en:

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

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")));
1
Shashank Saxena

J'ai une fonction qui fait presque ce que vous voulez, si vous faites quelques modifications, elle conviendra parfaitement:

<?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;
}
?>
1
Rikudou_Sennin

Basé sur les regex 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]. '...';
}
1
amateur barista

Voici un petit correctif 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 rep pour ajouter ceci comme commentaire.

1
tanc

Ajout d'instructions IF/ELSEIF au code de Dave et AmalMurali pour la gestion de 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);
}
0
jdorenbush

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)));
}
0
gosukiwi

Je crée une fonction plus similaire à substr, 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 à celle du substrat.

0
evandro777

Je crois que c'est la façon la plus simple de le faire:

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

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

0
Namida

J'ai déjà utilisé ça

<?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;
?>
0
Yousef Altaf

Je trouve que ça marche:

fonction 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;

}

La mémoire tampon vous permet de régler la longueur de la chaîne renvoyée.

0
Mat Barnett

Utilisez ceci: 

le code suivant supprimera ','. Si vous avez un autre caractère ou sous-chaîne, vous pouvez utiliser cela au lieu 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)), ','))
0
Mahbub Alam