如何将PHP中的字符串截断为最接近一定数量字符的单词?

How to Truncate a string in PHP to the word closest to a certain number of characters?

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


通过使用自动换行功能。它将文本拆分为多行,使最大宽度为您指定的宽度,在单词边界处中断。拆分后,您只需使用第一行:

1
2
substr($string, 0, strpos(wordwrap($string, $your_desired_width),"
"
));

OneLiner无法处理的一件事是,当文本本身短于所需的宽度时。要处理此边缘情况,应执行以下操作:

1
2
3
4
5
6
if (strlen($string) > $your_desired_width)
{
    $string = wordwrap($string, $your_desired_width);
    $string = substr($string, 0, strpos($string,"
"
));
}

如果文本在实际切点之前包含换行符,则上述解决方案存在过早剪切文本的问题。这里有一个解决这个问题的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function tokenTruncate($string, $your_desired_width) {
  $parts = preg_split('/([\s

]+)/'
, $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测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
5 7 9"
,
      tokenTruncate("1 3
5 7 9 11 14"
, 10));
  }
}

。编辑:

不处理"_"等特殊的utf8字符。在regex末尾添加"u"以处理它:

$parts = preg_split('/([\s

]+)/u', $string, null, PREG_SPLIT_DELIM_CAPTURE);。


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

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


1
$WidgetText = substr($string, 0, strrpos(substr($string, 0, 200), ' '));

这里有一个可靠的方法,可以将任何字符串截断为最接近的整个单词,同时保持最大字符串长度。

我试过上面的其他例子,但它们没有产生期望的结果。


当我注意到wordwap函数的$break参数时,就产生了以下解决方案:

string wordwrap ( string $str [, int $width = 75 [, string $break =
"
" [, bool $cut = false ]]] )

解决方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 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,"...
"
),"
"
);
}

示例1。

1
print truncate("This is very long string with many chars.", 25);

上面的示例将输出:

1
This is very long string...

示例2。

1
print truncate("This is short string.", 25);

上面的示例将输出:

1
This is short string.


当你在任何地方用"单词"分割时,请记住一些语言,如中文和日语,不使用空格字符来分割单词。另外,恶意用户可以简单地输入没有任何空格的文本,或者使用一些与标准空格字符类似的Unicode外观,在这种情况下,您使用的任何解决方案都可能最终显示整个文本。解决这个问题的一种方法可能是,在正常情况下将字符串拆分为空格后检查字符串长度,然后,如果字符串仍然高于异常限制(在本例中可能为225个字符),则继续执行并在该限制下将其简单拆分。

当涉及到非ASCII字符时,还有一个警告:包含这些字符的字符串可能会被PHP的标准strlen()解释为比实际长度长,因为一个字符可能需要两个或多个字节而不是一个。如果只使用strlen()/substr()函数拆分字符串,则可以在字符中间拆分字符串!当有疑问时,mb ou strlen()/mb ou substr()更加简单。


使用strpos和substr:

1
2
3
4
5
6
<?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方法的函数。

1
2
3
4
5
6
7
8
9
function shorten($string, $width) {
  if(strlen($string) > $width) {
    $string = wordwrap($string, $width);
    $string = substr($string, 0, strpos($string,"
"
));
  }

  return $string;
}

干得好:

1
2
3
4
5
6
7
8
9
10
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多字节字符。

1
2
3
4
5
function wholeWordTruncate($s, $characterCount)
{
    if (preg_match("/^.{1,$characterCount}\b/su", $s, $match)) return $match[0];
    return $s;
}

一个可能的边缘案例…如果字符串在前$characterCount个字符中没有任何空格,它将返回整个字符串。如果您希望它强制在$characterCount处中断,即使它不是单词边界,您也可以使用它:

1
2
3
4
5
function wholeWordTruncate($s, $characterCount)
{
    if (preg_match("/^.{1,$characterCount}\b/su", $s, $match)) return $match[0];
    return mb_substr($return, 0, $characterCount);
}

最后一个选项,如果您想让它添加省略号,如果它截断字符串…

1
2
3
4
5
6
7
8
9
10
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;
}


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

说明:

  • ^—从字符串开始
  • ([\s\S]{1,200})—任意字符从1到200
  • [\s]+?—不包括短文本末尾的空格,这样我们可以避免word ...而不是word...
  • [\s\S]+—匹配所有其他内容

测验:

  • regex101.com我们再加上or几个其他的r
  • regex101.comorrrr正好有200个字符。
  • 第五次rorrrrr后的regex101.com除外。
  • 享受。


    我将使用preg_match函数来实现这一点,因为您需要的是一个非常简单的表达式。

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

    表达式的意思是"匹配从长度1-200开始,以空格结尾的任何子字符串。"结果在$result中,匹配在$matches中。这就解决了你最初的问题,这个问题专门以任何空格结尾。如果要使其以换行符结尾,请将正则表达式更改为:

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

    好吧,我得到了基于上述答案的另一个版本,但是考虑到更多的因素(UTF-8、和 ;),如果与wp一起使用,还会有一行去掉wordpress的短代码注释。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function neatest_trim($content, $chars)
      if (strlen($content) > $chars)
      {
        $content = str_replace('&nbsp;', ' ', $content);
        $content = str_replace("
    "
    , '', $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;
      }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /*
    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);
    }

    用途:

    1
    2
    $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)元素将包含字符串的其余部分。


    这是对Mattmac答案的一个小修正:

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

    唯一的区别是在$string末尾添加一个空格。这确保了最后一个单词不会被rex357的评论切断。

    我没有足够的代表点添加此评论。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?php
    function stripByWords($string,$length,$delimiter = '') {
        $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;
    }
    ?>


    基于@justin poley的regex:

    1
    2
    3
    4
    5
    6
    // 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
    2
    3
    4
    $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),"
    "
    )));


    使用此:

    以下代码将删除","。如果您有任何其他字符或子字符串,可以使用它而不是","

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

    //如果您有另一个字符串帐户

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


    我以前用过这个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?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,"
    "
    )) ." More...";
        }
        echo $string;
    ?>


    我创建了一个更类似于substr的函数,并使用了@dave的思想。

    1
    2
    3
    4
    5
    6
    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:全长切割可能小于SUBSTR。


    在dave和amalmurali的代码中添加了if/elseif语句,用于处理没有空格的字符串

    1
    2
    3
    4
    5
    6
    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);
    }


    我认为这是最简单的方法:

    1
    2
    $lines = explode('???',wordwrap($string, $length, '???'));
    $newstring = $lines[0] . ' &bull; &bull; &bull;';

    我正在使用特殊字符分割文本并剪切它。


    我发现这个作品:

    函数将字符串缩写为单词($string,$max_length,$buffer){

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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;

    }

    缓冲区允许您调整返回字符串的长度。


    我知道这很旧,但是…

    1
    2
    3
    4
    5
    6
    function _truncate($str, $limit) {
        if(strlen($str) < $limit)
            return $str;
        $uid = uniqid();
        return array_shift(explode($uid, wordwrap($str, $limit, $uid)));
    }


    在这里你可以试试这个

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


    可能这对某人有帮助:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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;
        }

    ?>