有一段HTML如下:
<div>
巴拉巴拉<a>a标签包围的文本</a>度达<u>不</u><i>i标签包围的文本</i><u>u标签包围的文本</u>没有被内部标签包围的文本<u>度</u>要求。
</div><br>
<b>b标签包围的文本。</b><br>
<p>不带格式的p标签包围的文本</p>
<p style="text-indent:2em;">带首行缩进格式的p标签包围的文本</p>
<div style="vertical-align:middle;">带居中对齐格式的div标签包围的文本</div>
<div>
<table border="1">
<tr>
<td rowspan="2">合并单元格</td>
<td>第一行</td>
</tr>
<tr>
<td>第二行</td>
</tr>
</table>
</div>
现在想要保留其中的表格相关标签,img标签,br标签以及不带属性的div和p标签,但删除其他标签,以实现将该段内容变更为无格式内容但仍然保留文档中的换行、图片和表格的目标。
先考虑编写匹配排除某些标签(例如br、p、div)以外的html标签,这样才可能利用replace函数在将其他标签删除的同时保留br、p、div标签。且看下面的JavaScript代码:
let s = '<p style="text-indent:2em;">';
alert(/<\/?((?!br\b|div\b|p\b)\w)+[^>]*>/gi.test(s));
修改上面代码中的字符串s,可以看到只有标签名不是br、p和div时提示框才会显示true。
其中所使用的正则表达式具体含义如下:
1、br\b:\b为单词边界,匹配br标签的标签名。如果没有这个\b,自定义一个br开头的标签,例如<bre>,也会返回false。
2、(?!br\b)表示不匹配br标签的标签名。(?!br\b|div\b|p\b)分别表示不匹配br、div和p标签的标签名因为标签名前面或者是<或者是/,所以无需用\b限定。
3、形如(?!br\b)的正则表达式是0宽断言,所以为了可以匹配其他标签的标签名,后面加上了\w,即英文字母或数字。如果仅匹配字母本该写成[a-zA-Z],无所谓啦,\w短一点。
((?!br\b|div\b|p\b)\w)+
即表示匹配除了br、p和div标签以外的标签名。再在前面加上
<\/?
其实就是</?,反斜杠是转义字符,以区别JavaScript正则表达式的/符号与正斜杠。这样就可以匹配HTML元素标签的开始标签和结束标签。
4、[^>]*表示匹配任意多个(包括0个)不是“>”的字符,这样就既能匹配带属性的标签,也能匹配不带属性的标签。最后加上“>”,标签闭合了。
现在需要考虑如何删除带属性的p和div标签(br标签本就不带属性,无需再考虑)。参照第一段代码中的正则表达式,排除不带属性的p和div标签的正则表达式如下:
/<(div|p)>(((?!<\/\1>).)*)<\/\1>/gi
其中,<(div|p)>匹配<div>和<p>,即div和p元素的不带属性的开始标签。<\/\1>通过\1反向引用匹配开始标签的标签名,匹配与开始标签对应的结束标签。((?!<\/\1>).)*匹配的是开始标签与结束标签之间的内容,很显然,这部分内容的要求就是不能是结束标签(否则就不是开始标签与结束标签“之间”的内容了),这也正是((?!<\/\1>).)*的含义,其中的\1同样是反向引用匹配的标签名。((?!<\/\1>).)*外面再加上括号,是方便在替换操作时引用开始标签与结束标签之间的内容。
由于正则表达式有或操作符(|)却没有与操作符,其自身无法满足逻辑运算的需要,因此,要将本文开头的HTML片段中除表格相关标签,img标签,br标签以及不带属性的div和p标签以外的其他标签删除,只能考虑分两个步骤进行:
1、利用正则表达式:
/<\/?(((?!img\b|table\b|tr\b|td\b|br\b|div\b|p\b)\w)+[^>]*)>/gi
可以将其中标签名不是img、table、tr、td、br、div、p的所有HTML标签全部删除,但是带属性的p标签和div标签会被保留。
2、利用正则表达式:
/<(div|p)[^>]+>(((?!<\/\1>).)*)<\/\1>/gi
可以将带属性的div和p标签删除。
完整的测试JavaScript代码如下:
let html =
`<div>
巴拉巴拉<a>a标签包围的文本</a>度达<u>不</u><i>i标签包围的文本</i><u>u标签包围的文本</u>没有被内部标签包围的文本<u>度</u>要求。
</div><br>
<b>b标签包围的文本。</b><br>
<p>不带格式的p标签包围的文本</p>
<p style="text-indent:2em;">带首行缩进格式的p标签包围的文本</p>
<div style="vertical-align:middle;">带居中对齐格式的div标签包围的文本</div>
<div>
<table border="1">
<tr>
<td rowspan="2">合并单元格</td>
<td>第一行</td>
</tr>
<tr>
<td>第二行</td>
</tr>
</table>
</div>`
let reg = /<\/?(((?!img\b|table\b|tr\b|td\b|br\b|div\b|p\b)\w)+[^>]*)>/gi;
//替换掉img、table、tr、td、br、div、p以外的标签
html = html.replace(reg,'')
console.log(html);
//替换掉带属性的div和p标签
html = html.replace(/<(div|p)[^>]+>(((?!<\/\1>).)*)<\/\1>/gi,'$2');
console.log(html);
上述代码在浏览器中运行,两段控制台输出如下:
第一个console.log语句输出:
<div>
巴拉巴拉a标签包围的文本度达不i标签包围的文本u标签包围的文本没有被内部标签包围的文本度要求。
</div><br>
b标签包围的文本。<br>
<p>不带格式的p标签包围的文本</p>
<p style="text-indent:2em;">带首行缩进格式的p标签包围的文本</p>
<div style="vertical-align:middle;">带居中对齐格式的div标签包围的文本</div>
<div>
<table border="1">
<tr>
<td rowspan="2">合并单元格</td>
<td>第一行</td>
</tr>
<tr>
<td>第二行</td>
</tr>
</table>
</div>
第二个console.log语句输出:
<div>
巴拉巴拉a标签包围的文本度达不i标签包围的文本u标签包围的文本没有被内部标签包围的文本度要求。
</div><br>
b标签包围的文本。<br>
<p>不带格式的p标签包围的文本</p>
带首行缩进格式的p标签包围的文本
带居中对齐格式的div标签包围的文本
<div>
<table border="1">
<tr>
<td rowspan="2">合并单元格</td>
<td>第一行</td>
</tr>
<tr>
<td>第二行</td>
</tr>
</table>
</div>
可见第一步除带属性的div和p标签被保留外,其余想删除的标签都被删除了。第二步则将带属性的div标签和p标签也删除了。