国外门户网站源码,canvas可画网页版,中国十大网络销售公司,建网站的流程及注意事项转载自 Jsoup代码解读之二-DOM相关对象之前在文章中说到#xff0c;Jsoup使用了一套自己的DOM对象体系#xff0c;和Java XML API互不兼容。这样做的好处是从XML的API里解脱出来#xff0c;使得代码精炼了很多。这篇文章会说明Jsoup的DOM结构#xff0c;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是#DocumentElement则是对应的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的输出。