上一篇文章講到了狀態機和詞法分析的基本知識,這一節咱們來分析Jsoup是如何進行詞法分析的。html
先介紹如下parser包裏的主要類:java
Parser
git
Jsoup parser的入口facade,封裝了經常使用的parse靜態方法。能夠設置maxErrors
,用於收集錯誤記錄,默認是0,即不收集。與之相關的類有ParseError
,ParseErrorList
。基於這個功能,我寫了一個PageErrorChecker
來對頁面作語法檢查,並輸出語法錯誤。github
Token
ui
保存單個的詞法分析結果。Token是一個抽象類,它的實現有Doctype
,StartTag
,EndTag
,Comment
,Character
,EOF
6種,對應6種詞法類型。code
Tokeniser
htm
保存詞法分析過程的狀態及結果。比較重要的兩個字段是state
和emitPending
,前者保存狀態,後者保存輸出。其次還有tagPending
/doctypePending
/commentPending
,保存尚未填充完整的Token。blog
CharacterReader
token
對讀取字符的邏輯的封裝,用於Tokenize時候的字符輸入。CharacterReader包含了相似NIO裏ByteBuffer的consume()
、unconsume()
、mark()
、rewindToMark()
,還有高級的consumeTo()
這樣的用法。ip
TokeniserState
用枚舉實現的詞法分析狀態機。
HtmlTreeBuilder
語法分析,經過token構建DOM樹的類。
HtmlTreeBuilderState
語法分析狀態機。
TokenQueue
雖然披了個Token的馬甲,實際上是在query的時候用到,留到select部分再講。
如今咱們來說講HTML的詞法分析過程。這裏借用一下http://ued.ctrip.com/blog/?p=3295裏的圖,圖中描述了一個Tag標籤的狀態轉移過程,
這裏忽略了HTML註釋、實體以及屬性,只保留基本的開始/結束標籤,例以下面的HTML:
<!-- lang: html --> <div>test</div>
Jsoup裏詞法分析比較複雜,我從裏面抽取出了對應的部分,就成了咱們的miniSoupLexer(這裏省略了部分代碼,完整代碼能夠看這裏MiniSoupTokeniserState
):
<!-- lang: java --> enum MiniSoupTokeniserState implements ITokeniserState { /** * 什麼層級都沒有的狀態 * ⬇ * <div>test</div> * ⬇ * <div>test</div> */ Data { // in data state, gather characters until a character reference or tag is found public void read(Tokeniser t, CharacterReader r) { switch (r.current()) { case '<': t.advanceTransition(TagOpen); break; case eof: t.emit(new Token.EOF()); break; default: String data = r.consumeToAny('&', '<', nullChar); t.emit(data); break; } } }, /** * ⬇ * <div>test</div> */ TagOpen { ... }, /** * ⬇ * <div>test</div> */ EndTagOpen { ... }, /** * ⬇ * <div>test</div> */ TagName { ... }; }
參考這個程序,能夠看到Jsoup的詞法分析的大體思路。分析器自己的編寫是比較繁瑣的過程,涉及屬性值(區分單雙引號)、DocType、註釋、HTML實體,以及一些錯誤狀況。不過了解了其思路,代碼實現也是循序漸進的過程。
下一節開始介紹語法分析部分。
最後仍是附上個人Jsoup解讀系列文章及代碼地址: