首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP HTML 標籤修復庫:自動補全未閉合標籤、支援巢狀與就近閉合模式、可截斷長度

     編輯:PHP 綜合知識

處理使用者輸入的 HTML 片段 (如富文本編輯器內容、RSS 摘要、評論截斷) 時,常遇到標籤未閉合導致版面崩壞。本文提供生產級 fixHtmlTag() 函數,支援兩種閉合策略、指定標籤就近閉合、XHTML 自閉合標籤修正、長度截斷與標籤小寫化。

功能特性

  • 巢狀閉合模式 (預設):<div><p>內容 → <div><p>內容</p></div>,符合 HTML 規範樹狀結構。
  • 就近閉合模式:<p>段1<p>段2 → <p>段1</p><p>段2</p>,適合段落類標籤。
  • 混合模式:預設巢狀,指定特定標籤 (如 p, li, dt) 就近閉合。
  • 自閉合標籤修正:<br> → <br />,<img>、<input> 等自動識別。
  • 長度截斷:指定字元數,截斷時自動補全已開啟標籤。
  • 編碼安全:使用 mb_strlen、mb_substr 正確處理多位元組字元。

完整函數實作 (v0.2)

<?php
/**
 * HTML 標籤修復主函數
 *
 * @param array $params 參數陣列:
 *   - html (string) 必填,待修復 HTML 字串
 *   - options (array) 選項:
 *     - tagArray (string[]) 就近閉合標籤清單 (巢狀模式下生效)
 *     - type (string) 'NEST'|'CLOSE' 閉合模式
 *     - length (int|null) 截斷字元數
 *     - lowerTag (bool) 標籤轉小寫 (預設 true)
 *     - XHtmlFix (bool) 自閉合標籤修正 (預設 true)
 * @return string 修復後 HTML
 */
function fixHtmlTag(array $params = []): string {
    // 1. 參數預設值與解構
    $html      = $params['html'] ?? '';
    $options   = $params['options'] ?? [];
    $tagArray  = array_map('strtolower', $options['tagArray'] ?? []);
    $type      = strtoupper($options['type'] ?? 'NEST');
    $length    = $options['length'] ?? null;
    $lowerTag  = $options['lowerTag'] ?? true;
    $xhtmlFix  = $options['XHtmlFix'] ?? true;

    if (!in_array($type, ['NEST', 'CLOSE'])) $type = 'NEST';

    // 2. 自閉合標籤白名單 (無結束標籤)
    $voidTags = ['meta', 'link', 'base', 'br', 'hr', 'input', 'img', 'area', 'col', 'embed', 'param', 'source', 'track', 'wbr'];

    // 3. 以 <...> 為分隔符切割,保留標籤本身
    $tokens = preg_split('/(<[^>]+>)/si', $html, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);

    $result   = '';
    $tagStack = [];       // 標籤堆疊
    $currentLen = 0;      // 目前輸出長度
    $isClosed = true;     // 就近閉合狀態旗標

    foreach ($tokens as $token) {
        if (trim($token) === '') { $result .= $token; continue; }

        // 3.1 註解標籤直接輸出
        if (preg_match('/^$/si', $token)) { $result .= $token; continue; }

        // 3.2 已正確自閉合的標籤 
        if (preg_match('/^<(\w+)[^>]*?\/>$/si', $token)) { $result .= $token; continue; }

        // 3.3 開始標籤 
        if (preg_match('/^<(\w+)[^>]*>$/si', $token, $m)) {
            $tagName = $lowerTag ? strtolower($m[1]) : $m[1];
            $token   = $lowerTag ? '<' . $tagName . substr($token, 1 + strlen($m[1])) : $token;

            // 處理上一個就近閉合標籤
            if (!$isClosed) {
                if ($type === 'CLOSE' || in_array(end($tagStack), $tagArray)) {
                    $result .= '';
                }
            }

            $result .= $token;
            array_push($tagStack, $tagName);

            // 判斷是否為 void tag
            $isVoid = in_array($tagName, $voidTags);
            if ($isVoid) {
                if ($xhtmlFix && !preg_match('/\/>$/i', $token)) {
                    $result = substr_replace($result, ' />', -1); // 修正為 
                }
                array_pop($tagStack); // void tag 不入堆疊
            }

            // 更新就近閉合狀態
            $isClosed = !($type === 'CLOSE' || in_array($tagName, $tagArray));
            continue;
        }

        // 3.4 結束標籤 
        if (preg_match('/^<\/(\w+)[^>]*>$/si', $token, $m)) {
            $tagName = $lowerTag ? strtolower($m[1]) : $m[1];
            $token   = $lowerTag ? '' : $token;

            if (!empty($tagStack) && end($tagStack) === $tagName) {
                $result .= $token;
                array_pop($tagStack);
                $isClosed = true;
            } else {
                // 孤立結束標籤,直接輸出 (或可選丟棄)
                $result .= $token;
            }
            continue;
        }

        // 3.5 純文字內容
        if ($length !== null && $currentLen + mb_strlen($token) >= $length) {
            $token = mb_substr($token, 0, $length - $currentLen);
            $result .= $token;
            break;
        }
        $result .= $token;
        $currentLen += mb_strlen($token);
    }

    // 4. 結束時補全堆疊剩餘標籤 (巢狀閉合)
    while (!empty($tagStack)) {
        $result .= '';
    }

    return $result;
}
?>

使用範例

<?php
$html = '

第一段

第二段

  • 項目1
  • 項目2
結尾'; // 1. 預設巢狀模式 echo fixHtmlTag(['html' => $html]); /* 輸出:

第一段

第二段

  • 項目1
  • 項目2
結尾
*/ // 2. 指定 p, li 就近閉合 (混合模式) echo fixHtmlTag([ 'html' => $html, 'options' => ['tagArray' => ['p', 'li'], 'type' => 'NEST'] ]); /* 輸出:

第一段

第二段

  • 項目1
  • 項目2
結尾
*/ // 3. 全域就近閉合模式 echo fixHtmlTag([ 'html' => $html, 'options' => ['type' => 'CLOSE'] ]); /* 輸出:

第一段

第二段

  • 項目1
  • 項目2
結尾
*/ // 4. 截斷 30 字元並修復 echo fixHtmlTag([ 'html' => '

這是一段很長的測試內容用來驗證截斷功能

', 'options' => ['length' => 30] ]); /* 輸出:

這是一段很長的測試內容用來驗證截

*/ ?>

架構設計重點

模組說明
Token 化preg_split 保留分隔符,單次遍歷 O(n)
堆疊機制陣列模擬 Stack,array_push/pop O(1)
狀態機$isClosed 追蹤就近閉合狀態
多位元組安全全程使用 mb_strlen/substr
XHTML 修正void tag 自動補 />

局限性與替代方案

  • 不處理屬性值中的 >:極少見但可能誤判。嚴格場景請用 DOMDocument::loadHTML。
  • 不修正屬性值未加引號:<img src=test.jpg> 保持原樣。
  • 效能:百 KB 級片段 < 1ms;大文件 (MB 級) 建議用 HTMLPurifier 或 masterminds/html5-php (HTML5 解析器)。
  • 安全性:此函數**不防 XSS**,輸出前仍需配合 CSP、輸出編碼。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved