我有一个朋友叫小帅,是位前端开发工程师。
这天,小帅在处理文本编辑器中粘贴外部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
的解释:
element
DOM 接口的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
中的各种数据。
“真好呀,今天又学习了。”