页面元素转换成ast语法树
2021-08-18 本文已影响0人
JX灬君
html元素转换成ast语法树
=>html元素:
<div id='app' style="color:red">hello {{msg}}</div>
=>转换成ast语法树:
{"tag":"div","type":1,"children":[{"type":3,"text":"hello{{msg}}"}],"attrs":[{"name":"id","value":"app"},{"name":"style","value":"color:red"}],"parent":null}
/**ast语法树格式
* {
* tag:'div',
* attrs:[{id:'app'},style:{style:'color: "red"'}]
* children:[{tag:null,text:'hello'},{tag:'h1',text:'world'}]
* }
*/
源码里匹配正则表达式
// 标签名称
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // 小a-z 大A到Z 标签名称: div span a-aa
//?: 匹配不捕获
// 特殊标签名称 <span:xx>
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; // 捕获这种 <my:xx> </my:xx>
// tag标签开头
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
// tag标签结尾
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
// 匹配属性
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
//属性匹配 <div id="atts"></div> // aa = "aa" | aa = 'aa'
// 匹配结束标签
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 <div></div> <br/>
// 匹配{{}}
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g // {{xx}} 默认的 双大括号
创建模板编译方法compileToFunction()
function compileToFunction(template){
let ast = parseHTML(template)
}
在模板编译方法里创建解析html的方法parseHTML()
<div id='app' style="color:red">{{msg}}<h1>world</h1></div>
// 对html使用遍历的思想,识别一个元素,删除一个元素,知道将html变成空
// 一个标签的三大类:开始标签(<) 文本(id,class,style...) 结束标签(>)
function parseHTML(html){
while(html){ // 当html为空的时候结束
// 判断标签
let textEnd = html.indexof('<') // 当为0时,可以判断是标签
if(textEnd === 0){
// 使用正则判断是开始标签还是结束标签
const startTagMatch = parseStartTag()
if(startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs)
// 结束标签
let endTagMatch = html.match(endTag)
if(endTagMatch){
advance(endTagMatch[0].length)
}
continue;
}
}
// 文本
if(textEnd > 0){
//获取文本内容
let text = html.substring(0,textEnd) // 内容为:{{msg}}
}
// 判断是否有文本,如果有文本,将文本删除
if(text){
advance(text.length)
charts(text)
}
}
}
解析开始标签的方法parseStartTag()
// 解析开始标签
function parseStartTag(){
// 获取开始标签
// 返回值为:["<div", "div", index: 0, input: "<div id=\"app\" style=\"color:red\">{{msg}}<h1>world</h1></div>", groups: undefined]
const start = html.match(startTagOpen) // html是当前上下文的父级上下文中的html元素
// 创建一个语法树(初级形态),定义一个变量match 将返回值存起来
let match = {
tagName: start[1], // 内容为"<div"
attrs:[] // 通过处理开始标签之后,将对应的属性push进来
}
// 删除已经解析过的开始标签 <div
adance(start[0].length) // 传入html需要删除的长度
// 删除之后的html:id="app" style="color:red">{{msg}}<h1>world</h1></div>
// 处理属性
// warnning:多个属性,采用遍历的思想
// warnning: 注意结束标签>
let end; // 匹配到的标签结束标记 >
let attr; // 匹配到的标签内的属性
while(!(html.match(startTagClose)&&(attr=html.match(attribute))){ // 利用startTagClose正则匹配,判断是否是结尾标签 > ,利用attribute正则匹配是否有属性
// attr= [" id=\"app\"", "id", "=", "app", undefined, undefined, index: 0, input: " id=\"app\" style=\"color:red\">{{msg}}<h1>world</h1></div>", groups: undefined]
match.attrs.push({name:attr[1],value:attr[3]||attr[4]||attr[5]})
advance(attr[0].length) // 删除html中已经处理过的属性
// 删除完之后的html= '>{{msg}}<h1>world</h1></div>' ,所以还需要处理开始的 >
}
if(end){
// end = [">", "", index: 0, input: ">{{msg}}<h1>world</h1></div>", groups: undefined]
advance(end[0].length)
return match // 返回语法树
}
}
删除已经解析过的标签方法adance()
function adance(n){
html.substring(n) // 对html进行删除,并返回新的html
}
创建ast语法树
<div id='app' style="color:red">{{msg}}<h1>world</h1></div>
// ast语法树就是一个对象
function createASTElemnet(tag,attrs){
return{
tag, // 表示元素,div
attrs, // 属性 id class style
children:[] // 子节点,嵌套的div
type:1, // 元素的类型
parent:null
}
}
判断有没有根元素
let root; // 根元素
let createParent // 当前元素的父亲
// 数据结构 栈
let stack = [] // 页面元素解析完后需要入栈处理
定义开始标签处理方法start()
function start(tag, attrs){ // 开始标签
let element = createASTElement(tag, attrs)
if(!root){ // 如果没有根元素,则将获取到的标签设置为根元素
root = element
}
createParent = element // 赋值父亲元素
stack.push(element)
}
定义文本处理方法charts()
function charts(text){ // 文本标签
// 处理空格
text = text.replace(/\s/g,'')
if(text){
createParent.children.push({
type:3, // 文本的类型是3
text
})
}
}
定义结束标签处理方法end()
function end(tag){ // 结束标签
let element = stack.pop() // 获取最后一个元素
createParent = stack[stack.lenth - 1]
if (createParent){ // 元素闭合
element.parent = createParent.tag
createParent.children.push(element)
}
}