如何获取符合自定义格式规则的元素完整文本内容
如何获取符合自定义格式规则的元素完整文本内容
嘿,我来帮你搞定这个自定义文本格式化的问题!你想要的是严格按照给定规则把HTML内容转换成符合要求的多线字符串,不想用textContent或innerText完全合理——这俩确实没法精准控制咱们要的缩进、换行和标签过滤逻辑。之前你写的递归函数方向是对的,只是在缩进处理、换行控制和空白清理上有点小问题,咱们来一步步调整修复它。
首先,先把咱们要严格遵守的规则再明确一遍(从你的示例里提炼出来的):
- 段落
<p>:内容里的<br>换成换行,每个段落结束后加两个换行;如果段落在列表项<li>里,前后也要各加两个换行 - 列表
<li>:每个列表项前加换行+对应层级的缩进(每嵌套一层加4个空格),嵌套列表的缩进要逐层递增 - 忽略的元素:
<button>、带select-none类的元素直接跳过;<strong>、<i>、<span>这类标签只提取文本,标签本身忽略 <pre>标签:保留内部代码的格式,忽略不需要的元素(比如button、select-none的div),最终把有效代码包裹在<pre>标签里,同时要清理掉代码块里多余的前置空白
接下来是修复后的递归函数,我加了详细的注释,你可以对照着看调整的地方:
function flatten(elem, nesting = "") { let text = ""; const tag = elem.tagName?.toLowerCase(); // 统一转小写,避免大小写问题 // 处理不同标签的前置内容 switch(tag) { case "li": // 列表项前加换行+当前层级的缩进 text += `\n${nesting}`; break; case "pre": // pre标签开头先写<pre>换行 text += "<pre>\n"; // pre内部的内容单独处理,避免缩进干扰 let preContent = ""; break; } for (const child of elem.childNodes) { if (child.nodeType === Node.TEXT_NODE) { // 处理文本节点:非pre内部清理多余空白,pre内部保留原始格式 if (tag !== "pre") { // 替换多个换行/空格为单个空格,同时去掉首尾无效空白 const cleanedText = child.textContent.replace(/\s+/g, " ").trim(); if (cleanedText) text += cleanedText; } else { preContent += child.textContent; } } else { const childTag = child.tagName?.toLowerCase(); const className = child.className || ""; // 完全忽略的元素:button、带select-none类的元素 if (childTag === "button" || className.includes("select-none")) { continue; } // 处理br标签:直接替换为换行 if (childTag === "br") { text += "\n"; continue; } // 处理pre内部的子元素:直接提取文本,保留代码格式 if (tag === "pre") { preContent += flatten(child, ""); continue; } // 其他标签:递归处理,传递正确的嵌套缩进 const childNesting = tag === "li" ? nesting + " " : nesting; text += flatten(child, childNesting); } } // 处理不同标签的后置内容 switch(tag) { case "p": // 段落结束后加两个换行,符合示例要求 text += "\n\n"; break; case "pre": // 清理pre内容的多余前置空格:找到所有非空行的最小缩进量统一去除 const lines = preContent.split("\n").filter(line => line.trim() !== ""); if (lines.length === 0) { text += "</pre>\n"; break; } const minIndent = Math.min(...lines.map(line => line.match(/^\s*/)[0].length)); const cleanedPre = lines.map(line => line.slice(minIndent)).join("\n"); text += cleanedPre + "\n</pre>\n"; break; } return text; }
关键调整点说明:
- 缩进处理:原来的函数直接修改了
nesting变量,导致嵌套列表的缩进混乱,现在改用childNesting传递新的缩进,原变量保持不变,确保层级正确。 - 空白清理:非pre的文本节点会清理多余的换行和空格,避免HTML里的空白字符导致输出出现大量空行;pre内部的文本则通过计算最小缩进量来对齐代码,解决原输出里代码前置空格过多的问题。
- 标签过滤逻辑:明确区分了“完全忽略的元素”和“只提取文本的标签”,比如
<strong>、<i>这类标签会递归提取文本,而<button>直接跳过。 - 换行控制:调整了
<li>和<p>的换行逻辑,避免原输出里出现的多余空行,比如<li>结束后不再额外加换行,<p>结束后加两个换行完全匹配你的示例要求。
你可以把这个函数拿去测试,应该能得到和你期望一致的输出啦!
备注:内容来源于stack exchange,提问作者Ξένη Γήινος




