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

news2025/1/18 9:06:31

先看效果 展示如下:

  • 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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1720030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

掀桌子、降价、免费...之后,国内大模型应用进入高速时代

5月15日&#xff0c;字节跳动打响大模型市场价格战第一枪&#xff1b;5月21日阿里云更狠&#xff0c;价格降了97%&#xff0c;比字节还便宜37.5%同日&#xff0c;百度更为激进&#xff0c;直接宣布其两款主力模型ENIRE Speed和ENIRE Lite全面免费&#xff1b;5月22号&#xff0…

Windows10专业版系统安装Hyper-V虚拟机软件

Windows10专业版系统安装Hyper-V虚拟机软件 适用于在Windows10专业版系统安装Hyper-v虚拟机软件。 1. 安装准备 1.1 安装平台 Windows 10 1.2. 软件信息 软件名称软件版本安装路径windowswindows 10 专业版Hyper-vHyper-v 2. Hyper-v搭建 2.1打开cmd软件 2.2打开控制面…

【PostgreSQL17新特性之-explain命令新增选项】

EXPLAIN是一个用于显示语句执行计划的命令&#xff0c;可用于显示以下语句类型之一的执行计划&#xff1a; - SELECT - INSERT - UPDATE - DELETE - VALUES - EXECUTE - DECLARE - CREATE TABLE AS - CREATE MATERIALIZED VIEWPostgreSQL17-beta1版本近日发布了&#xff0c;新…

使用Prompt,轻松实现你的第一个全栈项目

前言 还有程序员没有应用过大模型技术吗&#xff1f;是工具也可以&#xff01;如果你还未使用过大模型技术&#xff0c;那么我劝你尽早行动&#xff0c;它会成为你开发的一大神器。如果你对大模型感兴趣&#xff0c;同时想使用大模型技术来开发产品&#xff0c;我接下来这个实…

Ubuntu24.04 LTS安装中文输入法

前言 最近&#xff0c;windows玩没了&#xff0c;一怒之下决定换一个操作系统&#xff0c;当然就是最新的Ubuntu24.04 LTS.&#xff0c;其中魔法和咒语&#xff08;汉语&#xff09;是inux遇到的第一大难关&#xff0c;我权限不够教不了魔法&#xff0c;但我可以教你咒语(๑•…

JVM优化之垃圾收集器

JVM优化之垃圾收集器 Serial收集器Parallel Scavenge收集器ParNew收集器 如果说垃圾收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体实现。 没有最好的垃圾收集器&#xff0c;只有根据具体应用场景选择适合自己的垃圾收集器。 Serial收集器 #使用方…

GPU学习(1)

一、为什么要GPU 我们先看一个基本的神经网络计算 YF(x)AxB 这就是一次乘法一次加法 &#xff0c;也叫FMA&#xff0c;(fused multiply-add) 如果矩阵乘&#xff0c;就是上面的那个式子扩展一下&#xff0c;所以又用了这张老图 比如你要多执行好几个yAxB&#xff0c;可能比较简…

C++11中的新特性(2)

C11 1 可变参数模板2 emplace_back函数3 lambda表达式3.1 捕捉列表的作用3.2 lambda表达式底层原理 4 包装器5 bind函数的使用 1 可变参数模板 在C11之前&#xff0c;模板利用class关键字定义了几个参数&#xff0c;那么我们在编译推演中&#xff0c;我们就必须传入对应的参数…

在鲲鹏服务器上安装nginx

华为鲲鹏服务器采用华为自研cpu ARMv8架构,提供 Windows 和多个Linux 系统 常使用 CentOS 7.6 64bit with ARM Nginx 和 Apache 一样都是一种 Web 服务器。是基于 REST 架构风格&#xff0c;以统一资源描述符URI 或者统一资源定位符URL 作为沟通依据&#xff0c;通过 HTTP 协议…

【算法】模拟算法——Z字形变换(medium)

题解&#xff1a;模拟算法——Z字形变换(medium) 目录 1.题目2.题解3.参考代码4.总结 1.题目 题目链接&#xff1a;LINK 2.题解 利用模拟&#xff0c;来解决问题。 首先创建出一个O(numRows*n)的数组来&#xff0c;并按照题目要求把每个字符按顺序填进去。 这里以numRows…

操作系统6_输入输出系统

操作系统6_输入输出系统 文章目录 操作系统6_输入输出系统1. I/O功能、模型接口2. I/O设备及控制器2.1 I/O设备2.2 设备与控制器接口2.3 设备控制器2.4 通道3. 中断及处理4. 设备驱动程序4.1 驱动程序的功能和特点4.2 驱动程序的处理过程4.3 设备控制方式4.3.1 程序I/O方式4.3.…

微信小程序-页面配置

一、页面配置文件的作用 小程序中&#xff0c;每个页面都有自己的.json配置文件&#xff0c;用来对当前页面的窗口外观、页面效果等进行配置 二、页面配置和全局配置的关系 小程序中&#xff0c;app.json中的window节点&#xff0c;可以全局配置小程序中每个页面的窗口表现 …

MySQL(十一) 用户管理

1.用户 1.1 用户信息 MySQL中的用户&#xff0c;都存储在系统数据库mysql的user表中 mysql> select host,user,authentication_string from user; --------------------------------------------------------------------- | host | user | authentication…

浏览器提示413 Request Entity Too Large

1 问题 2 解决 2.1 后端java配置 2.2 Nginx配置

7个卖出信号出现,昂首资本立即盈利收场

在上篇文章中&#xff0c;我们和各位投资者讨论了如果使用匕首交易策略进行交易&#xff0c;但是如果只买进不卖出&#xff0c;是不是还是盈利不了&#xff1f;Anzo Capital昂首资本认为只有低买高卖才能盈利赚钱&#xff0c;只要发现盈利信号就要立即卖出盈利收场&#xff01;…

【解决】Tree prefab at index 8 is missing.

开发平台&#xff1a;Unity 2020 版本以上   问题描述 翻译&#xff1a;树预制体集合中第8位预制体丢失。   解决方法&#xff1a;修复丢失树资产 关联 Unity Terrier 组件使用&#xff0c;前往 树绘制工作区&#xff0c;检查 “树资产” 引用是否丢失&#xff1f;删除或重…

力扣257. 二叉树的所有路径

思路&#xff1a;题目需要记录从根节点开始走的路径&#xff0c;无疑选用前序遍历&#xff0c;用一个数组paths 记录走过的节点信息&#xff0c;遇到叶子节点就用另一个list记录下路径&#xff0c;回溯时删掉paths尾节点即可 class Solution {public List<String> binar…

使用el-tab,el-tab-pane循环使用循环后不显示下划线问题

在vue项目中使用element-UI el-tab里的el-tab-pane是循环出来的&#xff0c;但是循环出来后选中tab不显示下划线了 文章目录 问题问题展示效果问题代码问题原因 解决方案解决后效果解决方案1代码 解决方案2代码 问题 问题展示效果 问题代码 <el-tabs v-model"activeNa…

SQL—DQL之执行顺序(基础)

一、引言 1、编写顺序 2、执行顺序 介绍&#xff1a; DQL语句&#xff08;数据查询语句&#xff09; 1、首先先执行的是 FROM &#xff0c;通过 FROM 来决定我要查询的是哪一张表的数据。 2、紧接着通过 WHERE 来指定查询的条件。 3、第三步就是通过 GROUP BY 以及 HAVING 来…

Jvm(二)新生代和老年代与GC回收

目录 新生代和老年代 新生代 MinorGC 老年代&#xff08;Old Generation&#xff09; MajorGC Minor GC、Major GC 和 Full GC 三个GC具体区别和使用场景 JVM GC及内存调优的参数 调优建议 前言-与正文无关 ​ 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无…