如何在PHP中截断字符串最接近于一定数量的字符?

我有一个用PHP编写的代码片段,它从数据库中提取一个文本块,并将其发送到网页上的一个小部件。原文可以是一篇很长的文章,也可以是一两个短句;但是对于这个小部件,我不能显示超过200个字符。我可以使用substr()在200个字符处截断文本,但结果将在单词中间截断——我真正想要的是在200个字符前截断最后结尾的文本。

198156 次浏览

通过使用自动换行函数。它将文本分割成多行,这样最大宽度就是您指定的宽度,在单词边界处断开。分割之后,你只需取第一行:

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字符不会被处理。在REGEX的末尾添加'u'来处理它:

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

使用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个字符后的第一个空格处截断的字符串。

给你:

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中,匹配在$matches中。这就解决了你最初的问题,即以任意空格结束的问题。如果你想让它以换行符结束,将正则表达式更改为:

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

这将返回单词的前200个字符:

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

请记住,当你在任何地方用“单词”来分隔单词时,一些语言(如汉语和日语)不使用空格字符来分隔单词。此外,恶意用户可能只是输入没有任何空格的文本,或者使用一些与标准空格字符类似的Unicode字符,在这种情况下,您使用的任何解决方案最终都可能显示整个文本。解决这个问题的一种方法可能是在正常地按空格分割字符串后检查字符串长度,然后,如果字符串仍然超过一个异常限制(在这种情况下可能是225个字符),则继续按该限制无声地分割它。

当涉及到非ascii字符时,还有一个类似的警告;包含它们的字符串可能会被PHP的标准strlen()解释为比实际更长,因为单个字符可能占用两个或更多字节,而不是一个字节。如果你只是使用strlen()/substr()函数来分割字符串,你可能会在字符中间分割字符串!当有疑问时,mb_strlen ()/mb_substr ()是比较万无一失的。

下面是基于@Cd-MaN方法的函数。

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


return $string;
}

基于@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]. '...';
}
$WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' '));

这样你就有了它——一种可靠的方法,可以将任何字符串截断为最近的整个单词,同时保持在最大字符串长度以下。

我尝试了上面的其他例子,它们没有产生预期的结果。

好吧,所以我得到了另一个版本的这个基于上面的答案,但考虑到更多的东西(utf-8, \n和 ),如果与wp一起使用,也是剥离wordpress shortcodes注释的行。

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

这是一个小修复mattmac的答案:

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

唯一的区别是在$string的末尾添加一个空格。这确保了最后一个词不会像ReX357的注释那样被切断。

我没有足够的代表点添加这作为一个评论。

/*
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)元素将包含字符串的其余部分

我知道这很旧,但是…

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

当我注意到自动换行函数的$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.

我以前用过这个

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

也许这能帮助到一些人:

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


?>

我有一个函数,它几乎可以做你想要的,如果你做一些编辑,它将完全适合:

<?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;
}
?>
$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排除。

享受。

我是这样做的:

$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的函数,并使用了@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。

你可以试试这个

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

找到这个问题的完美解决方案是多么棘手,这令人惊讶。我还没有在本页上找到至少在某些情况下不会失败的答案(特别是如果字符串包含换行符或制表符,或者如果单词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;
}

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

我相信这是最简单的方法:

$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)), ','))

虽然这是一个相当老的问题,但我想我可以提供一个替代方案,因为它没有被提到,而且对PHP 4.3+有效。

你可以使用sprintf系列函数,通过使用%.ℕs精度修饰符来截断文本。

句点.后面跟着一个整数,其含义取决于 说明符:< / p >

  • 对于e, e, f和f说明符:这是小数点后要打印的位数(默认情况下,这是6)。
  • 对于g和g说明符:这是要打印的有效数字的最大数量。
  • 对于说明符:它作为一个截断点,设置字符串的最大字符限制

简单截断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"
*/

你可以用这个:

function word_shortener($text, $words=10, $sp='...'){


$all = explode(' ', $text);
$str = '';
$count = 1;


foreach($all as $key){
$str .= $key . ($count >= $words ? '' : ' ');
$count++;
if($count > $words){
break;
}
}


return $str . (count($all) <= $words ? '' : $sp);


}

例子:

word_shortener("Hello world, this is a text", 3); // Hello world, this...
word_shortener("Hello world, this is a text", 3, ''); // Hello world, this
word_shortener("Hello world, this is a text", 3, '[read more]'); // Hello world, this[read more]

编辑

工作原理:

1. 从输入文本中打开空格:

$all = explode(' ', $text);

例如,如果$text将是"Hello world"那么$all是一个具有分解值的数组:

["Hello", "world"]

2. 对于每个单词:

选择展开文本中的每个元素:

foreach($all as $key){...

将当前单词($key)附加到$str,如果它是最后一个单词,则添加空格:

$str .= $key . ($count >= $words ? '' : ' ');

然后将1加到$count并检查它是否大于max limit($words)打破循环:

if($count > $words){
break;
}

只有当最终文本小于输入文本时,才返回$str和分隔符($sp):

return $str . (count($all) <= $words ? '' : $sp);

就我所知,这里所有的解只有在起点固定的情况下才有效。

允许你转动这个:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna liqua. Ut enim ad minim veniam.

到这个:

Lorem ipsum dolor sit amet, consectetur...

如果想要截断一组特定关键字周围的单词,该怎么办?

截断一组特定关键字周围的文本。

我们的目标是能够转换这个:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna liqua. Ut enim ad minim veniam.

到这个:

...consectetur adipisicing elit, sed do eiusmod tempor...

这是在显示搜索结果、摘要等时非常常见的情况。为了实现这一点,我们可以结合使用以下两种方法:

    /**
* Return the index of the $haystack matching $needle,
* or NULL if there is no match.
*
* This function is case-insensitive
*
* @param string $needle
* @param array $haystack
* @return false|int
*/
function regexFindInArray(string $needle, array $haystack): ?int
{
for ($i = 0; $i < count($haystack); $i++) {
if (preg_match('/' . preg_quote($needle) . '/i', $haystack[$i]) === 1) {
return $i;
}
}
return null;
}


/**
* If the keyword is not present, it returns the maximum number of full
* words that the max number of characters provided by $maxLength allow,
* starting from the left.
*
* If the keyword is present, it adds words to both sides of the keyword
* keeping a balanace between the length of the suffix and the prefix.
*
* @param string $text
* @param string $keyword
* @param int $maxLength
* @param string $ellipsis
* @return string
*/
function truncateWordSurroundingsByLength(string $text, string $keyword,
int $maxLength, string $ellipsis): string
{
if (strlen($text) < $maxLength) {
return $text;
}


$pattern = '/' . '^(.*?)\s' .
'([^\s]*' . preg_quote($keyword) . '[^\s]*)' .
'\s(.*)$' . '/i';
preg_match($pattern, $text, $matches);


// break everything into words except the matching keywords,
// which can contain spaces
if (count($matches) == 4) {
$words = preg_split("/\s+/", $matches[1], -1, PREG_SPLIT_NO_EMPTY);
$words[] = $matches[2];
$words = array_merge($words,
preg_split("/\s+/", $matches[3], -1, PREG_SPLIT_NO_EMPTY));
} else {
$words = preg_split("/\s+/", $text, -1, PREG_SPLIT_NO_EMPTY);
}


// find the index of the matching word
$firstMatchingWordIndex = regexFindInArray($keyword, $words) ?? 0;


$length = false;
$prefixLength = $suffixLength = 0;
$prefixIndex = $firstMatchingWordIndex - 1;
$suffixIndex = $firstMatchingWordIndex + 1;


// Initialize the text with the matching word
$text = $words[$firstMatchingWordIndex];


while (($prefixIndex >= 0 or $suffixIndex <= count($words))
and strlen($text) < $maxLength and strlen($text) !== $length) {
$length = strlen($text);
if (isset($words[$prefixIndex])
and (strlen($text) + strlen($words[$prefixIndex]) <= $maxLength)
and ($prefixLength <= $suffixLength
or strlen($text) + strlen($words[$suffixIndex]) <= $maxLength)) {
$prefixLength += strlen($words[$prefixIndex]);
$text = $words[$prefixIndex] . ' ' . $text;
$prefixIndex--;
}
if (isset($words[$suffixIndex])
and (strlen($text) + strlen($words[$suffixIndex]) <= $maxLength)
and ($suffixLength <= $prefixLength
or strlen($text) + strlen($words[$prefixIndex]) <= $maxLength)) {
$suffixLength += strlen($words[$suffixIndex]);
$text = $text . ' ' . $words[$suffixIndex];
$suffixIndex++;
}
}


if ($prefixIndex > 0) {
$text = $ellipsis . ' ' . $text;
}
if ($suffixIndex < count($words)) {
$text = $text . ' ' . $ellipsis;
}


return $text;
}

现在你可以做:

$text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do' .
'iusmod tempor incididunt ut labore et dolore magna liqua. Ut enim' .
'ad minim veniam.';


$text = truncateWordSurroundingsByLength($text, 'elit', 25, '...');


var_dump($text); // string(32) "... adipisicing elit, sed do ..."

运行代码

function trunc($phrase, $max_words) {
$phrase_array = explode(' ',$phrase);
if(count($phrase_array) > $max_words && $max_words > 0)
$phrase = implode(' ',array_slice($phrase_array, 0, $max_words)).'...';
return $phrase;
}