Jsoup代码解读之二-DOM相关对象
轉載自??Jsoup代碼解讀之二-DOM相關對象
之前在文章中說到,Jsoup使用了一套自己的DOM對象體系,和Java XML API互不兼容。這樣做的好處是從XML的API里解脫出來,使得代碼精煉了很多。這篇文章會說明Jsoup的DOM結構,DOM的遍歷方式。在下一篇文章,我會并結合這兩個基礎,分析一下Jsoup的HTML輸出功能。
DOM結構相關類
我們先來看看nodes包的類圖:
這里可以看到,核心無疑是Node類。
Node類是一個抽象類,它代表DOM樹中的一個節點,它包含:
- 父節點parentNode以及子節點childNodes的引用
- 屬性值集合attributes
- 頁面的uribaseUri,用于修正相對地址為絕對地址
- 在兄弟節點中的位置siblingIndex,用于進行DOM操作
Node里面包含一些獲取屬性、父子節點、修改元素的方法,其中比較有意思的是absUrl()。我們知道,在很多html頁面里,鏈接會使用相對地址,我們有時會需要將其轉變為絕對地址。Jsoup的解決方案是在attr()的參數開始加"abs:",例如attr("abs:href"),而absUrl()就是其實現方式。我寫的爬蟲框架webmagic里也用到了類似功能,當時是自己手寫的,看到Jsoup的實現,才發現自己是白費勁了,代碼如下:
<!-- lang: java --> 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 thatURL abs = new URL(relUrl);return abs.toExternalForm();}// workaround: java resolves '//path/file + ?foo' to '//path/?foo', not '//path/file?foo' as desiredif (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)。
Element也是一個重要的類,它代表的是一個HTML元素。它包含一個字段tag和classNames。classNames是"class"屬性解析出來的集合,因為CSS規范里,"class"屬性允許設置多個,并用空格隔開,而在用Selector選擇的時候,即使只指定其中一個,也能夠選中其中的元素。所以這里就把"class"屬性展開了。Element還有選取元素的入口,例如select、getElementByXXX,這些都用到了select包中的內容,這個留到下篇文章select再說。
Document是代表整個文檔,它也是一個特殊的Element,即根節點。Document除了Element的內容,還包括一些輸出的方法。
Document還有一個屬性quirksMode,大致意思是定義處理非標準HTML的幾個級別,這個留到以后分析parser的時候再說。
DOM樹的遍歷
Node還有一些方法,例如outerHtml(),用作節點及文檔HTML的輸出,用到了樹的遍歷。在DOM樹的遍歷上,用到了NodeVisitor和NodeTraversor來對樹的進行遍歷。NodeVisitor在上一篇文章提到過了,head()和tail()分別是遍歷開始和結束時的方法,而NodeTraversor的核心代碼如下:
<!-- lang: java --> public void traverse(Node root) {Node node = root;int depth = 0;//這里對樹進行后序(深度優先)遍歷while (node != null) {//開始遍歷nodevisitor.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。
在下一篇博客我會講講Document的輸出。
總結
以上是生活随笔為你收集整理的Jsoup代码解读之二-DOM相关对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 笔记本电脑主要看哪些配置(配置笔记本电脑
- 下一篇: Jsoup代码解读之一-概述