上一篇讲解了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遍历html树巧妙之处就是,htmlparser提供了遍历每个树节点的逻辑,而对于每个节点如何进行处理则要通过参数NodeVisitor进行实现,我们可以通过实现自己的NodeVisitor子类来完成自己需要的功能。上面代码大体意思是如果需要遍历自身节点则调用visitor.visitTag(this)处理自身;然后如果需要递归处理子节点而且子节点存在则需要逐个遍历子节点;最后处理尾部节点中需要递归处理的标签,处理完成。
htmlparser中的visitor访问机制和filter机制都能实现我们想要的功能,都是以html树状结构的遍历为基础的,所以理解好dom树对应用程序开发是十分关键的,还有就是htmlparser的源码相对比较小,而且调理清晰,通过源码的学习应该可以很好的理解htmlparser工作原理。