HTML 實體編碼 / 解碼

線上轉義與反轉義 HTML 實體。把 < > & " ' 轉成命名實體,可選對全部非 ASCII 字元編碼,也支援把命名或數字引用解碼迴文本。

把文本轉義成可安全嵌入 HTML 的形式,或把實體還原成字元。 在任意一側輸入即可按該方向轉換。最小模式只轉義真正影響正確性的五個字元;擴充套件模式還會編碼所有非 ASCII 字元,以獲得最大相容性。

常用實體對照

字元 命名實體 數字引用
& &amp; &#38;
< &lt; &#60;
> &gt; &#62;
" &quot; &#34;
' &#39; &#39;
\u{00A0} &nbsp; &#160;
\u{00A9} &copy; &#169;
\u{20AC} &euro; &#8364;

HTML 實體,以及轉義為什麼重要

會破壞 HTML 的五個字元

HTML 解析器把 < 當作標籤開始,把 & 當作實體引用開始。只要你的內容裡出現這兩者,解析器就不再把它當作純文本。轉義的做法是用一個引用替換該字元,解析器會把引用還原成原字元,而不賦予它任何結構含義。

HTML 中真正需要轉義的只有五個字元:

  • &&amp; —— 必須最先替換,否則後面的轉義會被二次轉義
  • <&lt;
  • >&gt;
  • "&quot; —— 出現在雙引號屬性值裡時必須轉義
  • '&#39; —— 出現在單引號屬性值裡時必須轉義

嚴格來說,元素文本中只需要處理 &<。另外三個只在文本可能落進屬性值時才要緊,而轉義那一刻你通常無法預知它最終會去哪裡,所以五個全轉義才是穩妥習慣。

命名、十進位制與十六進位制引用

同一個字元有三種寫法。不換行空格可以寫成 &nbsp;&#160;&#xA0;。命名引用可讀性最好,但 HTML5 的名稱表約有 2200 條,老解析器認識的遠少於此,因此冷門名稱存在移植風險。數字引用適用於任何 Unicode 碼點,永遠安全。HTML5 中所有命名引用都要求結尾分號;少數遺留名稱如 &amp 出於向後相容仍能在無分號時被解析,但依賴這一點遲早出事。

轉義不等於消毒

轉義是阻止跨站指令碼的關鍵:如果含 <script> 的使用者輸入被轉義成 &lt;script&gt;,瀏覽器只會顯示字面文本,不會執行任何指令碼。但轉義必須發生在正確的上下文裡。在 <script> 塊內、style 屬性內或 URL 中,HTML 轉義都不夠,這些位置各有自己的編碼規則。而且轉義只應在輸出時執行一次。輸入時轉一次、輸出時又轉一次,得到的是 &amp;lt; 和一屏亂碼。

什麼時候需要擴充套件模式

任何現代規範都不要求轉義非 ASCII 字元。UTF-8 文件可以直接包含 é,而且通常這才是更好的選擇:更短、更易讀、也更好搜尋。擴充套件模式是為那些位元組流必須穿過會破壞非 ASCII 的通道的場景準備的 —— 某些遺留郵件模板、老舊 CMS 欄位、宣告編碼含糊的 XML 流水線,或者卡在 Latin-1 的構建環節。如果你能端到端控制編碼,就留在最小模式。

不換行空格的陷阱

&nbsp; 是 U+00A0,與普通空格 U+0020 是不同的字元。它阻止換行,也阻止瀏覽器合併連續空白,所以所見即所得編輯器會大量產出它。因為外觀完全一致,它會悄無聲息地破壞字串比較、某些引擎中用 \s 寫的正則,以及 CSV 匯入。如果某個值看起來沒問題卻死活匹配不上,先檢查有沒有混進不換行空格。

開源說明:使用純字串與碼點運算實現,未使用第三方庫。

常見問題

大於號一定要轉義嗎?
嚴格來說不用 —— 文本中單獨的 > 沒有歧義,能正常解析。轉義它只是約定俗成,因為代價為零,還能避免生成的標記產生混淆,尤其是當輸出後續會被比瀏覽器更嚴格的工具處理時。
為什麼 & 必須先轉義?
因為其他所有轉義序列都以 & 開頭。如果你先把 < 換成 &lt; 再去轉義 &,下一輪會把那個 & 變成 &amp;,頁面上就出現 &amp;lt; 了。
只靠轉義就能防住 XSS 嗎?
只在 HTML 文本和屬性上下文中、並且在輸出時執行才有效。注入到 JavaScript、CSS 或 URL 中的內容需要各自語言的轉義規則。轉義是其中一層防護,不是完整方案。
命名實體和數字引用該用哪個?
數字引用普遍受支援,適用於任何碼點。命名實體對大家都認識的那幾個更可讀。實踐中:五個核心字元用命名,其他罕見字元用數字。
帶重音的字元需要編碼嗎?
在 UTF-8 文件裡不需要,而現代網頁內容基本都是 UTF-8,直接寫就好。只有當流水線中某個環節不能可靠傳遞非 ASCII 位元組時,才使用擴充套件模式。
&nbsp; 和普通空格有什麼區別?
不換行空格是碼點 U+00A0。看起來一樣,但它阻止換行、且不會與相鄰空白合併。在比較時它也是完全不同的字元,這讓它成為許多隱蔽 bug 的來源。