¿Cómo truncar una cadena en PHP a la palabra más cercana a una determinada cantidad de caracteres?

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

  •  09-06-2019
  •  | 
  •  

Pregunta

Tengo un fragmento de código escrito en PHP que extrae un bloque de texto de una base de datos y lo envía a un widget en una página web.El bloque de texto original puede ser un artículo extenso o una o dos frases cortas;pero para este widget no puedo mostrar más de, digamos, 200 caracteres.Podría usar substr() para cortar el texto en 200 caracteres, pero el resultado sería un corte en medio de las palabras; lo que realmente quiero es cortar el texto al final de la última palabra antes de 200 caracteres.

¿Fue útil?

Solución

Al utilizar el ajuste de línea función.Divide los textos en varias líneas de modo que el ancho máximo sea el que usted especificó, rompiendo los límites de las palabras.Después de dividir, simplemente toma la primera línea:

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

Una cosa que este oneliner no maneja es el caso en el que el texto en sí es más corto que el ancho deseado.Para manejar este caso límite, se debe hacer algo como:

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

La solución anterior tiene el problema de cortar prematuramente el texto si contiene una nueva línea antes del punto de corte real.Aquí una versión que resuelve este problema:

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

Además, aquí está la clase de prueba PHPUnit utilizada para probar la implementación:

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

EDITAR :

No se manejan caracteres especiales UTF8 como 'à'.Agregue 'u' al final de REGEX para manejarlo:

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

Otros consejos

Esto devolverá los primeros 200 caracteres de palabras:

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

Y ahí lo tiene: un método confiable para truncar cualquier cadena a la palabra completa más cercana, mientras se mantiene por debajo de la longitud máxima de la cadena.

Probé los otros ejemplos anteriores y no produjeron los resultados deseados.

La siguiente solución nació cuando noté un parámetro $break de ajuste de línea función:

String WordWrap (String $ str [, int $ width = 75 [, string $ break = " n" [, bool $ Cut = false]]])

Aquí está la solución:

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

Ejemplo 1.

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

El ejemplo anterior generará:

This is very long string...

Ejemplo #2.

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

El ejemplo anterior generará:

This is short string.

Cuando divida por "palabra" en cualquier lugar, tenga en cuenta que algunos idiomas, como el chino y el japonés, no utilizan un carácter de espacio para dividir palabras.Además, un usuario malintencionado podría simplemente ingresar texto sin espacios, o usar algún Unicode similar al carácter de espacio estándar, en cuyo caso cualquier solución que use puede terminar mostrando el texto completo de todos modos.Una forma de evitar esto puede ser verificar la longitud de la cadena después de dividirla en espacios como de costumbre, luego, si la cadena todavía está por encima de un límite anormal (tal vez 225 caracteres en este caso), seguir adelante y dividirla tontamente en ese límite.

Una advertencia más con cosas como esta cuando se trata de caracteres que no son ASCII;Las cadenas que los contienen pueden ser interpretadas por el estándar strlen() de PHP como más largas de lo que realmente son, porque un solo carácter puede ocupar dos o más bytes en lugar de solo uno.Si solo usas las funciones strlen()/substr() para dividir cadenas, ¡puedes dividir una cadena en medio de un carácter!En caso de duda, mb_strlen()/mb_substr() son un poco más infalibles.

Utilice strpos y 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;

Esto le dará una cadena truncada en el primer espacio después de 30 caracteres.

Aquí está mi función basada en el enfoque de @Cd-MaN.

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

  return $string;
}

Aquí tienes:

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

Es sorprendente lo complicado que es encontrar la solución perfecta a este problema.Todavía no he encontrado una respuesta en esta página que no falle al menos en algunas situaciones (especialmente si la cadena contiene nuevas líneas o tabulaciones, o si la palabra salto no es un espacio, o si la cadena tiene UTF- 8 caracteres multibyte).

Aquí tienes una solución sencilla que funciona en todos los casos.Hubo respuestas similares aquí, pero el modificador "s" es importante si desea que funcione con entradas de varias líneas, y el modificador "u" hace que evalúe correctamente los caracteres multibyte UTF-8.

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

Un posible caso límite con esto...si la cadena no tiene ningún espacio en blanco en los primeros caracteres $characterCount, devolverá la cadena completa.Si prefieres que fuerce una ruptura en $characterCount incluso si no es un límite de palabra, puedes usar esto:

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

Una última opción, si desea que agregue puntos suspensivos si trunca la cadena...

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

Descripción:

  • ^ - empezar desde el principio de la cadena
  • ([\s\S]{1,200}) - obtener de 1 a 200 de cualquier personaje
  • [\s]+? - No incluir espacios al final del texto corto para que podamos evitar word ... en lugar de word...
  • [\s\S]+ - coincide con todos los demás contenidos

Pruebas:

  1. regex101.com agreguemos a or algunos otros r
  2. regex101.com orrrr exactamente 200 caracteres.
  3. regex101.com después del quinto r orrrrr excluido.

Disfrutar.

Usaría la función preg_match para hacer esto, ya que lo que quieres es una expresión bastante simple.

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

La expresión significa "coincidir con cualquier subcadena a partir del comienzo de la longitud 1-200 que termina con un espacio". El resultado está en $ resultado, y la coincidencia está en $ coincidencias.Eso soluciona su pregunta original, que termina específicamente en cualquier espacio.Si desea que termine en nuevas líneas, cambie la expresión regular a:

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

Ok, obtuve otra versión de esto basada en las respuestas anteriores pero tomando más cosas en cuenta (utf-8, y  ), también una línea que elimina los códigos cortos de wordpress comentados si se usan con 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);
}

Uso:

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

Esto generará las primeras 10 palabras.

El preg_split La función se utiliza para dividir una cadena en subcadenas.Los límites a lo largo de los cuales se dividirá la cadena se especifican mediante un patrón de expresiones regulares.

preg_split La función toma 4 parámetros, pero solo los primeros 3 son relevantes para nosotros en este momento.

Primer parámetro: el patrón El primer parámetro es el patrón de expresiones regulares a lo largo del cual se va a dividir la cadena.En nuestro caso, queremos dividir la cadena entre los límites de las palabras.Por lo tanto utilizamos una clase de carácter predefinida. \s que coincide con caracteres de espacios en blanco como espacio, tabulación, retorno de carro y avance de línea.

Segundo parámetro: la cadena de entrada El segundo parámetro es la cadena de texto larga que queremos dividir.

Tercer parámetro: limite el tercer parámetro especifica el número de subcadenas que deben devolverse.Si establece el límite en n, preg_split devolverá una matriz de n elementos.La primera n-1 Los elementos contendrán las subcadenas.El último (n th) El elemento contendrá el resto de la cadena.

Basado en la expresión regular 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]. '...';
}

Esta es una pequeña solución para la respuesta de Mattmac:

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

La única diferencia es agregar un espacio al final de $string.Esto garantiza que la última palabra no se corte según el comentario de ReX357.

No tengo suficientes puntos de reputación para agregar esto como comentario.

Tengo una función que hace casi lo que quieres, si haces algunas ediciones, encajará exactamente:

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

Así es como lo hice:

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

Sé que esto es viejo, pero...

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

Usé esto antes

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

Creo una función más similar a substr y uso la idea 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);
}

PD.:El corte completo puede ser menor que substr.

Se agregaron declaraciones IF/ELSEIF al código de dave y amalmurali para manejar cadenas sin espacios

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

Creo que esta es la forma más sencilla de hacerlo:

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

Estoy usando caracteres especiales para dividir el texto y cortarlo.

Encuentro que esto funciona:

función 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;

}

El búfer le permite ajustar la longitud de la cadena devuelta.

Utilizar esta:

el siguiente código eliminará ','.Si tiene algún otro carácter o subcadena, puede usarlo en lugar de ','

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

// si tienes otra cuenta de cadena para

substr($string, 0, strrpos(substr($string, 0, $comparingLength-strlen($currentString)), ','))

Aquí puedes probar esto.

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

Quizás esto ayude a alguien:

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

?>
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top