首页 > 其他分享 >手写HTML字符串解析成对应的 AST语法树

手写HTML字符串解析成对应的 AST语法树

时间:2024-05-31 17:30:04浏览次数:20  
标签:const AST text html HTML let 手写

先看效果 展示如下:

  • HTML模版
    在这里插入图片描述
  • 转成ast语法树后
    在这里插入图片描述

在学习之前,我们需要了解这么一个问题,为什么要将HTML字符串解析成对应的 AST语法树。

为什么?

  • 语法分析:HTML字符串是一种标记语言,其中包含了大量的标签、属性、文本等内容。通过解析HTML字符串,可以将其转换为更易于操作和理解的数据结构,方便对HTML进行进一步处理。

  • 数据驱动:现代前端开发中,数据驱动视图渲染是一种常见的模式。在许多框架中,开发者可以使用类似JSX或模板语言的方式编写组件的结构,然后通过解析和转换成AST树来实现动态渲染。

  • 模板编译:许多前端框架(如Vue、React等)都会将模板编译成渲染函数,以实现高效的视图更新。在这个过程中,将模板解析成AST树是必不可少的步骤。

  • 性能优化:通过将HTML字符串转换成AST树,可以更方便地进行性能优化,比如进行静态节点的标记、事件绑定等操作,以提高页面渲染的效率。

  • 安全性:通过AST树可以更容易地进行XSS(跨站脚本攻击)防护,对用户输入的HTML进行合理的过滤和转义,避免恶意脚本的注入。

优势:

  • 更高效的处理和操作:AST提供了对HTML结构的抽象表示,使得可以更轻松地对HTML进行遍历、操作和分析。这使得在前端开发中,可以更方便地实现诸如模板编译、组件化等功能。

  • 提高性能:在前端框架中,将HTML模板转换为AST可以帮助进行模板编译,将模板转换为可执行的渲染函数。这样可以避免在每次渲染时重新解析模板,从而提高渲染性能。

  • 支持差异化更新:通过比较两个AST树,可以更高效地实现差异化更新,即只更新发生变化的部分,而不需要重新渲染整个页面。这对于提高页面渲染性能和用户体验至关重要。

  • 便于优化和分析:AST树可以用于静态分析,有助于发现潜在的性能问题或安全问题,以进行优化和安全防护。

  • 支持扩展和定制:通过AST树,可以轻松实现对HTML的自定义扩展,如自定义指令、自定义组件等功能,从而提供更灵活的开发和定制能力。

案例:可以动手敲一敲

直接新建一个html文件夹,将一下的代码放进script先运行一下,体验一下,然后再去理解,如果能手写的话,手写一遍。

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // 标签名 
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; //  用来获取的标签名的 match 后的索引为1的
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 匹配开始标签的 
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配闭合标签的
// 匹配属性的正则表达式
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
const startTagClose = /^\s*(\/?)>/; // 匹配开始标签的闭合部分
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // 匹配双花括号内容的正则表达式,用于处理文本节点中的插值表达式

// 创建 AST 元素
function createAstElement(tagName, attrs) {
    return {
        tag: tagName,
        type: 1, // 代表元素节点
        children: [],
        parent: null,
        attrs
    }
}

let root = null;
let stack = [];

// 处理开始标签
function start(tagName, attributes) {
    let parent = stack[stack.length - 1];
    let element = createAstElement(tagName, attributes);
    if (!root) {
        root = element;
    }
    if (parent) {
        element.parent = parent;
        parent.children.push(element);
    }
    stack.push(element);
}

// 处理结束标签
function end(tagName) {
    let last = stack.pop();
    if (last.tag !== tagName) {
        throw new Error('标签有误');
    }
}

// 处理文本节点
function chars(text) {
    text = text.replace(/\s/g, ""); // 移除空格
    let parent = stack[stack.length - 1];
    if (text) {
        parent.children.push({
            type: 3, // 代表文本节点
            text
        })
    }
}

// 解析 HTML 字符串,生成对应的 AST
function parserHTML(html) {
    function advance(len) {
        html = html.substring(len);
    }

    function parseStartTag() {
        const start = html.match(startTagOpen);
        if (start) {
            const match = {
                tagName: start[1],
                attrs: []
            }
            advance(start[0].length);
            let end;
            let attr;
            while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
                match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5] });
                advance(attr[0].length);
            }
            if (end) {
                advance(end[0].length);
            }
            return match;
        }
        return false; // 不是开始标签
    }

    // 逐步解析HTML字符串
    while (html) {
        let textEnd = html.indexOf('<'); // 当前解析的开头  
        if (textEnd == 0) {
            const startTagMatch = parseStartTag(html); // 解析开始标签
            if (startTagMatch) {
                start(startTagMatch.tagName, startTagMatch.attrs);
                continue;
            }
            const endTagMatch = html.match(endTag);
            if (endTagMatch) {
                end(endTagMatch[1]);
                advance(endTagMatch[0].length);
                continue;
            }
        }
        let text;
        if (textEnd > 0) {
            text = html.substring(0, textEnd)
        }
        if (text) {
            chars(text);
            advance(text.length);
        }
    }

    return root;
}

let htmls = '<div>111</div>';
let str = parserHTML(htmls);
console.log("str", str);

标签:const,AST,text,html,HTML,let,手写
From: https://blog.csdn.net/weixin_47818125/article/details/139357388

相关文章