上一篇文章讲到了状态机和词法分析的基本知识,这一节咱们来分析Jsoup是如何进行词法分析的。html
先介绍如下parser包里的主要类:java
Parser
git
Jsoup parser的入口facade,封装了经常使用的parse静态方法。能够设置maxErrors
,用于收集错误记录,默认是0,即不收集。与之相关的类有ParseError
,ParseErrorList
。基于这个功能,我写了一个PageErrorChecker
来对页面作语法检查,并输出语法错误。github
Token
ui
保存单个的词法分析结果。Token是一个抽象类,它的实现有Doctype
,StartTag
,EndTag
,Comment
,Character
,EOF
6种,对应6种词法类型。code
Tokeniser
htm
保存词法分析过程的状态及结果。比较重要的两个字段是state
和emitPending
,前者保存状态,后者保存输出。其次还有tagPending
/doctypePending
/commentPending
,保存尚未填充完整的Token。blog
CharacterReader
token
对读取字符的逻辑的封装,用于Tokenize时候的字符输入。CharacterReader包含了相似NIO里ByteBuffer的consume()
、unconsume()
、mark()
、rewindToMark()
,还有高级的consumeTo()
这样的用法。ip
TokeniserState
用枚举实现的词法分析状态机。
HtmlTreeBuilder
语法分析,经过token构建DOM树的类。
HtmlTreeBuilderState
语法分析状态机。
TokenQueue
虽然披了个Token的马甲,实际上是在query的时候用到,留到select部分再讲。
如今咱们来说讲HTML的词法分析过程。这里借用一下http://ued.ctrip.com/blog/?p=3295里的图,图中描述了一个Tag标签的状态转移过程,
这里忽略了HTML注释、实体以及属性,只保留基本的开始/结束标签,例以下面的HTML:
<!-- lang: html --> <div>test</div>
Jsoup里词法分析比较复杂,我从里面抽取出了对应的部分,就成了咱们的miniSoupLexer(这里省略了部分代码,完整代码能够看这里MiniSoupTokeniserState
):
<!-- lang: java --> enum MiniSoupTokeniserState implements ITokeniserState { /** * 什么层级都没有的状态 * ⬇ * <div>test</div> * ⬇ * <div>test</div> */ Data { // in data state, gather characters until a character reference or tag is found public void read(Tokeniser t, CharacterReader r) { switch (r.current()) { case '<': t.advanceTransition(TagOpen); break; case eof: t.emit(new Token.EOF()); break; default: String data = r.consumeToAny('&', '<', nullChar); t.emit(data); break; } } }, /** * ⬇ * <div>test</div> */ TagOpen { ... }, /** * ⬇ * <div>test</div> */ EndTagOpen { ... }, /** * ⬇ * <div>test</div> */ TagName { ... }; }
参考这个程序,能够看到Jsoup的词法分析的大体思路。分析器自己的编写是比较繁琐的过程,涉及属性值(区分单双引号)、DocType、注释、HTML实体,以及一些错误状况。不过了解了其思路,代码实现也是循序渐进的过程。
下一节开始介绍语法分析部分。
最后仍是附上个人Jsoup解读系列文章及代码地址: