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


183

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


2
Питання має намір сказати, що усічений текст поміститься у певній кількості пікселів на веб-сторінці. У цьому випадку, залежно від обраного шрифту, простір, необхідний для картки, не є постійним. Отже, ми не можемо припустити, що 200 символів найкраще впишуться у доступні пікселі. Поки що (до 02 березня 2011 р.) У всіх наведених нижче відповідях цього пункту немає, і тому жодна з них не дає надійного рішення. - :(
LionHeart

1
Ні, не дуже. Ви можете встановити шрифт надійними способами, а потім виміряти найгірший сценарій, а саме скільки найширших символів вмістилося б. І якщо вам потрібно бути на 100% впевненим у тому, як браузер його надав, це все одно не є проблемою PHP.
Молот

Спробуйте цю допомогу Link, Травень Ви 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, як теги абзацу?
limitlessloop

це мені дуже допомагає, головний біль був довгим Arabic головна літерами і її зводили до виправлення слів зараз за допомогою tokenTruncateфункції .. tnx мільйон :)
Aditya P Bhatt

1
Чому б не додати: якщо (strlen ($ string) <= $ your_desired_width) повернути $ string; як перше твердження?
Дарко Романов

139

Це поверне перші 200 символів слів:

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

7
Майже. Схоже, воно видаляє для мене останнє слово речення незалежно від того.
ReX357

відмінно працює, але я знайшов ту ж помилку, що і ReX357. Коли є більше одного слова, воно видаляє останнє.
Андрес СК

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

stringwrap word (рядок $ 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; рядки, що містять їх, можуть бути інтерпретовані стандартним strlen () PHP як довші, ніж вони є насправді, тому що один символ може займати два або більше байти замість одного. Якщо ви просто використовуєте функції 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 ..
Ер. Анураг Джайн

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

Дякую, я знайшов вашу найбільш корисну і надійну функцію з усіх цих відповідей для моїх потреб. Однак як я можу змусити його підтримувати багатобайтові рядки?
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;
}

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
@Anthony $1посилання на відповідність у дужках ([\s\S]{1,200}). $2буде посилатися на дві другі пари дужок, якщо вони є в шаблоні.
hlcs

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

Один можливий крайовий випадок із цим ... якщо рядок взагалі не має пробілів у перших символах $ символуCount, вона поверне всю рядку. Якщо ви віддаєте перевагу, це змушує перерву на $ 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;
}

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

2

Це невелике виправлення відповіді mattmac:

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

Єдина відмінність - додати пробіл наприкінці $ string. Це гарантує, що останнє слово не буде відрізане відповідно до коментаря 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 слів.

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

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

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

Пс.: Поріз на повну довжину може бути меншим за субстр.


0

До коду від Dave та AmalMurali додано оператори 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

Я вважаю, що це працює:

функція skraate_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;

}

Буфер дозволяє регулювати довжину повернутого рядка.


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

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

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


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

?>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.