Jsoup代碼解讀之四-parser(上)

做爲Java世界最好的HTML 解析庫,Jsoup的parser實現很是具備表明性。這部分也是Jsoup最複雜的部分,須要一些數據結構、狀態機乃至編譯器的知識。好在HTML語法不復雜,解析只是到DOM樹爲止,因此做爲編譯器入門卻是挺合適的。這一塊不要期望囫圇吞棗,咱們仍是泡一杯咖啡,細細品味其中的奧妙吧。html

基礎知識

編譯器

將計算機語言轉化爲另外一種計算機語言(一般是更底層的語言,例如機器碼、彙編、或者JVM字節碼)的過程就叫作編譯(compile)。編譯器(Compiler)是計算機科學的一個重要領域,已經有不少年曆史了,而最近各類通用語言層出不窮,加上跨語言編譯的興起、DSL概念的流行,都讓編譯器變成了一個很時髦的東西。前端

編譯器領域相關有三本公認的經典書籍,龍書《Compilers: Principles, Techniques, and Tools 》,虎書《Modern Compiler Implementation in X (X表示各類語言)》,鯨書《Advanced Compiler Design and Implementation》。其中龍書是編譯理論方面公認的不二之選,然後面兩本則對實踐更有指導意義。另外@裝配腦殼有個很好的編譯器入門系列博客:http://www.cnblogs.com/Ninputer/archive/2011/06/07/2074632.htmljava

編譯器的基本流程以下:git

compiler

其中詞法分析、語法分析、語義分析這部分又叫編譯器的前端(front-end),而此後的中間代碼生成直到目標生成、優化等屬於編譯器的後端(back-end)。編譯器的前端技術已經很成熟了,也有yacc這樣的工具來自動進行詞法、語法分析(Java裏也有一個相似的工具ANTLR),然後端技術更加複雜,也是目前編譯器研究的重點。github

說了這麼多,回到我們的HTML上來。HTML是一種聲明式的語言,能夠理解它的最終的輸出是瀏覽器裏圖形化的頁面,而並不是可執行的目標語言,所以我將這裏的Translate改成了Render。正則表達式

html compiler

在Jsoup(包括相似的HTML parser)裏,只作了Lex(詞法分析)、Parse(語法分析)兩步,而HTML parse最終產出結果,就是DOM樹。至於HTML的語義解析以及渲染,不妨看看攜程UED團隊的這篇文章:《瀏覽器是怎樣工做的:渲染引擎,HTML解析》編程

狀態機

Jsoup的詞法分析和語法分析都用到了狀態機。狀態機能夠理解爲一個特殊的程序模型,例如常常跟咱們打交道的正則表達式就是用狀態機實現的。後端

它由狀態(state)和轉移(transition)兩部分構成。根據狀態轉移的可能性,狀態機又分爲DFA(肯定有限狀態機)和NFA(非肯定有限狀態自動機)。這裏拿一個最簡單的正則表達式"a[b]*"做爲例子,咱們先把它映射到一個狀態機DFA,大概是這樣子:設計模式

state machine

狀態機自己是一個編程模型,這裏咱們嘗試用程序去實現它,那麼最直接的方式大概是這樣:瀏覽器

<!-- lang: java -->
public void process(StringReader reader) throws StringReader.EOFException {
    char ch;
    switch (state) {
        case Init:
            ch = reader.read();
            if (ch == 'a') {
                state = State.AfterA;
                accum.append(ch);
            }
            break;
        case AfterA:
            ...
            break;
        case AfterB:
            ...
            break;
        case Accept:
            ...
            break;
    }
}

這樣寫簡單的狀態機倒沒有問題,可是複雜狀況下就有點難受了。還有一種標準的狀態機解法,先創建狀態轉移表,而後使用這個表創建狀態機。這個方法的問題就是,只能作純狀態轉移,沒法在代碼級別操做輸入輸出。

Jsoup裏則使用了狀態模式來實現狀態機,初次看到時,確實讓人眼前一亮。狀態模式是設計模式的一種,它將狀態和對應的行爲綁定在一塊兒。而在狀態機的實現過程當中,使用它來實現狀態轉移時的處理再合適不過了。

"a[b]*"的例子的狀態模式實現以下,這裏採用了與Jsoup相同的方式,用到了枚舉來實現狀態模式:

<!-- lang: java -->
public class StateModelABStateMachine implements ABStateMachine {

    State state;

    StringBuilder accum;

    enum State {
        Init {
            @Override
            public void process(StateModelABStateMachine stateModelABStateMachine, StringReader reader) throws StringReader.EOFException {
                char ch = reader.read();
                if (ch == 'a') {
                    stateModelABStateMachine.state = AfterA;
                    stateModelABStateMachine.accum.append(ch);
                }
            }
        },
        Accept {
            ...
        },
        AfterA {
            ...
        },
        AfterB {
            ...
        };

        public void process(StateModelABStateMachine stateModelABStateMachine, StringReader reader) throws StringReader.EOFException {
        }
    }

    public void process(StringReader reader) throws StringReader.EOFException {
        state.process(this, reader);
    }
}

PS:我在github上fork了一份Jsoup的代碼,把這系列文章提交了上去,而且給一些代碼增長了中文註釋,有興趣的能夠看看https://github.com/code4craft/jsoup-learning。本文中提到的幾種狀態機的完整實如今這個倉庫的https://github.com/code4craft/jsoup-learning/tree/master/src/main/java/us/codecraft/learning路徑下。

下一篇文章將從Jsoup的詞法分析器開始來說狀態機的使用。

相關文章
相關標籤/搜索