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

183

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

Брайан
источник
2
В вопросе предполагается, что усеченный текст уместится в фиксированное количество пикселей на веб-странице. В этом случае, в зависимости от выбранного шрифта, пространство, необходимое для каждого символа, не является постоянным. И, следовательно, мы не можем предположить, что 200 символов будут соответствовать лучше всего в доступных пикселях. До сих пор (до 02 марта 2011 г.) во всех приведенных ниже ответах отсутствует этот пункт, и, следовательно, ни один из них не обеспечивает надежного решения. - :(
LionHeart
1
Нет, не совсем. Вы можете установить шрифт надежным способом, а затем измерить наихудший сценарий, например, сколько самых широких символов поместится. И если вам нужно быть на 100% уверенным в том, как его отобразил браузер, это все равно больше не проблема PHP.
Молот
Попробуйте эту ссылку, может помочь Вам stackoverflow.com/a/26098951/3944217
edCoder
Вы могли бы найти s($str)->truncateSafely(200)полезным, как найти в этой автономной библиотеке .
кар

Ответы:

221

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

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

Серая пантера
источник
1
Похоже, что это приведет к преждевременному обрезанию текста, если есть \nдо желаемой ширины.
Кендалл Хопкинс
@KendallHopkins: правда, действительно есть проблема. Я обновил ответ альтернативной реализацией, которая решает данную проблему.
Серая Пантера
Будет ли этот пример работать для строки, содержащей HTML-теги, такие как теги абзаца?
безлимитный
это действительно полезно для меня, моя головная боль состояла из длинных Arabicбукв, и теперь она сводится к правильным словам с помощью tokenTruncateфункции .. tnx миллион :)
Aditya P Bhatt
1
Почему бы не добавить: if (strlen ($ string) <= $ your_desired_width) return $ string; как первое утверждение?
Дарко Романов
139

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

preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, 201));
mattmac
источник
7
Почти. Кажется, что это удаляет последнее слово предложения для меня, несмотря ни на что.
ReX357
прекрасно работает, но я нашел ту же ошибку, что и ReX357. Когда есть больше чем 1 слово, оно удаляет последнее.
Андрес С.К.
25
Просто оберните его в чек, чтобы убедиться, что строка длиннее, чем то, что вы тестируете (то же самое, что принятый ответ)if (strlen($string) > $your_desired_width) { preg_replace(...); }
Блэр Макмиллан
Я отредактировал ответ, включив в него совет @BlairMcMillan
Ким Стеки
2
Крошечное улучшение в регулярном выражении: скобки делают финальный \ S + необязательным для совпадения, но они также фиксируют эти символы. Так как нам не нужно захватывать эти символы, сделайте так, чтобы скобки не захватывали, например так:/\s+?(?:\S+)?$/
pcronin
45
$WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' '));

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

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

Дейв
источник
11
Если длина данной строки меньше максимальной длины, это обрезает все до последнего пробела. Чтобы избежать этого, оберните это внутри ifутверждения:if (strlen($str) > 200) { ... }
Амаль Мурали
Просто и, вероятно, намного быстрее, чем другие решения.
Владан
1
Одна из проблем заключается в том, что она возвращает пустую строку, если строка не содержит пробела.
orrd
Можно упростить до:$WidgetText = substr($string, 0, strpos($string, ' ', 200));
wranvaud
36

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

строка wordwrap (строка $ str [, int $ width = 75 [, строка $ 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.
Сергей Соколенко
источник
2
это не работает, если строка уже имеет символ новой строки (например, если вы пытаетесь извлечь сообщение descriptionиз блога)
supersan
1
@supersan Всегда можно предварительно обработать, preg_replace('/\s+/', ' ', $description)чтобы заменить все пробельные символы одним пробелом;)
Mavelo
9

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

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

Гаррет Олбрайт
источник
8

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

Лукас Оман
источник
1
Привет, если длина строки без пробела будет меньше 30, будет возвращена ошибка. и здесь результат будет из первых 31 символа, а не 30 ..
Er. Анураг Джейн
5

Ну вот:

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
источник
Спасибо, я нашел вашу самую полезную и надежную функцию из всех этих ответов для моих нужд. Тем не менее, как я могу сделать так, чтобы он поддерживал многобайтовые строки?
Ctrlbrk
5

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

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

  return $string;
}
Camsoft
источник
4
$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исключен.

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

КГ
источник
Я не понимаю документацию PHP. Я знаю, что $1это «замена», но в этом конкретном контексте, что это означает ?? пустая переменная?
oldboy
1
@ Энтони $1ссылки на совпадения в квадратных скобках ([\s\S]{1,200}). $2будет ссылаться на две вторые пары скобок, если они есть в шаблоне.
КГ
3

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

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

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

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

$result = preg_match("/^(.{1,199})[\n]/i", $text, $matches);
Джастин Поли
источник
2

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

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

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

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

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

Танч
источник
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);
}

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

$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)элемент будет содержать остальную часть строки.

Бад Дамьянов
источник
1

Основано на регулярном выражении @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

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

<?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
источник
1

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

$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")));
Шашанк Саксена
источник
0

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

function _truncate($str, $limit) {
    if(strlen($str) < $limit)
        return $str;
    $uid = uniqid();
    return array_shift(explode($uid, wordwrap($str, $limit, $uid)));
}
gosukiwi
источник
0

Я создаю функцию, более похожую на 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 .: Полная длина реза может быть меньше, чем субстрат.

evandro777
источник
0

Добавлены операторы 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);
}
jdorenbush
источник
0

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

функция abbreviate_string_to_whole_word ($ строка, $ max_length, $ буфер) {

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

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

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

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

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

substr($string, 0, strrpos(substr($string, 0, $comparingLength-strlen($currentString)), ','))
Махбуб Алам
источник
0

Хотя это довольно старый вопрос, я решил предоставить альтернативу, поскольку он не был упомянут и действителен для PHP 4.3+.

Вы можете использовать sprintfсемейство функций для усечения текста, используя %.ℕsмодификатор точности.

Период, .за которым следует целое число, значение которого зависит от спецификатора:

  • Для спецификаторов e, E, f и F: это количество цифр, которые будут напечатаны после десятичной точки (по умолчанию это 6).
  • Для спецификаторов g и G: это максимальное количество значащих цифр для печати.
  • Для спецификатора s: он действует как точка отсечения, устанавливая максимальный предел символов для строки

Простое усечение https://3v4l.org/QJDJU

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

результат

string(10) "0123456789"

Расширенное усечение https://3v4l.org/FCD21

Так как sprintfфункции аналогичны substrи будут частично обрезать слова. Приведенный ниже подход гарантирует, что слова не будут обрезаны с помощью strpos(wordwrap(..., '[break]'), '[break]')специального разделителя. Это позволяет нам получить позицию и убедиться, что мы не совпадаем со стандартными структурами предложений.

Возврат строки без частичной обрезки слов, который не превышает заданную ширину, при сохранении разрыва строки при желании.

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

результат

/* 
string(36) "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
string(14) "Lorem Ipsum is" 
string(14) "Lorem Ipsum
is" 
*/

Результаты с использованием wordwrap($string, $width)илиstrtok(wordwrap($string, $width), "\n")

/*
string(14) "Lorem Ipsum is"
string(11) "Lorem Ipsum"
*/
fyrye
источник
-1

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

<?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;
?>
Юсеф Альтаф
источник
-1

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

substr( $str, 0, strpos($str, ' ', 200) ); 
Абхиджит кумар шарма
источник
Это решение уже упоминалось в других ответах. Проблема в том, что он не работает, если длина строки меньше 200 символов или если она не содержит пробелов. Он также не ограничивает строку до 200 символов, вместо этого он разбивает строку на пробел после 200 символов, что обычно не то, что вам нужно.
orrd
-1

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

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

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

Namida
источник
-2

Может быть, это кому-нибудь поможет

<?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
источник