日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Jsoup代码解读之六-parser(下)

發布時間:2023/12/3 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 --> @Override 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: --&gt;標明狀態):

<!-- lang: html --> <!-- State: Initial --> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!-- 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(下)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。