¿Cómo truncar una cadena en PHP a la palabra más cercana a un cierto número de caracteres?

183

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 largo o una oración corta o dos; 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 cortar en el medio de las palabras; lo que realmente quiero es cortar el texto al final de la última palabra antes de 200 caracteres.

Brian
fuente
2
La pregunta pretende decir que el texto truncado se ajustará en un número fijo de píxeles en una página web. En este caso, dependiendo de la fuente elegida, el espacio requerido por char no es constante. Y, por lo tanto, no podemos suponer que 200 caracteres encajarán mejor en píxeles disponibles. Hasta ahora (hasta el 02 de marzo de 2011), todas las respuestas a continuación no tienen este punto y, por lo tanto, ninguna de ellas proporciona una solución confiable. - :(
LionHeart
1
No, realmente no. Puede configurar la fuente de manera confiable, y luego medir el peor de los casos, es decir, cuántos caracteres más anchos encajarían. Y si necesita estar 100% seguro de cómo el navegador lo representa, ya no es un problema de PHP.
Mołot
Pruebe este enlace, puede ayudarle a stackoverflow.com/a/26098951/3944217
edCoder
Puede ser s($str)->truncateSafely(200)útil, como se encuentra en esta biblioteca independiente .
caw

Respuestas:

221

Mediante el uso de la función wordwrap . Divide los textos en varias líneas, de modo que el ancho máximo es el que 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 cuando el texto en sí es más corto que el ancho deseado. Para manejar este caso límite, uno 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 del REGEX para manejarlo:

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

Pantera gris
fuente
1
Esto parece cortar prematuramente el texto si hay un \nancho anterior al deseado.
Kendall Hopkins
@KendallHopkins: cierto, de hecho hay un problema. Actualicé la respuesta con una implementación alternativa que resuelve el problema dado.
Pantera Gris
¿Funcionaría este ejemplo para una cadena que contiene etiquetas html como etiquetas de párrafo?
limitlessloop
es realmente útil para mí, mi dolor de cabeza eran Arabicletras largas y ahora se reduce a palabras correctas con la ayuda de la tokenTruncatefunción ... tnx un millón :)
Aditya P Bhatt
1
¿Por qué no agregar: if (strlen ($ string) <= $ your_desired_width) return $ string; como primera declaración?
Darko Romanov
139

Esto devolverá los primeros 200 caracteres de palabras:

preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, 201));
Mattmac
fuente
77
Casi. Parece que elimina la última palabra de la oración para mí, pase lo que pase.
ReX357
funciona muy bien pero encontré el mismo error que ReX357. Cuando hay más de 1 palabra, elimina la última.
Andres SK
25
Simplemente envuélvalo en un cheque para asegurarse de que la cadena sea más larga de lo que está probando (igual que la respuesta aceptada)if (strlen($string) > $your_desired_width) { preg_replace(...); }
Blair McMillan
Edité la respuesta para incluir el consejo de @BlairMcMillan
Kim Stacks el
2
Pequeña mejora en la expresión regular: los paréntesis hacen que el \ S + final sea opcional para el partido, pero también capturan esos caracteres. Como no necesitamos capturar esos caracteres, haga que los paréntesis no se /\s+?(?:\S+)?$/
capturen
45
$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.

He probado los otros ejemplos anteriores y no produjeron los resultados deseados.

Dave
fuente
11
Si la longitud de la cadena dada es menor que la longitud máxima, esto cortaría todo hasta el último espacio. Para evitar esto, envuelva esto dentro de una ifdeclaración:if (strlen($str) > 200) { ... }
Amal Murali
Simple y probablemente mucho más rápido que otras soluciones.
Vladan
1
Un problema con esto es que devuelve una cadena vacía si la cadena no contiene un espacio.
orden
Se puede simplificar a:$WidgetText = substr($string, 0, strpos($string, ' ', 200));
wranvaud
36

La siguiente solución nació cuando noté un parámetro $ break de la función wordwrap :

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.
Sergiy Sokolenko
fuente
2
esto no funciona si la cadena ya tiene un nuevo carácter de línea (por ejemplo, si está tratando de extraer una descriptionde una publicación de blog)
supersan
1
@supersan Siempre puede preprocesar con preg_replace('/\s+/', ' ', $description)para reemplazar todos los caracteres de espacios en blanco con un solo espacio;)
Mavelo
9

Tenga en cuenta siempre que esté dividiendo por "palabra" en cualquier lugar donde algunos idiomas, como el chino y el japonés, no utilicen un carácter de espacio para dividir las palabras. Además, un usuario malintencionado podría simplemente ingresar texto sin espacios, o usar un poco de 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 es normal, 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 no ASCII; las cadenas que las contienen pueden ser interpretadas por strlen () estándar de PHP como más largas de lo que realmente son, porque un solo carácter puede tomar dos o más bytes en lugar de solo uno. Si solo usa las funciones strlen () / substr () para dividir cadenas, ¡puede dividir una cadena en el medio de un carácter! En caso de duda, mb_strlen () / mb_substr () son un poco más infalibles.

Garrett Albright
fuente
8

Use 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.

Lucas Oman
fuente
1
Hola, si la longitud de la cadena sin espacio será inferior a 30, se devolverá el error. y aquí el resultado será de los primeros 31 caracteres, no de 30 ..
Er. Anurag Jain
5

Aqui 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;
   }
}
UnkwnTech
fuente
Gracias, encontré la suya la función más útil y confiable de todas estas respuestas para mis necesidades. Sin embargo, ¿cómo puedo hacer que admita cadenas de varios bytes?
ctrlbrk
5

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;
}
Camsoft
fuente
4
$shorttext = preg_replace('/^([\s\S]{1,200})[\s]+?[\s\S]+/', '$1', $fulltext);

Descripción:

  • ^ - comenzar desde el principio de la cadena
  • ([\s\S]{1,200}) - Obtén de 1 a 200 de cualquier personaje
  • [\s]+?- no incluye espacios al final del texto corto para que podamos evitar en word ...lugar deword...
  • [\s\S]+ - coincide con el resto del contenido

Pruebas:

  1. regex101.comagreguemos a oralgunos otrosr
  2. regex101.com orrrr exactamente 200 caracteres
  3. regex101.comdespués del quinto r orrrrrexcluido.

Disfrutar.

hlcs
fuente
No entiendo la documentación de PHP. Sé que $1es un "reemplazo", pero en este contexto específico, ¿a qué se refiere? una variable vacía?
oldboy
1
@Anthony haciendo $1referencia para hacer coincidir los corchetes internos ([\s\S]{1,200}). $2hará referencia a dos segundos par de corchetes si hay alguno en el patrón.
hlcs
3

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

Aquí hay una solución simple que funciona en todos los casos. Aquí hubo respuestas similares, pero el modificador "s" es importante si desea que funcione con entrada de líneas múltiples, 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 lo prefiere, fuerza un descanso en $ characterCount incluso si no es un límite de palabra, puede 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 se 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;
}
orrd
fuente
2

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 "hacer coincidir cualquier subcadena que comience desde el comienzo de la longitud 1-200 que termine con un espacio". El resultado está en $ resultado y la coincidencia está en $ coincidencias. Eso se ocupa de su pregunta original, que termina específicamente en cualquier espacio. Si desea que finalice en nuevas líneas, cambie la expresión regular a:

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

Ok, obtuve otra versión de esto basada en las respuestas anteriores, pero teniendo en cuenta más cosas (utf-8, \ n y & nbsp;), también una línea que elimina los códigos abreviados de wordpress comentados si se usa 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;
  }
Yo-L
fuente
2

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 asegura que la última palabra no se corte según el comentario de ReX357.

No tengo suficientes puntos de representante para agregar esto como comentario.

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

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.

La preg_splitfunción se usa para dividir una cadena en subcadenas. Los límites a lo largo de los cuales se dividirá la cadena se especifican utilizando un patrón de expresiones regulares.

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

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

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

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

Bud Damyanov
fuente
1

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]. '...';
}
barista aficionado
fuente
1

Tengo una función que hace casi lo que quieres, si haces algunas ediciones, se ajustará 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;
}
?>
Rikudou Sennin
fuente
1

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

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

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

Ps .: El corte de longitud completa puede ser menor que substr.

evandro777
fuente
0

Se agregaron sentencias 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);
}
jdorenbush
fuente
0

Me parece que esto funciona:

function abreviatura_cadena_a_todo_palabra ($ 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.

Mat Barnett
fuente
0

Utilizar este:

el siguiente código eliminará ','. Si tiene 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)), ','))
Mahbub Alam
fuente
0

Si bien esta es una pregunta bastante antigua, pensé que proporcionaría una alternativa, ya que no se mencionó y es válida para PHP 4.3+.

Puede usar la sprintffamilia de funciones para truncar texto, utilizando el %.ℕsmodificador de precisión.

Un período .seguido de un número entero cuyo significado depende del especificador:

  • Para los especificadores e, E, f y F: este es el número de dígitos que se imprimirán después del punto decimal (por defecto, es 6).
  • Para los especificadores g y G: este es el número máximo de dígitos significativos que se imprimirán.
  • Para el especificador s: actúa como un punto de corte, estableciendo un límite máximo de caracteres para la cadena

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

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

Resultado

string(10) "0123456789"

Truncamiento ampliado https://3v4l.org/FCD21

Dado que sprintffunciona de manera similar substry parcialmente cortará las palabras. El siguiente enfoque garantizará que las palabras no se corten al usarlas strpos(wordwrap(..., '[break]'), '[break]')con un delimitador especial. Esto nos permite recuperar la posición y garantizar que no coincidamos con las estructuras de oración estándar.

Devolver una cadena sin cortar parcialmente las palabras y que no exceda el ancho especificado, conservando los saltos de línea si se desea.

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

Resultado

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

Resultados usando wordwrap($string, $width)ostrtok(wordwrap($string, $width), "\n")

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

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

Aquí puedes probar esto

substr( $str, 0, strpos($str, ' ', 200) ); 
Abhijeet kumar sharma
fuente
Esa solución ya se mencionó en otras respuestas. El problema es que falla si la cadena es menor que la longitud de 200 caracteres, o si no contiene espacios. Tampoco limita la cadena a 200 caracteres, sino que la rompe en el espacio después de 200 caracteres, que generalmente no es lo que desea.
orden
-1

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

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

Estoy usando los caracteres especiales para dividir el texto y cortarlo.

Namida
fuente
-2

Puede ser que 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;
    }

?>
slash3b
fuente