拨云见日:深入理解 HTML 解析器与有限状态机
文章目录
- 参考
- 描述
- 状态机
- 状态机
- 有限状态机与无限状态机
- 有限状态机与自动售货机
- 无限状态机与计算器
- HTML 解析器
- HTML 解析器
- HTML 与有限状态机
- HTML 解析器的常见状态
- 初始状态
- DOCTYPE 状态
- 注释状态
- 标签状态
- 开始标签状态
- 属性状态
- 属性名状态
- 属性值状态
- 结束标签状态
- 自闭和标签状态
- 文本状态
- 字符引用状态
- 数字字符引用状态
- RCDATA 状态
- RCDATA 元素
- RCDATA 状态
- RAWTEXT 状态
- PLAINTEXT 状态
- PLAINTEXT 元素
- PLAINTEXT 状态
参考
项目 | 描述 |
---|---|
搜索引擎 | Google 、Bing |
维基百科 | 首页 |
百度百科 | 首页 |
MDN | 文档 |
HTML Standard | parsing.html |
描述
项目 | 描述 |
---|---|
浏览器 | Edge 111.0.1661.62 (正式版本) (64 位) |
状态机
状态机
状态机(State Machine)是一种用来描述对象或系统的行为的抽象概念。它是由一组状态、一组转移条件和一组动作组成的。在任何时候,状态机只能处于其中一种状态。当满足某些转移条件时,状态机会从当前状态转移到下一个状态,并执行相应的动作。
优点
-
状态机提供了一种清晰的方式来描述复杂的行为。状态机可以把复杂的问题简化为一些离散的状态和转移条件,使问题更容易理解和解决。
-
状态机可以提高程序的可读性和可维护性。使用状态机,程序员可以更容易地理解程序的行为,更快速地进行故障排除和更改。
-
状态机可以用于设计高效的算法。许多计算机科学领域中的算法都是基于状态机设计的,如图形处理、自然语言处理和编译器等。
-
状态机可以用于实现复杂的控制逻辑,如自动机器人、工业自动化和网络通信等。
有限状态机与无限状态机
一般来说,状态机可以分为有限状态机和无限状态机两种类型。
有限状态机(Finite State Machine,FSM)是指状态机中状态的数量是有限的。在有限状态机中,状态之间的转移是根据预定义的规则进行的,这些规则可以用转移函数或转移表来表示。 有限状态机通常用于处理离散的问题,如自动机器人、电路设计、解释器和编译器等。
无限状态机(Infinite State Machine,ISM)是指状态机中状态的数量是无限的。在无限状态机中,状态之间的转移是根据某种计算规则进行的,这些规则可以是递归函数、迭代算法、差分方程等。 无限状态机通常用于处理连续的问题,如控制系统、信号处理、图像处理等。
需要注意的是,有限状态机和无限状态机并不是互相排斥的概念。 事实上,许多实际的问题都可以用有限状态机和无限状态机相结合的方式来处理。例如,在编译器中,词法分析器可以用有限状态机来处理代码中的单词,而语法分析器可以用无限状态机来处理代码中的语法结构。
有限状态机与自动售货机
当一个系统的状态数量是有限的时候,我们就可以使用有限状态机(FSM)来描述它的行为。例如,考虑一个简单的自动售货机,它可以接受硬币、选择饮料、进行交易等。自动售货机可以表示为一个有限状态机,它有以下状态:
- 等待选择饮料
- 已选择饮料
- 等待投币
- 已投币
- 正在进行交易
- 交易完成
这些状态之间的转移可以由用户或自身的行为触发,例如:
- 选择饮料:从 等待选择饮料 状态转移至 已选择饮料 状态。
- 投入硬币:从 等待投币 状态转移到 已投币 状态。
- 自动售货机吐出饮料:从 正在进行交易 状态转移至 交易完成 状态。
无限状态机与计算器
当我们要描述一个计数器时,可以使用一个无限状态机(ISM)。该状态机可以表示计数器从 0 开始不断递增的行为,状态机中的状态数量是无限的。在该状态机中,有一个可能的输入信号,即 增加计数器中存储的值。每当输入信号被触发时,计算器将执行相应的动作,同时切换至另一个状态之中。
在这个例子中,状态机的状态数量是无限的,因为计数器可以一直增加下去。 由于状态数量无限,因此我们无法用图形表示法或者表格表示法来表示它。但是,我们可以使用数学符号来描述它,比如 f(n)=n+1,表示当输入信号被触发时,状态机的下一个状态是当前状态加 1。
HTML 解析器
HTML 解析器
HTML 解析器是一种将 HTML 文本解析为 DOM(Document Object Model) 树的程序。DOM 树是一种以 分层 的方式表示 HTML 文档的树形结构,其中每个 HTML 元素对应一个节点,而元素中元素则作为该节点下的子节点。
HTML 解析器通常分为两个部分:词法分析器和语法分析器。词法分析器将 HTML 文本分解成一个个词法单元(token),例如标签、属性、文本等。语法分析器将这些词法单元组合成一棵 DOM 树,并验证 HTML 文档的语法正确性。
HTML 解析器在 Web 开发中具有重要作用,因为它是浏览器渲染 HTML 页面的关键组件之一。当用户在浏览器中请求一个 HTML 页面时,浏览器会先将该页面的 HTML 文本传递给 HTML 解析器进行解析,生成对应的 DOM 树。随后,浏览器会使用 DOM 树来渲染页面,并执行其中包含的 JavaScript 代码。
HTML 除了应用于浏览器中,还广泛应用于 Web 爬虫、数据挖掘和搜索引擎等领域,用于解析 HTML 文本,以从中提取所需要的信息。
HTML 与有限状态机
HTML 解析器通常使用有限状态机(FSM)来解析 HTML 文档,因为 HTML 文档本身是由有限的、预定义的标签和属性组成的。
具体来说,HTML 解析器可以被视为一种 流式 有限状态机,即解析器将文本数据作为输入流,然后逐个处理输入的字符,根据当前的状态进行相应的转移。解析器通过检查当前字符和上下文来确定下一个状态,并将所需的节点添加到 DOM 树中。
例如,当解析器遇到一个开始标签时,它将进入一个 开始标签 状态,然后逐个解析标签的名称和属性,并将这些信息存储为 DOM 节点的属性。当解析器遇到标签的结束标记时,它将退出 开始标签 状态,并将新的节点添加到 DOM 树中。
HTML 解析器的常见状态
HTML解析器通常包含多个状态,用于识别和处理HTML代码中不同类型的词法单元(lexical token)和语法结构。
初始状态
HTML 解析器中的初始状态是 解析器在开始解析 HTML 文档时所处的状态。在这个状态下,解析器还没有开始解析任何内容,它会等待输入的字符流。
当解析器接收到输入时,它会根据输入的内容判断应该转换到哪个状态。 如果输入的是空格、制表符、换行符等空白字符,解析器会忽略它们并继续等待输入。如果输入的是 <!DOCTYPE>
、<!--
或其他类型的标签开始符号 <
,则解析器会转换到对应的状态继续解析。
初始状态的主要作用是初始化解析器并等待输入,它是整个解析过程的起点。
DOCTYPE 状态
HTML 解析器中的 DOCTYPE 状态是指解析器遇到 DOCTYPE 声明 时所处的状态。在这个状态下,解析器会解析 DOCTYPE 声明并根据其内容设置文档类型,从而影响后续的解析行为。
如果 HTML 文档中没有 DOCTYPE 声明,解析器会默认将文档类型设置为 HTML5,这种情况下解析器会按照 HTML5 规范进行解析。
DOCTYPE 状态是 HTML 解析器的一个重要状态,因为它 决定了文档类型,从而影响后续的解析行为。
注释状态
HTML 解析器中的注释状态是指解析器遇到 <!--
标签时所处的状态。在这个状态下,解析器会将接下来的字符视为注释,直到遇到注释结束标记 -->
为止。
标签状态
标签状态(tag state)是 HTML 解析器中的一个主要状态,用于识别和解析 HTML 元素。在标签状态下,解析器会根据接下来的输入字符识别出当前的 HTML 元素,并将其转化为 DOM 树的节点。
开始标签状态
在标签状态下,如果解析器遇到 <
字符,则会切换到开始标签状态(start tag state),进而开始解析一个开始标签。开始标签状态用于解析 HTML 元素的名称、属性和属性值等信息,它是标签状态的子状态。
属性状态
属性状态(attribute state)是开始标签状态的子状态,用于解析 HTML 元素的属性。在属性状态下,解析器会识别出当前属性的名称和值,并将其添加到当前 HTML 元素的属性列表中。
属性名状态
在 HTML 解析器中,属性名状态(attribute name state)是在属性状态(start tag state)下触发的一种状态。在属性名状态中,HTML 解析器会解析开始标签中的属性名,直到遇到空格、斜杠、等号等分隔符号。
在属性名状态中,HTML 解析器会忽略多余的空格,并将下一个非空格字符解析为属性名的第一个字符。接下来,HTML 解析器会解析属性名的剩余字符,直到遇到分隔符号。
如果遇到的分隔符号是等号,那么 HTML 解析器会将状态切换到属性值状态(attribute value state);如果遇到的分隔符号是斜杠,那么 HTML 解析器会将状态切换到自闭合标签状态(self-closing tag state);如果遇到的分隔符号是空格,则属性名解析结束。
如果解析到的属性名已经存在于标签中,那么 HTML 解析器会将其视为一个新的属性,并覆盖原有的属性值。
属性值状态
属性值状态
在 HTML 解析器中,属性值状态(attribute value state)是在属性状态(start tag state)下触发的一种状态。在属性值状态中,HTML 解析器会解析开始标签中的属性值,直到遇到引号(单引号或双引号)或者空格等分隔符号。
引号状态与无引号状态
属性值可以有两种写法,分别是带引号的写法和不带引号的写法。在带引号的写法中,属性值必须被一对引号包含,可以是单引号或者双引号。在不带引号的写法中,属性值可以直接跟在等号后面,但是不能包含空格和其他分隔符号。
在属性值状态中,HTML 解析器会忽略开头的空格,直到解析到第一个非空格字符。 如果该字符是引号,则属性值状态切换到对应的引号状态(quoted string state),否则表示属性值是不带引号的写法,直接进入无引号属性值状态(unquoted value state)。
在引号状态或无引号属性值状态中,HTML 解析器会解析属性值中的字符,直到遇到对应的引号或空格等分隔符号为止。 如果属性值中存在字符引用或者数字字符引用,则 HTML 解析器会将其解析为对应的字符。 如果解析到的属性值包含非法字符,则会产生语法错误。
结束标签状态
HTML解析器中的结束标签状态(end tag state)用于解析 HTML 中的结束标签。当解析器遇到 <
符号时,会判断接下来的字符是否为 /
,如果是,则表示遇到了结束标签,解析器将进入结束标签状态。
在 HTML 解析器进入结束标签状态时,解析器将解析结束标签的标签名,并在 DOM 树中查找与之 对应的 开始标签,如果找到了匹配的开始标签,就将其与其内部的子节点全部关闭。如果没有找到匹配的开始标签,则会发出解析错误并忽略此结束标签。
需要注意的是,有一些 HTML 元素是没有结束标签的,例如 <br>
、<img>
、<input>
等,这些元素属于自闭合标签,即在开始标签中就已经包含了结束标签,因此在解析这些元素时 不需要进入结束标签状态。
自闭和标签状态
在开始标签状态中,若解析器遇到了 />
符号,则表示该标签为自闭合标签。 此时解析器会立即进入自闭和标签状态,将该标签解析为一个完整的元素节点,并将该节点插入到当前活动元素的父节点中。
自闭合标签状态与开始标签状态的主要区别在于,自闭合标签状态中不需要解析属性,因为自闭合标签不需要包含任何属性。此外,在自闭合标签状态中,解析器不需要等待闭合标签,因为 自闭合标签已经被视为一个完整的标签。
文本状态
在HTML解析器中,文本状态是指解析器正在解析的内容为文本节点,即一段纯文本字符串。当解析器遇到文本内容时,它将进入文本状态,直到遇到一个标签开始符号 <
,或者遇到字符引用符号 &
时,解析器将离开文本状态,并开始解析相应的标签或字符引用。
字符引用状态
在 HTML 解析器中,字符引用状态是指解析器遇到 &
字符时进入的状态,该状态用于解析 HTML 中的字符实体引用和数值字符实体引用。
HTML 中的字符实体引用以 &
开始,以 ;
结束,用于表示一些特殊字符(通过使用 HTML 字符实体来将特殊字符进行转义,以避免它们与 HTML 代码产生歧义),例如 <
表示小于号 <
。
当HTML解析器进入字符引用状态后,会读取字符引用的内容,并将其转换为相应的字符添加到当前活动元素的子节点中,然后解析器将离开字符引用状态,返回到先前的状态继续解析HTML文档。
需要注意的是,在字符引用状态中,解析器需要进行一些容错处理,例如忽略不合法的字符实体引用或数值字符实体引用,或者在遇到 &
字符后没有后续字符时,将 &
字符视为文本节点的一部分而不是字符实体引用。
数字字符引用状态
数字字符引用状态是HTML解析器中的一种状态,用于解析HTML文档中的数字字符实体。在HTML中,数字字符实体以 &#
开头,以 ;
结尾,例如 A
表示字符 A
。
在数字字符引用状态中,解析器会读取 &#
后面的数字,直到读取到;
为止。然后,解析器会将读取到的数字转换为对应的字符,并将该字符添加到解析树中。如果在读取数字的过程中遇到了不合法的字符(比如非数字字符),则该实体被视为无效实体,解析器不会将其转换为字符。
常见的字符引用
字符编码 | 字符名称 | 字符 |
---|---|---|
" | " | " |
& | & | & |
' | ' | ' |
< | < | < |
> | > | > |
  | | 空格 |
¥ | ¥ | ¥ |
© | © | © |
® | ® | ® |
¯ | ¯ | ¯ |
° | ° | ° |
± | ± | ± |
× | × | × |
RCDATA 状态
RCDATA 元素
RCDATA
的全称是 Raw Character Data
,意为 原始字符数据
。RCDATA 元素是 HTML 中的一类元素,其与普通元素的不同之处在于,它们允许字符数据而不会被解析成 HTML 标记,因此 在 RCDATA 元素内可以包含除外围 RCDATA 元素的结束标记外的其他类型的 HTML 元素。
举个栗子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--
HTML 解析器会将 textarea 元素中的内容
均视为纯文本数据。
-->
<textarea cols="100" rows="13">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 在 RCDATA 状态下的 HTML 字符实体可以被正常解析 -->
<title> <两侧均是由字符实体转换而来的尖括号> </title>
</head>
<body>
</body>
</html>
</textarea>
</body>
</html>
执行效果
HTML 规范与 RCDATA 元素
元素 | 功能 |
---|---|
<textarea> | 用于输入多行文本的文本框,可以包含文本和标记。 |
<title> | 用于指定文档的标题。 |
<pre> | pre 元素用于指定预格式化文本,保留文本中的空格、换行和其他特殊字符,并将其原样显示在页面上。 |
RCDATA 状态
RCDATA 元素需要有 RCDATA 状态来解析其中的内容。在 RCDATA 状态下,HTML 解析器会将标记之间的内容解析为纯文本,这意味着 HTML 解析器在遇到 RCDATA 元素中的内容时,将进入 与文本状态类似(在文本状态时,HTML 解析器无法将标签解析为文本但能够解析字符实体)的状态。
RCDATA 元素的存在可以方便地在 HTML 中插入纯文本内容,同时避免了因包含 HTML 标记而导致的解析错误。
RAWTEXT 状态
在 HTML 解析器中,RAWTEXT
状态表示解析器在解析 script
和 style
标签中的文本内容。RAWTEXT
状态与 RCDATA
状态类似,但是在 RAWTEXT
状态中,HTML 解析器 不会解析实体引用和标签,而是将其视为纯文本内容。这意味着在 RAWTEXT 状态中,所有的字符都被视为文本,包括标签符号 <
和实体引用 &
。在 RAWTEXT 状态下,解析器会一直解析到遇到 </script>
或 </style>
标签,这时会回到之前的状态继续解析 HTML 文档。
PLAINTEXT 状态
PLAINTEXT 元素
在 HTML 中,PLAINTEXT 元素是一种 已经被废弃(自 HTML3.2 废弃) 的元素,它曾经用于指定文档的一部分应该以纯文本形式显示,而不应该被解释为 HTML 标记。<plaintext>
标签会将 该标签后的所有内容,包括其可选的结束标签( </plaintext>
)视为普通文本。
举个栗子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<plaintext></plaintext>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
执行效果
PLAINTEXT 状态
当 HTML 解析器解析到 plaintext
元素时,HTML 解析器将进入 PLAINTEXT 状态。PLAINTEXT 状态 通常 是由服务器发送一个特殊的 MIME 类型 (text/plain) 来触发的,而不是由 HTML 中的元素来触发。 在 PLAINTEXT 状态下,浏览器不会解释 任何 HTML 标记,也不会执行 任何 嵌入式脚本(如 JavaScript)。