时间:2021-07-01 10:21:17 帮助过:14人阅读
读Jsoup源码并非无聊,目的其实是为了将webmagic做的更好一点,毕竟parser也是爬虫的重要组成部分之一。读了代码后,收获也不少,对HTML的知识也更进一步了。
这里单独将TreeBuilder部分抽出来叫做语法分析过程可能稍微不妥,其实就是根据Token生成DOM树的过程,不过我还是沿用这个编译器里的称呼了。
TreeBuilder同样是一个facade对象,真正进行语法解析的是以下一段代码:
protected void runParser() { while (true) { Token token = tokeniser.read();process(token);if (token.type == Token.TokenType.EOF) break; }}
TreeBuilder有两个子类,HtmlTreeBuilder和XmlTreeBuilder。XmlTreeBuilder自然是构建XML树的类,实现颇为简单,基本上是维护一个栈,并根据不同Token插入节点即可:
@Overrideprotected boolean process(Token token) { // start tag, end tag, doctype, comment, character, eof switch (token.type) { case StartTag: insert(token.asStartTag()); break; case EndTag: popStackToClose(token.asEndTag()); break; case Comment: insert(token.asComment()); break; case Character: insert(token.asCharacter()); break; case Doctype: insert(token.asDoctype()); break; case EOF: // could put some normalisation here if desired break; default: Validate.fail("Unexpected token type: " + token.type); } return true;}
insertNode的代码大致是这个样子(为了便于展示,对方法进行了一些整合):
Element insert(Token.StartTag startTag) { Tag tag = Tag.valueOf(startTag.name()); Element el = new Element(tag, baseUri, startTag.attributes); stack.getLast().appendChild(el); if (startTag.isSelfClosing()) { tokeniser.acknowledgeSelfClosingFlag(); if (!tag.isKnownTag()) // unknown tag, remember this is self closing for output. see above. tag.setSelfClosing(); } else { stack.add(el); } return el;}
相比XmlTreeBuilder,HtmlTreeBuilder则实现较为复杂,除了类似的栈结构以外,还用到了HtmlTreeBuilderState来构建了一个状态机来分析HTML。这是为什么呢?不妨看看HtmlTreeBuilderState到底用到了哪些状态吧(在代码中中用 标明状态):
这里可以看到,HTML标签是有嵌套要求的,例如