什么是词法分析

词法分析:根据词法规则,识别出字符序列 中的单词 ,并输出单词序列。
HTML词法分析就是根据HTML词法规则,识别出HTML文档字符流中的token,并输出token序列。

HTML的token有哪些

为了简化分析,本文使用最简单的情况进行描述:一个HTML元素由开始标签、元素内容和结束标签三部分组成。

<p>
    hello world
</p>

例如上文中,包含的token有:

  • "<p>"表示:p元素的开始标签;
  • "hello world"表示:p元素的内容;
  • "</p>"表示:p元素的结束标签;

为什么需要词法解析

html5lib解析器的安装方法 html怎么解析的_词法解析

HTML文档字符流经过词法分析得到的token序列,为后续的语法分析(DOM树构建)提供了原材料。

怎么实现词法解析

<p>hello world</p>

从左往右扫描字符序列,根据HTML词法规则,对以上p元素进行分析:

  1. 读到"<“字符,表示开始标签或结束标签的开始。读到"p"字符,表示开始标签p,读到”>"字符表示标签结束。这样就得到token 开始标签p
  2. ">“字符后面读到字符"h"表示 元素内容,直到遇到”<"字符,元素内容结束。这样就得到token 元素内容hello world
  3. 读到"<“字符,表示开始标签或结束标签的开始。读到”/“字符,表示结束标签的开始。读到"p"字符,表示结束标签p,读到”>"字符表示标签结束。这样就得到token 结束标签p

总结:按顺序扫描字符序列,然后根据HTML词法规则对输入每个字符进行判断,并且每次判断都跟当前的状态有关,最终识别出每个token并输出相应token的描述对象。

有限状态机

有限状态机通常由多个状态组成,每个状态会对输入事件进行判断并执行对应动作,然后转移到下一个状态

根据上面的概念,我们可以把词法分析的过程用有限状态机来实现:

把每个HTML token的特征看成是一个状态,每个状态会对输入的字符进行判断并输出token,最后根据词法规则转移到下一个状态。

html5lib解析器的安装方法 html怎么解析的_html5lib解析器的安装方法_02

<p>hello world</p>

用有限状态机模式解析上面的p元素:

  1. 初始状态为“data state”,当遇到“<”字符,状态变为“tag open state”。
  2. “tag open state”状态,当遇到a-z字符(这里为p)将创建开始开始标签token,状态变为“tag name state”。
  3. “tag name state”状态,当遇到“>”,则输出了一个token。重新回到“data state”状态。
  4. “data state”状态,当遇到a-z字符,则输出 元素内容token,直到遇到“<”(这里会创建字符hello world),状态变为“tag open state”。
  5. “tag open state”状态,当遇到“/”字符将创建结束标签token,状态变为“tag name state”。执行上面第三步骤,创建</p>标签。

代码实现

将每个状态封装成状态函数,在函数中对输入字符进行判断,然后返回下一个状态函数,并通过emitToken 函数输出解析好的 token。

// 初始data状态
var data = function(c){
    if(c=="<") {
        return tagOpen;
    }
    else if(c==EOF) {
        emitToken(EOF);
        return data;
    }
    else {
        emitToken(new TextToken(c));
        return data;
    }
};
// 标签开始状态
var tagOpen = function tagOpenState(c){
    if(c=="/") {
        return endTagOpen;
    }
    if(c.match(/[a-z]/)) {
        // 创建开始标签符号
        token = new StartTagToken();
        token.name = c;
        return tagName;
    }
};
// 标签符号状态
var tagName = function tagNameState(c){
    if(c==">") {
        // 输出一个token
        emitToken(token);
        return data;
    }else if(c.match(/[a-z]/)) {
        token.name += c;
        return tagName;
    }
};
// 结束标签状态
var endTagOpen = function endTagOpenState(c){
    if(c.match(/[a-z]/)) {
        // 创建结束标签符号
        token = new EndTagToken();
        token.name = c;
        return tagName;
    }
};


var state = data;
var char = "";
while(char = getInput())
    state = state(char);  // 通过状态转移函数,连接各个状态并输出对应的token