正则表达式-利用平衡组匹配html

从下面的HTML代码片段中匹配 到 span#test 标签内容

1
2
3
4
5
6
7
8
9
<span>
<span id="test">
<span id="span1"> hi~
<span id="span2"> 虫虫</span>
<em></em>
</span>
<span id="span3">早上好</span>
</span>
</span>

首先要找到 <span id="test"> 标签 和 </span>标签 ,如果这两个不会,后面不用看了……补基础吧~~

1
2
3
4
//起始
<span[^>]*id="test"[^>]*>
//结束
</span>


我们会发现代码中夹杂着多个HTML标签,em还好,因为正则里面写死了是 span, 所以它是可以直接忽略掉的, 但是多余的 </span>会造成干扰。那么如果是在程序中,我们是怎么处理的呢? 可以使用计数器(或者说是stack结构,但是我们只需要关心里面的元素个数,所以使用计数器就行了)。从id=”test” 后面的开始处理,遇到<span> 计数+1 ,遇到 </span> 时,如果计数>0则-1否则 就匹配到了

找到#test 的span 后开始,(如果找不到这个id的span,其实可以直接结束匹配了)
var c = 0; // 初始栈
遇到 <span id="span1"> ,入栈,栈内标签数量: c=c+1=1;
遇到<span id="span2"> ,入栈: c=c+1=2;
遇到虫虫后面的</span> (关闭span3) , 因为 c>0 所以c=c-1=1;
下一个</span> (关闭span1), 因为 c>0 所以所以c=c-1=0;
遇到 <span id="span3"> c=c+1 = 1
下一个</span> (关闭span3), 因为 c>0 所以所以c=c-1=0;
下一个</span> (关闭test),因为此时 c==0 , 所以匹配到这里结束了。
有同学会纠结<em>标签怎么办?em并不是我们要查找的标签名称,直接当普通字符串忽略掉就是了……


根据这个思路 ,我们可以使用正则表达式中平衡组的概念,首先将span定义到一个分组中,那么查找 #test 的开始标签就可以是
<(?'tag'span)[^>]*id="test"[^>]*> 结束符使用反向引用 </k'tag'>

说明一下:
(?'分组名'表达式) 表示将表达式匹配到的内容放入分组中。
k'分组名' 表示引用之前的分组。

上面的例子中 (?'tag'span) 表达式是 span 如果匹配到这四个字符的话,放进名称为 tag 的组中。那么后面k'tag' 其实就是匹配 span字符。

中间部分情况比较多:

一类是普通字符,包含类似<em>这样的标签,直接使用 ([dD])

一类是span的标签,我们同样,定义一个计数器 c 专门处理它:
遇到开始标签时 c+1 使用 (?'c'<k'tag'[^>]*>) // 其实是将匹配到的标签推送到c组
遇到结束标签时 c-1 使用 (?'-c'</k'tag'[^>]*>) // 其实是如果匹配到结束标签,从c组移除一个匹配项

上面这几个情况可以重复任意次,于是用(......)*,将它们串起来得到
((?'c'<k'tag'[^>]*>)|(?'-c'</k'tag'[^>]*>)|([dD]))*

最后,加上头尾的两个标签,最终的正则表达式是:
<(?'tag'span)[^>]*id="test"[^>]*>((?'c'<k'tag'[^>]*>)|(?'-c'</k'tag'[^>]*>)|([dD]))*</k'tag'>

如果 #test 不是 span 而是其他标签,为了匹配到任意标签,可以把span替换成 w+
<(?'tag'w+)[^>]*id="test"[^>]*>((?'c'<k'tag'[^>]*>)|(?'-c'</k'tag'[^>]*>)|([dD]))*</k'tag'>

匹配结果是:

1
2
3
4
5
6
7
 <span id="test">
<span id="span1"> hi~
<span id="span2"> 虫虫</span>
<em></em>
</span>
<span id="span3">早上好</span>
</span>


如果不需要带标签,只要里面的内容,那么加上两个断言
(?<=(<(?'tag'w+)[^>]*id="test"[^>]*>))((?'c'<k'tag'[^>]*>)|(?'-c'</k'tag'[^>]*>)|([dD]))*(?=(</k'tag'>))
匹配结果是

<span id="span1"> hi~
   <span id="span2"> 虫虫</span>
   <em></em>
</span>
<span id="span3">早上好</span>

说明一下
(?<=表达式) :前面要包含表达式匹配到的项目,但是不会被匹配进去
(?=表达式) :后面要包含表达式匹配到的项目,但是不会被匹配进去