HTML 檔案格式
HyperText Markup Language,網頁的結構格式。
- MIME 類型
- text/html
- 副檔名
- .html, .htm
- 分類
- 文字與標記語言
Magic number(檔案簽章)
以 '<!doctype html' 或 '<html' 開頭的純文字。
關於 HTML 格式
HTML 是由它的解析器定義的,不是由文法定義的。標準並沒有描述一組合法文件、然後把其他東西全部拒收;它是逐位元組描述瀏覽器面對任何輸入時該做什麼,包括格式錯誤的、被截斷的、甚至根本不是 HTML 的輸入。這就是為什麼實務上根本不存在「無效的 HTML 檔案」,為什麼兩個不同的瀏覽器面對同樣壞掉的標記會建出一模一樣的樹,也為什麼這個格式沒有標頭、沒有長度、沒有版本、也沒有尾標可以讓工具在開始解析之前先檢查。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 3 | UTF-8 byte-order mark | EF BB BF。它一出現,瀏覽器就不再往下找了:標準規定標記讓編碼成為確定的事實,優先於 Content-Type 標頭裡的 charset,也優先於文件裡任何 meta 元素。這是 HTML 裡極少數在固定位元組位移上就定案的東西。 |
| 0 | 15 | DOCTYPE | 3C 21 44 4F 43 54 59 50 45 20 68 74 6D 6C 3E,現代 doctype 的 15 個位元組。大小寫無所謂,而瀏覽器的嗅探規則只要求前 14 個——到 HTML 這幾個字母為止——後面接一個空白或右角括號就算數。 |
| 0 | 5 | html start tag | 3C 68 74 6D 6C,後面再接一個空白或右角括號。放在位移 0 完全合法,因為 doctype 不是必要的;沒有 doctype 的文件會用 quirks 模式解析,一堆幾十年前的排版行為回來了,而 box sizing 也不再是當初設計的那樣。 |
| 0 | variable | Any other recognised opener | 嗅探表還會比對 HEAD、SCRIPT、IFRAME、H1、DIV、FONT、TABLE、A、STYLE、TITLE、B、BODY、BR、P 以及註解開頭,每一個都允許前面有空白,而名稱後面都必須接空白或右角括號。從頁面裡存下來的片段,通常命中的是這些,而不是前面兩列的那兩種。 |
| 0 | 1024 | Encoding prescan window | 這不是一個欄位,而是一條值得記在腦子裡的界線:規範要求瀏覽器只在前 1024 個位元組裡找 meta charset。被一大段授權註解或行內指令碼推到那之後的 charset 宣告,等於根本沒被看見,頁面就退回去用猜的。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| XML | 開頭是 3C 3F 78 6D 的文件是 XML,而以 application/xhtml+xml 送出的 XHTML 會進 XML 解析器,那裡少關一個標籤就是致命的。同樣的位元組改用 text/html 送出,會被無聲地修復、正常算繪。差別根本不在檔案裡,而在伺服器送的標頭裡——這也是為什麼同一份文件放在同一個目錄下,行為可以完全是兩回事。 |
| SVG | 獨立的 .svg 是帶著 svg 根元素的 XML,由 XML 解析器處理。同樣的標記寫在 HTML 文件裡面時,則是由 HTML 解析器依 foreign content 規則處理,標籤名稱會被修正大小寫、自我關閉語法也有特殊處理。這些元素可以逐位元組相同;決定它們是什麼意思的,是哪個解析器看到它們。 |
| Plain text/Markdown | 沒有可靠的位元組測試。如果所有標籤樣式都沒命中,只要開頭那些位元組不含二進位資料,嗅探演算法就退回純文字,所以一份 README、一個以變數開頭的樣板,或一封信的內文,在位元組層級和一般散文分不出來。你手上只有副檔名與 Content-Type,而兩者都是宣稱、不是證據。 |
| Failed downloads | 拿 hex 檢視器看 HTML 最有用的一件事是:壞掉的下載最後往往就是它。打不開的 ZIP、載不出來的圖片、閱讀器拒收的 PDF——打開一看,位移 0 是 3C 21 44 4F 43 54 59 50 45,那你看的就是一頁登入畫面、一則流量限制通知,或是一個 404,只是被存成了你原本想要的那個檔名。 |
常見的壞法
- doctype 前面有東西。空白與註解會被忽略、沒有代價,但只要前面有真正的文字——伺服器端語言吐出來的警告、被 include 的檔案漏出來的一行——文件就掉進 quirks 模式。頁面照樣算繪,因為它永遠都會算繪,只是 box sizing 和繼承來的字級和當初測過的版面有微妙的差異。
- 哪裡都沒有宣告編碼。沒有標記、Content-Type 沒有 charset、前 1024 個位元組裡也沒有 meta,瀏覽器就用猜的,而猜法取決於讀者的地區設定。同一頁在做它的辦公室裡是對的,換個地方就滿是亂碼,而位元組從頭到尾一個都沒變。
- 被截斷。下載一半完全沒有跡象:輸入結束時,解析器把還開著的元素通通關掉,然後把手上的東西算繪出來。少了頁尾的頁面看起來像樣板有 bug,而不是像一個提早結束的檔案,唯一能確定的辦法是把位元組數和對方宣告的 Content-Length 比一比。
- 重複逸出。文字在通過樣板時被逸出了兩次,算繪出來就是看得見的標籤語法而不是格式,而這期間檔案完全格式良好。要搜尋的位元組是 26 61 6D 70 3B——一個被逸出的 & 符號——卡在一個被逸出的角括號前面。
- 存下來的頁面不是那個頁面。另存新檔會改寫網址、搬移或內嵌資源,還會注入一段記錄來源的註解,所以磁碟上的位元組不是伺服器送出的位元組。拿存下來的頁面和重新抓一次的結果做 diff,報出來的多半是瀏覽器自己的改動,這也讓存檔副本幾乎沒有證據價值。
這個格式現在還在用嗎
HTML 不會被取代,所以關於它比較有意思的問題是:它在瀏覽器之外會出現在哪。最大宗是電子郵件,而那裡跑的是化石級的子集——表格排版加行內樣式——因為郵件軟體從來沒有採用那套解析標準,以後也不會。Electron 和它的模仿者把瀏覽器引擎塞進桌面應用程式,意思是大家每天在用的軟體有很大一部分,表面上就是 HTML。靜態網站產生器、文件工具與設計系統全都把它當成建置產物吐出來,而不是還有人在手寫。至於磁碟上的檔案,最常是意外遇上它的:你打開一個本該是別種格式的東西,結果伺服器送來的是錯誤頁,那就是 HTML——所以認得出它前十五個位元組,是跟檔案打交道的人最值得背起來的東西之一。
在這個站上打開 HTML 檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 HTML 搞混的格式,可以比對它們的開頭位元組。