You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于DOM构建标题层级树?现有实现有误求修正

基于DOM标题构建层级树的问题修正

示例DOM

const dom = new DOMParser().parseFromString(
  `<!DOCTYPE html>
  <body>
    <h1>A</h1>
    <h2>B1</h2>
    <h2>C</h2>
    <h3>D</h3>
    <h3>E</h3>
    <h4>F</h4>
    <h2>B2</h2>
  </body>`,
  "text/html"
);

注:原示例中<h4>F</h3>标签闭合错误,已修正为<h4>F</h4>

期望输出(层级目录树)

const output = {
  A: {
    tag: "H1",
    children: [
      {
        B1: { tag: "H2" },
        C: {
          tag: "H2",
          children: [
            {
              D: { tag: "H3" },
              E: { tag: "H3" },
              children: [{ F: { tag: "H4" } }],
            },
          ],
        },
        B2: { tag: "H2" },
      },
    ],
  },
};

原尝试代码

const headerTagSet = new Set(["h1", "h2", "h3", "h4"]);
const headerTags = ["h1", "h2", "h3", "h4"];

function buildTree(nodes) {
  const tree = {};
  let currentNode = tree;
  let prevLevel = 0;
  const stack = [];

  for (const node of nodes) {
    if (node.tagName && headerTagSet.has(node.tagName.toLowerCase())) {
      const currentLevel = headerTags.findIndex(
        (tag) => tag === node.tagName.toLowerCase()
      );
      const isNested = currentLevel > prevLevel;
      if (isNested) {
        const newNode = {
          [node.textContent]: {
            tag: node.tagName,
          },
        };
        stack.push(newNode);
        if (currentNode.childre) {
          currentNode.childre.push(newNode);
        } else {
          currentNode.children = [newNode];
        }
        prevLevel = currentLevel;
        currentNode = newNode;
      } else {
        currentNode[node.textContent] = { tag: node.tagName };
        stack.length = 0;
      }
    }
  }

  return tree;
}

buildTree(Array.from(dom.body.childNodes));

问题分析与修正

原代码存在以下核心问题:

  • 拼写错误:currentNode.childre 应为 currentNode.children
  • 栈的使用逻辑缺失:仅在层级加深时入栈,但层级回退或同级时未正确回溯到对应父节点
  • 同级节点处理错误:同级标题未被放入同一父级的children容器中,而是直接挂载到错误的节点上
  • 节点结构不符合预期:未按照示例输出的格式,将同层级标题包裹在同一个对象里

修正后的代码

const headerTagSet = new Set(["h1", "h2", "h3", "h4"]);
const headerTags = ["h1", "h2", "h3", "h4"];

function buildTree(nodes) {
  const tree = {};
  // 栈中保存每个层级的父节点和对应层级,格式:{ node: 父节点对象, level: 层级 }
  const stack = [];

  for (const node of nodes) {
    const tagName = node.tagName?.toLowerCase();
    if (!tagName || !headerTagSet.has(tagName)) continue;

    const currentLevel = headerTags.findIndex(tag => tag === tagName);
    const titleText = node.textContent.trim();
    const titleNode = { [titleText]: { tag: node.tagName } };

    // 层级回退:弹出栈顶元素,直到找到当前层级的父节点
    while (stack.length > 0 && stack[stack.length - 1].level >= currentLevel) {
      stack.pop();
    }

    // 确定父节点容器
    let parent;
    if (stack.length === 0) {
      parent = tree;
    } else {
      const lastParent = stack[stack.length - 1].node;
      // 确保父节点有children数组,且最后一项是当前层级的容器对象
      if (!lastParent.children) lastParent.children = [];
      let container = lastParent.children[lastParent.children.length - 1];
      if (!container) {
        container = {};
        lastParent.children.push(container);
      }
      parent = container;
    }

    // 将当前标题节点添加到父容器
    Object.assign(parent, titleNode);

    // 将当前标题的内部对象入栈,作为子节点的父级
    const currentTitleObj = titleNode[titleText];
    stack.push({ node: currentTitleObj, level: currentLevel });
  }

  return tree;
}

// 测试调用
const result = buildTree(Array.from(dom.body.childNodes));
console.log(result);

关键修正点说明

  1. 栈的正确使用:栈中存储每个标题节点的内部对象(包含tag和children的对象)及其层级,层级回退时弹出栈顶元素,直到找到匹配的父节点
  2. 同级节点处理:同层级标题会被添加到同一个父级children数组的容器对象中,完全匹配示例输出结构
  3. 拼写错误修复:修正了childre的拼写错误
  4. 节点结构对齐:确保每个标题的子节点都放在自身的children数组中,同层级标题共享一个容器对象

内容的提问来源于stack exchange,提问作者Joji

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 20:57:21