Jsoup代碼解讀之二-DOM相關對象

Jsoup代碼解讀之二-DOM相關對象

 

 

以前在文章中說到,Jsoup使用了一套本身的DOM對象體系,和Java XML API互不兼容。這樣作的好處是從XML的API裏解脫出來,使得代碼精煉了不少。這篇文章會說明Jsoup的DOM結構,DOM的遍歷方式。在下一篇文章,我會並結合這兩個基礎,分析一下Jsoup的HTML輸出功能。html

DOM結構相關類

咱們先來看看nodes包的類圖:java

node類圖

這裏能夠看到,核心無疑是Node類。node

Node類是一個抽象類,它表明DOM樹中的一個節點,它包含:web

  • 父節點parentNode以及子節點childNodes的引用
  • 屬性值集合attributes
  • 頁面的uribaseUri,用於修正相對地址爲絕對地址
  • 在兄弟節點中的位置siblingIndex,用於進行DOM操做

Node裏面包含一些獲取屬性、父子節點、修改元素的方法,其中比較有意思的是absUrl()。咱們知道,在不少html頁面裏,連接會使用相對地址,咱們有時會須要將其轉變爲絕對地址。Jsoup的解決方案是在attr()的參數開始加"abs:「,例如attr(「abs:href」),而absUrl()就是其實現方式。我寫的爬蟲框架webmagic裏也用到了相似功能,當時是本身手寫的,看到Jsoup的實現,才發現本身是白費勁了,代碼以下:框架

URL base;
try { try { base = new URL(baseUri); } catch (MalformedURLException e) { // the base is unsuitable, but the attribute may be abs on its own, so try that URL abs = new URL(relUrl); return abs.toExternalForm(); } // workaround: java resolves '//path/file + ?foo' to '//path/?foo', not '//path/file?foo' as desired if (relUrl.startsWith("?")) relUrl = base.getPath() + relUrl; // java URL自帶的相對路徑解析 URL abs = new URL(base, relUrl); return abs.toExternalForm(); } catch (MalformedURLException e) { return ""; } 

Node還有一個比較值得一提的方法是abstract String nodeName(),這個至關於定義了節點的類型名(例如Document是'#Document',Element則是對應的TagName)。ui

Element也是一個重要的類,它表明的是一個HTML元素。它包含一個字段tagclassNames。classNames是"class"屬性解析出來的集合,由於CSS規範裏,「class"屬性容許設置多個,並用空格隔開,而在用Selector選擇的時候,即便只指定其中一個,也可以選中其中的元素。因此這裏就把"class"屬性展開了。Element還有選取元素的入口,例如selectgetElementByXXX,這些都用到了select包中的內容,這個留到下篇文章select再說。spa

Document是表明整個文檔,它也是一個特殊的Element,即根節點。Document除了Element的內容,還包括一些輸出的方法。.net

Document還有一個屬性quirksMode,大體意思是定義處理非標準HTML的幾個級別,這個留到之後分析parser的時候再說。code

DOM樹的遍歷

Node還有一些方法,例如outerHtml(),用做節點及文檔HTML的輸出,用到了樹的遍歷。在DOM樹的遍歷上,用到了NodeVisitorNodeTraversor來對樹的進行遍歷。NodeVisitor在上一篇文章提到過了,head()和tail()分別是遍歷開始和結束時的方法,而NodeTraversor的核心代碼以下:orm

public void traverse(Node root) { Node node = root; int depth = 0; //這裏對樹進行後序(深度優先)遍歷 while (node != null) { //開始遍歷node visitor.head(node, depth); if (node.childNodeSize() > 0) { node = node.childNode(0); depth++; } else { //沒有下一個兄弟節點,退棧 while (node.nextSibling() == null && depth > 0) { visitor.tail(node, depth); node = node.parent(); depth--; } //結束遍歷 visitor.tail(node, depth); if (node == root) break; node = node.nextSibling(); } } } 

這裏使用循環+回溯來替換掉了咱們經常使用的遞歸方式,從而避免了棧溢出的風險。

實際上,Jsoup的Selector機制也是基於NodeVisitor來實現的,能夠說NodeVisitor是更加底層和靈活的API。

相關文章
相關標籤/搜索