Jsoup中的设计模式一

来源:互联网 发布:淘宝公牛插座是正品吗 编辑:程序博客网 时间:2024/05/11 10:47

Jsoup是一个知名的html解析库,官网是http://jsoup.org/,github仓库在 https://github.com/jhy/jsoup/ 。最新的jsoup1.8源码含53 个 Java文件,269行/文件,共计 14300行代码,另外有两个1000行的超大properties文件。

1、组合模式

Jsoup中一个显而易见的模式是组合模式(定义:将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使用具有一致性。

Node类的定义正好是符合“树”的定义,如图

Node定义


看一下它的子类

node继承关系

其中最重要的是Element,表示一个html元素

element

Document继承Element,表示整个文档

doc

PS.Token(符号类)和Evaluator(识别器类)咋一看也像是组合模式,当是不符合组织成树形这一条。

token

evalutor

2、外观模式

外观模式,定义:为子系统中的一组接口提供一致的界面,facade提供了一高层接口,这个接口使得子系统更容易使用。但是本人觉得这句好更好理解--外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度(引自http://blog.csdn.net/longyulu/article/details/9159589)。一般外观模式的出现频率很高!

TreeBuilder(树构造器类)就是一个

TreeBuilder


包含字符输入流、符号分析器、当前符号、已打开元素栈、语法解析错误列表、目标文档等。提供一个比较简洁的parse接口给上层。

Document parse(String input, String baseUri, ParseErrorList errors) {
        initialiseParse(input, baseUri, errors);
        runParser();
        return doc;
 }

Parser(解析器类)进一步简化了接口TreeBuilder的接口(也是因为TreeBuilder不止一种实现)

Parser

提供更为简化的静态的parse接口

 public static Document parse(String html, String baseUri) {
        TreeBuilder treeBuilder = new HtmlTreeBuilder();
        return treeBuilder.parse(html, baseUri, ParseErrorList.noTracking());
 }

最后Jsoup(Jsoup的入口类)再一次封装了parse方法,作为框架的终极“门面”,提供给了开发者。

Jsoup

Jsoup提供了parse和它的各种快捷调用方式

jsoup2


当然作为最高级门面类,Jsoup还封装了过滤标签和发起http连接等API

jsoup3


3、状态模式

      状态模式核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。(引自http://blog.csdn.net/longyulu/article/details/9159589)

TokeniserState(记录符号解析器的状态)

TokeniserState


HtmlTreeBuilderState(记录html树构造器的状态)

state

4、解释器模式

QueryParser(css查询选择表达式解析器),Jsoup支持像jQuery一样用css选择器表达式查询dom元素,就是靠这个类实现了解析。

QueryParser


具体解析算法实现

   /**     * Parse the query     * @return Evaluator     */    Evaluator parse() {        tq.consumeWhitespace();        if (tq.matchesAny(combinators)) { // if starts with a combinator, use root as elements            evals.add(new StructuralEvaluator.Root());            combinator(tq.consume());        } else {            findElements();        }        while (!tq.isEmpty()) {            // hierarchy and extras            boolean seenWhite = tq.consumeWhitespace();            if (tq.matchesAny(combinators)) {                combinator(tq.consume());            } else if (seenWhite) {                combinator(' ');            } else { // E.class, E#id, E[attr] etc. AND                findElements(); // take next el, #. etc off queue            }        }        if (evals.size() == 1)            return evals.get(0);        return new CombiningEvaluator.And(evals);    }

1) 将查询表达式串,构造为符号队列(TokenQueue)

2) 一一消费掉符号队列里的符号,最后构造为一个识别器对象(Evaluator)

3)在selector中从页面根元素起遍历所有节点,匹配符合识别器的所有元素数组。

5、模板方法模式

      Template Method ,定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,Template Method使得子类可以不改变一个算法的结构即可以重定义该算法得某些特定步骤。

例子,TreeBuilder和它的子类

TemplateMethod

处理一个符号的步骤取决于是HTML还是XML,所有延迟到子类实现

protected abstract boolean process(Token token);

XmlTreeBuilder重写了父类的初始化方法

public class XmlTreeBuilder extends TreeBuilder {
    @Override
    protected void initialiseParse(String input, String baseUri, ParseErrorList errors) {
        super.initialiseParse(input, baseUri, errors);
        stack.add(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack)
        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);

    }

....

HtmlTreeBuilder重新了父类的解析方法

 @Override
    Document parse(String input, String baseUri, ParseErrorList errors) {
        state = HtmlTreeBuilderState.Initial;
        baseUriSetFromDoc = false;

        return super.parse(input, baseUri, errors);
  }

6、访问者模式

访问者模式表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。

NodeVisitor(节点访问者类)

/** * Node visitor interface. Provide an implementing class to {@link NodeTraversor} to iterate through nodes. * <p/> * This interface provides two methods, {@code head} and {@code tail}. The head method is called when the node is first * seen, and the tail method when all of the node's children have been visited. As an example, head can be used to * create a start tag for a node, and tail to create the end tag. */public interface NodeVisitor {    /**     * Callback for when a node is first visited.     *     * @param node the node being visited.     * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node     * of that will have depth 1.     */    public void head(Node node, int depth);    /**     * Callback for when a node is last visited, after all of its descendants have been visited.     *     * @param node the node being visited.     * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node     * of that will have depth 1.     */    public void tail(Node node, int depth);}
/** * NodeTraversor是经典的深度遍历器,   *每遇到一个节点,即可触发一次访问操作。 * Depth-first node traversor. Use to iterate through all nodes under and including the specified root node. * <p/> * This implementation does not use recursion, so a deep DOM does not risk blowing the stack. */public class NodeTraversor {    private NodeVisitor visitor;    /**     * Create a new traversor.     * @param visitor a class implementing the {@link NodeVisitor} interface, to be called when visiting each node.     */    public NodeTraversor(NodeVisitor visitor) {        this.visitor = visitor;    }    /**     * Start a depth-first traverse of the root and all of its descendants.     * @param root the root node point to traverse.     */    public void traverse(Node root) {        Node node = root;        int depth = 0;                while (node != null) {            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.parentNode();                    depth--;                }                visitor.tail(node, depth);                if (node == root)                    break;                node = node.nextSibling();            }        }    }}




0 0
原创粉丝点击