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

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

Используя функцию 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' в конец REGEX для его обработки:

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

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

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

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

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

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

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

Вот решение :

 /** * 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. 

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

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

Используйте strpos и 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 символов.

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

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

Ну вот:

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

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

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

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

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

Хорошо, поэтому я получил еще одну версию этого, основываясь на вышеупомянутых ответах, но занимая больше вещей в учетной записи (utf-8, \ n и & nbsp;), а также строку, лишающую короткие коды 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; } 
 /* 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) элемент будет содержать остальную часть строки.

На основе регулярного выражения @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]. '...'; } 

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

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

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

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

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

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

Удивительно, насколько сложно найти идеальное решение этой проблемы. Я еще не нашел ответа на этой странице, который не сработает, по крайней мере, в некоторых ситуациях (особенно если строка содержит символы новой строки или вкладки, или если слово break является чем-то другим, кроме пробела, или если строка имеет 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; } 
 $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 .

Наслаждаться.

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

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

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

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

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

Ps .: Полное разрезание может быть меньше, чем substr.

Добавлены инструкции IF / ELSEIF к коду от Dave и AmalMurali для обработки строк без пробелов

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

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

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

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

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

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

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