Web

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

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

 

Ответ 1

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

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

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

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

 

 

Ответ 2

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

string wordwrap (строка $str [, int $width = 75 [, строка $break = "\ n" [, bool $cut = false]]])

Вот решение:

/**

* Усекает заданную строку по указанной длине.

 *

 * @param string $str Входная строка.

 * @param int $width Количество символов, на которое будет усечена строка.

 * @return string

 */

function truncate($str, $width) {

    return strtok(wordwrap($str, $width, "...\n"), "\n");

}

 

Пример №1.

print truncate("Это очень длинная строка с большим количеством символов.", 24);

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

Это очень длинная строка...

 

Пример №2.

print truncate("Это короткая строка.", 24);

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

Это короткая строка.

  

Ответ 3

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

 

Ответ 4

Такой вариант:

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;

   }

}

 

Ответ 5

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

 

Описание:

  1. ^ - начать с начала строки

  2. ([\s\S]{1,200}) - получить от 1 до 200 любых символов

  3. [\s]+? - не включать пробелы в конце короткого текста, чтобы избежать слова «...»

  4. [\s\S]+ - соответствовать всему остальному содержимому

 

Ответ 6

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

}

 

Ответ 7

Итак, у меня есть другая версия, основанная на ответах выше, но учитывающая больше вещей (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);

    // использование с 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;

  }

 

Схожие статьи

Web

Как получить куки из php curl в переменную

Поиск уязвимости веб-сайта: как обеспечить безопасность сайта
Web

Поиск уязвимости веб-сайта: как обеспечить безопасность сайта

Какой выбрать CDN для сайта. Оптимизируем скорость загрузки
Web

Какой выбрать CDN для сайта. Оптимизируем скорость загрузки

Web

Простейшее двустороннее шифрование с использованием PHP

×