美文网首页
页面元素转换成ast语法树

页面元素转换成ast语法树

作者: JX灬君 | 来源:发表于2021-08-18 00:37 被阅读0次

    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)
        }
    }
    

    相关文章

      网友评论

          本文标题:页面元素转换成ast语法树

          本文链接:https://www.haomeiwen.com/subject/mxribltx.html