匹配一对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等其他用途[滑稽]。