上一篇讲解了filter访问html树的原理,今天在讲解一下htmlparser中visitor访问html的dom树的原理。网上关于htmlparser工作原理的资料比较少,要想学习htmlparser最好看htmlparser的源码,htmlparser源码不算大,代码都是大牛们写的,可读性非常好,只要从main函数中跟踪程序的执行过程就能够很好的了解htmlparser的工作原理。下面总结一下我关于htmlparser的visitor访问机制的一些理解。
和上一篇《HTMLParser使用Filter遍历html DOM树的原理》使用的html源码一样,如下所示:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白泽居-www.baizeju.com</title></head>
<body >
<div id="top_main" class="name">
<div id="logoindex">
白泽居-www.baizeju.com<a href="http://www.baizeju.com">白泽居-www.baizeju.com</a>
</div>
<div class="name">东方教主文成武德,一同江湖</div>
</div>
</body>
</html>
他被组织成三棵树的森林,其中以<html>标签为根节点的树高度最大,网页的树状结构图如下:
下面我们就跟踪代码来发现visitor机制的工作原理:
(1)首先是main函数,我们使用htmlparser中自带的LinkFindingVisitor进行试验(htmlparser中自带的Visitor类不能满足我们的需求,他们存在的目的是让我们能够了解Visitor访问机制的工作原理,如果我们要实现自己的功能需要写自己的Visitor子类),下面是main函数的代码,功能很简单就是统计源码中存在某类链接的个数。
package parsertool;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import org.htmlparser.Node;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.util.NodeList;
import org.htmlparser.visitors.LinkFindingVisitor;
import org.htmlparser.Parser;
public class Nodes {
public static void main(String[] args) {
try{
Parser parser = new Parser("http://211.87.234.91:8088/SearchEngine/mypage.html" );
LinkFindingVisitor visitor = new LinkFindingVisitor("xiao");
parser.visitAllNodesWith(visitor);
System.out.println("count=="+visitor.getCount());
System.out.println("finished..............................");
}
catch( Exception e ) {
System.out.println( "Exception:"+e );
}
}
(2)然后跟踪Parser的visitAllNodesWith()类,他的参数是LinkFindingVisitor实例,他的代码如下所示:
public void visitAllNodesWith (NodeVisitor visitor) throws ParserException
{
Node node;
visitor.beginParsing();
for (NodeIterator e = elements(); e.hasMoreNodes(); )
{
node = e.nextNode();
node.accept(visitor);
}
visitor.finishedParsing();
}
解释:for循环中对html源码形成的森林中的每个树根节点进行遍历,调用每个节点的accept方法,htmlparser将所有的html节点分成了四大类(TagNode,CompositeTag,TextNode和RemarkNode)。CompositeTag相当于中间节点,他有自己的子节点,其他三种节点都是叶节点,没有子节点。
(3)四种节点的accept方法负责完成dom树的遍历工作,因为CompositeTag有子节点,所有他除了需要遍历自身以外还需要遍历递归遍历他的所有子节点(但是Visitor有两个参数mRecurseChildren,mRecurseSelf可以指定是否需要遍历自身和是否需要遍历子节点);其他三种节点没有叶节点他们只需要调用参数Visitor对应的方法访问本身就行了,相对比较简单,下面我们重点介绍一下CompositeTag的accept方法:
public void accept (NodeVisitor visitor)
{
SimpleNodeIterator children;
Node child;
if (visitor.shouldRecurseSelf ())
visitor.visitTag (this);
if (visitor.shouldRecurseChildren ())
{
if (null != getChildren ())
{
children = children ();
while (children.hasMoreNodes ())
{
child = children.nextNode ();
child.accept (visitor);
}
}
if ((null != getEndTag ()) && (this != getEndTag ())) // 2nd guard handles <tag/>
getEndTag ().accept (visitor);
}
}
htmlparser中的visitor访问机制和filter机制都能实现我们想要的功能,都是以html树状结构的遍历为基础的,所以理解好dom树对应用程序开发是十分关键的,还有就是htmlparser的源码相对比较小,而且调理清晰,通过源码的学习应该可以很好的理解htmlparser工作原理。