1. 概览
HTMLParser 在很多地方都有它施展拳脚的地方, 例如在 Vue 中, Vue 把 template 模板字符串编译成 render 函数的过程就用到了 HTMLParser.
要注意的一点是, HTMLParser 做的工作是解析字符串并把参数传递给回调函数, 后续要执行什么操作全部依赖于传入的options中的start end comment chars等钩子函数
1.1 一般的简单 parser 逻辑
初始时若发现第一个 tagName 是 script 或 style, 则先做特殊处理. 原则上这种情况不应该出现
while(html) 循环判断当前处理的字符串是否为空
若当前字符串以< !--开头, 则进入注释处理逻辑, 调用 comment 钩子
若当前字符以< /开头, 则进入endTag处理逻辑, 进入内部的 parseEndTag 函数
检查 stack && 关闭那些特殊闭合方式的 tagName , 例如 和
若 tagName 不是自闭和节点则入栈
把 attrs 即id="app"的字符串replace 为{ name:'id', value:'app', escaped }的数据
调用 start(tagName, attrs, unary) 钩子
若当前字符以
parseStartTag 实际上做的工作就是: 找到合适的 tagName 出栈, 调用 end 钩子
其它情况则调用 chars 钩子
收尾工作再次调用 parseEndTag() 来检查标签是否闭合
1.2 Vue 额外增加的内容
大量的正确性检查&&验证
根节点只能有一个
根节点不能是等
tagName 要闭合(通过 stack 来判断)
对v-for v-if v-pre v-once等特殊属性的处理(增加到构建的 AST 的属性中)
对{ {text}}这类模板解析字符串的处理
2. 一个160行的 HTMLParser
该项目地址在Github blowsie, 约200个Star , 代码中正则+HTMLParser 函数体约160行, 只看函数体的话约130行
2.1 所用到的正则表达式
//匹配 tag 的开头(包含 attrs), 例如 '
var startTag = /^\s]+))?)*)\s*(\/?)>/
//匹配 tag 的结尾, 例如'
var endTag = /^]*>/
//匹配 attr 内容, 可以有等号, 等号后面可以是双引号或单引号, 例如 :class="c" :msg='msg'
//ps:该简单 parser 不支持es6的``字符串, 而 vue 的正则是支持的
var attr = /([a-zA-Z_:@][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
//HTML5中的可以不写 endTag 的那些元素的集合, 例如
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr");
//普通块状 HTML5标签 tagName 的集合, 例如
var block = makeMap("a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video");
// Inline Elements - HTML 5
var inline = makeMap("ab