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

StackOverflow https://stackoverflow.com/questions/79960

  •  09-06-2019
  •  | 
  •  

Question

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.

Était-ce utile?

La solution

En utilisant la fonction wordwrap . Il divise les textes en plusieurs lignes de manière à ce que la largeur maximale soit celle spécifiée, dépassant ainsi les limites des mots. 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, 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 tests PHPUnit utilisée pour tester la mise en oeuvre:

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

MODIFIER:

Caractères UTF8 spéciaux tels que '& # 224;' ne sont pas manipulés. Ajoutez "u" à la fin du REGEX pour le gérer:

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

Autres conseils

Ceci renverra les 200 premiers caractères des mots:

preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, 201));
$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 donné les résultats souhaités.

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

  

chaîne wordwrap (chaîne $ str [, int $ width = 75 [, chaîne $ 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 n ° 1.

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

L'exemple ci-dessus générera:

This is very long string...

Exemple n ° 2.

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

L'exemple ci-dessus générera:

This is short string.

N'oubliez pas que chaque fois que vous vous séparez par un "mot". Partout où certaines 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 entrer 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.

Encore une mise en garde 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 () sont un peu plus sûrs.

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;

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

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

Voilà:

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

Il est surprenant de constater à 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 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 "s" Le modificateur est important si vous voulez que cela fonctionne avec une entrée multiligne et que l'option "u" Le modificateur 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 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 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 souhaitez lui faire ajouter 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;
}
$shorttext = preg_replace('/^([\s\S]{1,200})[\s]+?[\s\S]+/', '$1', $fulltext);

Description:

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

Tests:

  1. regex101.com ajoutons à ou quelques autres r
  2. regex101.com orrrr exactement 200 caractères.
  3. regex101.com après le cinquième r orrrrr exclu.

Profitez.

Je voudrais utiliser la fonction preg_match pour le 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 "faire correspondre toute chaîne de caractères commençant au début de la longueur 1-200 qui se termine par un espace". Le résultat est en $ result et la correspondance en $ correspond. Cela répond à votre question initiale, qui se termine spécifiquement sur n'importe quel espace. Si vous souhaitez le terminer sur les nouvelles lignes, modifiez l'expression régulière en:

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

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

Utilisation:

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

Ceci 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 sont pertinents pour nous maintenant.

Premier paramètre - Motif 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 fractionner la chaîne entre les mots. Par conséquent, nous utilisons une classe de caractères prédéfinie \ s qui correspond aux caractères d'espaces, 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 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 éléments n-1 contiendront les sous-chaînes. Le dernier élément (n ème) contiendra le reste de la chaîne.

D'après 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]. '...';
}

Ceci est 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 en tant que commentaire.

J'ai une fonction qui fait presque ce que vous voulez, si vous faites quelques modifications, elle ira 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;
}
?>

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

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

Je l'ai déjà utilisé

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

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 à la valeur de base.

Ajout des instructions IF / ELSEIF au code provenant de Dave et AmalMurali pour manipulation 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);
}

Je pense 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.

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 de régler la longueur de la chaîne renvoyée.

Utilisez ceci:

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

Ici, vous pouvez essayer ceci

substr( $str, 0, strpos($str, ' ', 200) ); 

Cela aidera peut-être 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;
    }

?>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top