html - 如何构建 HTML 解析器?

标签 html parsing structure

在您开始链接到 RegEx match open tags except XHTML self-contained tags 之前阅读整个问题。

我想写一个 HTML 解析器(只针对 HTML 5,它应该检查它是否是 HTML 5,如果不是,返回一个错误)只是为了让自己学习一些新东西,但我不知道什么是最好的方法。让我举个例子:

<!doctype html>
<html>
<head>
    <!-- #TITLE -->
    <title>Just an example</title>
</head>
<body>
    <p class='main'>Simple paragraph with an <a href='/a.html'>anchor</a></p>
</body>
</html>

现在,谁能告诉我如何解析它(最终形式无关紧要,只是一个概念)?我有一些想法(比如使用递归函数,或者引用包含实际标签的数组),但我不认为这些是最好的概念。 我应该逐个检查一个字符然后调用特定的函数还是使用正则表达式(下面解释)?

我并不是说使用正则表达式对整个标记使用一种模式。我的意思是对标记名使用一种模式(如果这个返回真,检查下一个模式),然后对于属性 (如果这个返回真,再次检查),最后检查标签的结尾。

找到tag怎么办?运行一个检查标签的循环(如果它找到标签,一次又一次地调用它......)?但对我来说,当函数 X 调用 Y 而 Y 又调用 X 时,它似乎是递归函数或至少是半递归的......

所以最后一个问题是:最有效和正确的结构是什么?

最佳答案

@Kian 的回答提到使用词法分析器,但就算法而言,我认为您会想要使用递归。 HTML 毕竟是一个递归结构:

<div>
    <div>
        <div>
        </div>
    </div>
</div>

这是一个简单的 JS 示例 - 虽然它不是一个完整的实现。 (我没有包含对 <empty /> 元素的支持;对于 <!-- comments --> ;对于 &entities; ;对于 xmlns:namespaces ... 编写一个完整的 HTML 或 XML 解析器是一项艰巨的任务,所以不要掉以轻心)

这个解决方案明显跳过了词法分析的过程,但我故意省略了它以将我的答案与@Kian 的答案进行对比。

var markup = "<!DOCTYPE html>\n"+
             "<html>\n"+
             " <head>\n"+
             "   <title>Example Input Markup</title>\n"+
             " </head>\n"+
             " <body>\n"+
             "   <p id=\"msg\">\n"+
             "     Hello World!\n"+
             "   </p>\n"+
             " </body>\n"+
             "</html>";

parseHtmlDocument(markup);

// Function definitions

function parseHtmlDocument(markup) {
    console.log("BEGIN DOCUMENT");
    markup = parseDoctypeDeclaration(markup);
    markup = parseElement(markup);
    console.log("END DOCUMENT");
}

function parseDoctypeDeclaration(markup) {
    var regEx = /^(\<!DOCTYPE .*\>\s*)/i;
    console.log("DOCTYPE DECLARATION");
    var matches = regEx.exec(markup);
    var doctypeDeclaration = matches[1];
    markup = markup.substring(doctypeDeclaration.length);
    return markup;
}

function parseElement(markup) {
    var regEx = /^\<(\w*)/i;
    var matches = regEx.exec(markup);
    var tagName = matches[1];
    console.log("BEGIN ELEMENT: "+tagName);
    markup = markup.substring(matches[0].length);
    markup = parseAttributeList(markup);
    regEx = /^\>/i;
    matches = regEx.exec(markup);
    markup = markup.substring(matches[0].length);
    markup = parseNodeList(markup);
    regEx = new RegExp("^\<\/"+tagName+"\>");
    matches = regEx.exec(markup);
    markup = markup.substring(matches[0].length);
    console.log("END ELEMENT: "+tagName);
    return markup;
}

function parseAttributeList(markup) {
    var regEx = /^\s+(\w+)\=\"([^\"]*)\"/i;
    var matches;
    while(matches = regEx.exec(markup)) {
        var attrName = matches[1];
        var attrValue = matches[2];
        console.log("ATTRIBUTE: "+attrName);
        markup = markup.substring(matches[0].length);
    }
    return markup;
}

function parseNodeList(markup) {
    while(markup) {
        markup = parseTextNode(markup);
        var regEx = /^\<(.)/i;
        var matches = regEx.exec(markup);
        if(matches[1] !== '/') {

            markup = parseElement(markup);
        }
        else {
            return markup;
        }
    }
}

function parseTextNode(markup) {
    var regEx = /([^\<]*)\</i;
    var matches = regEx.exec(markup);
    markup = markup.substring(matches[1].length);
    return markup;
}

理想情况下,这些函数中的每一个都将非常紧密地映射到 XML specification 中定义的语法。 .例如,规范定义了一个 element像这样:

element    ::=    EmptyElemTag | STag content ETag

...所以理想情况下我们想要 parseElement()函数看起来更像这样:

function parseElement(markup) {
    if(nextTokenIsEmptyElemTag) { // this kind of logic is where a lexer will help!
        parseEmptyElemTag(markup);
    }
    else {
        parseSTag(markup);
        parseContent(markup);
        parseETag(markup);
    }
}

...但是我在编写我的示例时偷工减料,因此它没有尽可能准确地反射(reflect)实际语法。

关于html - 如何构建 HTML 解析器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17999309/

相关文章:

html - 在移动页面中将按钮放置在正方形中

javascript - 为什么 "\n"在 Javascript 中是一个数字 "NaN"

c - 从队列打印的程序改为打印一个数字

c - 在其他结构中使用结构。 C

javascript - 段落中的长字符串,不打断完整字符

html - Orbeon Forms 中的只读字段行为

我可以用 popen() 解析 ngrep 的输出吗?

javascript - 在 javascript 中更新 JSON.parsed 对象

php - 我应该声明并检查 PHP 中是否存在变量吗?

css - 在 html 5 中,如何将 div 元素定位在 canvas 元素之上?