在PHP中如何截断带HTML标签的富文本字符串

前言

在开发中,截断字符串是一个常见的操作。在PHP中,截断字符串十分方便,使用mb_substr函数就可以。
但这只是针对普通的字符串而言,如果要截断的是一个带HTML标签的富文本字符串,就不能简单的使用这个函数了。
大部分HTML标签都是成对出现的,我们不能在一对标签的中间进行截断,也不能把标签本身截断,否则就会出问题。

代码

<?php

class HtmlText
{

    /**
     * 截断带HTML标签的字符串
     *
     * @param string $text     带HTML标签的字符串
     * @param int    $length   要截取的字数
     * @param string $ellipsis 省略符号
     *
     * @return string 截断后的字符串
     */
    public static function truncate(string $text, int $length, string $ellipsis = '...'): string
    {
        $text = str_replace(["\r\n", "\n"], '', $text);
        $openTagsBucket = [];

        $result = '';
        $loop = true;
        $isCut = false;
        while ($loop) {
            if (preg_match('/^</', $text)) {
                // 寻找下一个">"字符的位置
                $nextGtCharPos = mb_strpos($text, '>');
                if ($nextGtCharPos !== false) {
                    $splitPos = $nextGtCharPos + 1;

                    // 将<>之间的内容截取出来,并判断是否是合法的HTML标签
                    $isTag = false;
                    $tagStr = mb_substr($text, 0, $splitPos);
                    if (preg_match('/^<([a-z]+[1-9]*).*>$/i', $tagStr, $matches)) {
                        array_unshift($openTagsBucket, [
                            'tag' => $matches[0],
                            'tag_name' => strtolower($matches[1]),
                        ]);
                        $isTag = true;
                    } elseif (preg_match('/^<\/([a-z]+[1-9]*)>$/i', $tagStr, $matches)) {
                        if (isset($openTagsBucket[0])
                            && strcasecmp($openTagsBucket[0]['tag_name'], $matches[1]) == 0) {
                            array_shift($openTagsBucket);
                        }
                        $isTag = true;
                    }
                    // 如果是HTML标签,则直接拼接
                    if ($isTag) {
                        $result .= $tagStr;
                        $text = mb_substr($text, $splitPos);
                        continue;
                    }
                }
            }

            if ($text === '') {
                break;
            }
            $nextLtCharPos = mb_strpos($text, '<', 1);
            if ($nextLtCharPos === false) {
                $loop = false;
                $normalStr = $text;
            } else {
                $normalStr = mb_substr($text, 0, $nextLtCharPos);
                $text = mb_substr($text, $nextLtCharPos);
            }

            $normalStrLen = mb_strlen($normalStr);
            if ($normalStrLen >= $length) {
                $loop = false;
                $isCut = true;
                $subStr = mb_substr($normalStr, 0, $length);
                $result .= "{$subStr}{$ellipsis}";
            } else {
                $result .= $normalStr;
                $length -= $normalStrLen;
            }
        }

        // 字符串被截断才需要补闭合标签
        if ($isCut) {
            foreach ($openTagsBucket as $item) {
                // 忽略自闭合标签
                if (in_array($item['tag_name'], ['link', 'meta', 'base', 'img', 'input', 'br', 'hr'])) {
                    continue;
                }
                $result .= "</{$item['tag_name']}>";
            }
        }

        return $result;
    }

}

用例测试

用例1:

<?php
$text =<<<EOT
<div>
<script src="jquery-2.1.1.min.js"></script>
<p style="color: red;">
<a href="#">床前明月光</a>
</p>
<p>疑似地上霜</p>
<img src="jquery-2.1.1.min.js" alt=""/>
<h2>举头望明月</h2>
</div>
EOT;

// 截取8个字
echo HtmlText::truncate($text, 8);

输出:

<div>
<script src="jquery-2.1.1.min.js"></script>
<p style="color: red;">
<a href="#">床前明月光</a>
</p>
<p>疑似地...</p>
</div>

刚好是8个字,HTML标签也没有被截断,并自动在末尾拼接上了...省略号,正常。

用例2:

$text = '这是一段不带标签的文本';
echo HtmlText::truncate($text, 8); // 输出:这是一段不带标签...

也可以用于不带HTML标签的文本

用例3:

$text = '这是一段只<p>有一个标签的文本';
echo HtmlText::truncate($text, 8); // 输出:这是一段只<p>有一个...</p>

其它

另外,开源框架cakephp也提供了一个截断HTML富文本的功能类,Text类的truncate方法,详情见github

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值