Como truncar uma string no PHP para a palavra mais próxima de um determinado número de caracteres?

183

Eu tenho um trecho de código escrito em PHP que extrai um bloco de texto de um banco de dados e o envia para um widget em uma página da web. O bloco de texto original pode ser um artigo longo ou uma frase curta ou duas; mas para esse widget não posso exibir mais do que, digamos, 200 caracteres. Eu poderia usar substr () para cortar o texto em 200 caracteres, mas o resultado seria cortado no meio das palavras - o que eu realmente quero é cortar o texto no final da última palavra antes de 200 caracteres.

Brian
fonte
2
A pergunta pretende dizer que o texto truncado caberá em um número fixo de pixels em uma página da web. Nesse caso, dependendo da fonte escolhida, o espaço necessário por caractere não é constante. Portanto, não podemos assumir que 200 caracteres se ajustem melhor aos pixels disponíveis. Até o momento (até 02 de março de 2011), todas as respostas abaixo estão ausentes e, portanto, nenhuma delas fornece uma solução confiável. - :(
LionHeart
1
Não, na verdade não. Você pode definir a fonte de maneira confiável e, em seguida, medir o pior cenário, quantos caracteres mais largos caberiam. E se você precisar ter 100% de certeza de como o navegador a processou, não será mais um problema de PHP.
Mołot 29/08
Tente Isto ajuda link, May You stackoverflow.com/a/26098951/3944217
edCoder
Você pode achar s($str)->truncateSafely(200)útil, conforme encontrado nesta biblioteca autônoma .
caw

Respostas:

221

Usando a função wordwrap . Ele divide os textos em várias linhas, de modo que a largura máxima seja a especificada, quebrando nos limites das palavras. Após a divisão, você simplesmente pega a primeira linha:

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

Uma coisa que este oneliner não trata é o caso em que o texto em si é menor que a largura desejada. Para lidar com esse caso extremo, deve-se fazer algo como:

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

A solução acima tem o problema de cortar prematuramente o texto se ele contiver uma nova linha antes do ponto de corte real. Aqui está uma versão que resolve esse 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));
}

Além disso, aqui está a classe de teste PHPUnit usada para testar a implementação:

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:

Caracteres UTF8 especiais como 'à' não são manipulados. Adicione 'u' no final do REGEX para lidar com isso:

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

Pantera Cinza
fonte
1
Parece que isso cortaria prematuramente o texto se houver um \nantes da largura desejada.
Kendall Hopkins
@ KendallHopkins: verdade, há realmente um problema. Atualizei a resposta com uma implementação alternativa que resolve o problema em questão.
Grey Panther
Este exemplo funcionaria para uma sequência que contenha tags html, como tags de parágrafo?
limitlessloop
a sua realmente útil para mim, minha dor de cabeça era longos Arabicletras e seu reduzido a palavras corretas agora com ajuda da tokenTruncatefunção .. TNX a :) milhões
Aditya P Bhatt
1
Por que não adicionar: if (strlen ($ string) <= $ your_desired_width) retorna $ string; como primeira afirmação?
Darko Romanov
139

Isso retornará os primeiros 200 caracteres de palavras:

preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, 201));
mattmac
fonte
7
Quase. Parece que remove a última palavra da frase para mim, não importa o quê.
ReX357
funciona muito bem, mas encontrei o mesmo erro que o ReX357. Quando houver mais de uma palavra, ela excluirá a última.
Andres SK
25
Apenas envolvê-la em uma verificação para garantir que a corda é maior do que o que você está testando (o mesmo que a resposta aceite)if (strlen($string) > $your_desired_width) { preg_replace(...); }
Blair McMillan
Eu editei a resposta para incluir conselhos @BlairMcMillan
Kim Stacks
2
Pequena melhoria no regex: os parênteses tornam o \ S + final opcional para a correspondência, mas eles também capturam esses caracteres. Uma vez que não precisa capturar esses personagens, fazer os parênteses não-capturando assim:/\s+?(?:\S+)?$/
pcronin
45
$WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' '));

E aí está - um método confiável de truncar qualquer string para a palavra inteira mais próxima, mantendo o comprimento máximo da string.

Eu tentei os outros exemplos acima e eles não produziram os resultados desejados.

Dave
fonte
11
Se o comprimento da string fornecida for menor que o comprimento máximo, isso cortaria tudo até o último espaço. Para evitar isso, envolva-o em uma ifdeclaração:if (strlen($str) > 200) { ... }
Amal Murali 8/14
Simples e provavelmente muito mais rápido que outras soluções.
Vladan
1
Um problema é que ele retorna uma string vazia se a string não contiver um espaço.
orrd 1/09/15
Pode ser simplificado para:$WidgetText = substr($string, 0, strpos($string, ' ', 200));
wranvaud
36

A seguinte solução nasceu quando notei um parâmetro $ break da função wordwrap :

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

Aqui está a solução :

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

Exemplo 1.

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

O exemplo acima exibirá:

This is very long string...

Exemplo # 2.

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

O exemplo acima exibirá:

This is short string.
Sergiy Sokolenko
fonte
2
isso não funciona se a cadeia já tem um caractere de nova linha (por exemplo, se você está tentando extrair um descriptionde um post)
supersan
1
@supersan pode sempre pré-processo com preg_replace('/\s+/', ' ', $description)a substituir todos os espaços em branco com um único espaço;)
Mavelo
9

Lembre-se sempre que estiver dividindo por "palavra" em qualquer lugar em que alguns idiomas, como chinês e japonês, não usem um caractere de espaço para dividir palavras. Além disso, um usuário mal-intencionado pode simplesmente inserir texto sem espaços, ou usar algum caractere semelhante ao Unicode para o caractere de espaço padrão; nesse caso, qualquer solução usada pode acabar exibindo o texto inteiro de qualquer maneira. Uma maneira de contornar isso pode ser verificar o comprimento da string depois de dividi-la em espaços normalmente; se a string ainda estiver acima de um limite anormal - talvez 225 caracteres neste caso -, vá em frente e divida-a de maneira tênue nesse limite.

Mais uma ressalva com coisas assim quando se trata de caracteres não ASCII; strings que os contêm podem ser interpretadas pelo padrão strlen () do PHP como sendo mais longo do que realmente são, porque um único caractere pode levar dois ou mais bytes em vez de apenas um. Se você apenas usar as funções strlen () / substr () para dividir strings, poderá dividir uma string no meio de um caractere! Em caso de dúvida, mb_strlen () / mb_substr () são um pouco mais infalíveis.

Garrett Albright
fonte
8

Use strpos e 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;

Isso fornecerá uma sequência truncada no primeiro espaço após 30 caracteres.

Lucas Oman
fonte
1
Oi, se o comprimento da string sem espaço for menor que 30, será um erro de retorno. e aqui o resultado será dos primeiros 31 caracteres e não 30 ..
Er. Anurag Jain
5

Aqui está:

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
fonte
Obrigado, achei a sua a função mais útil e confiável de todas essas respostas para minhas necessidades. No entanto, como posso fazer o suporte a cadeias de bytes múltiplos?
ctrlbrk 8/04
5

Aqui está minha função com base na abordagem do @ Cd-MaN.

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

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

Descrição:

  • ^ - começar do início da string
  • ([\s\S]{1,200}) - obtenha de 1 a 200 de qualquer personagem
  • [\s]+?- não inclua espaços no final do texto breve, para que possamos evitar em word ...vez deword...
  • [\s\S]+ - corresponder a todo o outro conteúdo

Testes:

  1. regex101.comvamos adicionar a oralguns outrosr
  2. regex101.com orrrr exatamente 200 caracteres.
  3. regex101.comdepois do quinto r orrrrrexcluído.

Aproveitar.

hlcs
fonte
Eu não entendo a documentação do PHP. Eu sei que o $1é um "substituto", mas neste contexto específico a que se refere? uma variável vazia?
Oldboy
1
@ Anthony $1referenciando para combinar parênteses ([\s\S]{1,200}). $2fará referência a dois segundos par de colchetes, se houver algum padrão.
Hlcs 14/04/19
3

É surpreendente como é complicado encontrar a solução perfeita para esse problema. Ainda não encontrei uma resposta nesta página que não falhe em pelo menos algumas situações (especialmente se a sequência contiver novas linhas ou tabulações, ou se a quebra de palavra for algo diferente de um espaço ou se a sequência tiver UTF- 8 caracteres multibyte).

Aqui está uma solução simples que funciona em todos os casos. Havia respostas semelhantes aqui, mas o modificador "s" é importante se você deseja que ele funcione com entrada de várias linhas, e o modificador "u" faz com que ele avalie corretamente os caracteres multibyte UTF-8.

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

Um caso de borda possível com isso ... se a sequência não tiver nenhum espaço em branco nos primeiros caracteres $ characterCount, ela retornará a sequência inteira. Se você preferir, isso força uma quebra em $ characterCount, mesmo que não seja um limite de palavras, você pode usar o seguinte:

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

Uma última opção, se você quiser, adicione reticências se truncar a string ...

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
fonte
2

Eu usaria a função preg_match para fazer isso, pois o que você deseja é uma expressão bastante simples.

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

A expressão significa "corresponder a qualquer substring, começando do início de comprimento de 1 a 200 que termina com um espaço". O resultado está em $ result e a correspondência está em $ correspondências. Isso cuida da sua pergunta original, que termina especificamente em qualquer espaço. Se você quiser terminar com novas linhas, altere a expressão regular para:

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

Ok, então eu tenho outra versão disso com base nas respostas acima, mas levando mais em conta (utf-8, \ ne & nbsp;), também uma linha que remove os códigos de acesso wordpress comentados se usados ​​com o 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
fonte
2

Esta é uma pequena correção para a resposta do mattmac:

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

A única diferença é adicionar um espaço no final de $ string. Isso garante que a última palavra não seja cortada conforme o comentário do ReX357.

Não tenho pontos de representação suficientes para adicionar isso como um comentário.

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

Isso produzirá as primeiras 10 palavras.

A preg_splitfunção é usada para dividir uma string em substrings. Os limites ao longo dos quais a cadeia deve ser dividida são especificados usando um padrão de expressões regulares.

preg_split A função usa 4 parâmetros, mas apenas os 3 primeiros são relevantes para nós no momento.

Primeiro Parâmetro - Padrão O primeiro parâmetro é o padrão de expressões regulares ao longo do qual a string deve ser dividida. No nosso caso, queremos dividir a string entre os limites das palavras. Portanto, usamos uma classe de caracteres predefinida\s que corresponde aos caracteres de espaço em branco, como espaço, tabulação, retorno de carro e avanço de linha.

Segundo Parâmetro - String de Entrada O segundo parâmetro é a string de texto longo que queremos dividir.

Terceiro parâmetro - limite O terceiro parâmetro especifica o número de substrings que devem ser retornados. Se você definir o limite como n, preg_split retornará uma matriz de n elementos. Os primeiros n-1elementos conterão as substrings. O último (n th)elemento conterá o restante da string.

Bud Damyanov
fonte
1

Com base no 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]. '...';
}
barista amador
fonte
1

Eu tenho uma função que faz quase o que você deseja; se você fizer algumas edições, ela se ajustará exatamente:

<?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
fonte
1

Foi assim que eu fiz:

$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
fonte
0

Eu sei que isso é velho, mas ...

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

Crio uma função mais semelhante ao substr e usando a ideia do @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 .: O comprimento total do corte pode ser menor que o substr.

evandro777
fonte
0

Adicionadas instruções IF / ELSEIF ao código de Dave e AmalMurali para lidar com seqüências de caracteres sem espaços

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
fonte
0

Acho que isso funciona:

função 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;

}

O buffer permite ajustar o comprimento da string retornada.

Mat Barnett
fonte
0

Usa isto:

o código a seguir removerá ','. Se você tiver outro caractere ou sub-string, poderá usá-lo em vez de ','

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

// se você tiver outra conta de string para

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

Embora essa seja uma pergunta bastante antiga, achei que forneceria uma alternativa, pois ela não era mencionada e válida para o PHP 4.3 ou superior.

Você pode usar a sprintffamília de funções para truncar o texto usando o %.ℕsmodificador de precisão.

Um período .seguido por um número inteiro cujo significado depende do especificador:

  • Para especificadores e, E, f e F: este é o número de dígitos a serem impressos após o ponto decimal (por padrão, este é 6).
  • Para especificadores g e G: este é o número máximo de dígitos significativos a serem impressos.
  • Para o especificador s: ele atua como um ponto de corte, definindo um limite máximo de caracteres para a sequência

Truncamento simples https://3v4l.org/QJDJU

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

Resultado

string(10) "0123456789"

Truncamento expandido https://3v4l.org/FCD21

Desde sprintffunções semelhantes substre parcialmente cortam palavras. A abordagem abaixo garantirá que as palavras não sejam cortadas usandostrpos(wordwrap(..., '[break]'), '[break]') um delimitador especial. Isso nos permite recuperar a posição e garantir que não correspondamos às estruturas de sentenças padrão.

Retornando uma sequência sem cortar parcialmente as palavras e que não exceda a largura especificada, preservando as quebras de linha, se desejado.

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)oustrtok(wordwrap($string, $width), "\n")

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

Eu usei isso 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
fonte
-1

Aqui você pode tentar isso

substr( $str, 0, strpos($str, ' ', 200) ); 
Abhijeet kumar sharma
fonte
Essa solução já foi mencionada em outras respostas. O problema é que ele falha se a cadeia tiver menos de 200 caracteres ou se não houver espaços. Também não limita a cadeia a 200 caracteres; em vez disso, quebra a cadeia no espaço após 200 caracteres, o que geralmente não é o que você deseja.
orrd
-1

Eu acredito que esta é a maneira mais fácil de fazer isso:

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

Estou usando os caracteres especiais para dividir o texto e cortá-lo.

Namida
fonte
-2

Pode ser que isso ajude alguém:

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