首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

安全截斷 HTML/UBB 字串:堆疊法確保標籤完整性

     編輯:關於ASP.NET

在 CMS、部落格摘要、搜尋結果等場景,需將含標籤的 HTML/UBB 字串截斷至指定長度。直接 Substring 會導致標籤未閉合、版面崩潰。本文實作基於堆疊追蹤標籤狀態的安全截斷演算法,支援自訂標籤字元、指定必閉合標籤、半截標籤保留/移除策略。

核心演算法邏輯

  1. 遍歷字串字元,遇到標籤起始字元(如 <)進入標籤解析狀態
  2. 解析標籤名稱,判斷為開啟標籤(<div>)或關閉標籤(</div>)
  3. 開啟標籤推入堆疊,關閉標籤彈出堆疊(配對驗證)
  4. 累積輸出字元至指定長度
  5. 遍歷結束後,堆疊剩餘標籤即為未閉合標籤,依策略補全或移除

完整實作碼

public static class HtmlTruncator
{
    private const char EndSlash = '/';

    /// 
    /// 安全截斷 HTML/UBB 字串
    /// 
    public static string SafeTrim(
        string input, 
        int length, 
        bool trimHalfTag, 
        char tagStartChar, 
        char tagEndChar, 
        string[] mustCloseTags)
    {
        if (length <= 0) throw new ArgumentException("length 必須為正數");
        if (mustCloseTags == null) throw new ArgumentNullException(nameof(mustCloseTags));

        if (string.IsNullOrEmpty(input) || input.Length <= length) return input;

        var result = new StringBuilder(length + 50); // 預留補全標籤空間
        var tags = new Stack();

        for (int i = 0; i < length; i++)
        {
            char c = input[i];

            if (c == tagStartChar)
            {
                result.Append(c);
                var tag = new StringBuilder();
                int tagIndex = i + 1;
                bool isTagEnd = false;
                bool isTagNameEnd = false;
                bool hasMarked = false;

                while (tagIndex < input.Length)
                {
                    char tagC = input[tagIndex];
                    result.Append(tagC);
                    tagIndex++;

                    if (tag.Length == 0 && tagC == EndSlash)
                    {
                        isTagEnd = true;
                        continue;
                    }

                    if (!isTagNameEnd)
                    {
                        if (char.IsLetterOrDigit(tagC))
                            tag.Append(tagC);
                        else
                            isTagNameEnd = true;
                    }

                    if (tag.Length > 0 && isTagNameEnd && !hasMarked)
                    {
                        if (isTagEnd)
                            tags.Pop();
                        else
                            tags.Push(tag.ToString());
                        hasMarked = true;
                    }

                    if (isTagNameEnd && tagC == tagEndChar)
                    {
                        i = tagIndex - 1;
                        break;
                    }
                }
            }
            else
            {
                result.Append(c);
            }
        }

        // 處理未閉合標籤
        while (tags.Count > 0)
        {
            string tag = tags.Pop();
            bool isMustClose = mustCloseTags.Any(t => 
                string.Equals(t, tag, StringComparison.OrdinalIgnoreCase));

            if (isMustClose)
            {
                if (trimHalfTag)
                {
                    // 移除半截標籤
                    int lastIdx = result.ToString().LastIndexOf(
                        tagStartChar + tag, StringComparison.OrdinalIgnoreCase);
                    if (lastIdx >= 0)
                        result.Length = lastIdx;
                }
                else
                {
                    // 補全關閉標籤
                    result.Append(tagStartChar).Append(EndSlash).Append(tag).Append(tagEndChar);
                }
            }
        }

        return result.ToString();
    }
}

使用範例

var html = "<div><p>這是一段<strong>重要</strong>內容</p><span>尾巴</span></div>";

// 截斷 20 字,補全標籤,強制閉合 div、p、strong
var trimmed = HtmlTruncator.SafeTrim(
    html, 20, 
    trimHalfTag: false, 
    tagStartChar: '<', 
    tagEndChar: '>', 
    mustCloseTags: new[] { "div", "p", "strong", "span" });

// 結果: "<div><p>這是一段<strong>重</strong></p></div>"

UBB 碼支援

var ubb = "[b]粗體[/b][color=red]紅字[/color]";
var trimmed = HtmlTruncator.SafeTrim(
    ubb, 10, false, '[', ']', new[] { "b", "color" });
// 結果: "[b]粗體[/b][color=red]紅[/color]"

效能與擴充建議

  • StringBuilder 替代字串串接,避免大量記憶體配置
  • 可加入 HtmlAgilityPack 整合選項:複雜 HTML 結構建議改用 DOM 解析
  • 支援 void 標籤(<br/>, <img>)自動識別,不納入堆疊
  • 加入單元測試覆蓋:巢狀標籤、屬性含引號、實體字元、跨標籤截斷等邊界情況

適用場景對照

場景建議策略
文章摘要列表 補全標籤,保留版面結構
搜尋關鍵字高亮片段 移除半截標籤,避免殘留樣式干擾
UBB 論壇回覆預覽 自訂 [ ] 標籤字元,強制閉合常用標籤
郵件內容純文字預覽 trimHalfTag=true,完全移除所有標籤

此演算法已在多個產品專案驗證穩定,歡迎參考改良。若有更優雅的實作(如正則預編譯、狀態機優化),歡迎分享交流。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved