什么是语法分析
语法分析:分析单词序列是否符合语法规则,并构建出由输入单词组成的数据结构(语法树)。
浏览器请求回来的HTML文档字符流, 经过词法分析得到了token序列。语法分析会对token序列进行语法检查,并构建成浏览器可以理解的数据结构-DOM树。
HTML的DOM结构
根据DOM标准(程序访问HTML文档的标准),HTML文档是由节点构成的,节点之间拥有层级关系并构成了一颗节点树。
节点可分为元素节点和文本节点两大类。
<html>
<head>
<title>文档标题</title>
</head>
<body>
<a href="www.baidu.com">我的链接</a>
<h1>我的标题</h1>
</body>
</html>
上面HTML文档对应的DOM树如下:
从上图可以看到,文本节点是当前元素节点的子节点。
为什么需要语法分析
程序 = 数据结构 + 算法。
HTML文档字符流经过词法解析和语法分析,构建成一颗DOM树。这样浏览器才能访问和操作HTML文档。
怎么实现语法分析
栈结构
HTML词法解析是按顺序解析字符流的,第一个开始标签对应的结束标签应该是最后出现的。为了匹配开始标签和结束标签,DOM树的构建可以使用栈结构来处理。
第一步,我们先定义元素节点和文本节点两者的数据结构。
// 元素节点
function Element(){
this.type = 'Element';
this.open; // 开始标签, 比如 <div id="1">
this.name; // 标签名称, 全部转换为小写
this.close; // 结束标签, 比如 </div>
this.attributes = []; // 标签属性
this.childNodes = []; // 子节点
}
// 文本节点
function Text(value){
this.type = 'Text';
this.value = value;
}
第二步,定义一个parse()函数来接收HTML词法解析得到的token列表,并用一个栈来存储处理过程中的节点。
function HTMLSyntaticalParser(){
var stack = [new HTMLDocument];
this.parse = function(token) {
// 1.栈顶就是当前节点
let curNode = stack[stack.length-1];
let node = buildElement(token);
switch(token.type){
case 'StartTag':
// 2.遇到开始标签就入栈,当前节点就是这个节点的父节点;
curNode.childNodes.push(node);
stack.push(node);
break;
case 'EndTag':
// 3.遇到结束标签就出栈;
stack.pop();
break;
case 'Text':
// 4.遇到文本节点,如果当前节点是文本节点,则跟文本节点合并,
// 否则入栈成为当前节点的子节点;
if('Element' == curNode.type){
curNode.childNodes.push(node);
stack.push(node);
} else if('Text' == curNode.type){
curNode.value += node.value;
}
}
}
this.getOutput = function(){
// 5.构建过程中,遇到开始标签就入栈,遇到结束标签就出栈,最终栈顶就是根节点。
return stack[0];
}
}