通过删除额外/冗余的格式化标签来清理 HTML

Posted

技术标签:

【中文标题】通过删除额外/冗余的格式化标签来清理 HTML【英文标题】:Cleaning HTML by removing extra/redundant formatting tags 【发布时间】:2012-05-02 03:40:54 【问题描述】:

我一直在使用CKEditor 所见即所得编辑器的网站,允许用户使用 html 编辑器添加一些 cmets。我最终在我的数据库中有一些极其冗余的嵌套 HTML 代码,这会减慢这些 cmets 的查看/编辑速度。

我有这样的 cmets(这是一个非常小的例子。我有超过 100 个嵌套标签的 cmets):

<p>
 <strong>
  <span style="font-size: 14px">
   <span style="color: #006400">
     <span style="font-size: 14px">
      <span style="font-size: 16px">
       <span style="color: #006400">
        <span style="font-size: 14px">
         <span style="font-size: 16px">
          <span style="color: #006400">This is a </span>
         </span>
        </span>
       </span>
      </span>
     </span>
    </span>
    <span style="color: #006400">
     <span style="font-size: 16px">
      <span style="color: #b22222">Test</span>
     </span>
    </span>
   </span>
  </span>
 </strong>
</p>

我的问题是:

是否有任何库/代码/软件可以对 HTML 代码进行智能(即格式感知)清理,删除所有对格式没有影响的冗余标签(因为它们已被覆盖通过内部标签)?我尝试了许多现有的在线解决方案(例如 HTML Tidy)。他们都没有做我想做的事。

如果没有,我需要编写一些用于 HTML 解析和清理的代码。我打算用php Simple HTML DOM遍历HTML树,找到所有没有效果的标签。您是否建议任何其他更适合我的目的的 HTML 解析器?

谢谢

.

更新:

我已经编写了一些代码来分析我拥有的 HTML 代码。我拥有的所有 HTML 标签都是:

&lt;span&gt; 带有font-size 和/或color 的样式 &lt;font&gt; 具有color 和/或size 属性 &lt;a&gt; 用于链接(href&lt;strong&gt; &lt;p&gt;(单个标签包裹整个评论) &lt;u&gt;

我可以轻松编写一些代码将 HTML 代码转换为 bbcode(例如 [b][color=blue][size=3] 等)。所以我上面的 HTML 会变成这样:

[b][size=14][color=#006400][size=14][size=16][color=#006400]
[size=14][size=16][color=#006400]This is a [/color][/size]
[/size][/color][/size][/size][color=#006400][size=16]
[color=#b22222]Test[/color][/size][/color][/color][/size][/b]

现在的问题是:有没有一种简单的方法(算法/库/等)来清理将要生成的杂乱(与原始 HTML 一样杂乱)的 bbcode?​​p >

再次感谢

【问题讨论】:

这将是一个很难解决的问题。 +1 我的建议,下次使用markdown而不是WYSIWYG。 没有看到更新说明 是可能的。是否可以提供带有 、 标记的示例代码,以便我们调整解决方案。 文本内容可以和html混合吗?意思是这可能:这个一个测试?还是最后一个元素内容总是只有文本?如果是后者,那么这是对以下内容的更新:jsfiddle.net/mmeah/fUpe8/3 【参考方案1】:

简介

到目前为止,最好的解决方案是使用 HTML Tidy http://tidy.sourceforge.net/

除了转换文档的格式之外,Tidy 还能够通过使用 clean 选项将已弃用的 HTML 标记自动转换为相应的层叠样式表 (CSS)。生成的输出包含一个内联样式声明。

它还确保 HTML 文档与xhtml 兼容

示例

$code ='<p>
 <strong>
  <span style="font-size: 14px">
   <span style="color: #006400">
     <span style="font-size: 14px">
      <span style="font-size: 16px">
       <span style="color: #006400">
        <span style="font-size: 14px">
         <span style="font-size: 16px">
          <span style="color: #006400">This is a </span>
         </span>
        </span>
       </span>
      </span>
     </span>
    </span>
    <span style="color: #006400">
     <span style="font-size: 16px">
      <span style="color: #b22222">Test</span>
     </span>
    </span>
   </span>
  </span>
 </strong>
</p>';

如果你跑了

$clean = cleaning($code);
print($clean['body']);

输出

<p>
    <strong>
        <span class="c3">
            <span class="c1">This is a</span> 
                <span class="c2">Test</span>
            </span>
        </strong>
</p>

你可以得到 CSS

$clean = cleaning($code);
print($clean['style']);

输出

<style type="text/css">
    span.c3 
        font-size: 14px
    

    span.c2 
        color: #006400;
        font-size: 16px
    

    span.c1 
        color: #006400;
        font-size: 14px
    
</style>

我们的完整 HTML

$clean = cleaning($code);
print($clean['full']);

输出

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title></title>
    <style type="text/css">
/*<![CDATA[*/
    span.c3 font-size: 14px
    span.c2 color: #006400; font-size: 16px
    span.c1 color: #006400; font-size: 14px
    /*]]>*/
    </style>
  </head>
  <body>
    <p>
      <strong><span class="c3"><span class="c1">This is a</span>
      <span class="c2">Test</span></span></strong>
    </p>
  </body>
</html>

使用的功能

function cleaning($string, $tidyConfig = null) 
    $out = array ();
    $config = array (
            'indent' => true,
            'show-body-only' => false,
            'clean' => true,
            'output-xhtml' => true,
            'preserve-entities' => true 
    );
    if ($tidyConfig == null) 
        $tidyConfig = &$config;
    
    $tidy = new tidy ();
    $out ['full'] = $tidy->repairString ( $string, $tidyConfig, 'UTF8' );
    unset ( $tidy );
    unset ( $tidyConfig );
    $out ['body'] = preg_replace ( "/.*<body[^>]*>|<\/body>.*/si", "", $out ['full'] );
    $out ['style'] = '<style type="text/css">' . preg_replace ( "/.*<style[^>]*>|<\/style>.*/si", "", $out ['full'] ) . '</style>';
    return ($out);

================================================ =

编辑 1:Dirty Hack(不推荐)

================================================ =

根据您的最后一条评论,您希望保留贬值风格.. HTML Tidy 可能不允许您这样做,因为它是 depreciated,但您可以这样做

$out = cleaning ( $code );
$getStyle = new css2string ();
$getStyle->parseStr ( $out ['style'] );
$body = $out ['body'];
$search = array ();
$replace = array ();

foreach ( $getStyle->css as $key => $value ) 
    list ( $selector, $name ) = explode ( ".", $key );
    $search [] = "<$selector class=\"$name\">";
    $style = array ();
    foreach ( $value as $type => $att ) 
        $style [] = "$type:$att";
    
    $replace [] = "<$selector style=\"" . implode ( ";", $style ) . ";\">";

输出

<p>
  <strong>
      <span style="font-size:14px;">
        <span style="color:#006400;font-size:14px;">This is a</span>
        <span style="color:#006400;font-size:16px;">Test</span>
        </span>
  </strong>
</p>

使用的类

//Credit : http://***.com/a/8511837/1226894
class css2string 
var $css;

function parseStr($string) 
    preg_match_all ( '/(?ims)([a-z0-9, \s\.\:#_\-@]+)\([^\]*)\/', $string, $arr );
    $this->css = array ();
    foreach ( $arr [0] as $i => $x ) 
        $selector = trim ( $arr [1] [$i] );
        $rules = explode ( ';', trim ( $arr [2] [$i] ) );
        $this->css [$selector] = array ();
        foreach ( $rules as $strRule ) 
            if (! empty ( $strRule )) 
                $rule = explode ( ":", $strRule );
                $this->css [$selector] [trim ( $rule [0] )] = trim ( $rule [1] );
            
        
    


function arrayImplode($glue, $separator, $array) 
    if (! is_array ( $array ))
        return $array;
    $styleString = array ();
    foreach ( $array as $key => $val ) 
        if (is_array ( $val ))
            $val = implode ( ',', $val );
        $styleString [] = "$key$glue$val";

    
    return implode ( $separator, $styleString );


function getSelector($selectorName) 
    return $this->arrayImplode ( ":", ";", $this->css [$selectorName] );



【讨论】:

非常努力。谢谢你。我已经开始用 PHP 编写自己的清理代码(使用“简单的 HTML DOM 解析器”),但是这花费了太多时间!我现在就试试你的解决方案。 这比你想象的更简单、更快捷......你只需要调整 HTMLTidy 配置即可。它不仅适用于 span p div 。 . 所有 HTML 和 CSS 标签 你的配置中的关键选项是什么?我尝试使用--clean,但它并没有为我消除多余的&lt;font color= "#000000"&gt;&lt;font color="#000000"&gt; 标签。忘记我的愚蠢。是-c--clean yes【参考方案2】:

这是一个使用浏览器获取嵌套元素属性的解决方案。无需级联属性,因为 css 计算样式已准备好从浏览器读取。

这里是一个例子:http://jsfiddle.net/mmeah/fUpe8/3/

var fixedCode = readNestProp($("#redo"));
$("#simp").html( fixedCode );

function readNestProp(el)
 var output = "";
 $(el).children().each( function()
    if($(this).children().length==0)
        var _that=this;
        var _cssAttributeNames = ["font-size","color"];
        var _tag = $(_that).prop("nodeName").toLowerCase();
        var _text = $(_that).text();
        var _style = "";
        $.each(_cssAttributeNames, function(_index,_value)
            var css_value = $(_that).css(_value);
            if(typeof css_value!= "undefined")
                _style += _value + ":";
                _style += css_value + ";";
            
        );
        output += "<"+_tag+" style='"+_style+"'>"+_text+"</"+_tag+">";
    else if(
        $(this).prop("nodeName").toLowerCase() !=
        $(this).find(">:first-child").prop("nodeName").toLowerCase()
    )
        var _tag = $(this).prop("nodeName").toLowerCase();
        output += "<"+_tag+">" + readNestProp(this) + "</"+_tag+">";
    else
        output += readNestProp(this);
    ;
 );
 return output;

输入所有可能的 css 属性的更好解决方案,例如: var _cssAttributeNames = ["字体大小","颜色"]; 是使用这里提到的解决方案: Can jQuery get all CSS styles associated with an element?

【讨论】:

更新版本,检测不同于嵌套子元素的父元素:jsfiddle.net/pLkwD/7 更新版本,检测不同于嵌套子元素的父元素并将结果输出到文本区域:jsfiddle.net/mmeah/fUpe8/1【参考方案3】:

您应该查看HTMLPurifier,它是解析 HTML 并从中删除不必要和不安全内容的好工具。查看删除空跨度配置和东西。我承认,配置起来可能有点麻烦,但这只是因为它用途广泛。

它也很重,所以你想把它的输出保存到数据库中(而不是从数据库中读取原始数据,然后每次都用净化器解析它。

【讨论】:

感谢您的回答。我之前尝试过 HTMLPurifier(使用他们的在线演示)。它不会删除冗余标签(例如&lt;b&gt;&lt;b&gt;test&lt;/b&gt;&lt;/b&gt;)。可以在配置中启用吗? 嗯,现在查看文档可能无法涵盖您的确切问题。尝试查看这些设置:htmlpurifier.org/live/configdoc/… 如果这对您没有帮助,您可能需要使用更智能的 WYSIWYG 编辑器【参考方案4】:

我没有时间完成这个……也许其他人可以帮忙。这个 javascript 也删除了完全重复的标签和不允许的标签......

有一些问题/事情要做, 1) 重新生成的标签需要关闭 2) 如果标签名和属性与该节点子节点中的另一个相同,它只会删除一个标签,因此它不够“智能”以删除所有不必要的标签。 3) 它将查看允许的 CSS 变量并从元素中提取所有这些值,然后将其写入输出 HTML,例如:

var allowed_css = ["color","font-size"];
<span style="font-size: 12px"><span style="color: #123123">

将被翻译成:

<span style="color:#000000;font-size:12px;"> <!-- inherited colour from parent -->
<span style="color:#123123;font-size:12px;"> <!-- inherited font-size from parent -->

代码:

<html>

<head>
<script type="text/javascript">
var allowed_css = ["font-size", "color"];
var allowed_tags = ["p","strong","span","br","b"];
function initialise() 
    var comment = document.getElementById("comment");
    var commentHTML = document.getElementById("commentHTML");
    var output = document.getElementById("output");
    var outputHTML = document.getElementById("outputHTML");
    print(commentHTML, comment.innerHTML, false);
    var out = getNodes(comment);
    print(output, out, true);
    print(outputHTML, out, false);

function print(out, stringCode, allowHTML) 
    out.innerHTML = allowHTML? stringCode : getHTMLCode(stringCode);

function getHTMLCode(stringCode) 
    return "<code>"+((stringCode).replace(/</g,"&lt;")).replace(/>/g,"&gt;")+"</code>";

function getNodes(elem) 
    var output = "";
    var nodesArr = new Array(elem.childNodes.length);
    for (var i=0; i<nodesArr.length; i++) 
        nodesArr[i] = new Array();
        nodesArr[i].push(elem.childNodes[i]);
        getChildNodes(elem.childNodes[i], nodesArr[i]);
        nodesArr[i] = removeDuplicates(nodesArr[i]);
        output += nodesArr[i].join("");
    
    return output;

function removeDuplicates(arrayName) 
    var newArray = new Array();
    label:
    for (var i=0; i<arrayName.length; i++)   
        for (var j=0; j<newArray.length; j++) 
            if(newArray[j]==arrayName[i])
                continue label;
        
        newArray[newArray.length] = arrayName[i];
    
    return newArray;

function getChildNodes(elemParent, nodesArr) 
    var children = elemParent.childNodes;
    for (var i=0; i<children.length; i++) 
        nodesArr.push(children[i]);
        if (children[i].hasChildNodes())
            getChildNodes(children[i], nodesArr);
    
    return cleanHTML(nodesArr);

function cleanHTML(arr) 
    for (var i=0; i<arr.length; i++) 
        var elem = arr[i];
        if (elem.nodeType == 1) 
            if (tagNotAllowed(elem.nodeName)) 
                arr.splice(i,1);
                i--;
                continue;
            
            elem = "<"+elem.nodeName+ getAttributes(elem) +">";
        
        else if (elem.nodeType == 3) 
            elem = elem.nodeValue;
        
        arr[i] = elem;
    
    return arr;

function tagNotAllowed(tagName) 
    var allowed = " "+allowed_tags.join(" ").toUpperCase()+" ";
    if (allowed.search(" "+tagName.toUpperCase()+" ") == -1)
        return true;
    else
        return false;

function getAttributes(elem) 
    var attributes = "";
    for (var i=0; i<elem.attributes.length; i++) 
      var attrib = elem.attributes[i];
      if (attrib.specified == true) 
        if (attrib.name == "style") 
            attributes += " style=\""+getCSS(elem)+"\"";
         else 
            attributes += " "+attrib.name+"=\""+attrib.value+"\"";
        
      
    
    return attributes

function getCSS(elem) 
    var style="";
    if (elem.currentStyle) 
        for (var i=0; i<allowed_css.length; i++) 
            var styleProp = allowed_css[i];
            style += styleProp+":"+elem.currentStyle[styleProp]+";";
        
     else if (window.getComputedStyle) 
        for (var i=0; i<allowed_css.length; i++) 
            var styleProp = allowed_css[i];
            style += styleProp+":"+document.defaultView.getComputedStyle(elem,null).getPropertyValue(styleProp)+";";
        
    
    return style;

</script>
</head>

<body onload="initialise()">

<div style="float: left; width: 300px;">
<h2>Input</h2>
<div id="comment">
<p> 
 <strong> 
  <span style="font-size: 14px"> 
   <span style="color: #006400"> 
     <span style="font-size: 14px"> 
      <span style="font-size: 16px"> 
       <span style="color: #006400"> 
        <span style="font-size: 14px"> 
         <span style="font-size: 16px"> 
          <span style="color: #006400">This is a </span> 
         </span> 
        </span> 
       </span> 
      </span> 
     </span> 
    </span> 
    <span style="color: #006400"> 
     <span style="font-size: 16px"> 
      <span style="color: #b22222"><b>Test</b></span> 
     </span> 
    </span> 
   </span> 
  </span> 
 </strong> 
</p> 
<p>Second paragraph.
<span style="color: #006400">This is a span</span></p>
</div>
<h3>HTML code:</h3>
<div id="commentHTML"> </div>
</div>

<div style="float: left; width: 300px;">
<h2>Output</h2>
<div id="output"> </div>
<h3>HTML code:</h3>
<div id="outputHTML"> </div>
</div>

<div style="float: left; width: 300px;">
<h2>Tasks</h2>
<big>
<ul>
<li>Close Tags</li>
<li>Ignore inherited CSS style in method getCSS(elem)</li>
<li>Test with different input HTML</li>
</ul>
</big>
</div>

</body>

</html>

【讨论】:

【参考方案5】:

它可能无法完全解决您的确切问题,但我会在您的位置上做的是简单地完全消除所有 HTML 标记,只保留痛苦文本和换行符。

完成后,切换到 markdown 我们的 bbcode 以更好地格式化您的 cmets。 WYSIWYG 很少有用。

这样做的原因是因为您说您在 cmets 中拥有的只是演示数据,坦率地说,这并不是那么重要。

【讨论】:

我同意使用 WYSIWYG 编辑器是一个非常糟糕的主意。我正在将我网站中的所有编辑器切换到 BBCode,但我需要首先将所有现有的 cmets 转换为 BBCode(同时保持它们的样式/格式)。谢谢【参考方案6】:

Cleanup HTML 折叠标签,这似乎是您所要求的。但是,它会创建一个经过验证的 HTML 文档,其中的 CSS 已移至内联样式。许多其他 HTML 格式化程序不会这样做,因为它会改变 HTML 文档的结构。

【讨论】:

【参考方案7】:

我记得 Adob​​e (Macromedia) Dreamweaver,至少稍微旧的版本有一个选项,“清理 HTML”,还有一个“清理 word html”,可以从任何网页中删除多余的标签等。

【讨论】:

很好。不过,这并不是问题的真正答案。 谢谢,Manoj。我实际上已经尝试过该功能。它对 HTML 标记进行“盲目”清理,但无法清理 &lt;i&gt;&lt;b&gt;&lt;i&gt;test&lt;/i&gt;&lt;/b&gt;&lt;/i&gt; 之类的内容。 我通过 Dreamweaver 尝试了 test,它取出了多余的 。然而,原来的例子是另一个故事,并没有奏效。由于您嵌套了相同的标签,但手动设置了不同的内联样式,这使其复杂化。也许需要另一种方法。你能得到没有带有样式属性的所有这些跨度的 html 吗?你也可以试试 HTML Tidy,它还有一个库,你可以用它来帮助你以你想要的方式整理它。 哦..也许我记错了。但是,正如您所提到的,我面临的实际情况更为复杂,Dreamweaver 无法处理。我会看看 HTML Tidy 是否可以做点什么。再次感谢。【参考方案8】:

我知道您正在寻找 HTML DOM 清理器,但也许 js 可以提供帮助?

function getSpans() 
var spans=document.getElementsByTagName('span') 
    for (var i=0;i<spans.length;i++) 
    spans[i].removeNode(true);
        if(i == spans.length) 
        //add the styling you want here
        
     
 

【讨论】:

【参考方案9】:

与其浪费您宝贵的服务器时间来解析错误的 HTML,我建议您解决问题的根源。

一个简单的解决方案是限制每个评论者可以包含的字符,以包含整个 html 字符数,而不仅仅是文本数(至少这会阻止无限大的嵌套标签)。

您可以通过允许用户在 HTML 视图和文本视图之间切换来改进这一点 - 我相信大多数人在 HTML 视图中会看到一堆垃圾,只需 CTRL+A 和 DEL 即可。

我认为如果你有自己的格式化字符,你会解析并替换为格式,例如堆栈溢出有**bold text**,对海报可见。或者只是 BB 代码就可以了,对海报可见。

【讨论】:

我完全同意。使用 HTML 所见即所得的编辑器显然是一个大错误。我正在努力用带有一小部分格式标签的 BBCode 编辑器替换它。但是,我需要一种清理和修复现有 cmets 而不删除/销毁/破坏它们的方法。 @Aziz 只需删除所有跨度标签并保留纯文本和换行符......我相信它不会有太大的损失。无论如何,仅仅为你的老 cmets 开发这样的东西真的不值得。【参考方案10】:

尽量不要使用 DOM 解析 HTML,但可以使用 SAX (http://www.brainbell.com/tutorials/php/Parsing_XML_With_SAX.htm)

SAX 从开头解析文档并发送诸如“元素开始”和“元素结束”之类的事件以调用您定义的回调函数

然后你可以为所有事件构建一种堆栈如果你有文本,你可以保存你的堆栈对该文本的影响。

之后,您处理堆栈以构建具有您想要的效果的新 HTML。

【讨论】:

谢谢。 DOM 提供了一种树状结构,它比嵌套标签更容易处理,这对于确定哪些样式已被其他标签/样式覆盖非常重要。 你当然可以用这两种方式来实现你的目标,但我认为这需要更多的资源来爬上 DOM 树,每一个元素【参考方案11】:

如果你想使用 jQuery,试试这个:

<p>
<strong>
  <span style="font-size: 14px">
   <span style="color: #006400">
     <span style="font-size: 14px">
      <span style="font-size: 16px">
       <span style="color: #006400">
        <span style="font-size: 14px">
         <span style="font-size: 16px">
          <span style="color: #006400">This is a </span>
         </span>
        </span>
       </span>
      </span>
     </span>
    </span>
    <span style="color: #006400">
     <span style="font-size: 16px">
      <span style="color: #b22222">Test</span>
     </span>
    </span>
   </span>
  </span>
 </strong>
</p>
<br><br>
<div id="out"></div> <!-- Just to print it out -->


$("span").each(function(i)
    var ntext = $(this).text();
    ntext = $.trim(ntext.replace(/(\r\n|\n|\r)/gm," "));
    if(i==0)
        $("#out").text(ntext);
            
);

你得到这个结果:

<div id="out">This is a                                                                    Test</div>

然后你可以随意格式化它。希望这可以帮助您对此有所不同...

【讨论】:

谢谢。我的目标是保持文本格式不变。我只想删除所有由于被覆盖而无效的 extra 格式标记。您的解决方案在没有任何格式的情况下提取字符串。

以上是关于通过删除额外/冗余的格式化标签来清理 HTML的主要内容,如果未能解决你的问题,请参考以下文章

删除和清理镜像

Office2016安装完成后的冗余文件清理

在保留基本格式的同时,我可以使用啥来清理收到的 HTML?

Teleport Ultra/Teleport Pro的冗余代码批量清理方法

Teleport Ultra/Teleport Pro的冗余代码批量清理方法

使用python,从字符串中删除HTML标签/格式[重复]