如何从文本编辑器生成的 html 中删除不必要的标签
How to remove unnecessary tags from html generate from text editor
下面是文本编辑器从 word 文档自动生成的 html 脚本,summernote。
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>`;
在它为我生成 hmlt 代码后,我尝试通过删除不必要的空标签和不包含任何值的标签来清理它。
所以,我尝试了如下的 JS 脚本:
html.replace('<p><br></p>', ''); // remove unneccessary tage
html.replace(' ', ''); // remove space
console.log(html);
但是,上面的JS脚本没有任何变化后,空的和不需要的标签仍然存在。
我不知道为什么它不起作用,但我只是尝试了非常简单的替换 '<p><br></p>not replaced'.replace('<p><br></p>','')
,它工作得很好。
上面有什么问题吗?我怎样才能从上面删除所有不必要的标签?谢谢。
您的 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>
中的 <b>
犯规了。您可以制作越来越复杂的正则表达式来处理越来越深奥的情况,但是 that way lies madness and 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
helps us here, because we can assign an HTML string to its innerHTML
property and pull it back out as a DocumentFragment
from the content
属性。如果我们更改 DocumentFragment
的结构,这些更改将反映在 innerHTML
属性 中。*
*我找不到支持我的文档,但它适用于 Firefox 和 Chromium。
现在我们需要实际移除 "unnecessary, empty tags, and tags that [do] not contain any value." 我们将定义 无用节点 来帮助做到这一点:
- Comment nodes are useless.
- Text nodes that are empty or contain only whitespace are useless.
- Non-void element nodes whose child nodes contain only useless nodes or
<br>
elements are useless.
All other nodes are not useless.
我们需要一个函数来识别和删除无用的节点。由于我们要在整个树中搜索无用节点,因此我们将在节点的子节点上递归调用该函数:
function removeUselessNodes(node) {
for (let i = node.childNodes.length - 1; i >= 0; --i) {
removeUselessNodes(node.childNodes.item(i));
}
我们反向迭代子节点,因为 Node.childNodes
是一个活动列表,我们将从中删除元素。循环不知道我们正在做的改变,如果我们继续前进,它会跳过元素。从列表末尾删除元素不会中断向后迭代循环。我们首先执行递归调用,因为它可以更容易地检查最后一个无用节点条件。
所有的树遍历都完成后,我们可以从无用节点条件开始。让我们一一道来:
- Comment nodes are useless.
这个很简单。 Node
s 有一个 属性 表示它们的类型,nodeType
。我们可以检查它并删除该节点,如果它是评论:
if (node.nodeType === Node.COMMENT_NODE) {
node.remove();
return;
}
我们return删除了无用的节点后立即;没有什么可做的了。下一篇:
- Text nodes that are empty or contain only whitespace are useless.
"[E]mpty or contain[s] only whitespace" 是另一种说法 "doesn't contain non-whitespace",我们可以用 RegExp.test
.
来测试
if (
node.nodeType === Node.TEXT_NODE
&& !/\S/.test(node.textContent)
) {
node.remove();
return;
}
(\s
是空白字符,\S
(注意大小写)是非空白字符。)
最后一个测试需要一点拆包:
- Non-void element nodes whose child nodes contain only useless nodes or
<br>
elements are useless.
空元素是不能有子元素的元素:比如 <img>
s 和 <hr>
s。它们并非毫无用处;他们有自己的意义。出于我们的目的,非空元素需要有意义的子元素才能有意义。 <p>
本身只会在页面上腾出一些空间。它的子文本节点是文本的来源。 <br>
在与其他节点相邻时并非毫无用处,但它本身不足以使其父节点有意义。
将其分解为单独的测试,我们得到
- 必须是元素节点
- 必须是非空的
- 子节点必须只包含无用节点或
<br>
个元素
我们之前测试过节点类型:
if (
node.nodeType === Node.ELEMENT_NODE
在 JavaScript 中没有方便的方法来检查是否为空,但 HTML5 规范包括 a list of void elements we can check against with the 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;
}
}
至此,所有 fragment
无用的节点都被删除了。您可以从 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>
下面是文本编辑器从 word 文档自动生成的 html 脚本,summernote。
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>`;
在它为我生成 hmlt 代码后,我尝试通过删除不必要的空标签和不包含任何值的标签来清理它。
所以,我尝试了如下的 JS 脚本:
html.replace('<p><br></p>', ''); // remove unneccessary tage
html.replace(' ', ''); // remove space
console.log(html);
但是,上面的JS脚本没有任何变化后,空的和不需要的标签仍然存在。
我不知道为什么它不起作用,但我只是尝试了非常简单的替换 '<p><br></p>not replaced'.replace('<p><br></p>','')
,它工作得很好。
上面有什么问题吗?我怎样才能从上面删除所有不必要的标签?谢谢。
您的 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>
中的 <b>
犯规了。您可以制作越来越复杂的正则表达式来处理越来越深奥的情况,但是 that way lies madness and 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
helps us here, because we can assign an HTML string to its innerHTML
property and pull it back out as a DocumentFragment
from the content
属性。如果我们更改 DocumentFragment
的结构,这些更改将反映在 innerHTML
属性 中。*
*我找不到支持我的文档,但它适用于 Firefox 和 Chromium。
现在我们需要实际移除 "unnecessary, empty tags, and tags that [do] not contain any value." 我们将定义 无用节点 来帮助做到这一点:
- Comment nodes are useless.
- Text nodes that are empty or contain only whitespace are useless.
- Non-void element nodes whose child nodes contain only useless nodes or
<br>
elements are useless.All other nodes are not useless.
我们需要一个函数来识别和删除无用的节点。由于我们要在整个树中搜索无用节点,因此我们将在节点的子节点上递归调用该函数:
function removeUselessNodes(node) {
for (let i = node.childNodes.length - 1; i >= 0; --i) {
removeUselessNodes(node.childNodes.item(i));
}
我们反向迭代子节点,因为 Node.childNodes
是一个活动列表,我们将从中删除元素。循环不知道我们正在做的改变,如果我们继续前进,它会跳过元素。从列表末尾删除元素不会中断向后迭代循环。我们首先执行递归调用,因为它可以更容易地检查最后一个无用节点条件。
所有的树遍历都完成后,我们可以从无用节点条件开始。让我们一一道来:
- Comment nodes are useless.
这个很简单。 Node
s 有一个 属性 表示它们的类型,nodeType
。我们可以检查它并删除该节点,如果它是评论:
if (node.nodeType === Node.COMMENT_NODE) {
node.remove();
return;
}
我们return删除了无用的节点后立即;没有什么可做的了。下一篇:
- Text nodes that are empty or contain only whitespace are useless.
"[E]mpty or contain[s] only whitespace" 是另一种说法 "doesn't contain non-whitespace",我们可以用 RegExp.test
.
if (
node.nodeType === Node.TEXT_NODE
&& !/\S/.test(node.textContent)
) {
node.remove();
return;
}
(\s
是空白字符,\S
(注意大小写)是非空白字符。)
最后一个测试需要一点拆包:
- Non-void element nodes whose child nodes contain only useless nodes or
<br>
elements are useless.
空元素是不能有子元素的元素:比如 <img>
s 和 <hr>
s。它们并非毫无用处;他们有自己的意义。出于我们的目的,非空元素需要有意义的子元素才能有意义。 <p>
本身只会在页面上腾出一些空间。它的子文本节点是文本的来源。 <br>
在与其他节点相邻时并非毫无用处,但它本身不足以使其父节点有意义。
将其分解为单独的测试,我们得到
- 必须是元素节点
- 必须是非空的
- 子节点必须只包含无用节点或
<br>
个元素
我们之前测试过节点类型:
if (
node.nodeType === Node.ELEMENT_NODE
在 JavaScript 中没有方便的方法来检查是否为空,但 HTML5 规范包括 a list of void elements we can check against with the 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;
}
}
至此,所有 fragment
无用的节点都被删除了。您可以从 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>