转载自   Jsoup代码解读之三-Document的输出

Jsoup官方说明里,一个重要的功能就是***output tidy HTML***。这里我们看看Jsoup是如何输出HTML的。

HTML相关知识

分析代码前,我们不妨先想想,"tidy HTML"到底包括哪些东西:

  • 换行,块级标签习惯上都会独占一行
  • 缩进,根据HTML标签嵌套层数,行首缩进会不同
  • 严格的标签闭合,如果是可以自闭合的标签并且没有内容,则进行自闭合
  • HTML实体的转义

这里要补充一下HTML标签的知识。HTML Tag可以分为block和inline两类。关于Tag的inline和block的定义可以参考http://www.w3schools.com/html/html_blocks.asp,而Jsoup的Tag类则是对Java开发者非常好的学习资料。

<!-- lang: java -->
// internal static initialisers:
// prepped from http://www.w3.org/TR/REC-html40/sgml/dtd.html and other sources
//block tags,需要换行
private static final String[] blockTags = {"html", "head", "body", "frameset", "script", "noscript", "style", "meta", "link", "title", "frame","noframes", "section", "nav", "aside", "hgroup", "header", "footer", "p", "h1", "h2", "h3", "h4", "h5", "h6","ul", "ol", "pre", "div", "blockquote", "hr", "address", "figure", "figcaption", "form", "fieldset", "ins","del", "s", "dl", "dt", "dd", "li", "table", "caption", "thead", "tfoot", "tbody", "colgroup", "col", "tr", "th","td", "video", "audio", "canvas", "details", "menu", "plaintext"
};
//inline tags,无需换行
private static final String[] inlineTags = {"object", "base", "font", "tt", "i", "b", "u", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd","var", "cite", "abbr", "time", "acronym", "mark", "ruby", "rt", "rp", "a", "img", "br", "wbr", "map", "q","sub", "sup", "bdo", "iframe", "embed", "span", "input", "select", "textarea", "label", "button", "optgroup","option", "legend", "datalist", "keygen", "output", "progress", "meter", "area", "param", "source", "track","summary", "command", "device"
};
//emptyTags是不能有内容的标签,这类标签都是可以自闭合的
private static final String[] emptyTags = {"meta", "link", "base", "frame", "img", "br", "wbr", "embed", "hr", "input", "keygen", "col", "command","device"
};
private static final String[] formatAsInlineTags = {"title", "a", "p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "address", "li", "th", "td", "script", "style","ins", "del", "s"
};
//在这些标签里,需要保留空格
private static final String[] preserveWhitespaceTags = {"pre", "plaintext", "title", "textarea"
};

另外,Jsoup的Entities类里包含了一些HTML实体转义的东西。这些转义的对应数据保存在entities-full.propertiesentities-base.properties里。

Jsoup的格式化实现

在Jsoup里,直接调用Document.toString()(继承自Element),即可对文档进行输出。另外OutputSettings可以控制输出格式,主要是prettyPrint(是否重新格式化)、outline(是否强制所有标签换行)、indentAmount(缩进长度)等。

里面的继承和互相调用关系略微复杂,大概是这样子:

Document.toString()=>Document.outerHtml()=>Element.html(),最终Element.html()又会循环调用所有子元素的outerHtml(),拼接起来作为输出。

<!-- lang: java -->
private void html(StringBuilder accum) {for (Node node : childNodes)node.outerHtml(accum);
}

outerHtml()会使用一个OuterHtmlVisitor对所以子节点做遍历,并拼装起来作为结果。

<!-- lang: java -->
protected void outerHtml(StringBuilder accum) {new NodeTraversor(new OuterHtmlVisitor(accum, getOutputSettings())).traverse(this);
}

OuterHtmlVisitor会对所有子节点做遍历,并调用node.outerHtmlHead()node.outerHtmlTail两个方法。

<!-- lang: java -->
private static class OuterHtmlVisitor implements NodeVisitor {private StringBuilder accum;private Document.OutputSettings out;public void head(Node node, int depth){node.outerHtmlHead(accum, depth, out);}public void tail(Node node, int depth){if (!node.nodeName().equals("#text")) // saves a void hit.node.outerHtmlTail(accum, depth, out);}
}

我们终于找到了真正工作的代码,node.outerHtmlHead()node.outerHtmlTail。Jsoup里每种Node的输出方式都不太一样,这里只讲讲两种主要节点:ElementTextNodeElement是格式化的主要对象,它的两个方法代码如下:

<!-- lang: java -->
void outerHtmlHead(StringBuilder accum, int depth, Document.OutputSettings out){if (accum.length() > 0 && out.prettyPrint()&& (tag.formatAsBlock() || (parent() != null && parent().tag().formatAsBlock()) || out.outline()) )//换行并调整缩进indent(accum, depth, out);accum.append("<").append(tagName());attributes.html(accum, out);if (childNodes.isEmpty() && tag.isSelfClosing())accum.append(" />");elseaccum.append(">");
}void outerHtmlTail(StringBuilder accum, int depth, Document.OutputSettings out){if (!(childNodes.isEmpty() && tag.isSelfClosing())) {if (out.prettyPrint() && (!childNodes.isEmpty() && (tag.formatAsBlock() || (out.outline() && (childNodes.size()>1 || (childNodes.size()==1 && !(childNodes.get(0) instanceof TextNode)))))))//换行并调整缩进indent(accum, depth, out);accum.append("</").append(tagName()).append(">");}
}

而ident方法的代码只有一行:

<!-- lang: java -->
protected void indent(StringBuilder accum, int depth, Document.OutputSettings out) {//out.indentAmount()是缩进长度,默认是1accum.append("\n").append(StringUtil.padding(depth * out.indentAmount()));
}

代码简单明了,就没什么好说的了。值得一提的是,StringUtil.padding()方法为了减少字符串生成,把常用的缩进保存到了一个数组中。

好了,水了一篇文章,下一篇将比较有技术含量的parser部分。

另外,通过本节的学习,我们学到了要把StringBuilder命名为accum,而不是sb

Jsoup代码解读之三-Document的输出相关推荐

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

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

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

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

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

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

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

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

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

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

  6. Jsoup代码解读之六-parser(下)

    转载自   Jsoup代码解读之六-parser(下) 最近生活上有点忙,女儿老是半夜不睡,精神状态也不是很好.工作上的事情也谈不上顺心,有很多想法但是没有几个被认可,有些事情也不是说代码写得好就行的 ...

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

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

  8. java传值给js 换行_JavaScript用document.write()输出换行的示例代码

    当我们想用document.write()输出换行时,可能会第一时间想到加"\n",但是其实不能达到我们的想要效果,只会得到一个空格的效果. 正确的方法是使用: 样例代码: // ...

  9. 装逼一步到位!GauGAN代码解读来了

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:游璐颖,福州大学,Datawhale成员 AI神笔马良 如何装逼一 ...

最新文章

  1. SQL Server 中 SELECT INTO 和 INSERT INTO SELECT语句的区别
  2. c#如何跳出一个函数_C# mysql 学生信息管理系统
  3. 防止UI界面被输入法遮挡(画面随输入法自适应)
  4. oracle存储过程使用ftp,ASM存储FTP上传文件
  5. vue 带全选和多选的表格怎么写_vue+element-ui里面table组件多选框实现批量操作
  6. wcf分布式构架集群案例解决方案
  7. [ES6] 细化ES6之 -- 对象的扩展
  8. android 模仿 弹性菜单
  9. 元素内容必须由格式正确的字符数据或标记组成_字符编码是什么?html5如何设置字符编码?...
  10. 在Vue项目中引入 ECharts 3D 路径图 Flights GL(需安装echarts、echarts-gl、jQuery依赖,已踩坑)
  11. c语言编程屏保动画实例,用C语言自己动手制作炫彩电脑屏保
  12. 一个人九月份开始考北邮的经验
  13. 【DeepSORT系列之】Cosine Metric Learning训练与demo可视化
  14. tomcat启动后出现乱码解决
  15. 白光模块?彩光模块?
  16. Arduino Uno 实验6——LM35温度传感器
  17. 计算机的控制菜单的移动命令,操作“控制”菜单命令
  18. python把英语句子成分字母_(完整版)英语句子成分分析大全
  19. ubuntu18.04 安装Nvidia驱动的三种方式(必看)
  20. 为什么无法链接mysql_为什么不能连接到SQL数据库?

热门文章

  1. [C++11]不允许使用auto的四个场景
  2. [蓝桥杯][基础练习VIP]分解质因数-质数筛选+模拟+双指针
  3. 数据结构与算法--有序数组中找出和为s的两个数字
  4. 使用Arduino开发ESP32:wifi基本功能使用
  5. numpy创建zeros数组时报错TypeError: Cannot interpret ‘8‘ as a data type
  6. LeetCode动态规划 最大子序和
  7. Codeforces Round #593 (Div. 2) D. Alice and the Doll 暴力 + 二分
  8. Codeforces Round #715 (Div. 1) B. Almost Sorted 找规律
  9. Codeforces Round #603 (Div. 2) E. Editor 线段树维护括号序列
  10. cf1553E. Permutation Shift