我有一个用PHP编写的代码片段,它从数据库中提取一个文本块,并将其发送到网页上的一个小部件。原文可以是一篇很长的文章,也可以是一两个短句;但是对于这个小部件,我不能显示超过200个字符。我可以使用substr()在200个字符处切断文本,但结果将在单词中间切断——我真正想要的是在200个字符前的最后一个单词的末尾切断文本。
当前回答
就我所知,这里所有的解只有在起点固定的情况下才有效。 允许你转动这个: 悲伤之神,神圣之神,痛苦之神,痛苦之神。Ut enim ad minim veniam。 到这个: 神圣的,神圣的… 如果想要截断一组特定关键字周围的单词,该怎么办?
截断一组特定关键字周围的文本。
我们的目标是能够转换这个:
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 ..."
运行代码。
其他回答
通过使用wordwrap函数。它将文本分割成多行,这样最大宽度就是您指定的宽度,在单词边界处断开。分割之后,你只需取第一行:
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'来处理它:
部分美元= preg_split (' / ([\ s \ n \ r] +) / u ',美元字符串,null, PREG_SPLIT_DELIM_CAPTURE);
Keep in mind whenever you're splitting by "word" anywhere that some languages such as Chinese and Japanese do not use a space character to split words. Also, a malicious user could simply enter text without any spaces, or using some Unicode look-alike to the standard space character, in which case any solution you use may end up displaying the entire text anyway. A way around this may be to check the string length after splitting it on spaces as normal, then, if the string is still above an abnormal limit - maybe 225 characters in this case - going ahead and splitting it dumbly at that limit.
当涉及到非ascii字符时,还有一个类似的警告;包含它们的字符串可能会被PHP的标准strlen()解释为比实际更长,因为单个字符可能占用两个或更多字节,而不是一个字节。如果你只是使用strlen()/substr()函数来分割字符串,你可能会在字符中间分割字符串!如果有疑问,mb_strlen()/mb_substr()更简单一些。
我相信这是最简单的方法:
$lines = explode('♦♣♠',wordwrap($string, $length, '♦♣♠'));
$newstring = $lines[0] . ' • • •';
我正在使用特殊字符分割文本并剪切它。
找到这个问题的完美解决方案是多么棘手,这令人惊讶。我还没有在本页上找到至少在某些情况下不会失败的答案(特别是如果字符串包含换行符或制表符,或者如果单词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;
}
好吧,所以我得到了另一个版本的这个基于上面的答案,但考虑到更多的东西(utf-8, \n和 ),如果与wp一起使用,也是剥离wordpress shortcodes注释的行。
function neatest_trim($content, $chars)
if (strlen($content) > $chars)
{
$content = str_replace(' ', ' ', $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;
}
推荐文章
- Printf与std::字符串?
- 不区分大小写的“in”
- 原则-如何打印出真正的sql,而不仅仅是准备好的语句?
- 如何从关联PHP数组中获得第一项?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- PHP/MySQL插入一行然后获取id
- 我如何排序一个多维数组在PHP
- 如何在PHP中截断字符串最接近于一定数量的字符?
- PHP错误:“zip扩展名和unzip命令都没有,跳过。”
- Nginx提供下载。php文件,而不是执行它们
- Json_encode()转义正斜杠
- 如何在PHP中捕获cURL错误
- Ruby数组到字符串的转换
- 为什么在Java和。net中不能修改字符串?
- 如何要求一个分叉与作曲家?