匹配一对HTML标签的正则表达式

匹配一对HTML标签主要是找到正确的闭合标签,其重点就是匹配不包含某个模式的正则表达式。

比如有如下结构

<div class="a">
    ...
    <div class="b">
        ...<!--这里不含div标签-->
    </div>
    ...
</div>

我要匹配class=”b”的那个div的内容,就是要匹配到第一个</div>。如果我用<div class=”b”>.*<\/div> 匹配,则很容易略过第一个</div>标签而匹配到class=”a”的标签。

(下面讨论一种预见匹配的方式,后记有一种更简单的方式可以参考。)

所以主要难点就是匹配<div>与</div>之间不含</div>的一段字符串。要匹配不含某个模式的字符串其实不是正则表达式擅长的工作,但它还是提供了一种可行的方案。

那就是预见匹配,预见匹配分为肯定预见否定预见。我们引用一段perl教程:

肯定的预见匹配语法为/pattern(?=string)/,其意义为匹配后面为string的模式,相反的,(?!string)意义为匹配后面非string的模式,如:
$string = “25abc8”;
$string =~ /abc(?=[0-9])/;
$matched = $&; # $&为已匹配的模式,此处为abc,而不是abc8

我们这里利用的就是否定预见,我们可以用((?!string).)+ 来表示不含string的一个串。

那么我们要匹配class=”b”的div就可以用<div class=”b”>((?!<\/div).)+<\/div>来匹配了,为了支持多行我们再把换行符也匹配进去<div class=”b”>((?!<\/div)(.|\n))+<\/div> ,这样就可以正确的获得class=”b”的那段了。

嵌套的处理方式

那么怎么匹配class=”a”的那段呢,要获取好像不太可行,但我的需求是要去掉class=”a”的div,这样是可行的。我们可以先用如上方法去掉class=”b”的div,这样一来class=”a”的div中便没有其他div标签了,再用同样方式去掉class=”a”的div。

做成一个比较方便又兼容度高的方法就是

function cutDivByClass($message, $class){
        return preg_replace("/<div[^>]*class=['\"][^'\"]*{$class}[^>]*>((?!<\/div)(.|\n))+<\/div>/i", '',$message);
}

我们要去掉class=”a”的div就可以这样了

$message = cutDivByClass($message, "b");
$message = cutDivByClass($message, "a");

后记

在进一步的学习中发现了导致匹配到最远</div>的原因其实是因为使用了贪婪模式导致的,如果将<div class=”b”>.*<\/div> 改为懒惰模式,就会匹配到最近的</div>。他们的区别只是将其中的.* 改为.*?  哈哈。

但是不代表以上的讨论没有用,你可以用上面的思路来匹配不含img 的div等其他用途[滑稽]。

发表评论