Jsoup代码解读之六-parser(下)
轉載自? ?Jsoup代碼解讀之六-parser(下)
最近生活上有點忙,女兒老是半夜不睡,精神狀態也不是很好。工作上的事情也談不上順心,有很多想法但是沒有幾個被認可,有些事情也不是說代碼寫得好就行的。算了,還是端正態度,畢竟資歷尚淺,我還是繼續我的。
讀Jsoup源碼并非無聊,目的其實是為了將webmagic做的更好一點,畢竟parser也是爬蟲的重要組成部分之一。讀了代碼后,收獲也不少,對HTML的知識也更進一步了。
DOM樹產生過程
這里單獨將TreeBuilder部分抽出來叫做語法分析過程可能稍微不妥,其實就是根據Token生成DOM樹的過程,不過我還是沿用這個編譯器里的稱呼了。
TreeBuilder同樣是一個facade對象,真正進行語法解析的是以下一段代碼:
<!-- lang: java --> protected void runParser() {while (true) {Token token = tokeniser.read();process(token);if (token.type == Token.TokenType.EOF)break;} }TreeBuilder有兩個子類,HtmlTreeBuilder和XmlTreeBuilder。XmlTreeBuilder自然是構建XML樹的類,實現頗為簡單,基本上是維護一個棧,并根據不同Token插入節點即可:
<!-- lang: java --> protected boolean process(Token token) {// start tag, end tag, doctype, comment, character, eofswitch (token.type) {case StartTag:insert(token.asStartTag());break;case EndTag:popStackToClose(token.asEndTag());break;case Comment:insert(token.asComment());break;case Character:insert(token.asCharacter());break;case Doctype:insert(token.asDoctype());break;case EOF: // could put some normalisation here if desiredbreak;default:Validate.fail("Unexpected token type: " + token.type);}return true; }insertNode的代碼大致是這個樣子(為了便于展示,對方法進行了一些整合):
<!-- lang: java --> Element insert(Token.StartTag startTag) {Tag tag = Tag.valueOf(startTag.name());Element el = new Element(tag, baseUri, startTag.attributes);stack.getLast().appendChild(el);if (startTag.isSelfClosing()) {tokeniser.acknowledgeSelfClosingFlag();if (!tag.isKnownTag()) // unknown tag, remember this is self closing for output. see above.tag.setSelfClosing();} else {stack.add(el);}return el; }HTML解析狀態機
相比XmlTreeBuilder,HtmlTreeBuilder則實現較為復雜,除了類似的棧結構以外,還用到了HtmlTreeBuilderState來構建了一個狀態機來分析HTML。這是為什么呢?不妨看看HtmlTreeBuilderState到底用到了哪些狀態吧(在代碼中中用<!-- State: -->標明狀態):
<!-- lang: html --> <!-- State: Initial --> <!-- State: BeforeHtml --> <html lang='zh-CN' xml:lang='zh-CN' xmlns='http://www.w3.org/1999/xhtml'> <!-- State: BeforeHead --> <head><!-- State: InHead --><script type="text/javascript">//<!-- State: Text -->function xx(){}</script><noscript><!-- State: InHeadNoscript -->Your browser does not support JavaScript!</noscript> </head> <!-- State: AfterHead --> <body> <!-- State: InBody --> <textarea><!-- State: Text -->xxx </textarea> <table><!-- State: InTable --><!-- State: InTableText -->xxx<tbody><!-- State: InTableBody --></tbody><tr><!-- State: InRow --><td><!-- State: InCell --></td></tr> </table> </html>這里可以看到,HTML標簽是有嵌套要求的,例如<tr>,<td>需要組合<table>來使用。根據Jsoup的代碼,可以發現,HtmlTreeBuilderState做了以下一些事情:
語法檢查
例如tr沒有嵌套在table標簽內,則是一個語法錯誤。當InBody狀態直接出現以下tag時,則出錯。Jsoup里遇到這種錯誤,會發現這個Token的解析并記錄錯誤,然后繼續解析下面內容,并不會直接退出。
<!-- lang: java -->InBody {boolean process(Token t, HtmlTreeBuilder tb) {if (StringUtil.in(name,"caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr")) {tb.error(this);return false;}}標簽補全
例如head標簽沒有閉合,就寫入了一些只有body內才允許出現的標簽,則自動閉合</head>。HtmlTreeBuilderState有的方法anythingElse()就提供了自動補全標簽,例如InHead狀態的自動閉合代碼如下:
<!-- lang: java -->private boolean anythingElse(Token t, TreeBuilder tb) {tb.process(new Token.EndTag("head"));return tb.process(t);}還有一種標簽閉合方式,例如下面的代碼:
<!-- lang: java -->private void closeCell(HtmlTreeBuilder tb) {if (tb.inTableScope("td"))tb.process(new Token.EndTag("td"));elsetb.process(new Token.EndTag("th")); // only here if th or td in scope}
實例研究
缺少標簽時,會發生什么事?
好了,看了這么多parser的源碼,不妨回到我們的日常應用上來。我們知道,在頁面里多寫一個兩個未閉合的標簽是很正常的事,那么它們會被怎么解析呢?
就拿<div>標簽為例:
漏寫了開始標簽,只寫了結束標簽
<!-- lang: java -->case EndTag:if (StringUtil.in(name,"div","dl", "fieldset", "figcaption", "figure", "footer", "header", "pre", "section", "summary", "ul")) { if (!tb.inScope(name)) {tb.error(this);return false;} }恭喜你,這個</div>會被當做錯誤處理掉,于是你的頁面就毫無疑問的亂掉了!當然,如果單純多寫了一個</div>,好像也不會有什么影響哦?(記得有人跟我講過為了防止標簽未閉合,而在頁面底部多寫了幾個</div>的故事)
寫了開始標簽,漏寫了結束標簽
這個情況分析起來更復雜一點。如果是無法在內部嵌套內容的標簽,那么在遇到不可接受的標簽時,會進行閉合。而<div>標簽可以包括大多數標簽,這種情況下,其作用域會持續到HTML結束。
好了,parser系列算是分析結束了,其間學到不少HTML及狀態機內容,但是離實際使用比較遠。下面開始select部分,這部分可能對日常使用更有意義一點。
最后附上我的Jsoup系列博客及源碼地址:http://github.com/code4craft/jsoup-learning
總結
以上是生活随笔為你收集整理的Jsoup代码解读之六-parser(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: solidworks2014电脑配置(2
- 下一篇: 电脑配置2015年(电脑配置 2015)