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

StackOverflow https://stackoverflow.com/questions/79960

  •  09-06-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

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 перенос слова функция:

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.

При разделении слов по «слову» помните, что в некоторых языках, таких как китайский и японский, для разделения слов не используется пробел.Кроме того, злоумышленник может просто ввести текст без пробелов или использовать какой-либо Unicode, аналогичный стандартному символу пробела, и в этом случае любое используемое вами решение может в любом случае отобразить весь текст.Обходной путь может состоять в том, чтобы проверить длину строки после ее разделения на пробелы, как обычно, а затем, если строка все еще превышает ненормальный предел - в данном случае, возможно, 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;
   }
}

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

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

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

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

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

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

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

Единственное отличие состоит в добавлении пробела в конце $string.Это гарантирует, что последнее слово не будет обрезано, как в комментарии 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;
}
?>

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

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

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

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

Пс.:Полная длина может быть меньше, чем у substr.

Добавлены операторы 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);
}

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

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

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

Я считаю, что это работает:

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

}

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

Использовать это:

следующий код удалит ','.Если у вас есть другой символ или подстрока, вы можете использовать его вместо ','

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

// если у вас есть другая строковая учетная запись для

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

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

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

?>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top