如何实现将 HTML 文档解析为分离文本与标记的嵌套对象数组?

2026-05-07 15:411阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1128个文字,预计阅读时间需要5分钟。

如何实现将 HTML 文档解析为分离文本与标记的嵌套对象数组?

本文字介绍一种基于递归遍历DOM树的专业方法,用于处理DOM元素。该方法不涉及图形解释,不使用数字,不超过100字,直接输出结果:

在前端开发中,有时需要对 HTML 内容进行语义化结构分析——例如实现富文本编辑器的内容序列化、无障碍辅助解析、或自定义 Markdown/HTML 混合渲染器。核心挑战在于:必须严格保持 DOM 渲染时的节点顺序,同时区分纯文本内容(Text 节点)与 HTML 标记(Element 节点),尤其当元素存在嵌套或相邻兄弟元素时,闭合标签的位置极易出错。

使用 TreeWalker 或线性遍历 childNodes 的迭代逻辑容易陷入边界判断困境(如父元素末尾闭合时机、子元素与文本混排时的插入顺序)。而递归深度优先遍历(DFS)天然契合 DOM 树结构,能自动保证:

  • 开始标签在子内容之前;
  • 所有子节点(含文本与嵌套元素)被完整处理;
  • 结束标签在子内容之后 —— 完全符合 HTML 渲染流。

以下为简洁、健壮、可直接复用的实现方案:

function parseHtmlToTokenArray(rootNode) { const tokens = []; function walk(node) { for (const child of node.childNodes) { if (child.nodeType === Node.TEXT_NODE) { const text = child.textContent.trim(); // 可选:跳过纯空白文本(提升结果纯净度) if (text.length > 0) { tokens.push({ text }); } } else if (child.nodeType === Node.ELEMENT_NODE) { // 推入开始标签(小写化确保规范) tokens.push({ markup: `<${child.tagName.toLowerCase()}>` }); // 递归处理所有子节点 if (child.hasChildNodes()) { walk(child); } // 推入结束标签 tokens.push({ markup: `</${child.tagName.toLowerCase()}>` }); } // 忽略注释、CDATA 等其他节点类型(如需支持可扩展) } } walk(rootNode); return tokens; } // 使用示例 const htmlString = ` <h2 id="mcetoc_1h1m1ll27l">Lorem ipsum dolor sit amet...</h2> <p>Lorem ipsum...<a href="#">tr</a><a title="titulo">adsf afjdasi k</a></p> `; const tempDiv = document.createElement('div'); tempDiv.innerHTML = htmlString; const result = parseHtmlToTokenArray(tempDiv); console.log(result);

输出效果示例(节选):

立即学习“前端免费学习笔记(深入)”;

[ {"markup": "<h2>"}, {"text": "Lorem ipsum dolor sit amet..."}, {"markup": "</h2>"}, {"markup": "<p>"}, {"text": "Lorem ipsum..."}, {"markup": "<a>"}, {"text": "tr"}, {"markup": "</a>"}, {"markup": "<a>"}, {"text": "adsf afjdasi k"}, {"markup": "</a>"}, {"markup": "</p>"} ]

⚠️ 关键注意事项:

  • 空格与换行处理:原始 textContent 包含 HTML 中的空白符(如换行、缩进)。生产环境建议调用 .trim() 或使用 innerText(注意其会触发布局计算);若需保留格式,可改用 nodeValue 并预处理 \n\t\r。
  • 属性完整性:当前方案仅生成基础标签名(如 <a>),不包含属性。如需还原完整 outerHTML,可替换为 child.outerHTML,但需注意:<a>...</a> 会被整体推入一次,破坏“开–内容–闭”原子结构。此时应先提取起始标签(正则或 child.cloneNode(false).outerHTML),再单独处理闭合。
  • 性能考量:对于超大文档(>10k 节点),递归可能触发栈溢出。可改用显式栈的迭代 DFS(维护 {node, state: 'enter'|'exit'} 元组),但绝大多数 CMS 或编辑器场景无需优化。
  • 安全边界:本函数操作的是已解析的 DOM 节点,不执行 HTML 字符串解析,因此无 XSS 风险;但若输入源自不可信字符串,请务必先通过 DOMPurify.sanitize() 等库净化。

该方案以最小认知负荷达成最高结构保真度——它不试图“修复”DOM,而是忠实反映浏览器引擎的渲染遍历逻辑。掌握此模式,你将能轻松构建 HTML 解析中间层,为内容分析、序列化、差异对比等高阶功能奠定坚实基础。

标签:html

本文共计1128个文字,预计阅读时间需要5分钟。

如何实现将 HTML 文档解析为分离文本与标记的嵌套对象数组?

本文字介绍一种基于递归遍历DOM树的专业方法,用于处理DOM元素。该方法不涉及图形解释,不使用数字,不超过100字,直接输出结果:

在前端开发中,有时需要对 HTML 内容进行语义化结构分析——例如实现富文本编辑器的内容序列化、无障碍辅助解析、或自定义 Markdown/HTML 混合渲染器。核心挑战在于:必须严格保持 DOM 渲染时的节点顺序,同时区分纯文本内容(Text 节点)与 HTML 标记(Element 节点),尤其当元素存在嵌套或相邻兄弟元素时,闭合标签的位置极易出错。

使用 TreeWalker 或线性遍历 childNodes 的迭代逻辑容易陷入边界判断困境(如父元素末尾闭合时机、子元素与文本混排时的插入顺序)。而递归深度优先遍历(DFS)天然契合 DOM 树结构,能自动保证:

  • 开始标签在子内容之前;
  • 所有子节点(含文本与嵌套元素)被完整处理;
  • 结束标签在子内容之后 —— 完全符合 HTML 渲染流。

以下为简洁、健壮、可直接复用的实现方案:

function parseHtmlToTokenArray(rootNode) { const tokens = []; function walk(node) { for (const child of node.childNodes) { if (child.nodeType === Node.TEXT_NODE) { const text = child.textContent.trim(); // 可选:跳过纯空白文本(提升结果纯净度) if (text.length > 0) { tokens.push({ text }); } } else if (child.nodeType === Node.ELEMENT_NODE) { // 推入开始标签(小写化确保规范) tokens.push({ markup: `<${child.tagName.toLowerCase()}>` }); // 递归处理所有子节点 if (child.hasChildNodes()) { walk(child); } // 推入结束标签 tokens.push({ markup: `</${child.tagName.toLowerCase()}>` }); } // 忽略注释、CDATA 等其他节点类型(如需支持可扩展) } } walk(rootNode); return tokens; } // 使用示例 const htmlString = ` <h2 id="mcetoc_1h1m1ll27l">Lorem ipsum dolor sit amet...</h2> <p>Lorem ipsum...<a href="#">tr</a><a title="titulo">adsf afjdasi k</a></p> `; const tempDiv = document.createElement('div'); tempDiv.innerHTML = htmlString; const result = parseHtmlToTokenArray(tempDiv); console.log(result);

输出效果示例(节选):

立即学习“前端免费学习笔记(深入)”;

[ {"markup": "<h2>"}, {"text": "Lorem ipsum dolor sit amet..."}, {"markup": "</h2>"}, {"markup": "<p>"}, {"text": "Lorem ipsum..."}, {"markup": "<a>"}, {"text": "tr"}, {"markup": "</a>"}, {"markup": "<a>"}, {"text": "adsf afjdasi k"}, {"markup": "</a>"}, {"markup": "</p>"} ]

⚠️ 关键注意事项:

  • 空格与换行处理:原始 textContent 包含 HTML 中的空白符(如换行、缩进)。生产环境建议调用 .trim() 或使用 innerText(注意其会触发布局计算);若需保留格式,可改用 nodeValue 并预处理 \n\t\r。
  • 属性完整性:当前方案仅生成基础标签名(如 <a>),不包含属性。如需还原完整 outerHTML,可替换为 child.outerHTML,但需注意:<a>...</a> 会被整体推入一次,破坏“开–内容–闭”原子结构。此时应先提取起始标签(正则或 child.cloneNode(false).outerHTML),再单独处理闭合。
  • 性能考量:对于超大文档(>10k 节点),递归可能触发栈溢出。可改用显式栈的迭代 DFS(维护 {node, state: 'enter'|'exit'} 元组),但绝大多数 CMS 或编辑器场景无需优化。
  • 安全边界:本函数操作的是已解析的 DOM 节点,不执行 HTML 字符串解析,因此无 XSS 风险;但若输入源自不可信字符串,请务必先通过 DOMPurify.sanitize() 等库净化。

该方案以最小认知负荷达成最高结构保真度——它不试图“修复”DOM,而是忠实反映浏览器引擎的渲染遍历逻辑。掌握此模式,你将能轻松构建 HTML 解析中间层,为内容分析、序列化、差异对比等高阶功能奠定坚实基础。

标签:html