我有一个朋友叫小帅,是位前端开发工程师。
这天,小帅在处理文本编辑器中粘贴外部HTML时遇到了点问题。
当我们复制外部带有样式的内容时,我们复制的其实是一段完整的HTML文本,需求描述当将该文本粘贴进编辑器时,需要让用户选择是否保留原文本的样式,若保留,则需要获取HTML中包含文本的DOM字符串(即那些额外的标签都不需要),否则需要获取纯文本内容。
小帅同学眼珠一转便想到用 document.createElement('div').innerHTML 的方式先将文本变成可获取的DOM,再去获取其中包含文本的具体元素,最后填入到编辑器中。
然而到了实践的时候小帅又犯了难,当HTML文本中包含<style>标签样式时怎么处理呢。
此时,旁边的小美悠悠地来了一句:难道你不知道HTML有个DOM对象叫DOMParser吗❓
DOMParser
定义
从 MDN 和 W3C官方描述 中可以知道,DOMParser 的作用是将存储在字符串中的XML或HTML源代码解析为一个 DOM Document(注意,这里是将字符串解析为 Document 而不是字符串对应的 DOM)。
如果要直接获取HTML字符串对应的 DOM,我们可以使用Element.innerHTML的方式。
DOMParser对象只有一个parseFromString方法,官方也认为目前的 API 设计并不是最好,如果重新设计的话会将该功能设计为一个单独的 API 方法,而不是通过DOMParser对象去调用。
用法
DOMParser API 的用法如下所示,实例化完成后通过 parseFromString 方法去完成解析:
new DOMParser().parseFromString(string, mimeType);
参数
parseFromString 方法支持以下两个参数
-
string: 要解析的
DOMString,它必须包含HTML、xml、xhtml+xml或svg文档; -
type:待解析的
DOMString类型,包含以下几个可选项。| mimeType | doc.constructor | | ----------------------- | --------------- | |
text/html|Document| |text/xml|XMLDocument| |application/xml|XMLDocument| |application/xhtml+xml|XMLDocument| |image/svg+xml|XMLDocument|
返回值
解析正确的情况下,parseFromString 方法会根据 mimeType 参数,返回 Document 或 XMLDocument 类型文档。
当解析失败时,该方法会返回一个给定的 Document 或 XMLDocument 文档,如下所示:
<parsererror xmlns="http://www.mozilla.org/newlayout/xml/parsererror.xml">
(error description)
<sourcetext>(a snippet of the source XML)</sourcetext>
</parsererror>
解析过程
当调用 parseFromString(string, type) 方法时,会依次经历以下几个步骤:
- 创建一个新的类型为
type的Document,并且设置Document.URL为上下文全局对象中关联的Document.URL(如在浏览器中为window.document.URL); - 判断
type是否为text/html; - 若是,则设置
Document的type值为html,并创建一个HTML Parser解析器,然后将字符串string传入到解析器中开始执行解析;在解析过程中始终使用UTF-8的编码,并且不会执行字符串中的script脚本; - 若否,则会创建一个
XML Parser解析器并开始解析字符串string,解析完成后将解析后的结果作为根节点添加到上下文中的document中; - 返回前面几步执行完成后的
Document文档。
XMLSerializer
定义
XMLSerializer接口提供serializeToString()方法来构建一个代表DOM树的XML字符串。
MDN 上的定义读着怪怪的,这里简单翻译一下就是,XMLSerializer 提供了一个将 Document 元素转换为 XML 字符串的 serializeToString() 方法,这里的 Document 与 DOMParser 中的文档相对应,包含 HTML、XML 及 SVG。
用法
XMLSerializer API 的用法如下所示,实例化完成后通过 serializeToString 方法去完成解析:
new XMLSerializer().serializeToString(node);
参数
serializeToString 方法接受一个参数,为待序列化文档的根节点,该根节点是必须是一个 DOM 节点或一个 Attr Object(拥有类 DOM 节点的一些属性,但并不是真正的 DOM)。
如果参数不符合要求,则会抛出一个 TypeError。
返回值
serializeToString 方法将返回一个 DOMString 类型字符串。
DOMString 在官方规范中是 一组 UTF-16 字符串。这里没有很明确的定义,它本质上就是一组字符串,大概是跟 DOM 相关,所有就叫 DOMString(好像说了,又好像没说)。不过可以明确的是,所有的 JavaScript 字符串都是 DOMString。
与 outerHTML 的区别
先来看看 MDN 上对于 outerHTML 的解释:
elementDOM 接口的outerHTML属性获取描述元素(包括其后代)的序列化 HTML 片段。它也可以设置为用从给定字符串解析的节点替换元素。
从 获取元素的序列化 HTML 片段 部分看 outerHTML 与 XMLSerializer 的效果是一样的,但仔细回顾一下 XMLSerializer 的定义就会发现其中的区别: XMLSerializer 是将元素转换为 Document 字符串,相比于普通的 HTML 字符串会多出 xmlns 属性,同时 XMLSerializer 也可以作用于非 HTLM 元素上,例如注释节点、文本节点等。
我们可以在浏览器中执行以下代码片段来查看具体的效果:
打开浏览器的 console 面板,可以看到以下输出内容:
<div xmlns="http://www.w3.org/1999/xhtml" id="app">hello world</div>
<div id="app">hello world</div>
<svg xmlns="http://www.w3.org/2000/svg"><rect width="30" height="30"/></svg>
<svg><rect width="30" height="30"></rect></svg>
对于大部分情况来说,XMLSerializer 的结果只是多了 xmlns 的属性声明,当将该字符串作为 innerHTML 直接插入在页面中时与 outerHTML 没有任何区别。但部分情况,例如 svg 作为 image.src 的属性值时,xmlns 的属性声明变为必要。
因此当我们想要将文档节点转换成字符串处理完成后再显示为文档元素时,使用 XMLSerializer 会比 outerHTML 是更完善、出现BUG概率更低的一个选择。
应用场景
富文本解析
在富文本编辑器中,文本内容通常都是通过 HTML 字符串的方式进行传输,前端在获取到字符串之后需要将其转换为 DOM 元素并展示在页面中。在这个过程中,可以通过 DOMParser 将 HTML 字符串转换为可操作的 Document,然后通过 DOM API 对文档内容进行更加方便的修改操作。
网页快照
通过 XMLSerializer 配合 SVG 可以很方便的将前端页面处理成快照,可用于场景回放、页面截图等功能中。
经过小美的一番教导,小帅终于明白了,原来只需要使用new DOMParser().parseFromString(str, 'text/html')方法就可以还原HTML文本为完整的Document,之后就可以很方便的获取Document中的各种数据。
“真好呀,今天又学习了。”