2010-06-30 38 views
35

對於參數的緣故讓我們假設一個HTML解析器。解析器(例如,HTML)如何工作?

我讀過它標記爲首先,然後解析它。

標記化意味着什麼?

解析器是否每個都讀取每個字符,構建一個多維數組來存儲結構?

例如,它是否讀取<然後開始捕獲該元素,然後一旦遇到關閉>(屬性外部),它會被推送到某個數組堆棧上?

我很感興趣,爲了知道(我很好奇)。

如果我是通過類似HTML Purifier讀取源,將是給我的HTML是如何解析的好主意嗎?

+0

看http://en.wikipedia.org/wiki/Lexical_parser一個非常簡短的介紹;還請查看那裏的「解析」文章。而HTML Purifier在某種程度上就是這樣做的。 – Piskvor 2010-06-30 14:40:07

+0

HTML Agility Pack是開源的,基於tokanizer。 http://htmlagilitypack.codeplex.com/ – Oded 2010-06-30 14:43:12

+0

如果您可以閱讀C(ocaml,lisp),請嘗試查看yacc/lex(ocamlyacc/ocamllex,cl-yacc/cl-lex ...)上的一些教程。您將從代碼中快速瞭解基礎知識。如果你可以閱讀代碼。 – Amadan 2010-06-30 14:48:40

回答

29

首先,你應該知道,解析HTML特別難看 - HTML是被標準化前寬(和發散)的使用。這導致了各種醜陋,例如標準規定某些構造不被允許,但是隨後指定這些構造所需的行爲。

前往你直接的問題:標記化大致相當於服用英語,並分解成單詞。在英語中,大多數單詞是連續的字母流,可能包括撇號,連字符等。大多數單詞被空格包圍,但是句號,問號,感嘆號等也可以表示單詞的結尾。同樣,對於HTML(或其他),您可以指定一些關於可以用這種語言構成標記(單詞)的規則。將輸入分成令牌的代碼通常稱爲詞法分析器。

至少在正常情況下,你做而不是在開始解析之前,將所有輸入分成標記。相反,解析器調用詞法分析器來獲取下一個需要的標記。當它被調用時,詞法分析器查看足夠的輸入來查找一個標記,將其提供給解析器,並且直到下一次解析器需要更多輸入時纔再對輸入進行標記。

通常,解析器的工作方式是正確的,但是(至少在典型的解析器中)它在解析語句的過程中使用堆棧,但它通常構建來表示語句一棵樹(和抽象語法樹,又名AST),而不是一個多維數組。

基礎上解析HTML的複雜性,我會儲備看着它的分析器,直到你通過一些別人看了第一。如果你四處尋找,你應該能夠找到相當數量的解析器/詞法分析器,用於像數學表達式這樣的東西,它可能更適合作爲介紹(更小,更簡單,更容易理解等)。)

52

符號化可以由幾個步驟,例如,如果你有這樣的HTML代碼:

<html> 
    <head> 
     <title>My HTML Page</title> 
    </head> 
    <body> 
     <p style="special"> 
      This paragraph has special style 
     </p> 
     <p> 
      This paragraph is not special 
     </p> 
    </body> 
</html> 

標記生成器可以是字符串轉換爲顯著令牌, 丟棄空格 的平面列表(感謝,SasQ的校正):

["<", "html", ">", 
    "<", "head", ">", 
     "<", "title", ">", "My HTML Page", "</", "title", ">", 
    "</", "head", ">", 
    "<", "body", ">", 
     "<", "p", "style", "=", "\"", "special", "\"", ">", 
      "This paragraph has special style", 
     "</", "p", ">", 
     "<", "p", ">", 
      "This paragraph is not special", 
     "</", "p", ">", 
    "</", "body", ">", 
"</", "html", ">" 
] 

可能有多個標記化傳遞到標記列表轉換爲更高級別的令牌,如以下假設HTML解析器可以做的名單(這仍然是平坦的列表):

("<html>", {}, [ 
    ("<head>", {}, [ 
     ("<title>", {}, ["My HTML Page"]), 
    ]), 
    ("<body>", {}, [ 
     ("<p>", {"style": "special"}, ["This paragraph has special style"]), 
     ("<p>", {}, ["This paragraph is not special"]), 
    ]), 
]) 

[("<html>", {}), 
    ("<head>", {}), 
     ("<title>", {}), "My HTML Page", "</title>", 
    "</head>", 
    ("<body>", {}), 
     ("<p>", {"style": "special"}), 
      "This paragraph has special style", 
     "</p>", 
     ("<p>", {}), 
      "This paragraph is not special", 
     "</p>", 
    "</body>", 
"</html>" 
] 

然後解析器轉換令牌該列表以形成樹或圖,它表示的方式是更方便的訪問源文本由程序/操縱

此時,解析完成;然後由用戶來解釋樹,修改它等。

+7

+1喜歡這個例子 – 2010-06-30 15:58:45

+8

+1實際上顯示了什麼是標記化 – alex 2010-07-01 01:52:51

+0

+1用於回答(意外)我的關於哪些文本片段應該構成基於HTML/XML/SGML的語言的令牌的長期持久問題! (我在其他主題中詢問過這個問題。)謝謝,夥計!非常好的例子,的確如此! :-) – SasQ 2011-09-09 13:30:19

8

千萬不要錯過W3C關於parsing HTML5的註釋。

有關掃描/樂視的有趣介紹,請在網上搜索桌面驅動掃描儀的高效生成。它顯示了掃描如何最終由自動機理論驅動。正則表達式集合被轉換爲一個單一的NFA。然後將NFA轉換爲DFA以確定狀態轉換。本文隨後描述了一種將DFA轉換爲轉換表的方法。

一個關鍵點:掃描儀使用正則表達式理論,但可能不使用現有的正則表達式庫。爲了獲得更好的性能,狀態轉換被編碼爲巨大的case語句或轉換表。

掃描儀保證使用正確的單詞(標記)。解析器保證這些單詞以正確的組合和順序使用。掃描儀使用正則表達式和自動機理論。解析器使用語法理論,尤其是context-free grammars

一對夫婦解析資源:

+0

+1感謝W3C鏈接。它看起來像一個內容豐富的(和長)閱讀! – alex 2010-07-01 01:53:34

+0

而且,如果將來您的語法不會改變,您可以將轉換表「烘焙」到源代碼中,然後編譯一次。這是可能的,因爲你運行程序的機器實際上也是一個狀態自動機!所以你可以「在硬件中實現你的自動機」。方法如下:狀態可以用代碼中的位置(CPU中的指令指針)表示。狀態轉換隻是(不)條件跳轉(分支)。您也可以使用程序的堆棧來存儲/恢復狀態(過程調用和返回)。這會加速很多事情。 – SasQ 2011-09-09 13:36:14

6

HTML和XML語法(基於SGML等)是非常難以分析和他們不適合早已進入lexing的情況,因爲他們不是常規。在解析理論中,正則語法是沒有任何遞歸的那個,即自相似,嵌套模式或圓括號,就像包裝必須相互匹配。但基於HTML/XML/SGML的語言確實有嵌套模式:標記可以嵌套。嵌套模式的語法在喬姆斯基分類中的級別較高:它的上下文無關或甚至與上下文相關。

但是,回到你的問題有關詞法分析器:
每個語法由兩種符號的:非終端符號(那些放鬆到其他語法規則)和終端符號(那些「原子」 - 它們是語法樹的葉子,不放鬆其他任何東西)。終端符號通常只是代幣。令牌從詞法分析器中逐一抽出並與相應的終端符號匹配。

那些終端符號(標記)通常具有常規語法,這更容易識別(這就是爲什麼它被分解爲詞法分析器,這對於常規語法更加專業化,並且可以比使用更一般的方法非常規語法)。因此,要爲HTML/XML/SGML類語言編寫詞法分析器,您需要找到足夠原子和規則的語法部分,以便由詞法分析器輕鬆處理。在這裏出現問題,因爲一開始並不清楚哪些部分是這些。我長期以來一直在努力解決這個問題。

但是列瑞恩上面已經做了很好的認識這些部分。布拉沃爲他!令牌類型如下:

  • TagOpener:< lexeme,用於啓動標籤。
  • TagCloser:> lexeme,用於結束標籤。
  • ClosingTagMarker:/ lexeme用於結束標記。
  • 名稱:以字母開頭的字母數字序列,用於標記名稱和屬性名稱。
  • 值:可以包含各種不同字符,空格等的文本用於屬性的值。
  • 等於:= lexeme,用於從屬性值中分離屬性名稱。
  • Quote:' lexeme,用於封閉屬性值。
  • DoubleQuote:" lexeme,用於包含屬性值。
  • PlainText:任何不包含<字符的文字,不包含在上述類型中。

您也可以爲實體引用設置一些標記,如&nbsp;&amp;。大概是:

  • 的EntityReference:由&其次是一些字母數字字符,並與;結束一語義。

爲什麼我對'"使用單獨的標記,而不是一個標記用於屬性值?由於常規語法無法識別哪些字符應該結束序列 - 它取決於啓動它的字符(結尾字符必須與起始字符匹配)。這種「括號」被認爲是非常規的語法。所以我把它提升到一個更高的層次 - 對於解析器。將這些令牌(開始和結束)匹配在一起(或者根本沒有,對於不包含空格的簡單屬性值)是他的工作。

事後反思: 不幸的是,其中一些令牌可能只發生在其他標記內。因此需要使用詞彙上下文,畢竟這是另一個控制狀態機識別特定令牌的狀態機。這就是爲什麼我說SGML類語言不適合詞法分析的模式。

1

這是HTML 5分析器是如何工作的:

This is how HTML 5 Parser works