从HTML数据中提取目录

需求:在返回过来的文章中生成目录,并且点击目录文章可以跳转到对应的位置
解决:约定使用h1-h6标签来生成目录

/**
 * 从HTML数据中提取内容目录树。
 *
 * @param htmlData HTML字符串,包含<h1-6>标题。
 * @returns 返回一个对象,包含目录树和修改后的HTML字符串,其中标题标签添加了id属性。
 */
const getContentDirTree: any = (htmlData) => {
  // 如果HTML数据为空,则直接返回空数组。
  if (!htmlData) {
    return [];
  }
  // 使用正则表达式匹配所有的<h1-6>标签。
  let hDomList = htmlData.match(
    /<h[1-6]{1}[^>]*>([\s\S]*?)<\/h[1-6]{1}>/g
  );
  if (!Array.isArray(hDomList)) {
    return [];
  }
  // 初始化根节点
  let stack: any[] = [{ id: 0, pid: 0, children: [] }];
  // 在原有函数中增加一个映射来存储标题内容与ID的关系
  let titleToIdMap: Record<string, string> = {};
  // 遍历所有匹配到的标题标签。
  hDomList.forEach((hDom, index) => {
    // 提取标题的级别和内容。
    const startIndex = hDom.indexOf("<");
    const endIndex = hDom.indexOf(">");
    if (startIndex != -1 && endIndex != -1) {
      const re = /<h([1-6]{1})[^>]*>([\s\S]*?)<\/h[1-6]{1}>/
      let hLevel = Number(hDom.replace(re, "$1"));
      let titleContent = hDom.replace(re, "$2");
      // 移除标题内容中的HTML标签。
      let title = titleContent.replaceAll(/<([\s\S]*?)>|<\/([\s\S]*?)>/g, "");
      // 为每个标题生成唯一的id。
      const id = `generatedId_${index}`; // 如果没有ID,则生成一个
      // 更新映射
      titleToIdMap[title] = id;
      // 当前标题的层级小于等于栈顶元素的层级时,出栈直到找到合适的父级或回到根节点
      while (stack.length > 1 && hLevel <= stack[stack.length - 1].hLevel) {
        stack.pop();
      }
      // 构建当前目录项,并将其添加到栈顶元素(即当前的父级)的children中
      const currentDir: any = {
        id,//用于点击的时候选中态
        title, // 标题
        domId: id, // 用于定位到标题
        hLevel, // 层级
        pid: stack[stack.length - 1].id,
        children: []
      }
      stack[stack.length - 1].children.push(currentDir);
      stack.push(currentDir); // 将当前目录项压入栈作为新的父级候选
    }
  })
  // 最终的dirTree应为根节点的children
  const dirTree = stack[0].children;
  // 为每个目录项添加子项
  dirTree.forEach((item) => {
    if (item.pid !== 0) {
      const findParent = dirTree.find(i => i.id === item.pid);
      if (findParent) {
        if (!Array.isArray(findParent.children)) {
          findParent.children = []
        }
        findParent.children.push(item)
      }
    }
  })
  // 修改原始HTML的逻辑更新为使用titleToIdMap
  const modifiedHtml = htmlData.replace(
    /<h([1-6]){1}[^>]*>([\s\S]*?)<\/h[1-6]{1}>/g,
    (match, hLevel, titleContent) => {
      const cleanedTitle = titleContent.replace(/<([\s\S]*?)>|<\/([\s\S]*?)>/g, ""); // 简化清理标签的正则
      const id = titleToIdMap[cleanedTitle];
      if (id) {
        return `<h${hLevel} id="${id}">${titleContent}</h${hLevel}>`;
      } else {
        console.warn(`No ID found for title: "${cleanedTitle}"`);
        return match; // 如果没有找到ID,原样返回匹配的文本
      }
    }
  );

  // 返回目录树和修改后的HTML。
  return { dirTree: dirTree.filter((i) => i.pid === 0), modifiedHtml };
}

// 调用
const { dirTree, modifiedHtml } = getContentDirTree(props.dataSource?.messageContent);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值