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

在TypeScript遍历DOM时如何追踪最近遇到的HTML标题ID

问题:如何跟踪最近遇到的标题ID并关联到所有文本节点?

我有一个TypeScript/React应用,使用marked.js将Markdown文件转换为HTML并展示。当前代码遍历文本节点,提取所有显示的原始文本值存入searchIndexReference,每个文本对应一个数字ID,但无法正确跟踪最近遇到的标题ID并关联到后续文本节点——当标题包含<a>、<em>这类嵌套元素时,文本节点的直接父元素不是标题,导致标题ID无法被获取,最终headerReference中的数据不符合预期。

当前使用的代码片段:

let id: number = 1;
let headerIdFromHTML = '';
const headerTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'];

files.forEach(file => {
    // convert markdown content to html using marked.js
    const htmlString = marked(file.markDown);
    const parser = new DOMParser();
    const doc = parser.parseFromString(htmlString, 'text/html');
    const walker = document.createTreeWalker(doc, NodeFilter.SHOW_TEXT);

    let currentNode = walker.currentNode;
    // gather raw text from every HTML element
    while (currentNode != null) {
        if (currentNode.textContent != null) {
            if (currentNode.parentElement) {
                // only care about h1-h6
                if (headerTags.includes(currentNode.parentElement.tagName)) {
                    // store most recently seen header id, update when new header encountered
                    headerIdFromHTML = currentNode.parentElement.id;
                }
            }
            searchIndexReference.push({ id, text: currentNode.textContent });
            headerReference.push({ id, source: file.source, headerId: headerIdFromHTML });
            id++;
        }
        const nextNode = walker.nextNode();
        if (nextNode != null) {
            currentNode = nextNode;
        } else {
            break;
        }
    }
});

问题复现

以下HTML示例中,最后两个元素的ID无法被正确获取:

<h3 id="banner">BANNER</h3>
<h1 id="project-name">Project Name</h1>
<h2 id="quick-links-a-nameproject_linksa">Quick Links <a name="project_links"></a></h2>
<li><h5 id="basic-components"><a href="#project_components">Basic Components</a></h5></li>
<h4 id="general"><em>General</em></h4>

期望结果

headerReference应生成如下数据:

{id: 1, headerId: 'banner', text: 'BANNER'}
{id: 2, headerId: 'project-name', text: 'Project Name'}
{id: 3, headerId: 'quick-links-a-nameproject_linksa', text: 'Quick Links'}
{id: 4, headerId: 'basic-components', text: 'Basic Components'}
{id: 5, headerId: 'general', text: 'General'}

解决方案

问题根源是当前代码只检查文本节点的直接父元素是否为标题,无法处理嵌套元素的情况。解决思路是:对每个文本节点,向上遍历DOM树,找到最近的标题元素(h1-h6),用它的ID更新当前跟踪的标题ID;如果找不到新标题,则沿用之前的ID。

修改后的完整代码:

let id: number = 1;
let headerIdFromHTML = '';

// 工具函数:向上查找最近的标题元素,返回其ID
const findClosestHeader = (node: Node | null): string => {
  if (!node) return '';
  const element = node instanceof Element ? node : node.parentElement;
  if (!element) return '';
  const tagName = element.tagName.toLowerCase();
  if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
    return element.id || '';
  }
  return findClosestHeader(element.parentElement);
};

files.forEach(file => {
    const htmlString = marked(file.markDown);
    const parser = new DOMParser();
    const doc = parser.parseFromString(htmlString, 'text/html');
    const walker = document.createTreeWalker(doc, NodeFilter.SHOW_TEXT);

    let currentNode = walker.currentNode;
    while (currentNode != null) {
        if (currentNode.textContent != null) {
            const closestHeaderId = findClosestHeader(currentNode);
            // 找到新标题ID则更新跟踪值
            if (closestHeaderId) {
                headerIdFromHTML = closestHeaderId;
            }

            // 可选:trim()去除文本前后空格,避免无效空文本存入
            const cleanText = currentNode.textContent.trim();
            if (cleanText) {
                searchIndexReference.push({ id, text: cleanText });
                headerReference.push({ id, source: file.source, headerId: headerIdFromHTML });
                id++;
            }
        }
        currentNode = walker.nextNode();
    }
});

关键修改说明

  1. 新增findClosestHeader工具函数:递归向上遍历DOM树,直到找到最近的h1-h6元素,返回其ID;如果遍历到根节点仍未找到,返回空字符串。
  2. 替换标题ID检测逻辑:不再只检查直接父元素,而是调用工具函数查找最近标题,确保嵌套元素内的文本节点也能关联到正确的标题ID。
  3. 优化标签判断:统一将标签名转小写,无需维护大小写重复的标签数组。
  4. 可选文本清理:增加trim()和空文本判断,避免多余空格或空文本被存入索引。

这样修改后,无论标题内嵌套多少层元素,都能正确跟踪最近的标题ID,生成符合预期的headerReference数据。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 12:31:00