从零实现一个简单的HTML语法树要多久?

前言

如果您以前问我这个问题,我会说,肯定很难吧,我们用用开源项目就好。

何必重复造轮子

应不应该重复造轮子呢?

我想每个人心中都有答案。但是我鼓励大家,我们一起重复造轮子

后续会出一篇文章,关于Chat的文章。是的,不是ChatGPT,是Chat,没有机器学习,我们普通研发人员,也应该继续前进,当然还是和正则表达式有关系。


现在,我们回到今天的话题,正则表达式实例与HTML语法树。

前面的文章,我们知道了,怎么匹配HTML标签,怎么匹配HTML标签属性,今天我们从零实现一个HTML语法树。

看这篇文章之前,建议先看前面两篇文章,如果您对正则非常熟悉,那么现在就开始吧。


1、收集需求

在开始前,我们需要整理需求,我们需要知道,一个完整的HTML文档,最基本的语法,标签以及不同框架增加的特殊属性等,我们因该如何去匹配实现。

为什么要加上其它框架属性,因为当时想通过一套代码生成VUE、React版本。[我想静静]

下面截图文档中,基本囊括了,一个HTML文档的所有情况信息,您可以仔细看下。其中包括(注释、文档声明、单标签、双标签、样式、脚本、有属性值属性、属性名即属性值属性、第三方框架属性等)。

需求文档图


2、整理需求

2.1、匹配注释

const reg_comment = /<!--.*?-->/s;

2.2、匹配文档声明

const reg_doctype = /]*>/i;

2.3、匹配开始标签及单标签

const reg_element = /<[^/!]((["'])+.*?2|[^>])+>/

2.4、匹配结束标签

const reg_elementEnd = /</.*?>/

2.5、匹配标签名称

<p>p>

var reg_tagName = /(?<=<[/s]*)w+(?=(s+(?![s=])|>))/;

2.6、匹配所有属性

type='text' disabled value="" class="txt txt-md" v-on:click="save('button')" />

const reg_tagAttrs = /(?<=s)[w:.-@]+(=(["']).*?2)*/g;

2.7、匹配属性名称及属性值

v-on:click="save('button')"

var reg_tagAttrDatas = /(^[^=]+|(?<=(['"])).*?(?=2))/g;


3、技术方案

3.1、直接使用正则表达嵌套匹配,然后无限递归。

查阅了相关资料、同时编写Demo案例验证、对于Javascript版本的正则表达式,发现对于嵌套匹配目前支持不完善,所以放弃这种方式。

当然,也有可能,我挖掘的还不够深入。

3.2、直接逐句匹配,然后无限递归。

怎么理解?

请看下面的例子。


沧海一站


看到这个例子,你大概明白。就是一行一行的匹配,或者说每次只匹配一种类型。

1、遇到非input、img、link、hr、br这几个单行标签,或者结束标记,就向下递归。

2、遇到结束标记,就返回上一层。

3、遇到style、script标签,他们内部的内容,直接按文本处理。

到这,我想部分朋友,都不需要看代码实例,都知道怎么实现了。

4、主要需求实现

测试代码从PrvtCMS中,临时拆分出来的,只要思路有了,实现起来还是很容易。

const reg_tagName = /(?<=<[/s]*)w+(?=(s+(?![s=])|>))/;
const reg_tagAttrs = /(?<=s)[w:.-@]+(=(["']).*?2)*/g;
const reg_tagAttrDatas = /(^[^=]+|(?<=(['"])).*?(?=2))/g;
const regMap = new Map();
regMap.set("element", /^<[^/!]((["'])+.*?2|[^>])+>/);
regMap.set("elementEnd", /</.*?>/);
regMap.set("note", /<!--.*?-->/is);
regMap.set("declare", //i);
regMap.set("text", /^[^<>]+/);
/**
 * 返回Json语法树
 * @param {父节点名称} name
 * @param {被解析的html} htmlContent
 * @param {节点集合} nodes
 */
const toJson = (name, htmlContent, nodes) => {
  regMap.forEach((value, key) => {
    if (key !== "elementEnd") {
      let res = regFirstResult(value, htmlContent);
      if (res.length) {
        htmlContent = toNext(name, res, key, htmlContent, nodes);
      }
    }
  });
  // 处理同级
  let res = regFirstResult(regMap.get("element"), htmlContent);
  if (res.length) return toJson(name, htmlContent, nodes);
  return htmlContent;
};

/**
 * 下一匹配
 * @param {父节点名称} name
 * @param {匹配结果} parseRes
 * @param {匹配类型} nodeType
 * @param {被解析的html} htmlContent
 * @param {节点集合} nodes
 * @returns
 */
const toNext = (name, parseRes, nodeType, htmlContent, nodes) => {
  if (nodeType == "elementEnd")
    return parseElementEnd(name, parseRes, nodeType, htmlContent, nodes);
  if (nodeType == "element")
    return parseElement(name, parseRes, nodeType, htmlContent, nodes);
  return parseText(name, parseRes, nodeType, htmlContent, nodes);
};

const nodes = [];
toJson("", strHtml, nodes);
console.log(JSON.stringify(nodes));

结果图


实际应用案例探讨

了解或者使用Svelte、Angular、Vue、React、Htmx、Qwik、Astro、Web Component等等技术,AST似乎无处不在

所以一起重复造轮子吧。

PrvtCMS模板在线管理、支持实时预览、跨模板、跨组件引用等。能实现这些功能的基础就是写了一个HTML语法树。是的,重复造轮子。

功能截图


……思考中……

人人为我,我为人人,欢迎您的浏览,我们一起加油吧。

展开阅读全文

页面更新:2024-05-31

标签:语法   轮子   节点   注释   框架   属性   名称   需求   标签   简单   文档

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top