转载自   Jsoup代码解读之六-parser(下)

最近生活上有点忙,女儿老是半夜不睡,精神状态也不是很好。工作上的事情也谈不上顺心,有很多想法但是没有几个被认可,有些事情也不是说代码写得好就行的。算了,还是端正态度,毕竟资历尚浅,我还是继续我的。

读Jsoup源码并非无聊,目的其实是为了将webmagic做的更好一点,毕竟parser也是爬虫的重要组成部分之一。读了代码后,收获也不少,对HTML的知识也更进一步了。

DOM树产生过程

这里单独将TreeBuilder部分抽出来叫做语法分析过程可能稍微不妥,其实就是根据Token生成DOM树的过程,不过我还是沿用这个编译器里的称呼了。

TreeBuilder同样是一个facade对象,真正进行语法解析的是以下一段代码:

<!-- lang: java -->
protected void runParser() {while (true) {Token token = tokeniser.read();process(token);if (token.type == Token.TokenType.EOF)break;}
}

TreeBuilder有两个子类,HtmlTreeBuilderXmlTreeBuilderXmlTreeBuilder自然是构建XML树的类,实现颇为简单,基本上是维护一个栈,并根据不同Token插入节点即可:

<!-- lang: java -->
@Override
protected boolean process(Token token) {// start tag, end tag, doctype, comment, character, eofswitch (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 desiredbreak;default:Validate.fail("Unexpected token type: " + token.type);}return true;
}

insertNode的代码大致是这个样子(为了便于展示,对方法进行了一些整合):

<!-- lang: java -->
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;
}

HTML解析状态机

相比XmlTreeBuilderHtmlTreeBuilder则实现较为复杂,除了类似的栈结构以外,还用到了HtmlTreeBuilderState来构建了一个状态机来分析HTML。这是为什么呢?不妨看看HtmlTreeBuilderState到底用到了哪些状态吧(在代码中中用<!-- State: --&gt;标明状态):

<!-- lang: html -->
<!-- State: Initial -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- State: BeforeHtml -->
<html lang='zh-CN' xml:lang='zh-CN' xmlns='http://www.w3.org/1999/xhtml'>
<!-- State: BeforeHead -->
<head><!-- State: InHead --><script type="text/javascript">//<!-- State: Text -->function xx(){}</script><noscript><!-- State: InHeadNoscript -->Your browser does not support JavaScript!</noscript>
</head>
<!-- State: AfterHead -->
<body>
<!-- State: InBody -->
<textarea><!-- State: Text -->xxx
</textarea>
<table><!-- State: InTable --><!-- State: InTableText -->xxx<tbody><!-- State: InTableBody --></tbody><tr><!-- State: InRow --><td><!-- State: InCell --></td></tr>
</table>
</html>

这里可以看到,HTML标签是有嵌套要求的,例如<tr>,<td>需要组合<table>来使用。根据Jsoup的代码,可以发现,HtmlTreeBuilderState做了以下一些事情:

  • 语法检查

    例如tr没有嵌套在table标签内,则是一个语法错误。当InBody状态直接出现以下tag时,则出错。Jsoup里遇到这种错误,会发现这个Token的解析并记录错误,然后继续解析下面内容,并不会直接退出。

      <!-- lang: java -->InBody {boolean process(Token t, HtmlTreeBuilder tb) {if (StringUtil.in(name,"caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr")) {tb.error(this);return false;}}
    
  • 标签补全

    例如head标签没有闭合,就写入了一些只有body内才允许出现的标签,则自动闭合</head>HtmlTreeBuilderState有的方法anythingElse()就提供了自动补全标签,例如InHead状态的自动闭合代码如下:

       <!-- lang: java -->private boolean anythingElse(Token t, TreeBuilder tb) {tb.process(new Token.EndTag("head"));return tb.process(t);}
    

    还有一种标签闭合方式,例如下面的代码:

      <!-- lang: java -->private void closeCell(HtmlTreeBuilder tb) {if (tb.inTableScope("td"))tb.process(new Token.EndTag("td"));elsetb.process(new Token.EndTag("th")); // only here if th or td in scope}
    

实例研究

缺少标签时,会发生什么事?

好了,看了这么多parser的源码,不妨回到我们的日常应用上来。我们知道,在页面里多写一个两个未闭合的标签是很正常的事,那么它们会被怎么解析呢?

就拿<div>标签为例:

  1. 漏写了开始标签,只写了结束标签

     <!-- lang: java -->case EndTag:if (StringUtil.in(name,"div","dl", "fieldset", "figcaption", "figure", "footer", "header", "pre", "section", "summary", "ul")) {                if (!tb.inScope(name)) {tb.error(this);return false;} }
    

    恭喜你,这个</div>会被当做错误处理掉,于是你的页面就毫无疑问的乱掉了!当然,如果单纯多写了一个</div>,好像也不会有什么影响哦?(记得有人跟我讲过为了防止标签未闭合,而在页面底部多写了几个</div>的故事)

  2. 写了开始标签,漏写了结束标签

    这个情况分析起来更复杂一点。如果是无法在内部嵌套内容的标签,那么在遇到不可接受的标签时,会进行闭合。而<div>标签可以包括大多数标签,这种情况下,其作用域会持续到HTML结束。

好了,parser系列算是分析结束了,其间学到不少HTML及状态机内容,但是离实际使用比较远。下面开始select部分,这部分可能对日常使用更有意义一点。

最后附上我的Jsoup系列博客及源码地址:http://github.com/code4craft/jsoup-learning

Jsoup代码解读之六-parser(下)相关推荐

  1. Jsoup代码解读之四-parser(上)

    转载自  Jsoup代码解读之四-parser(上) 作为Java世界最好的HTML 解析库,Jsoup的parser实现非常具有代表性.这部分也是Jsoup最复杂的部分,需要一些数据结构.状态机乃至 ...

  2. Jsoup代码解读之五-parser(中)

    转载自    Jsoup代码解读之五-parser(中) 上一篇文章讲到了状态机和词法分析的基本知识,这一节我们来分析Jsoup是如何进行词法分析的. 代码结构 先介绍以下parser包里的主要类: ...

  3. jsoup获得css,Jsoup代码解读之五-实现一个CSS Selector

    Jsoup代码解读之七-实现一个CSS Selector 当当当!终于来到了Jsoup的特色:CSS Selector部分.selector也是我写的爬虫框架webmagic开发的一个重点.附上一张s ...

  4. Jsoup代码解读之一-概述

    转载自   Jsoup代码解读之一-概述 今天看到一个用python写的抽取正文的东东,美滋滋的用Java实现了一番,放到了webmagic里,然后发现Jsoup里已经有了-觉得自己各种不靠谱啊!算了 ...

  5. Jsoup代码解读之二-DOM相关对象

    转载自  Jsoup代码解读之二-DOM相关对象 之前在文章中说到,Jsoup使用了一套自己的DOM对象体系,和Java XML API互不兼容.这样做的好处是从XML的API里解脱出来,使得代码精炼 ...

  6. Jsoup代码解读之三-Document的输出

    转载自   Jsoup代码解读之三-Document的输出 Jsoup官方说明里,一个重要的功能就是***output tidy HTML***.这里我们看看Jsoup是如何输出HTML的. HTML ...

  7. Jsoup代码解读之七-实现一个CSS Selector

    转载自    Jsoup代码解读之七-实现一个CSS Selector 当当当!终于来到了Jsoup的特色:CSS Selector部分.selector也是我写的爬虫框架webmagic开发的一个重 ...

  8. 编译原理语义分析代码_Pix2Pix原理分析与代码解读

    原理分析: 图像.视觉中很多问题都涉及到将一副图像转换为另一幅图像(Image-to-Image Translation Problem),这些问题通常都使用特定的方法来解决,不存在一个通用的方法.但 ...

  9. 飞桨PP-HumanSeg本地实时视频推理代码解读

    文章同样发布在百度AIStudio,Fork后即可在线运行,请点击这里 本人希望基于PaddleSeg对视频实时进行图像分割,但在AiStudio中检索分割和实时两个关键词后并没有得到理想的结果,大部 ...

最新文章

  1. linux常用指令笔记(1)
  2. 小店怎么做内容营销?这个家居店铺有诀窍
  3. RenderManager - cssHook - select_all icon render logic
  4. opacity:0.99;
  5. 自适应关于帧场编码问题
  6. tomcat最大连接数_SpringBoot内嵌Tomcat自定义配置用法
  7. Pulseaudio之load-module加载module-always-sink(十四)
  8. python爬虫简历项目怎么写_python爬虫简历
  9. python使用ttf文件_python – 如何在matplotlib中使用(随机)* .otf或* .ttf字体?
  10. 鼎捷T100权限管控设定
  11. java模拟选课_模拟学生选课系统的练习
  12. mas6a801 sw tree disp
  13. 如何设置一个可扩展的MongoDB数据库?
  14. RFID-上位机软件界面设计
  15. 利用手机作为渗透工具的一些思路
  16. 好程序员Java培训分享如何快速入门Java编程
  17. format函数用法详解
  18. 基于php的网上书店系统,基于PHP的网上书店的设计
  19. [倚天屠龙记] vim 查找与替换(正则表达式)
  20. oracle表空间默认增长大小,Oracle 用户表空间查看、修改大小、设置自增长等

热门文章

  1. 关于导入c3p0-0.9.5.5.jar包引发NoClassDefFoundError、ClassNotFoundException
  2. windows 禁用ipv6服务_在 Windows 7 中禁用IPv6协议/IPv6隧道
  3. [蓝桥杯2016决赛]反幻方-next_permutation枚举
  4. The Last Non-zero Digit POJ - 1150(n!mod p)
  5. word List 34
  6. P6329 【模板】点分树 | 震波
  7. 生成函数Euler变换学习笔记(无标号有根树计数)
  8. 【洛谷P4719】动态DP【LCT】【矩阵】
  9. AGC023F - 01 on Tree
  10. E. Colorings and Dominoes(未解决)