繁体   English   中英

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

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

我有一个用 PHP 编写的代码片段,它从数据库中提取一段文本并将其发送到网页上的小部件。 原始文本块可以是一篇长文章,也可以是一两句短句; 但是对于这个小部件,我不能显示超过 200 个字符。 before 200 chars.我可以使用 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 字符。 在 REGEX 末尾添加 'u' 来处理它:

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

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

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

当我注意到wordwrap函数的 $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 个字符后的第一个空格处截断的字符串。

干得好:

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

享受。

这是我基于@Cd-MaN 方法的函数。

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

  return $string;
}

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

好的,所以我根据上述答案得到了另一个版本,但考虑了更多内容(utf-8,\\n 和 &nbsp; ),如果与 wp 一起使用,还有一行剥离了评论的 wordpress 短代码。

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

我会使用 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);

这是 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")));

基于@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]. '...';
}

虽然这是一个相当古老的问题,但我想我会提供一个替代方案,因为它没有被提及并且对 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"
*/
// a looonnng string ...
$str = "Le Lorem Ipsum est simplement du 
faux texte employé dans la composition et 
la mise en page avant impression. 
Le Lorem Ipsum est le faux texte standard de 
l'imprimerie depuis les années 1500, quand un 
imprimeur anonyme assembla ensemble des morceaux 
de texte pour réaliser un livre spécimen de polices
de texte. Il n'a pas fait que survivre cinq siècles,
mais s'est aussi adapté à la bureautique informatique,
sans que son contenu n'en soit modifié. Il a été 
popularisé dans les années 1960 grâce à la vente 
de feuilles Letraset contenant des passages du
Lorem Ipsum, et, plus récemment, par son inclusion 
dans des applications de mise en page de texte, 
comme Aldus PageMaker";
// number chars to cut
$number_to_cut = 300;
// string truncated in one line !
$truncated_string = 
substr($str, 0, strrpos(substr($str, 0, $number_to_cut), ' '));
// test return
echo $truncated_string;

// variation (add ellipsis) : echo $truncated_string.' ...';

// output :
/* Le Lorem Ipsum est simplement du 
faux texte employé dans la composition et 
la mise en page avant impression. 
Le Lorem Ipsum est le faux texte standard de 
l'imprimerie depuis les années 1500, quand un 
imprimeur anonyme assembla ensemble des morceaux 
de texte pour réaliser un livre
*/

我知道这很旧,但是...

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

我创建了一个更类似于 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.:全长切割可能小于substr。

DaveAmalMurali的代码中添加了 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);
}

我发现这有效:

功能 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;

}

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

据我所知,这里的所有解决方案仅适用于起点固定的情况。

允许你转这个:

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

我以前用过这个

<?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( $str, 0, strpos($str, ' ', 200) ); 

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

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

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

用这个:

以下代码将删除“,”。 如果您有任何其他字符或子字符串,您可以使用它代替 ','

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

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

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

可能这会帮助某人:

<?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 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM