雜記 › 技術筆記 › 「允許 HTML」開啟後,為什麼代碼塊裡的 div 標籤還是被轉義

「允許 HTML」開啟後,為什麼代碼塊裡的 div 標籤還是被轉義

一、滿屏的字面量是從哪一步冒出來的

整理一篇內部文檔時,我在 Markdown 裡嵌了一段現成的 HTML:一個 <div class="callout"> 包住三行提醒,想靠它自帶的邊框把「這段設定別亂改」標出來。轉完以後邊框沒有,標籤倒整整齊齊排在地面上,尖括號全變成了 < 與 >。

卡住的是「允許 HTML」這個預設關著的開關:關掉時正文裡的 <b> 一律被轉義成字面量,開啟後行內與塊級標籤才原樣透傳。同一份源碼 x <b>y</b> z,關著產出 28 字元,開著產出 20 字元,差的就是被轉義撐出來的那幾個。

開關撥上去之後新的困惑跟著來了:那段示例代碼塊裡的 <div> 仍是字面量,像什麼都沒發生過。這一篇把透傳的邊界逐條講清,順帶說說表格、任務清單和標題層級上容易歸錯因的幾處。

關閉時 28 字元 開啟時 20 字元 同一條源碼 x <b>y</b> z 差的 8 個字元全在尖括號上 圖 1:切換「允許 HTML」前後的輸出長度

二、透傳有三條硬邊界,差一條就退回轉義

第一條是實體要成串。源碼裡的 &copy;、&amp;、&#65;、&#x41; 在開關關閉時被整體二次轉義,螢幕上留下 &copy; 這種前面多套一層的字樣;開啟後具名、十進位、十六進位三種成形實體一起放行。裸的 & 號與引號不在放行範圍,開著也照樣被寫成 &amp; 和 "。

第二條是標籤形狀。透傳要求小於號後面緊跟字母或驚嘆號,並以大於號收尾。夾在段落中間的 a <!-- hidden --> b 開著也被轉義,行內那一路不接受註釋;獨佔一行的 <!-- c --> 反倒原樣落地,塊級分支的字元類別裡帶著驚嘆號。沒收尾的 <b c 同樣按普通文字處理。

第三條是屬性體的長度。屬性段正好 400 字元時 <b x="…"> 整句透傳;再加一個字元到 401,開標籤被轉義、閉標籤 </b> 仍透傳,一句 HTML 被劈成半轉義半標籤。

能碰上的上限就這兩處:屬性體 400 字元、轉換提示 20 條(第 21 條起靜默丟棄)。字數與體積沒有門檻,輸入區不做長度判斷,也沒有超限回退。

400 字元 整句透傳 401 字元 一句被劈成兩半 左段:開標籤 <b x="…"> 被轉義 右段:閉標籤 </b> 仍原樣透傳 圖 2:屬性體長度卡在 400 與 401 之間

三、代碼塊這一路只經過轉義,開關碰不到它

圍欄代碼塊和 4 空格縮進代碼塊都直接進轉義那一步,不經過處理原始 HTML 的分支。於是 ```html 圍欄裡的 <div class="x">y</div>,開關開著與關著拿到逐位元組相同的輸出;縮進代碼裡貼一段腳本,兩種設定下同樣寫成轉義字樣。

行內代碼是同一口徑:一對反引號包住的 <b>,開關兩側都輸出 <code><b></code>。甚至開著「允許 HTML」之後,反引號裡的成形實體會被再多轉一層,落點比關著時更繞。

這麼一想就順了:開關回答的是「我寫的這段是不是真 HTML」,代碼塊天生在說「我在展示別人的 HTML」。我是把同一段源碼在 Markdown 轉 HTML 的兩種設定下各跑一遍、再逐字元比輸出才確認的。

四、開啟之後,反而變差的兩件事

第一件是尖括號自動連結。獨佔一行的 <https://example.org>,關著時正常出錨點;開著時塊級分支先命中,輸出原樣還是 <https://example.org> 這一段文字,連結沒了。同一行前面墊幾個字才回到行內那一路,錨點重新出現。

第二件是它根本不是過濾器。沒有標籤白名單這回事:開著時 <script>alert(1)</script> 原樣進結果,<span onclick="alert(1)"> 的事件屬性不會被洗掉,<iframe> 也照抄,而預覽那一側走的是 innerHTML,這些標籤是真會生效的。所以它的定位是「我知道自己在寫什麼」,不是「我貼的內容可以隨便信」。

五、表格、任務清單和標題:靜默的地方比提示多

表格的入場券是一行分隔行,且那一行裡同時有短橫與豎線。差一點,落點就完全不同:

輸入實測輸出
表頭、分隔行、一行資料206 字元,thead 與 tbody 齊全
同一張表關掉 GFM整張表退回一個 <p>,不出 table 標籤
把分隔行刪掉同樣退回 <p>,且不給任何提示
資料行比表頭多一格只出 2 個 <td>,多餘內容沒了,提示寫「表格第 1 行儲存格比表頭多」
:---、:---:、---: 三欄只有置中與右對齊拿到 style,左對齊那欄不帶 style
| a | 下面緊接一行 ---被認成底線式標題,整張表變成 <h2 id="a">
  • 提示裡的「第 1 行」是資料行序號,不是來源檔案的行號,照著它去數原文容易錯位。
  • 重新點一次轉換是把結果整體覆蓋,不會把兩輪的提示或正文疊起來。
  • 任務清單 - [x] 與 - [ ] 出 <input type="checkbox" checked disabled>,每條 <li> 帶 class="md-task",大寫 X 同樣算勾選;關掉 GFM 後方括號標記按原文留在正文裡,沒有 class 也沒有 input。

標題上有兩處容易歸錯因:七個井字號起頭不會被認成標題,層級最高只到 h6,目錄的嵌套因此嚴格跟著標題層級走、跳級會插進空的一層 <ul>。標題 id 走自建 slug——ASCII 標點整枚丟棄、中文保留、空格轉連字號、同名依次追加 -1、-2,這套並不屬於 CommonMark 或 GFM 的規定動作。

而 id 的寫入條件是「標題錨點」與「產生目錄」有一個為真:只關掉錨點、目錄還開著,正文裡照樣出現 <h1 id="a">;兩個都關才拿到不帶 id 的標籤。

六、三個繞不過去的小問題

Q:代碼塊裡的 HTML 有辦法透傳出來嗎?

沒有辦法。圍欄與縮進兩條分支只經過轉義,根本不看「允許 HTML」。想讓它真的變成結構,就寫進正文並開啟開關;只想展示代碼本身,留在代碼塊裡,字面量正是你要的結果。

Q:開著「允許 HTML」,貼來的腳本會被擋住嗎?

擋不住,它不是安全過濾,也沒有標籤白名單。帶腳本的標籤、帶事件屬性的 span、iframe 都原樣進結果,預覽那一側還會真的生效。要發到公網的稿子,我先把開關關著跑一遍,把滿地字面量當一份待審清單。

Q:統計那一行的「字元」和「HTML」為什麼對不上?

兩欄用的是不同的尺:「字元」按輸入源碼的 UTF-16 碼元數計,「HTML」按輸出文字的 UTF-8 位元組計,混著中文時差距會被拉大——一個漢字算一個碼元,卻不止一個位元組。

透傳只覆蓋正文裡成形且沒超長的標籤,代碼塊、不成串的實體、寫到一半的尖括號都在覆蓋之外。先弄清自己那段文字走的是哪一路分支,比記住開關的名字有用得多。

📎 相關文章

70 個位元組沒動,MIME 類型換了 13 次:副檔名才是唯一輸入 2026-10-23 轉 JPG 時透明被強制填底色:91 個品質檔位在 PNG 面前不參與編碼 2026-10-23 「全部清除」後還剩 553 位元組,「只去掉位置信息」連縮圖都沒動 2026-10-23 序號位數只是下限、重名時 (2) 插在後綴之前:批次重新命名 2000 個檔案的實測口徑 2026-10-23 兩條憑證指紋一條 95 個字元一條 64 個:差的 31 個全是冒號,摘要物件是整張 DER 2026-10-23