Как обрезать строку в PHP до слова, ближайшего к определенному количеству символов?


У меня есть фрагмент кода, написанный на PHP, который извлекает блок текста из базы данных и отправляет его виджету на веб-странице. Исходный блок текста может быть длинной статьей или коротким предложением или двумя; но для этого виджета я не могу отображать более, скажем, 200 символов. Я мог бы использовать substr(), чтобы обрезать текст на 200 символов, но результатом будет обрезание в середине слов - что я действительно хочу, так это обрезать текст в конце последнего слова до 200 символы.

Author: Brian, 2008-09-17

25 answers

С помощью функции wordwrap. Он разбивает тексты на несколько строк таким образом, чтобы максимальная ширина была той, которую вы указали, нарушая границы слов. После разделения вы просто берете первую строку:

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

Одна вещь, которую не обрабатывает этот oneliner, - это случай, когда сам текст короче желаемой ширины. Чтобы справиться с этим крайним случаем, нужно сделать что-то вроде:

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

В приведенном выше решении есть проблема преждевременного сокращения текста если он содержит новую строку перед фактической точкой отсечения. Вот версия, которая решает эту проблему:

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

Кроме того, вот тестовый класс PHPUnit, используемый для тестирования реализации:

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

РЕДАКТИРОВАТЬ :

Специальные символы UTF8, такие как "à", не обрабатываются. Добавьте "u" в конце РЕГУЛЯРНОГО выражения, чтобы обработать его:

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

 210
Author: Grey Panther, 2015-09-09 20:32:10

Это вернет первые 200 символов слов:

preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, 201));
 121
Author: mattmac, 2008-09-17 04:41:34
$WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' '));

И вот он у вас - надежный метод усечения любой строки до ближайшего целого слова, оставаясь при этом под максимальной длиной строки.

Я пробовал другие примеры выше, и они не дали желаемых результатов.

 42
Author: Dave, 2011-01-12 04:29:50

Следующее решение родилось, когда я заметил параметр $break функции wordwrap:

Строка wordwrap (строка $str [, int $ширина =75[, строка $разрыв= "\n" [, bool $cut =ложь]]])

Вот решение:

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

Пример #1.

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

Приведенный выше пример выведет:

This is very long string...

Пример #2.

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

Приведенный выше пример выведет:

This is short string.
 32
Author: Sergiy Sokolenko, 2014-08-20 13:19:35

Всякий раз, когда вы разделяете слова по "слову", имейте в виду, что в некоторых языках, таких как китайский и японский, для разделения слов не используется пробел. Кроме того, злоумышленник может просто ввести текст без пробелов или использовать какой-либо Юникод, похожий на стандартный символ пробела, и в этом случае любое используемое вами решение может в конечном итоге отобразить весь текст в любом случае. Способом обойти это может быть проверка длины строки после разделения ее на пробелы, как обычно, затем, если строка все еще превышающий ненормальный предел - в данном случае, возможно, 225 символов - продвигается вперед и тупо разбивает его на этом пределе.

Еще одно предостережение в отношении подобных вещей, когда речь заходит о символах, отличных от ASCII; строки, содержащие их, могут интерпретироваться стандартом PHP strlen() как более длинные, чем они есть на самом деле, потому что один символ может занимать два или более байта вместо одного. Если вы просто используете функции strlen()/substr() для разделения строк, вы можете разделить строку в середине характер! Если вы сомневаетесь, mb_strlen()/ mb_substr() немного более надежны.

 9
Author: Garrett Albright, 2013-12-06 18:18:36

Используйте strpo и 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;

Это даст вам строку, усеченную в первом пробеле после 30 символов.

 8
Author: Lucas Oman, 2011-03-03 19:28:54

Вот моя функция, основанная на подходе @Cd-Man.

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

  return $string;
}
 5
Author: Camsoft, 2010-03-26 12:36:03

Вот, пожалуйста:

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
Author: UnkwnTech, 2008-09-17 04:31:26

Удивительно, насколько сложно найти идеальное решение этой проблемы. Я еще не нашел ответа на этой странице, который не подводил бы, по крайней мере, в некоторых ситуациях (особенно если строка содержит новые строки или вкладки, или если разрыв слова - это что-то иное, чем пробел, или если строка содержит многобайтовые символы UTF-8).

Вот простое решение, которое работает во всех случаях. Здесь были похожие ответы, но модификатор "s" важен, если вы хотите, чтобы он работал с многострочный ввод, а модификатор "u" позволяет корректно вычислять многобайтовые символы UTF-8.

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

Один из возможных крайних случаев с этим... если в строке вообще нет пробелов в первых символах $charactercount, она вернет всю строку. Если вы предпочитаете, чтобы он вызывал разрыв в $charactercount, даже если это не граница слова, вы можете использовать это:

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

Последний вариант, если вы хотите, чтобы он добавлял многоточие, если он усекает строку...

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
Author: orrd, 2015-09-01 21:04:36
$shorttext = preg_replace('/^([\s\S]{1,200})[\s]+?[\s\S]+/', '$1', $fulltext);

Описание:

  • ^ - начинать с начала строки
  • ([\s\S]{1,200}) - получить от 1 до 200 любого символа
  • [\s]+? - не включать пробелы в конце короткого текста, чтобы мы могли избежать word ... вместо word...
  • [\s\S]+ - соответствует всему остальному контенту

Тесты:

  1. regex101.com давайте добавим к or несколько других r
  2. regex101.com orrrr ровно 200 символов.
  3. regex101.com после пятого r orrrrr исключен.

Наслаждайтесь.

 3
Author: hlcs, 2016-11-04 17:06:08

Я бы использовал для этого функцию preg_match, так как то, что вы хотите, - это довольно простое выражение.

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

Выражение означает "сопоставьте любую подстроку, начинающуюся с начала длины 1-200, которая заканчивается пробелом". Результат указан в $result, а совпадение - в $matches. Это решает ваш первоначальный вопрос, который конкретно заканчивается на любом месте. Если вы хотите, чтобы он заканчивался на новых строках, измените регулярное выражение на:

$result = preg_match("/^(.{1,199})[\n]/i", $text, $matches);
 2
Author: Justin Poliey, 2008-09-17 04:59:08

Хорошо, поэтому я получил другую версию этого, основанную на приведенных выше ответах, но учитывающую больше вещей (utf-8, \n и  ), а также строку, удаляющую короткие коды wordpress, прокомментированные, если они используются с 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
Author: Yo-L, 2011-10-28 10:52:18
/*
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);
}

Использование:

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

Это выведет первые 10 слов.

Функция preg_split используется для разделения строки на подстроки. Границы, вдоль которых должна быть разделена строка, задаются с помощью шаблона регулярных выражений.

preg_split функция принимает 4 параметра, но только первые 3 имеют отношение к нам прямо сейчас.

Первый параметр – Шаблон Первый параметр - это шаблон регулярных выражений, по которому должна быть разделена строка. В нашем в данном случае мы хотим разделить строку по границам слов. Поэтому мы используем предопределенный класс символов \s, который соответствует пробелам, таким как пробел, табуляция, возврат каретки и перевод строки.

Второй Параметр – Строка ввода Второй параметр - это длинная текстовая строка, которую мы хотим разделить.

Третий параметр – Предел Третий параметр указывает количество подстрок, которые должны быть возвращены. Если вы установите ограничение на n, preg_split вернет массив из n элементы. Первые элементы n-1 будут содержать подстроки. Последний элемент (n th) будет содержать остальную часть строки.

 2
Author: bodi0, 2012-04-05 09:32:56

На основе регулярного выражения @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
Author: amateur barista, 2010-12-09 16:28:08

Это небольшое исправление для ответа mattmac:

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

Единственная разница заключается в добавлении пробела в конце строки $. Это гарантирует, что последнее слово не будет отрезано в соответствии с комментарием ReX357.

У меня недостаточно очков репутации, чтобы добавить это в качестве комментария.

 1
Author: tanc, 2011-11-09 22:29:11

У меня есть функция, которая делает почти то, что вы хотите, если вы сделаете несколько правок, она точно подойдет:

<?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
Author: Rikudou_Sennin, 2014-06-13 11:37:05

Вот как я это сделал:

$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
Author: Shashank Saxena, 2016-02-18 12:20:43

Я знаю, что это старое, но...

function _truncate($str, $limit) {
    if(strlen($str) < $limit)
        return $str;
    $uid = uniqid();
    return array_shift(explode($uid, wordwrap($str, $limit, $uid)));
}
 0
Author: gosukiwi, 2013-02-26 12:53:52

Я использовал это раньше

<?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
Author: Yousef Altaf, 2014-07-08 11:31:18

Я создаю функцию, более похожую на substr, и использую идею @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);
}

Пс.: Разрез по всей длине может быть меньше, чем substr.

 0
Author: evandro777, 2015-06-25 14:27:18

Добавлены операторы IF/ELSEIF в код из Дейва и аМальМурали для обработки строк без пробелов

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
Author: jdorenbush, 2017-05-23 10:31:37

Я считаю, что это самый простой способ сделать это:

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

Я использую специальные символы, чтобы разделить текст и вырезать его.

 0
Author: Namida, 2018-03-09 13:26:34

Я нахожу, что это работает:

Функция abbreviate_string_to_whole_word($строка,$максимальная длина,$буфер) {

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;

}

Буфер позволяет регулировать длину возвращаемой строки.

 0
Author: Mat Barnett, 2018-05-11 11:00:25

Здесь вы можете попробовать это

substr( $str, 0, strpos($str, ' ', 200) ); 
 -1
Author: Abhijeet kumar sharma, 2015-08-26 12:46:05

Может быть, это кому-то поможет:

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

?>
 -2
Author: slash3b, 2014-04-01 14:31:24