«

»

浏览器的HTML解析器(一)

浏览器 HTML 解析浏览器组件中的HTML解析器的作用是把HTML标签解析成一棵分析树。HTML是一种语言,但这种语言跟常见的编程语言又有所不同。这种语言专注于因特网内容的展现。HTML语言的词汇和语法在W3C组织创建的规范(http://taligarsiel.com/Projects/howbrowserswork1.htm#w3c)中定义。当前版本是HTML4,正在制订HTML5的标准,届时HTML将拥有更强大的功能。

非上下文无关语法

我们在分析介绍中已经看到,语言的语法可以使用BNF之类进行规范的定义。

不幸的是所有常规的解析器类别都不适用于HTML(我并没有为了好玩把它们列出来—他们将在解析CSS和JavaScript时用到)。

HTML没法很容易的用解析器需要的上下文无关语法来定义。

有一种规范的格式来定义HTML—DTD(文档类型定义)—但它并不是上下文无关语法。

这在早期的网站上显的有些奇怪—HTML跟XML非常相似。有大批可用的XML解析器。还有种HTML的XML变种—XHTML—所以它们之间有何大的不同的呢?

区别在于HTML非常“宽容”,它允许你忽略某种隐含加入的标签,有时候忽略开始和结束标签等等。整体来说,相对于XML的严苛语法而言,这是种“软”语法。

显然看起来象是一个小小的区别创造了个不同的世界。一方面这是HTML为何如此流行的主要原因—它宽容对待你的错误,使网站编程者日子过的更轻松。另一方面,这也使得写出规范语法定义变的困难。现在总结一下—HTML不能方便的解析,不能被常规语法分析器处理因为它的语法不是上下文无关语法,也不能被XML解析器解析。

HTML的DTD

HTML的定义是DTD格式。这种格式通常用来定义SGML语言家族。这种格式包含所有允许使用元素的定义,它们的属性和层次关系。象我们之前看到的,HTML的DTD不构成上下文无关语法。

DTD也有一些变化。严格模式只支持纯粹的规范但其它模式包含对早期浏览器使用的一些标记的支持。目的是为了向后兼容于早期内容。当前的严格模式DTD在这里:http://www.w3.org/TR/html4/strict.dtd

DOM

输出树—分析树是一棵DOM元素和属性结点构成的树。DOM是文档对象模型的缩写。它是HTML文档的对象表示,也是HTML元素对外部世界比如JavaScript的接口。

树的根是“Document”对象。

DOM和标签几乎有一对一的关系。例如,这个标签:

将会转换成以下DOM树:

浏览器的HTML解析器

图8:示例标记的DOM树

象HTML一样,DOM有W3C组织的规范。查看:http://www.w3.org/DOM/DOMTR。它是个操作文档的通用规范,一个描述HTML具体元素的特殊模块。HTML的定义能在:http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html找到。

当我提及包含DOM结点的树时,我是指由实现了DOM接口之一的元素们构成的树。浏览器采用的是包含其它一些浏览器内部使用的属性的实际实现。

解析算法

象我们在先前章节看到的,HTML不能被常规的自顶而下或自底而上解析器解析。

原因是:

1 这种语言天生的宽容度。

2 现实里浏览器对无效HTML广为流传的一些错误用法传统上具有的容错度。

3 解析过程是可重入的。通常源码在解析过程中不会变化,但是在HTML里,包含“document.write”的脚本标签能够增加另外的语法元素,因此这解析过程实际上修改了输入。

既然不能使用常规解析技术,浏览器创造了解析HTML的自已的解析器。

解析算法在HTML5规范里有详细描述。算法由两步组成—标记化和构造树。

标记化是词法分析,把输入分拆成词汇。在HTML中的词汇是开始标签,结束标签,属性名和属性值。

分词器识别词汇,把它传送给树构造器,再取得下一个字符用于识别出下一个词汇,如此继续直到输入结束。

浏览器的HTML解析器

图6:HTML解析流程(来自HTML5规范)

标记化算法

算法的输出是HTML标记。这种算法表达成一个状态机。每个状态消耗输入流的一个或多个字符,根据这些字符更新下一个状态。判定受当前标记化状态和树构造状态的影响。这意味着同一个被消耗掉的字符依据当前状态不同会产生不同的结果,以得到正确的下一步状态。算法过于复杂难以完整叙述,因此我们用一个简单的例子来帮助理解这个概念。

基本例子—对下面的HTML分词:

初始状态是“数据状态”。当遇到“<”字符时,状态切换成“标签开放状态”。消耗一个“a-z”字符导致“开始标签记号”,状态被改为“标签命名状态”。我们保持在这个状态,直到“>”字符被读到。每个字符被添加到新的标记名上。在我们的场景中创建的标记是一个“html”标记。

当到达“>”标签时,当前标记被发送出去,状态变回了“数据状态”。“<body>”标签将会被同样流程处理。截止目前我们发送出了“html”和“body”标签。我们现在回到了“数据状态”。消耗“H”字符会导致创建和发送一个字符标记,这会继续到到达“</body>”的“<”为止。我们会为“Hello world”的每个字符发送一个字符标记。

现在我们回到了“标签开放状态”。消耗下个输入的“/”将导致创建个“结束标签记号”并迁移到“标签命名状态”。我们再次保持在这个状态直到到达了“>”。然后新的标签记号会被发送, 我们返回“数据状态”。“</html>”输入会象先前情形一样被处理。

浏览器的HTML解析器

图9:标记化例子输入。

树的构造算法

当分析器创建时,文档对象也被创建了。在树的构造阶段,DOM树及它的根文档会被修改,一些元素会被加到里面。分词器发送的每个节点会被树构造器处理。对于每个标记,规范定义了哪个DOM元素与之相关,并且据此创建DOM元素。除了增加元素到DOM之外,它也增加了一个开放元素的栈。这个栈用于纠正嵌套不匹配和未关闭的标签。这个算法也用状态机描述。状态被称为“插入模式”。

我们来看一下例子输入的构造树的过程:

给树构造阶段的输入是来自分词阶段的标记序列。第一个模式是“初始化模式”。收到HTML标记后会转换到“前html”模式,重新处理此种模式下的标记。这会导致创建HTMLHtmlElement元素,并把它添加到根文档对象。

状态会切换到“前head”模式。我们收到“body”标记。尽管我们没有一个“head”标记,一个HTMLHeadElement将会被隐含创建并添加到树上。

我们现在转换到“内head”模式,然后是“后head”模式。body标记被重新处理,一个HTMLBodyElement元素被创建并插入,然后模式转换到“内body”模式。

“Hello world”字符串的字符标记现在收到了。第一个会引起创建并插入一个“Text”节点,其它字符会添加到这个节点。

收到body结束标记会引发到“后body”模式的转换。我们会接收html结束标签,这会转换模式成“后后body”模式。接收文件结束标记会结束这次解析。

浏览器的HTML解析器

图10:示例html的树构造

(待续)

1 星2 星3 星4 星5 星 (尚无评分)
Loading...Loading...

相关文章

浏览器中元素的定位类型 网页中元素的定位用由position属性确定,分为绝对定位,相对定位。除此之外还有浮动定位。position的取...
浏览器中CSS的盒模型 首先是CSS中画布的概念,根据CSS2规范,名词“画布”描述为“格式化的结构渲染所用的空间”---也就是浏览器...
浏览器的绘制阶段 在浏览器的绘制阶段,会遍历渲染者树,并调用渲染者的“paint”方法在屏幕上显示内容。绘制需要用到UI基础...
浏览器的布局 下来就是浏览器的布局过程。当渲染者被创建并且增加到树上时,它并没有位置和尺寸。计算这些值被称为布...
浏览器样式表的规则匹配和级联顺序 浏览器中CSS样式的匹配并非总是那么简单,某些情况下可能需要对规则加以处理以使匹配更容易。下面是几个...
浏览器中使用规则树来计算样式 当计算某个元素的样式内容时,我们首先计算规则树里的一条路径或是使用一条已经存在的路径。接下来就开...
浏览器的样式计算 建立渲染树时需要计算每个渲染对象的可视化属性。这是通过计算每个元素的样式属性来实现的。样式包含HTM...
浏览器中渲染树的构造 当DOM树被构造的同时,浏览器构造了另外一棵树,渲染树。这是棵按其显示顺序排列的可视化元素构成的树。...

发表评论

电子邮件地址不会被公开。