如何从文本编辑器中删除html生成中不必要的标签

时间:2018-01-28 10:00:11

标签: javascript summernote

下面是一个由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('&nbsp;', ''); // remove &nbsp; space
console.log(html);

然而,在JS脚本上面没有任何变化之后,空的和不必要的标签仍然存在。

我不知道为什么它不起作用,但我尝试了非常简单的替换'<p><br></p>not replaced'.replace('<p><br></p>',''),它工作得很好。

上面有什么问题?如何从上面删除所有不必要的标签?感谢。

1 个答案:

答案 0 :(得分:3)

您的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>,但<b>内的<p>会犯规。你可以制作越来越复杂的正则表达式来处理越来越多的深奥情境,但是that way lies madnessisn'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可以帮助我们,因为我们可以为其innerHTML属性分配一个HTML字符串,并将其作为DocumentFragment从{{3}中拉出来属性。如果我们更改DocumentFragment的结构,则这些更改将反映在innerHTML属性中。*

*我无法找到支持我的文档,但它在Firefox和Chromium中适用于我。

现在我们需要实际删除那些[不]包含任何值的&#34;不必要的空标签和标签。&#34;我们将定义无用节点来帮助实现这一目标:

  
      
  • 评论节点无用
  •   
  • 空白或仅包含空格的文本节点无用
  •   
  • content仅包含无用节点或<br>元素的非void元素节点无用
  •   
     

所有其他节点都不是无用的

我们需要一个功能来识别和删除无用的节点。由于我们希望在整个树中搜索无用的节点,因此我们将在节点的子节点上递归调用该函数:

function removeUselessNodes(node) {
  for (let i = node.childNodes.length - 1; i >= 0; --i) {
    removeUselessNodes(node.childNodes.item(i));
  }

我们反过来遍历子节点,因为Node.childNodes是一个实时列表,我们将从中删除元素。循环不知道我们正在进行的更改,如果我们前进,将会跳过元素。从列表末尾删除元素不会破坏向后迭代循环。我们首先执行递归调用,因为它可以更容易地检查最后一个无用节点条件。

通过所有树遍历,我们可以从无用节点条件开始。让我们逐一介绍它们:

  
      
  • 评论节点无用
  •   

这很容易。 Node有一个属性,表明其类型child nodes。我们可以检查并删除节点,如果它是评论:

  if (node.nodeType === Node.COMMENT_NODE) {
    node.remove();
    return;
  }

删除无用节点后立即返回;没有什么可做的。下一个:

  
      
  • 空白或仅包含空格的文本节点无用
  •   

&#34; [E] mpty或仅包含[s]空格&#34;是另一种表示&#34;不包含非空格&#34;的方式,我们可以使用nodeType进行测试。

  if (
    node.nodeType === Node.TEXT_NODE
    && !/\S/.test(node.textContent)
  ) {
    node.remove();
    return;
  }

\s是一个空白字符,\S(注意大小写)是一个非空白字符。)

最后一次测试需要一点点拆包:

  
      
  • RegExp.test仅包含无用节点或<br>元素的非void元素节点无用
  •   

Void元素是不能生孩子的元素:<img><hr>之类的元素。他们并非无用;他们有自己的意义。出于我们的目的,非空白元素需要有意义的孩子才有意义。 <p>本身只会在页面上留出一些空间。它的子文本节点是文本的来源。当<br>与其他节点相邻时,<br>并不是无用的,但它本身并不足以使其父节点有意义。

将其分解为单独的测试,我们得到

  • 必须是元素节点
  • 必须是无效的
  • 子节点必须只包含无用的节点或 if ( node.nodeType === Node.ELEMENT_NODE 元素

我们之前测试了节点类型:

    && ![
      'AREA',
      'BASE',
      'BR',
      'COL',
      'EMBED',
      'HR',
      'IMG',
      'INPUT',
      'LINK',
      'META',
      'PARAM',
      'SOURCE',
      'TRACK',
      'WBR'
    ].includes(node.tagName)

没有方便的方法来检查JavaScript中的无效性,但HTML5规范包括child nodes我们可以使用a list of void elements属性进行检查:

<br>

由于我们已经从此节点中删除了所有无用的子节点,因此如果节点的所有子节点都是childNodes元素,则该节点将通过第三次测试。 NodeListevery,它没有length方法,但是使用0索引元素和 && Array.prototype.every.call(node.childNodes, n => n.tagName === 'BR') ) { node.remove(); return; } } 属性,我们可以调用数组&#39}。其上的Element.tagName方法:

fragment

这样,所有template.innerHTML个无用节点都被删除了。您可以从const adoptedNode = document.adoptNode(fragment); document.querySelector('#destination').appendChild(adoptedNode); 获取生成的HTML,也可以将其直接发送到every的其他元素:

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);

全部放在一起:

&#13;
&#13;
#rawHTML {
  width: 95vw;
  height: 10em;
}
&#13;
<textarea id="rawHTML"></textarea>
<div id="destination"></div>
&#13;
{{1}}
&#13;
&#13;
&#13;