在浏览器的背后(二) —— HTML语言的语法解析

当你看到这篇文章意味着我辜负了@教主的殷切期望周末木有去约会,以及苏老师@我思故我在北京鼓楼落井下石成功了……

本文demo powered by 已经结婚的@老赵的不再维护的wind.js

物是人非啊……

 

说回正经事,在上一篇文章中,我们取得了初步成果,毫无意义的字符变成了有意义的token。

接下来我们要把这些简单的词变成DOM树,这个过程我们是使用栈来实现的,任何语言几乎都有栈,为了给大家跑着玩我们还是用JS来实现吧,JS中的栈只要用数组就好了:

function HTMLSyntaticalParser(){
    var stack = [new HTMLDocument];
    this.receiveInput = function(token) {
        //TODO
    }
    this.getOutput = function(){
        return stack[0];
    }
}

为了构建DOM树,我们需要一个Node类,接下来我们所有的节点都会是这个Node类的实例。在完全符合标准的浏览器中,不一样的HTML节点对应了不同的Node的子类,我们为了简化,就不完整实现这个继承体系了。我们仅仅把Node分为Element和Text(如果是基于类的OOP的话,我们需要抽象工厂来创建对象。)

function Element(){
    this.childNodes = [];
}
function Text(value){
this.value = value || "";
}

前面我们的token中,以下两个是需要成对匹配的:

  • tag start
  • tag end

于是我们的做法是遇到tag start就入栈,遇到tag end就出栈,并且校验一下是否匹配。

对于Text节点,我们则需要把相邻的Text节点合并起来,我们的做法是当字符token入栈时检查栈顶是否是Text节点,如果是的话就合并Text节点

同样我们来看看直观的解析过程:

 
 

当我们的源代码完全遵循xhtml时,这非常简单问题,然而HTML具有很强的容错能力,奥妙在于当tag end跟栈顶的start tag不匹配的时候如何处理。

于是有一个极其复杂的规则来的,幸好w3c又一次很贴心地把全部规则都整理的很好,我们只要翻译成对应的伪代码就好了:

http://www.w3.org/html/wg/drafts/html/master/syntax.html#tree-construction

略微干净的代码可以在这个gist找到:

https://gist.github.com/wintercn/5618683#file-htmlsyntaticalparser-js

posted @ 2013-06-18 12:16  winter-cn  阅读(7046)  评论(10编辑  收藏  举报