Warning:
This wiki has been archived and is now read-only.

HTML5/parsing

From HTML5 Chinese Interest Group Wiki
Jump to: navigation, search


解析 HTML 文档

本章節僅適用於使用者代理、資料挖掘工具與規範符合檢驗器。

注:將 XML 文件解析成 DOM 樹的規則是下一個章節 「XHTML 語法」的範疇。

使用者代理必須使用本章節描述的解析規則處理 HTML 文件。這些規則定義了所謂的 HTML 解析器

注:雖然本規範描述的 HTML 語法與 SGML 與 XML 相似,HTML 卻是有自己

解析規則的新語言。

之前的 HTML 版本(特別是從 HTML2 到 HTML4)以 SGML 為中心並使用 SGML 的解析規則。然而,幾乎沒有瀏覽器實作真正的 SGML 解析 — 唯一以嚴格的 SGML 處理 HTML 的應用程式是 HTML 驗證器。驗證器與廣泛使用的瀏覽器給予的結果不同的這個問題,已讓開發者浪費了十年以上的時間。因此,這個版本的 HTML 不以 SGML 為中心。

本規範鼓勵對在創作過程中使用 SGML 工具有興趣的網頁作者使用 XML 工具與

HTML 的 XML 序列化寫法。

本規範定義不論語法正確還是錯誤的 HTML 文件的解析規則,並將解析演算法中的某些點稱為解析錯誤。解析錯誤的錯誤處理是有完整定義的:當使用者代理碰到這些錯誤時必須以本規範描述的方式處理錯誤,或者必須在碰到第一個不想以本規範描述的方式處理的錯誤的同時退出整個處理過程。

若文件有一個以上的解析錯誤,則規範符合檢驗器必須回報至少一個錯誤資訊給使用者。若文件裡沒有解析錯誤,則規範符合驗證器必不可回報解析錯誤。若文件有超過一個解析錯誤,則規範符合驗證器可回報超過一個錯誤資訊。本規範不要求規範符合驗證器從解析錯誤中回復。

注:解析錯誤只是 HTML 語法上的錯誤。規範符合驗證器還會驗證本規範描述的其他規範符合要求。

對於規範符合驗證器來說,若規範驗證器確認某個資源是用 HTML 語法,則它是個 HTML 文件

解析模型概述

輸入串流

後來作為標記化階段輸入的 Unicode 字符串流一開始對於使用者代理來說,僅僅是透過網路或是從局部檔案系統來的位元組串流。這些位元組是用某種「字符編碼」轉換原來字符得到的,而使用者代理必須用這個「字符編碼」將字元組解碼成字符。

注:在 XML 文件的情形下,使用者代理必須使用 XML 規範裡定義的決定字符編碼演算法。本小節不適用於 XML 文件。[XML]
決定字符編碼

一些情況下,在解析之前決定文件的編碼可能是不實際的。因此,本規範提供了一個兩段機制與瀏覽器可選擇性實作的預掃描。如同下面所述,本規範允許使用者代理在開始解析文件之前,對已取得的位元組使用一個簡單版的解析演算法。然後,使用者代理使用預解析得到的暫時編碼與其他帶外 後設資料啟動真正的解析器。若載入文件的過程中使用者代理發現編碼宣告與這份資料不符合,則使用者代理可能會用真正的編碼重啟動解析器以進行文件解析。

使用者代理必須使用以下演算法(編碼嗅探演算法)以決定在地一次解析一份文件的時候使用的字符編碼。本演算法的輸入是任何的帶外後設資料(例如,文件的 Content-Type 後設資料)與已取得的所有位元組,回傳是一個編碼與一個可信度。可信度可能是「暫時」、「確定」或「無關」。不管可信度是「暫時」還是「確定」,使用者代理會在解析過程中使用得到的編碼以決定是否變更編碼。若編碼不是必要的(例如,解析器作用在 Unicode 字符串流上因此沒有編碼解碼的必要),則可信度是「無關」。

  1. 若使用者指示使用者代理以某個編碼覆蓋文件的字符編碼,則使用者代理可回傳該編碼,可信度為「確定」,並退出這些步驟。
  2. 若傳輸層有指定編碼,且使用者代理支援該編碼,則回傳該編碼,可信度為「確定」,並退出這些步驟。
  3. 使用者代理可在此或是本演算法接下來的步驟中,等候更多的位元組。舉例來說,使用者代理可能花 500 微秒等待 1024 個位元組。一般來說實施尋找編碼的預解析可以提昇效率,因為會減少在解析的時候在找到編碼資訊的同時必須丟棄資料結構的可能性。然而若使用者代理為了獲得決定編碼用的資料延遲了太長的時間,浪費的時間可能比預處理得到的效能優勢來的多。
    注:字符編碼宣告的寫作的規範符合條件規定字符編碼宣告只能出現在前 1024 個位元組。本規範因此鼓勵使用者者代理僅對前 1024 的位元組使用以下的欲解析演算法(這些步驟),而不超過它。
  4. 對於以下表格裡面的每一行,由第一行往下看,若已經有跟第一欄裡的位元組數一樣多的位元組存在,且檔案裡前幾個位元組與第一欄的位元組一致,則回傳該行第二欄的編碼,可信度為「確定」,並退出這些步驟。
    位元組的十六進位表示編碼
    FE FF大端序 UTF-16
    FF FE小端序 UTF-16
    EF BB BFUTF-8
    注:本步驟在尋找 Unicode 位元組順序記號(BOM)
  5. 否則,使用者代理要找檔案本身裡的字符編碼資訊。過程如下:

    位置為指向輸入串流裡一個位元組的指針,一開始指向第一個位元組。若在這些子步驟中的任意個位置上,位元組不夠或是使用者代理決定繼續掃描是無效率的,則跳至整個字符編碼偵測演算法的下一個步驟。使用者代理可決定掃描「任何」的位元組都是無效率的,在這種情況下使用者代理會完全跳過這些子步驟。

    重複執行以下「兩個」步驟直到退出演算法(因為上述原因使用者代理退出演算法,或是因為找到字符編碼了):

    1. 位置指向:
      ↪ 開頭為:0x3C 0x21 0x2D 0x2D(ASCII '<!--')的位元組序列
      位置指標指向下一個前面有兩個 0x2D 位元組的 0x3E 位元組(也就是 ASCII '-->' 序列的結尾,這兩個 0x2D 位元組可以是 '<!--' 序列裡的那兩個)。
      ↪ 開頭為:0x3C, 0x4D 或 0x6D, 0x45 或 0x65, 0x54 或 0x74, 0x41 或 0x61, 最後是 0x09, 0x0A, 0x0C, 0x0D, 0x20, 0x2F(不分大小寫的 ASCII '<meta' 接一個空白或斜線)的位元組序列
      1. 位置指標指向下一個是 0x09, 0x0A, 0x0C, 0x0D, 0x20, 或 0x2F 的位元組(上對配對到的序列裡的那一個)。
      2. 屬性列表為空的字串列表。
      3. 已得指令為假。
      4. 需要指令為空。
      5. 字集為空值(對於本演算法來說,與無法辨識的編碼或是空字串不同)。
      6. 「屬性」:抓一個屬性與它的取值。若沒有嗅出屬性,則跳到下面的「處理」步驟。
      7. 若屬性的名稱已經在屬性列表內,則回到「屬性」步驟。
      8. 將屬性的名稱加入至屬性列表
      9. 執行以下列表中適用的步驟(若存在):
        ↪ 若屬性的名稱是 "http-equiv"
        若屬性的取值是 "content-type",則設已得指令為真。
        ↪ 若屬性的名稱是 "content"
        對屬性的取值使用meta 元素萃取編碼的演算法。若演算法回傳編碼,且字集仍為空值,設字集為該編碼,且設需要指令為真。
        ↪ 若屬性的名稱是 "charset"
        字集為對應屬性的取值的編碼,且設需要指令為假。
      10. 回到「屬性」步驟。
      11. 「處理」:若需要指令為空值,則跳到整個「兩步」演算法的第二步。
      12. 需要指令為真但是已得指令為假,則跳到整個「兩步」演算法的第二步。
      13. 字集一種 UTF-16 編碼,將字集的值更改為 UTF-8。
      14. 字集不是使用者代理支援的編碼,則跳到整個「兩步」演算法的第二步。
      15. 回傳字集對應的編碼,可信度為「暫時」,並退出這些步驟。
        ↪ 開頭為一個 0x3C 位元組(ASCII <),可能接著一個 0x2F 位元組(ASCII /),最後是一個範圍在 0x41-0x5A 或 0x61-0x7A(一個 ASCII 子母)的位元組組成的位元組序列
        1. 位置指標指向下一個是 0x09(ASCII TAB), 0x0A(ASCII LF), 0x0C(ASCII FF), 0x0D(ASCII CR), 0x20(ASCII 空白), 或 0x3E(ASCII >)的位元組。
        2. 重複抓一個屬性直到無法再找屬性,並跳到整個「兩步」演算法的第二步。
        ↪ 開頭為:0x3C 0x21(ASCII '<!-')的位元組序列
        ↪ 開頭為:0x3C 0x2F(ASCII '</')的位元組序列
        ↪ 開頭為:0x3C 0x3F(ASCII '<?')的位元組序列
        位置指標指向下一個 0x3E 位元組(ASCII >)
        ↪ 其他位元組
        不做任何事
      16. 位置指標移至輸入串流的下一個位元組,並回到這個「兩步」演算法的第一步。
      當上面的「兩步」演算法要抓一個屬性時,代表進行:
      1. 位置上的位元組是 0x09(ASCII TAB), 0x0A(ASCII LF), 0x0C(ASCII FF), 0x0D(ASCII CR), 0x20(ASCII 空白), 或 0x2F(ASCII /)其一,則將位置指向下一個位元組並重做這個子步驟。
      2. 位置上的位元組是 0x3E(ASCII >),則退出「抓一個屬性」演算法。沒有屬性。
      3. 否則,位置上的位元組是屬性名稱的起點。設屬性名稱屬性取值為空字串。
      4. 「屬性名稱」:用下述方式處理位置上的位元組:
        ↪ 若位元組為 0x3D(ASCII =),且屬性名稱不是空字串
        位置指向下一個位元組並跳到「取值」步驟。
        ↪ 若位元組為 0x09(ASCII TAB), 0x0A(ASCII LF), 0x0C(ASCII FF), 0x0D(ASCII CR),或 0x20(ASCII 空白)
        跳到「空白」步驟。
        ↪ 若位元組為 0x2F(ASCII /)或 0x31(ASCII >)
        退出「抓一個屬性」演算法。屬性名稱是屬性名稱的值,屬性取值是空字串。
        ↪ 若位元組在 0x41(ASCII A)到 0x51(ASCII Z)的範圍裡
        將代碼點是 b+0x20 的 Unicode 字符附上屬性名稱(其中 b位置上位元組的值)。
        ↪ 其他所有情形
        將代表點是位置上位元組的值的 Unicode 字符附上屬性名稱。(ASCII 範圍外的位元組是如何處理的並不重要,畢竟只有 ASCII 字符才對字符編碼的偵測有影響。)
      5. 位置指向下一個位元組並回到前一個步驟。
      6. 「空白」:若位置上的位元組是 0x09(ASCII TAB), 0x0A(ASCII LF), 0x0C(ASCII FF), 0x0D(ASCII CR), 0x20(ASCII 空白), 或 0x2F(ASCII /)其一,則將位置指向下一個位元組並重複這個步驟。
      7. 位置上的位元組「不是」0x3D(ASCII =),則退出「抓一個屬性」演算法。屬性名稱是屬性名稱的值,屬性取值是空字串。
      8. 位置移過該 0x3D(ASCII =)位元組。
      9. 「取值」:若位置上的位元組是 0x09(ASCII TAB), 0x0A(ASCII LF), 0x0C(ASCII FF), 0x0D(ASCII CR), 0x20(ASCII 空白), 或 0x2F(ASCII /)其一,則將位置指向下一個位元組並重複這個步驟。
      10. 用下述方式處理位置上的位元組:
        ↪ 若位元組為 0x22(ASCII ")或 0x27(ASCII ')
        1. b位置上位元組的值。
        2. 位置指向下一個位元組
        3. 位置上位元組的值是 b,則將位置指向下一個位元組並退出「抓一個屬性」演算法。屬性名稱是屬性名稱的值,屬性取值是屬性取值的值。
        4. 否則,若位置上的位元組在 0x41(ASCII A)到 0x51(ASCII Z)的範圍裡,則將一個代碼點比位置上位元組的值大 0x20 的 Unicode 字符附上屬性取值
        5. 否則,將代表點是位置上位元組的值的 Unicode 字符附上屬性取值
        6. 回到這些子步驟的第二步。
        ↪ 若位元組為 0x31(ASCII >)
        退出「抓一個屬性」演算法。屬性名稱是屬性名稱的值,屬性取值是空字串。
        ↪ 若位元組在 0x41(ASCII A)到 0x51(ASCII Z)的範圍裡
        將代碼點是 b+0x20 的 Unicode 字符附上屬性取值(其中 b位置上位元組的值)。將位置指向下一個位元組。
        ↪ 其他所有情形
        將代表點是位置上位元組的值的 Unicode 字符附上屬性取值。將位置指向下一個位元組。
      11. 用下述方式處理位置上的位元組:
        ↪ 若位元組為 0x09(ASCII TAB), 0x0A(ASCII LF), 0x0C(ASCII FF), 0x0D(ASCII CR), 0x20(ASCII 空白),或 0x3E(ASCII >)
        退出「抓一個屬性」演算法。屬性名稱是屬性名稱的值,屬性取值是屬性取值的值。
        ↪ 若位元組在 0x41(ASCII A)到 0x51(ASCII Z)的範圍裡
        將代碼點是 b+0x20 的 Unicode 字符附上屬性取值(其中 b位置上位元組的值)。
        ↪ 其他所有情形
        將代表點是位置上位元組的值的 Unicode 字符附上屬性取值
      12. 位置指向下一個位元組並回到前一個步驟。
      為了兼容性,使用者代理不應該使用會得到跟上述結果不同的預掃描演算法。(但是,如果你真的這樣做了,請告訴我們,這樣我們才可以改善這個演算法並讓大家受惠...)
    2. 若使用者代理透過上次訪問時的編碼之類的資訊,能大致確定頁面的編碼,則回傳該編碼,可信度為「暫時」,並退出這些步驟。
    3. 使用者代理可嘗試透過頻率分析或其他演算法自動偵測資料串流的編碼。這些演算法可使用檔案內容以外的資訊,包括檔案的網址。若自動偵測成功並可得到字符編碼,則回傳該編碼,可信度為「暫時」,並退出這些步驟。[UNIVCHARDET]
      注:UTF-8 編碼有很容易偵測的位圖樣。具有批配 UTF-8 樣式的大於 0x7F 位元組的文件,編碼十分有可能是 UTF-8,而不批配的則十分有可能不是。本規範因此鼓勵使用者代理找尋這個樣式。[PPUTF8] [UTF8DET]
    4. 否則,回傳實作定義的預設編碼或是使用者指定的預設編碼,可信度為「暫時」。

      在受限環境或是有文件編碼有規定的環境(舉例來說,只在新網路使用的使用者代理)之下,本規範建議使用全方位的 UTF-8 編碼。

      在其他環境下,預設編碼一般與使用者的語言環境有關(作為使用者大致會逛的頁面的語言與編碼的一種猜測)。為與老舊內容兼容,本規範建議使用的對照表,以 BCP 47 語言標籤識別語言環境。[BCP47]

      語言環境建議預設編碼
      arUTF-8
      beISO-8859-5
      bgwindows-1251
      csISO-8859-2
      cyUTF-8
      faUTF-8
      hewindows-1255
      hrUTF-8
      huISO-8859-2
      jaWindows-31J
      kkUTF-8
      kowindows-949
      kuwindows-1254
      ltwindows-1257
      lvISO-8859-13
      mkUTF-8
      orUTF-8
      plISO-8859-2
      roUTF-8
      ruwindows-1251
      skwindows-1250
      slISO-8859-2
      srUTF-8
      thwindows-874
      trwindows-1254
      ukwindows-1251
      viUTF-8
      zh-CNGB18030
      zh-TWBig5
      其他語言環境windows-1252
    使用者代理必須在使用本演算法的回傳值選擇解碼器的那個瞬間,將文件的字符編碼設為該值。
    注:本演算法肆意違抗要求在缺少字符編碼宣告的情況下預設編碼是 ISO-8859-1 的 HTTP 規範,也肆意違抗要求在缺少字符編碼宣告的情況下預設編碼是 US-ASCII 的 RFC 2046。本規範的出發點在於與老舊內容達到最大的兼容。[HTTP] [RFC2046]
    字符编码
    輸入串流的前置處理

    使用者代理必須用給定的編碼,使用該編碼的規則將輸入串流內的位元組轉換成 Unicode 字符作為 tokenizer 的輸入,但是前頭若有 U+FEFF BYTE ORDER MARK 字符,使用者代理必不可在編碼層將之剝離(下面規則會剝離這個字符)。

    若前頭若有 U+FEFF BYTE ORDER MARK 字符,使用者代理必須無視該字符。

    注:不管 U+FEFF BYTE ORDER MARK 字符有沒有用來決定字元組序都把第一個該字符剝離的這項要求是對 Unicode 的肆意違抗,目的是增進使用者代理面對不正確轉碼器回覆力。

    U+000D CARRIAGE RETURN(CR)字符與 U+000A LINE FEED(LF)字符有特殊的處理方式。使用者代理必須移除所有後面是 LF 字符的 CR 字符,並轉換所有後面不是 LF 字符的 CR 字符成 LR 字符。因此,HTML DOM 裡的換行以 LF 字符表示,tokenization 階段的輸入不會有任何的 CR 字符。

    解析時的編碼變更

    當解析器要求使用者代理變更編碼的時候,使用者代理必須執行以下步驟。這可能會在上述的編碼嗅探演算法無法找到編碼或是找到的編碼不是檔案的實際編碼的時候發生。

    1. 若新的編碼與用來解讀輸入串流的編碼一樣或等同,則設可信度為「確定」並退出這些步驟。這發生在檔案裡找到的編碼資訊與編碼嗅探演算法決定的編碼一致的時候,亦或是在第一次通過解析器時發現前面小節描述的編碼嗅探演算法無法照到正確編碼的情況下第二次通過的時候。
    2. 若已經用來解讀輸入串流的編碼是一個 UTF-16 編碼,則設可信度為「確定」並退出這些步驟。使用者代理無視新編碼,若新編碼與原來編碼不一樣,則新編碼很明顯不正確。
    3. 若新編碼是一個 UTF-16 編碼,改成 UTF-8。
    4. 若到上一個現在解碼器解碼過的位元組為止,新編碼與舊編碼解讀到的 Unicode 序列是一樣的,且使用者代理具有即時變更轉換器的功能,則使用者代理可即時改用新的轉換器來解碼。設文件的字符編碼與用來轉換輸入串流的編碼為新的編碼,設可信度為「確定」並退出這些步驟。
    5. 否則,再次瀏覽這份文件,啟用置換並使用相同的資源瀏覽上下文,但是這次跳過編碼嗅探演算法而僅僅設編碼為新的編碼,可信度為「確定」。這應該盡可能在不使用網路層達成(使用者代理應該從記憶體裡的位元組重新解析),就算文件標注為不可緩存也一樣。若不可能不使用網路層且使用網路層會再一次發出方法不是 HTTP GET 的要求(或非 HTTP URL 的同等情形),則改設可信度為「確定」並無視新編碼。這會造成資源的錯誤解讀。使用者代理可告知使用者這種狀況以幫助應用程式的開發。

    解析态

    插入模式
    开元素栈
    活动格式元素列表
    元素指针
    ===== 其他解析态标记 =====