截取html

Fork Me On Github
zodream 编程技术 PHP 2019年12月

截取html

需求

从一段html 中截取指定长度的内容,

要求:

  1. 长度为不包括html标签的内容长度,即 innerTEXT 长度
  2. 保留相关标签,并进行闭合

代码

简单版代码

php
                                                                              

/**
 * 截取html, 标签不计入长度,自动闭合标签
 * @param string $html
 * @param int $length
 * @param string $endWith
 * @bug 本方法缺陷: 未进行严格标签判断 例如 < <gg data="<a>"
 * @example ::substr('<p>1111<div/>111<br>111<i class="444">11</i>111 55555</p>', 12)
 * @return string
 */
public static function substr(string $html, int $length, string $endWith = '...'): string {
    if ($length < 1) {
        return $endWith;
    }
    $maxLength = mb_strlen($html);
    if ($maxLength < $length) {
        return $html;
    }
    $result = '';
    $n = 0;
    $unClosedTags = [];
    $isCode = false; // 是不是HTML代码
    $isHTML = false; // 是不是HTML特殊字符,如
    $notClosedTags = ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'link', 'meta', 'param', 'embed', 'command', 'keygen', 'source', 'track', 'wbr'];
    $tag = '';
    for ($i = 0; $i < $maxLength; $i++) {
        $char = mb_substr($html, $i, 1);
        if ($char == '<') {
            // 进入标签
            $isCode = true;
            $tag = '';
        }
        else if ($char == '&') {
            $isHTML = true;
        }
        else if ($char == '>' && $isCode) {
            $n = $n - 1;
            $isCode = false;
            $tag = explode(' ', $tag, 2)[0];
            if (substr($tag, 0, 1) === '/') {
                // 判断是否时结束标签, 倒序找到邻近开始标签,进行移除
                for ($j = count($unClosedTags) - 1; $j >= 0; $j --) {
                    if ($tag === $unClosedTags[$j]) {
                        $unClosedTags = array_splice($unClosedTags, 0, $j - 1);
                        break;
                    }
                }
                $tag = '';
            }
            if (!empty($tag) &&
                substr($tag, strlen($tag) - 1, 1) !== '/'
                && !in_array(strtolower($tag), $notClosedTags)) {
                // 不是结束标签且不是自闭合且不是无需闭合把标签加入
                $unClosedTags[] = $tag;
            }
            $tag = '';
        }
        else if ($char == ';' && $isHTML) {
            $isHTML = false;
        }
        if ($isCode && ($tag !== '' || $char !== '<')) {
            $tag .= $char;
        }
        if (!$isCode && !$isHTML && $char !== ' ') {
            $n = $n + 1;
        }
        $result .= $char;
        if ($n >= $length) {
            break;
        }
    }
    $result .= $endWith;
    for ($j = count($unClosedTags) - 1; $j >= 0; $j --) {
        $result .= sprintf('</%s>', $unClosedTags[$j]);
    }
    return $result;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778

此代码存在的问题

  1. 未对 < 进行验证是否真的为标签开始
  2. 未对标签属性值中可能出现的标记 进行过滤

相关需求

  1. 指定行的截取
点击查看全文
0 257 0