• home > webfront > SGML > html >

    浏览器HTML解析里的容错机制

    Author:zhoulujun@live.cn Date:

    您在浏览 HTML 网页时从来不会看到“语法无效”的错误。这是因为浏览器会纠正任何无效内容,然后继续工作。

    您在浏览 HTML 网页时从来不会看到“语法无效”的错误。这是因为浏览器会纠正任何无效内容,然后继续工作。

    以下面的 HTML 代码为例:

    1 <html>
    2   <mytag>
    3   </mytag>
    4   <div>
    5   <p>
    6   </div>
    7     Really lousy HTML
    8   </p>
    9 </html>

    在这里,我已经违反了很多语法规则(“mytag”不是标准的标记,“p”和“div”元素之间的嵌套有误等等),但是浏览器仍然会正确地显示这些内容,并且毫无怨言。因为有大量的解析器代码会纠正 HTML 网页作者的错误。

    不同浏览器的错误处理机制相当一致,但令人称奇的是,这种机制并不是 HTML 当前规范的一部分。和书签管理以及前进/后退按钮一样,它也是浏览器在多年发展中的产物。很多网站都普遍存在着一些已知的无效 HTML 结构,每一种浏览器都会尝试通过和其他浏览器一样的方式来修复这些无效结构。

    HTML5 规范定义了一部分这样的要求。Webkit 在 HTML 解析器类的开头注释中对此做了很好的概括。

    解析器对标记化输入内容进行解析,以构建文档树。如果文档的格式正确,就直接进行解析。

    遗憾的是,我们不得不处理很多格式错误的 HTML 文档,所以解析器必须具备一定的容错性。

    我们至少要能够处理以下错误情况:

    1. 明显不能在某些外部标记中添加的元素。在此情况下,我们应该关闭所有标记,直到出现禁止添加的元素,然后再加入该元素。
    2. 我们不能直接添加的元素。这很可能是网页作者忘记添加了其中的一些标记(或者其中的标记是可选的)。这些标签可能包括:HTML HEAD BODY TBODY TR TD LI(还有遗漏的吗?)。
    3. 向 inline 元素内添加 block 元素。关闭所有 inline 元素,直到出现下一个较高级的 block 元素。
    4. 如果这样仍然无效,可关闭所有元素,直到可以添加元素为止,或者忽略该标记。

    让我们看一些 Webkit 容错的示例:

    使用了 </br> 而不是 <br>

    有些网站使用了 </br> 而不是 <br>。为了与 IE 和 Firefox 兼容,Webkit 将其与 <br> 做同样的处理。代码如下:

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

    请注意,错误处理是在内部进行的,用户并不会看到这个过程。

    离散表格

    离散表格是指位于其他表格内容中,但又不在任何一个单元格内的表格。 比如以下的示例:

    1 <table>
    2     <table>
    3         <tr><td>inner table</td></tr>
    4     </table>
    5     <tr><td>outer table</td></tr>
    6 </table>

    Webkit 会将其层次结构更改为两个同级表格:

    1 <table>
    2     <tr><td>outer table</td></tr>
    3 </table>
    4 <table>
    5     <tr><td>inner table</td></tr>
    6 </table>

    代码如下:

    1 if (m_inStrayTableContent && localName == tableTag)
    2         popBlock(tableTag);

    Webkit 使用一个堆栈来保存当前的元素内容,它会从外部表格的堆栈中弹出内部表格。现在,这两个表格就变成了同级关系。

    嵌套的表单元素

    如果用户在一个表单元素中又放入了另一个表单,那么第二个表单将被忽略。 代码如下:

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

    过于复杂的标记层次结构

    代码的注释已经说得很清楚了。

    比如一个网站嵌套了约 1500 个标记,全都来自一堆 <b> 标记。我们只允许最多 20 层同类型标记的嵌套,如果再嵌套更多,就会全部忽略。

    1 bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)
    2 {
    3  
    4 unsigned i = 0;
    5 for (HTMLStackElem* curr = m_blockStack;
    6          i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
    7      curr = curr->next, i++) { }
    8 return i != cMaxRedundantTagDepth;
    9 }

    放错位置的 html 或者 body 结束标记

    同样,代码的注释已经说得很清楚了。

    支持格式非常糟糕的 HTML 代码。我们从不关闭 body 标记,因为一些愚蠢的网页会在实际文档结束之前就关闭。我们通过调用 end() 来执行关闭操作。

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

    所以网页作者需要注意,除非您想作为反面教材出现在 Webkit 容错代码段的示例中,否则还请编写格式正确的 HTML 代码。



    转载本站文章《浏览器HTML解析里的容错机制》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/htmlBase/2015_1213_365.html