首页 > 编程知识 正文

浏览器工作原理(四):HTML解析器 HTML Parser

时间:2023-05-05 04:16:14 阅读:179995 作者:762

HTML解析器的工作是将HTML标签解析为解析树。

HTML语法定义(The HTML grammar definition ) W3C组织规定了定义HTML词汇表和语法的规范。

正如上下文无关语法(Not a context free grammar )分析的概要所述,上下文无关语法的语法可以以BNF这样的形式定义。

遗憾的是,所有传统分析方法都不适用于html。 当然,不仅仅是因为开心,还建议用于分析css和js。 (html不能轻易用分析所需的上下文相关语法定义。

Html有正式的格式定义——文档类型定义(dtd ) ——,但不是上下文相关语法。 Html与xml很接近。 现在有很多可用的xml解析器,Html有xml的变体——xHtml。 不同的是html。总的来说,它是soft语法,xml死板,不固执。

很明显,这个小小的差异带来了很大的不同。 另一方面,这是html流行的原因——宽容使web开发人员的工作变得更加轻松,同时格式化语法也变得困难。 因此,html解析并不简单,无论是传统解析器还是xml解析器都无法解析。

HTML DTD

Html是以DTD格式定义的。 这种格式是用于定义SGML族的语言,包括所有允许的元素及其属性和层次关系的定义。 如上所述,html DTD不生成上下文相关语法。

DTD有几个变体。 标准模式符合规范,但其他模式包括浏览器对过去使用的选项卡的支持。 这是为了确保与以前的内容的兼容性。 最新标准DTD为http://www.w3.org/tr/html4/strict.dtd

DOM输出的树或分析树由DOM元素和属性节点组成。 DOM是文档对象模型的缩写,是html文档的对象表示,由js等作为html元素的外部接口调用。

树根是“文档”对象。

DOM和标签基本上是一对一的关系。 例如,以下标签:

htmlbodyphellodom/pdivimgsrc=" example.png "//div/body/html

将转换为下一个DOM树。

图8 :示例标签的DOM树

与html一样,DOM规范也由W3C组织制定。 访问http://www.w3.org/DOM/DOMTR是使用文档的一般规范。 模型描述特定的html元素,并在http://www.w3.org/tr/2003/rec-DOM-level-2-html-2003 01 09/IDL-definitions.htm中显示html定义

分析算法(The parsing algorithm )

如前一章所讨论的,hmtl无法用一般的自顶向下或自底向上的解析器进行解析。

理由如下。

1 .这种语言本身的容忍特性

2 .浏览器对一般的非法html具有容错功能

3 .分析过程是往返的。 源代码通常在分析过程中不会更改,但在html中,脚本标记中包含的“document.write”可能已被标记。 这表示在分析期间实际上已修改了输入。

不能使用正态分析技术。 浏览器定制了html专用的解析器。

该分析算法描述在Html5规范中,算法包括两个阶段的——编码和构造树。

符号化是词法分析的过程,将输入分析为符号。 html符号包括开始标记、结束标记、属性名称和属性值。

符号识别器识别符号后,将其传递给树生成器,读取下一个字符并识别下一个符号,直到处理完所有输入。

图9:HTML分析过程

符号识别算法(The tokenization algorithm ) ) )。

算法输出html符号。 该算法用状态机表示。 每次读取输入流中的一个或多个字符时,都会根据这些字符转换到下一个状态。 当前的符号状态和构建树状态会影响结果。 这意味着,读取同一字符可能会产生不同的结果,以进入下一个正确的状态,具体取决于当前状态。

这个算法很复杂。 在这里用简单的例子说明这个原理。

基本示例——编码下的html :

htmlbodyHello world/body/html的初始状态为“数据状态”,遇到“”字符时为“Tag open s”

tate”,读取一个a-z的字符将产生一个开始标签符号,状态相应变为“Tag name state”,一直保持这个状态直到读取到“>”,每个字符都附加到这个符号名上,例子中创建的是一个html符号。

  当读取到“>”,当前的符号就完成了,此时,状态回到“Data state”,“<body>”重复这一处理过程。到这里,html和body标签都识别出来了。现在,回到“Data state”,读取“Hello world”中的字符“H”将创建并识别出一个字符符号,这里会为“Hello world”中的每个字符生成一个字符符号。

  这样直到遇到“</body>”中的“<”。现在,又回到了“Tag open state”,读取下一个字符“/”将创建一个闭合标签符号,并且状态转移到“Tag name state”,还是保持这一状态,直到遇到“>”。然后,产生一个新的标签符号并回到“Data state”。后面的“</html>”将和“</body>”一样处理。

图10:符号化示例输入

树的构建算法(Tree construction algorithm)

 在树的构建阶段,将修改以Document为根的DOM树,将元素附加到树上。每个由符号识别器识别生成的节点将会被树构造器进行处理,规范中定义了每个符号相对应的Dom元素,对应的Dom元素将会被创建。这些元素除了会被添加到Dom树上,还将被添加到开放元素堆栈中。这个堆栈用来纠正嵌套的未匹配和未闭合标签,这个算法也是用状态机来描述,所有的状态采用插入模式。

  来看一下示例中树的创建过程:

<html> <body> <span style="white-space:pre"></span>Hello world </body></html>

构建树这一阶段的输入是符号识别阶段生成的符号序列。

  首先是“initial mode”,接收到html符号后将转换为“before html”模式,在这个模式中对这个符号进行再处理。此时,创建了一个HTMLHtmlElement元素,并将其附加到根Document对象上。

  状态此时变为“before head”,接收到body符号时,即使这里没有head符号,也将自动创建一个HTMLHeadElement元素并附加到树上。

  现在,转到“in head”模式,然后是“after head”。到这里,body符号会被再次处理,将创建一个HTMLBodyElement并插入到树中,同时,转移到“in body”模式。

  然后,接收到字符串“Hello world”的字符符号,第一个字符将导致创建并插入一个text节点,其他字符将附加到该节点。

  接收到body结束符号时,转移到“after body”模式,接着接收到html结束符号,这个符号意味着转移到了“after after body”模式,当接收到文件结束符时,整个解析过程结束。


图11:示例html树的构建过程

解析结束时的处理(Action when the parsing is finished)  在这个阶段,浏览器将文档标记为可交互的,并开始解析处于延时模式中的脚本——这些脚本在文档解析后执行。

 文档状态将被设置为完成,同时触发一个load事件。

 Html5规范中有符号化及构建树的完整算法(http://www.w3.org/TR/html5/syntax.html#html-parser)。

浏览器容错(Browsers error tolerance)

你从来不会在一个html页面上看到“无效语法”这样的错误,浏览器修复了无效内容并继续工作。

以下面这段html为例:

<html><mytag></mytag><div><p></div> Really lousy HTML</p></html>这段html违反了很多规则(mytag不是合法的标签,p及div错误的嵌套等等),但是浏览器仍然可以没有任何怨言的继续显示,它在解析的过程中修复了html出现的错误。

浏览器都具有错误处理的能力,但是,另人惊讶的是,这并不是html最新规范的内容,就像书签及前进后退按钮一样,它只是浏览器长期发展的结果。一些比较知名的非法html结构,在许多站点中出现过,浏览器都试着以一种和其他浏览器一致的方式去修复。

  Html5规范定义了这方面的需求,webkit在html解析类开始部分的注释中做了很好的总结。

  解析器将符号化的输入解析为文档并创建文档,但不幸的是,我们必须处理很多没有很好格式化的html文档,至少要小心下面几种错误情况。

  1. 在未闭合的标签中添加明确禁止的元素。这种情况下,应该先将前一标签闭合

  2. 不能直接添加元素。有些人在写文档的时候会忘了中间一些标签(或者中间标签是可选的),比如HTML HEAD BODY TR TD LI等

  3. 想在一个行内元素中添加块状元素。关闭所有的行内元素,直到下一个更高的块状元素

  4. 如果这些都不行,就闭合当前标签直到可以添加该元素。

  下面来看一些webkit容错的例子:

  </br>替代<br>

  一些网站使用</br>替代<br>,为了兼容IE和Firefox,webkit将其看作<br>。

  代码:

if (t->isCloseTag(brTag) && m_document->inCompatMode()) {reportError(MalformedBRError);t->beginTag = true;}

  Note -这里的错误处理在内部进行,用户看不到。

  迷路的表格

  这指一个表格嵌套在另一个表格中,但不在它的某个单元格内。

  比如下面这个例子:

<table><table><tr><td>inner table</td></tr></table><tr><td>outer table</td></tr></table>webkit将会将嵌套的表格变为两个兄弟表格:
<table><tr><td>outer table</td></tr></table><table><tr><td>inner table</td></tr></table>
代码:
if (m_inStrayTableContent && localName == tableTag)popBlock(tableTag);

webkit使用堆栈存放当前的元素内容,它将从外部表格的堆栈中弹出内部的表格,则它们变为了兄弟表格。

  嵌套的表单元素

  用户将一个表单嵌套到另一个表单中,则第二个表单将被忽略。

  代码:

if (!m_currentFormElement) {m_currentFormElement = new HTMLFormElement(formTag,m_document);}

太深的标签继承

  www.liceo.edu.mx是一个由嵌套层次的站点的例子,最多只允许20个相同类型的标签嵌套,多出来的将被忽略。

  代码:

bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName){unsigned i = 0;for (HTMLStackElem* curr = m_blockStack;i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;curr = curr->next, i++) { }return i != cMaxRedundantTagDepth;}

放错了地方的html、body闭合标签

  又一次不言自明。

  支持不完整的html。我们从来不闭合body,因为一些愚蠢的网页总是在还未真正结束时就闭合它。我们依赖调用end方法去执行关闭的处理。

  代码:

if (t->tagName == htmlTag || t->tagName == bodyTag )return;

  所以,web开发者要小心了,除非你想成为webkit容错代码的范例,否则还是写格式良好的html吧。


------------------------------------下一节,CSS解析(CSS parsing)------------------------------------










<html><body>Hello world</body></html>

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。