页面元素转换成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)
    }
}
上一篇下一篇

猜你喜欢

热点阅读