如何将 HTML 结构解析为文本与标记分离的扁平对象数组

6次阅读

如何将 HTML 结构解析为文本与标记分离的扁平对象数组

本文介绍一种基于递归遍历 dom 树的方法,将任意 html 片段准确拆解为按渲染顺序排列的对象数组,每个对象明确标识 “text” 或 “markup” 类型,完美处理嵌套、兄弟节点及闭合标签位置问题。

本文介绍一种基于递归遍历 dom 树的方法,将任意 html 片段准确拆解为按渲染顺序排列的对象数组,每个对象明确标识 `”text”` 或 `”markup”` 类型,完美处理嵌套、兄弟节点及闭合标签位置问题。

在前端开发中,常需对 HTML 内容进行结构化分析——例如实现富文本编辑器的内容序列化、无障碍语义提取、或自定义 Markdown/HTML 混合渲染器。核心挑战在于:如何忠实还原浏览器渲染时的节点流顺序,同时严格区分纯文本内容与 HTML 标记(含开/闭标签)? 直接使用正则表达式解析 HTML 是危险且不可靠的;而基于 TreeWalker 的线性遍历虽规避了正则风险,却极易在处理嵌套关系(如

textmore text

)时丢失标签闭合时机,导致 错位出现在子元素之前。

✅ 正确解法是采用深度优先递归遍历(DFS),利用 DOM 天然的树形结构,确保:

  • 每个元素节点的开始标签()在进入其子树前推入;
  • 递归处理所有子节点(包括文本与嵌套元素);
  • 其结束标签()在子树完全处理完毕后推入。

以下是经过生产验证的简洁实现:

function parseHtmlToTokens(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()}>`                  });                 // 递归处理子树                 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</a></p><div class="aritcle_card flexRow">                                                         <div class="artcardd flexRow">                                                                 <a class="aritcle_card_img" href="/ai/1788" title="Stable Diffusion Online"><img                                                                                 src="https://img.php.cn/upload/ai_manual/000/969/633/68b6cd5567066214.png" alt="Stable Diffusion Online"  onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>                                                                 <div class="aritcle_card_info flexColumn">                                                                         <a href="/ai/1788" title="Stable Diffusion Online">Stable Diffusion Online</a>                                                                         <p>基于Stable Diffusion搭建的AI绘图工具</p>                                                                 </div>                                                                 <a href="/ai/1788" title="Stable Diffusion Online" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>                                                         </div>                                                 </div><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/cb6835dc7db1" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">前端免费学习笔记(深入)</a>”;</p> `; const tempDiv = document.createElement('div'); tempDiv.innerHTML = htmlString; const result = parseHtmlToTokens(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>"}, ...  // ]

? 关键设计说明:

  • 顺序保证:递归天然遵循“根→子→根”的 DFS 顺序,使 总在

    所有后代处理完毕后出现,彻底解决原问题中闭合标签前置的逻辑漏洞。

  • 健壮性:不依赖 outerHTML 或 textContent 的字符串拼接,避免属性丢失(如 id、aria-invalid)或转义错误;所有标记均通过 tagName 安全生成。
  • 可扩展性:若需保留属性,可增强为 markup:`;若需过滤注释/脚本节点,增加else if (child.nodeType === Node.COMMENT_NODE)` 分支即可。
  • 性能考量:对于超长文档(>10k 节点),可改用模拟递归避免调用栈溢出,但日常场景中递归更清晰易维护。

⚠️ 注意事项:

  • 输入必须是有效 DOM 节点(非 HTML 字符串),因此需先通过 document.createElement(‘div’).innerHTML = str 解析;注意 xss 风险,服务端渲染场景请使用 DOMPurify 等库净化。
  • 空白文本(如换行、缩进)会被 textContent 包含,建议用 .trim() 过滤(如上例所示),或根据业务需求保留(如代码高亮场景)。
  • 自闭合标签(如 如何将
    )在此模型中视为无子节点的元素,将仅生成单个 如何将 HTML 结构解析为文本与标记分离的扁平对象数组 标记,符合 HTML 规范语义。

该方案以最小认知成本达成最高准确性,是解析 HTML 结构化令牌的推荐实践。

text=ZqhQzanResources