您的replace 行不起作用,因为它与您的 HTML 的确切结构不匹配,并且不考虑标签之间的空格。您可以通过在 replace 调用中使用 RegExp 来处理空白,如下所示:
html.replace(/<p>\s*<br>\s*<\/p>/, '');
// / start of the regex literal
// <p> a literal "<p>"
// \s any whitespace character
// * previous char, zero or more times
// <br> a literal "br"
// \s any whitespace character
// * previous char, zero or more times
// <\/p> a literal "</p>" (with escaped slash)
// / end of regex
这将匹配<p><br></p>,但你的<p>s 中的<b>s 犯规了。您可以制作更复杂的正则表达式来处理越来越深奥的情况,但that way lies madness 和isn't possible in the general case。
相反,我们可以将生成的 HTML 提取到 DocumentFragment 中。然后我们可以将它作为一个 DOM 树来使用,而不是一个字符串:
const template = document.createElement('template');
template.innerHTML = html;
const fragment = template.content;
removeUselessNodes(fragment); // we'll need to write this one
<template> HTMLTemplateElement 在这里可以帮助我们,因为我们可以将 HTML 字符串分配给其 innerHTML 属性,然后将其作为 DocumentFragment 从 content 属性中拉出。如果我们更改DocumentFragment 的结构,这些更改将反映在innerHTML 属性中。*
*我找不到支持我的文档,但它适用于 Firefox 和 Chromium。
现在我们需要实际删除“不必要的、空的标签和[不] 包含任何值的标签”。我们将定义无用节点来帮助做到这一点:
所有其他节点并非无用。
我们需要一个函数来识别和删除无用的节点。由于我们想在整个树中搜索无用的节点,我们将在节点的子节点上递归调用该函数:
function removeUselessNodes(node) {
for (let i = node.childNodes.length - 1; i >= 0; --i) {
removeUselessNodes(node.childNodes.item(i));
}
我们反向迭代子节点,因为Node.childNodes 是一个活动列表,我们将从其中删除元素。循环不知道我们正在进行的更改,如果我们继续前进,它会跳过元素。从列表末尾删除元素不会破坏向后迭代循环。我们首先执行递归调用,因为它使检查最后一个无用节点的条件更容易。
通过所有的树遍历,我们可以从无用节点条件开始。让我们一一来看:
这个很简单。 Nodes 有一个属性表明它们的类型,nodeType。如果是评论,我们可以检查它并删除节点:
if (node.nodeType === Node.COMMENT_NODE) {
node.remove();
return;
}
删除无用节点后立即返回;没有什么可做的了。下一个:
“[E]mpty or contains[s] only whitespace”是“不包含非空白”的另一种说法,我们可以使用RegExp.test 进行测试。
if (
node.nodeType === Node.TEXT_NODE
&& !/\S/.test(node.textContent)
) {
node.remove();
return;
}
(\s 是空白字符,\S(注意大写)是非空白字符。)
最后的测试需要稍微拆包:
Void 元素是不能有子元素的元素:比如<img>s 和<hr>s。它们不是没用的;它们本身就有意义。为了我们的目的,非空元素需要有意义的子元素才有意义。 <p> 本身只是在页面上腾出一些空间。它的子文本节点是文本的来源。 <br> 与其他节点相邻时并不是无用的,但它本身不足以使其父节点有意义。
将其分解为单独的测试,我们得到
- 必须是元素节点
- 必须是非空的
- 子节点只能包含无用节点或
<br>元素
我们之前已经测试过节点类型:
if (
node.nodeType === Node.ELEMENT_NODE
没有方便的方法来检查 JavaScript 中的 void-ness,但 HTML5 规范包含 a list of void elements,我们可以使用 Element.tagName 属性进行检查:
&& ![
'AREA',
'BASE',
'BR',
'COL',
'EMBED',
'HR',
'IMG',
'INPUT',
'LINK',
'META',
'PARAM',
'SOURCE',
'TRACK',
'WBR'
].includes(node.tagName)
由于我们已经从这个节点中移除了所有无用的子节点,如果它的所有子节点都是<br> 元素,则该节点通过第三次测试。 childNodes 是一个NodeList,它没有every 方法,但是有0 索引元素和length 属性,我们可以在其上调用Array 的every 方法:
&& Array.prototype.every.call(node.childNodes, n => n.tagName === 'BR')
) {
node.remove();
return;
}
}
这样,fragments 的所有无用节点都已被删除。您可以从 template.innerHTML 获取生成的 HTML,也可以使用 document.adoptNode 将其直接发送到另一个元素:
const adoptedNode = document.adoptNode(fragment);
document.querySelector('#destination').appendChild(adoptedNode);
把它们放在一起:
var html = `
<p>
<b>
<br>
</b>
</p>
<p>
<b>អ្នកធានា</b>
</p>
<p>
<b>ឈ្មោះ: ……………………………</b>
</p>
<p>
<b>អត្តសញ្ញាណប័ណ្ណលេខៈ………………...............
<span style="white-space:pre"></span>..........................................
</b>
</p>
<p>
<b>
<span style="white-space:pre"></span>ហត្ថលេខានិង ស្នាមមេដៃស្តាំ
<span style="white-space:pre"></span>
</b>
</p>
<p>
<b>
<br>
</b>
</p>
<p>`;
function removeUselessNodes(node) {
for (let i = node.childNodes.length - 1; i >= 0; --i) {
removeUselessNodes(node.childNodes.item(i));
}
if (node.nodeType === Node.COMMENT_NODE) {
node.remove();
return;
}
if (
node.nodeType === Node.TEXT_NODE
&& !/\S/.test(node.textContent)
) {
node.remove();
return;
}
if (
node.nodeType === Node.ELEMENT_NODE
&& ![
'AREA',
'BASE',
'BR',
'COL',
'EMBED',
'HR',
'IMG',
'INPUT',
'LINK',
'META',
'PARAM',
'SOURCE',
'TRACK',
'WBR'
].includes(node.tagName)
&& Array.prototype.every.call(node.childNodes, n => n.tagName === 'BR')
) {
node.remove();
return;
}
}
const template = document.createElement('template');
template.innerHTML = html;
const fragment = template.content;
removeUselessNodes(fragment);
document.querySelector('#rawHTML').value = template.innerHTML;
const adoptedNode = document.adoptNode(fragment);
document.querySelector('#destination').appendChild(adoptedNode);
#rawHTML {
width: 95vw;
height: 10em;
}
<textarea id="rawHTML"></textarea>
<div id="destination"></div>