在开始链接到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>
现在,有人能告诉我如何解析这个(最终形式无所谓,只是一个概念)?我有一些想法(比如使用递归函数,或者引用包含实际标记的数组),但我不认为这些是最好的概念。 我应该通过char检查char,然后调用特定的函数或使用正则表达式(如下所述)?
通过使用正则表达式,我并不是指整个标记的一种模式。我的意思是使用一种模式作为标记名(如果这个模式返回true,请检查下一个模式),然后是属性(如果这个返回true,再次检查),最后检查标签的结束。
找到标签后该怎么办?运行一个循环来检查标签(如果它找到标签,再次调用它......)?但对我来说,当函数X调用Y调用X时,它似乎是递归函数或至少半递归...
所以最后一个问题是:最有效和最正确的结构是什么?
答案 0 :(得分:4)
编写基于SGML的解析器的最大部分是词法分析器。这是一篇关于构建自定义词法分析器的文章:http://onoffswitch.net/building-a-custom-lexer/。
在我看来,正则表达式可能过度/不合适 - 你想要匹配HTML标记,逐字符解析可能是最好的方法。
答案 1 :(得分:4)
@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);
}
}
...但我在编写我的例子时已经削减了一些角落,所以它并没有尽可能地反映实际的语法。