【问题标题】:Remove html tags without attributes (php)删除没有属性的html标签(php)
【发布时间】:2011-12-19 17:04:49
【问题描述】:

我有功能

function remove_font_tags_without_attr($html)
{  
  $pattern = "/<font[\s]*?>(.*?)<\/font[\s]*>/im";    
  while(preg_match($pattern, $html)) {
    $html = preg_replace($pattern, "$1", $html);
  } 
  return $html;  
}

和html输入

$html=
<p>
First: 0<font>1<font>2</font>3</font>4
Second: 0<font style="color:red">1<font>2</font>3</font>4
Third: 0<font>1<font style="color:green">2</font>3</font>4
Fourth: 0<font style="color:red">1<font style="color:green">2</font>3</font>4
</p>

A 我需要删除所有没有属性的字体标签

我上面的函数返回

<p>
First: 01234
Second: 0<font style="color:red">123</font>4
Third: 01<font style="color:green">23</font>4
Fourth: 0<font style="color:red">1<font style="color:green">2</font>3</font>4
</p>

但问题出在第三行第三行,正确的返回是

01<font style="color:green">2</font>34

完全正确的结果:

<p>
First: 01234
Second: 0<font style="color:red">123</font>4
Third: 01<font style="color:green">2</font>34
Fourth: 0<font style="color:red">1<font style="color:green">2</font>3</font>4
</p>

你帮帮我好吗?

【问题讨论】:

标签: php regex recursion tags


【解决方案1】:

免责声明:不要使用正则表达式!

不建议使用正则表达式解析 HTML(或任何其他非正则语言)。解决方案失败的陷阱和方法有很多。也就是说,我非常喜欢使用正则表达式来解决复杂的问题,例如涉及嵌套结构的问题。如果其他人提供了有效的非正则表达式解决方案,我建议您使用该解决方案而不是以下解决方案。

正则表达式解决方案:

以下解决方案实现了一个递归正则表达式,它与preg_replace_callback() 函数结合使用(当 FONT 元素的内容包含嵌套的 FONT 元素时,它会递归调用自身)。正则表达式匹配最外层的 FONT 元素(可能包含嵌套的 FONT 元素)。回调函数只去除那些没有属性的 FONT 元素的开始和结束标记。具有属性的 FONT 标签会被保留。我想你会发现这做得很好:

函数 remove_font_tags_without_attr($text)

<?php // test.php Rev:20111219_1100
// Recursive regex matches an outermost FONT element and its contents.
$re = '% # Match outermost FONT element.
    <                     # Start of HTML start tag
    (                     # $1: FONT element start tag.
      font                # Tag name = FONT
      (                   # $2: FONT start tag attributes.
        (?:               # Group for zero or more attributes.
          \s+             # Required whitespace precedes attrib.
          [\w.\-:]+       # Attribute name.
          (?:             # Group for optional attribute value.
            \s*=\s*       # Name and value separated by =
            (?:           # Group for value alternatives.
              \'[^\']*\'  # Either single quoted,
            | "[^"]*"     # or double quoted,
            | [\w.\-:]+   # or unquoted value.
            )             # End group of value alternatives.
          )?              # Attribute value is optional.
        )*                # Zero or more attributes.
      )                   # End $2: FONT start tag attributes.
      \s*                 # Optional whitespace before closing >.
      >                   # End FONT element start tag.
    )                     # End $1: FONT element start tag.
    (                     # $3: FONT element contents.
      (?:                 # Group for zero or more content alts.
        (?R)              # Either a nested FONT element.
      |                   # or non-FONT tag stuff.
        [^<]*             # {normal*} Non-< start of tag stuff.
        (?:               # Begin "unrolling-the-loop".
          <               # {special} A "<", but only if it is
          (?:!/?font)     # NOT start of a <font or </font
          [^<]*           # more {normal*} Non-< start of tag.
        )*                # End {(special normal*)*} construct.
      )*                  # Zero or more content alternatives.
    )                     # End $3: FONT element contents.
    </font\s*>            # FONT element end tag.
    %xi';

// Remove matching start and end tags of FONT elements having no attributes.
function remove_font_tags_without_attr($text) {
    global $re;
    $text = preg_replace_callback($re,
            '_remove_font_tags_without_attr_cb', $text);
    $text = str_replace("<\0", '<', $text);
    return $text;
}
function _remove_font_tags_without_attr_cb($matches) {
    global $re;
    if (preg_match($re, $matches[3])) {
        $matches[3] = preg_replace_callback($re,
            '_remove_font_tags_without_attr_cb', $matches[3]);
    }
    if ($matches[2] == '') {    // If this FONT tag has no attributes,
        return $matches[3];     // Then strip both start and end tag.
    }
    // Hide the start and end tags by inserting a temporary null char.
    return "<\0". $matches[1] . $matches[3] . "<\0/font>";
}
$data = file_get_contents('testdata.html');
$output = remove_font_tags_without_attr($data);
file_put_contents('testdata_out.html', $output);
?>

示例输入:

<font attrib="value">
    <font>
        <font attrib="value">
            <font>
                <font attrib="value">
                </font>
            </font>
        </font>
    </font>
</font>

示例输出:

<font attrib="value">

        <font attrib="value">

                <font attrib="value">
                </font>

        </font>

</font>

需要正则表达式的复杂性才能正确处理具有可能包含&lt;&gt; 尖括号的值的标签属性。

【讨论】:

  • +1 用于可靠的工作,但我怀疑如果您使用适当的 xml 解析器,这将更容易维护,例如 us.php.net/dom
  • 是的,但是在使用 DOM 时,即使标记中没有指定任何属性,也可能会为元素分配默认属性 - 例如:elem.class==""(我在使用DOM 与 JavaScript。)但我绝对确实同意,对于任何生产代码,适当的 HTML 解析器比使用正则表达式更可取。
【解决方案2】:

让它变得贪婪:

$pattern = "/<font[\s]*?>(.*)<\/font[\s]*>/im"; 

贪婪: *(星号)重复上一项零次或多次。贪心,所以在尝试与前一项匹配较少的排列之前,将匹配尽可能多的项,直到前一项根本不匹配。

【讨论】:

  • 不得不说,这可能有效,但不是正确的方法,有一些方法可以将嵌套标签与正则表达式匹配,但这对我来说太复杂了。也许其他人可以。
  • 返回不正确的第二行Second: 0&lt;font style="color:red"&gt;12&lt;/font&gt;34 正确:Second: 0&lt;font style="color:red"&gt;123&lt;/font&gt;4
  • @user1102223:如果你想根据嵌套匹配标签,那么你需要一个(?R)递归正则表达式。请自己研究并解决这个问题。因为 html 库已经可以处理嵌套的 html,所以解决您的一个正则表达式问题与任何人都无关。太费劲了。
  • @mario - 除非有人(显然他们手上有太多时间)实际上喜欢免费解决棘手的正则表达式问题。被指控有罪!
【解决方案3】:

如果 php 不能做到这一点,那么它就不能。我将尝试这样做,如果可以的话,我会发回 php 代码。 Perl 代码只是我如何尝试的模板。

编辑
删除 Perl 代码,添加 PHP 代码。 Ideone 测试用例在这里http://www.ideone.com/9b2Ap

扩展正则表达式 -

  $regex = "~
     $comment
   | (                     #1                            
       (?:                                             
           $open                                       
         | ($openatt)        #2                          
       )                                               
       (                     #3                       
          (?:  $comment                                         
             | (?> (?:(?!$openclose|$comment) . )+ ) 
             | (?1)                                         
          )*                                               
       )                                                   
       ($close)              #4                              
     )                                                 
  ~xs";

php -

<?php

//##
  $html = '
      <font>
      <p>
        First: _0<font>_1<font>_2</font>_3</font>_4
        Second: _5<font style="color:red">_6<font>_7</font>_8</font>_9
        Third: _10<font>_11<font style="color:green">_12</font>_13</font>_14
        Fourth: _15<font style="color:red">_16<font style="color:green">_17</font>_18</font>_19
      </p>
      </font>
   ';

//##
  $comment   = '<!--.*?-->';
  $open      = '<font\s*>';
  $openatt   = '<font \s+(?:".*?"|\'.*?\'|[^>]*?)+ (?<!/)>';
  $close     = '</font\s*>';
  $openclose = '</?font (?:\s+(?:".*?"|\'.*?\'|[^>]*?)+)? (?<!/)>';

  $regex = "~
     $comment
   | (                             #1
       (?: $open | ($openatt) )                                       #2
       ( (?:$comment | (?>(?:(?!$openclose|$comment).)+) | (?1))* )   #3
       ($close)                                                       #4
     )
  ~xs";


//##
  print "Before:\n$html\n\n";
  $html = remove_font_tags_without_attr( $html );
  print "After:\n$html\n";
  exit;

//##
 function
   remove_font_tags_without_attr( $html_seg )
   {
     global $regex;
     return  preg_replace_callback( $regex, 'check_attr_cb', $html_seg );
   }

 function
   check_attr_cb( $matches )
   {
     if ($matches[1] == '')
        return $matches[0];
     $begin = $matches[2];
     $core  = $matches[3];
     $end   = $matches[4];

     if ($begin == '')
        $end = '';
     return  $begin . (remove_font_tags_without_attr( $core )) . $end;
   }
?>

【讨论】:

    猜你喜欢
    • 2012-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 1970-01-01
    • 2013-08-19
    • 1970-01-01
    相关资源
    最近更新 更多