(.NET FrameWork支持下)正则表达式中的平衡组

问题的提出:
<table> 
<tr> 
<td   id="td1"> </td> 
<td   id="td2"> 
<table>
<tr>
<td>snhame</td>
<td>f</td>
</tr>
</table> 
</td> 
<td></td>
</tr> </table> 

以上为部分的HTML代码.现在我们的问题是要提取出其<td id="td2">的<td>标签并将其删除掉
以往我们惯用的方法都是直接去取,像<td\s*id="td2">[\s\S]+?\</td>
不过问题出来了,我们提取到的不是我们想要的内容,而是
<td   id="td2"> 
<table>
<tr>
<td>snhame</td>

原因也很简单,它和离他最近的</td>标签匹配上了,不过它不知道这个标签不是它的-_-
是不是就是?符号的原因呢,我们去掉让他无限制贪婪,可这下问题更大了,什么乱七八糟的东东它都匹配到了

<td   id="td2"> 
<table>
<tr>
<td>snhame</td>
<td>f</td>
</tr>

</td> 
<td></td>

它还真是贪婪,多吃多喝,也不什么给什么了.看来问题可不想我们想像的那样简单^_^
在网络上搜索了半天,资料倒是找到了不少,不过都是E文 -_-,谁叫我没有学好,该打
硬着头皮看呗http://weblogs.asp.net/whaggard/archive/2005/02/20/377025.aspx
哎,能用的翻译工具都用上吧.总算看出点眉目来.

.NET正则表达式引擎

注意:这里介绍的平衡组语法是由.Net Framework支持的;其它语言/库不一定支持这种功能,或者支持此功能但需要使用不同的语法。 
有时我们需要匹配像( 100 * ( 50 + 15 ) )这样的可嵌套的层次性结构,这时简单地使用\(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容(这里我们讨论的是贪婪模式,懒惰模式也有下面的问题)。假如原来的字符串里的左括号和右括号出现的次数不相等,比如( 5 / ( 3 + 2 ) ) ),那我们的匹配结果里两者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的,配对的括号之间的内容呢? 
为了避免(和\(把你的大脑彻底搞糊涂,我们还是用尖括号代替圆括号吧。现在我们的问题变成了如何把xx <aa <bbb> <bbb> aa> yy这样的字符串里,最长的配对的尖括号内的内容捕获出来? 
这里需要用到以下的语法构造:
  • (?<group>)或者(?’group’) - 把捕获的结果内容命名为group,并压入堆栈;
  • (?<-group>)或者(?’-group’)- 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败;
  • (?(group)yes|no)或者(?(group)yes|no)- 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分;
  • (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败 ;

    上面的表达可以用更简单的表达式让大家理解一下堆栈,堆栈就像往箱子里面装东西,最先装的放在最下面,而最后装的放在最上面,在对堆栈有一定认识以后,这就非常好理解了,(?<group>)就为往箱子里面装一个东东,(?<-group>)就是在箱子里面取一个东东(当然第一次只能取到最上面的),(?(group)yes|no)就是最后再来检查箱子里是否还有东西,如果有则继续匹配yes部分,否则就匹配no的部分.

    很简单的道理:
    我们需要做的是每碰到了左括号,就在黑板上写一个"group",每碰到一个右括号,就擦掉一个,到了最后就看看黑板上还有没有--如果有那就证明左括号比右括号多,那匹配就应该失败.

    下在这个是Jeffrey Friedl在精通正则表达式第二版当中所提供的例子
    Jeffrey Friedl provides the following example in his excellent book Mastering Regular Expressions, 2nd Edition.

    Balanced Parentheses. (匹配括号)

    Dim R As Regex = New Regex(" \(                   " & _
    "   (?>                " & _
    "       [^()]+         " & _
    "     |                " & _
    "       \( (?<DEPTH>)  " & _
    "     |                " & _
    "       \) (?<-DEPTH>) " & _
    "   )*                 " & _
    "   (?(DEPTH)(?!))     " & _
    " \)                   ", _
    RegexOptions.IgnorePatternWhitespace);
    现在这个正则表达式可以匹配正确的嵌套组但是这种结构不能匹配到像XML标签其嵌套字符多于一个简单字符的嵌套组
    Regex re = new Regex(@"^
    (?>
    \( (?<LEVEL>)   # On opening paren push level
    |
    \) (?<-LEVEL>)  # On closing paren pop level
    |
    (?! \( | \) ) . # Match any char except ( or )
    )+
    (?(LEVEL)(?!))      # If level exists then fail
    $", RegexOptions.IgnorePatternWhitespace);
    这个表达式也能匹配正确的嵌套组,但这里最大的不同就是这里用否定查找来确定这个字符不是一个匹配来代替[^()]+
    这一类的的匹配.它也只能匹配到一个字符的限制符.

    Balanced XML tags.
    Regex re = new Regex(@"^
    (?>
    <tag>  (?<LEVEL>)      # On opening <tag> push level
    |
    </tag> (?<-LEVEL>)     # On closing </tag> pop level
    |
    (?! <tag> | </tag> ) . # Match any char unless the strings
    )+                         # <tag> or </tag> in the lookahead string
    (?(LEVEL)(?!))             # If level exists then fail
    $", RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);
     
    这个正则表达式可以匹配到正确的xml嵌套的标签<tag>和</tag>.
    它只是把匹配括号的表达式中的"("修改为"<tag>"和")"修改为了"</tag>".

    General version of Balanced constructs (HTML Anchor tags used in example).

    现在这个正则表达式用一个简单的字符串格式符替换掉表达式中弹出和加入的限定符.

    这种方式就更加的常用,它可以很简单的就把开始和结束限制符表示在其表达式当中.
    这一切在.NET正则表达式当中都显得非常的简单.

    Regex re = new Regex(string.Format(@"^
    (?>
    {0} (?<LEVEL>)      # On opening delimiter push level
    |
    {1} (?<-LEVEL>)     # On closing delimiter pop level
    |
    (?! {0} | {1} ) .   # Match any char unless the opening
    )+                      # or closing delimiters are in the lookahead string
    (?(LEVEL)(?!))          # If level exists then fail
    $", "<a[^>]*>", "</a>"),
    RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);



     

    Retrieving data between delimiters where there are possible nested delimiters

    有一个比较通常的应用就是在两限制符之间的字符中包含里限制符标签(这也就是我们上面所提到的问题)

    If there were no nested tags then this regular expression would be rather simple but since there are one essentially needs to wrap the expression from above with the set of outer tags and then capture the inner text. 

    Regex re = new Regex(string.Format(@"^
    {0}                       # Match first opeing delimiter
    (?<inner>
    (?>
    {0} (?<LEVEL>)      # On opening delimiter push level
    |
    {1} (?<-LEVEL>)     # On closing delimiter pop level
    |
    (?! {0} | {1} ) .   # Match any char unless the opening
    )+                      # or closing delimiters are in the lookahead string
    (?(LEVEL)(?!))          # If level exists then fail
    )
    {1}                       # Match last closing delimiter
    $", "<quote>", "</quote>"),
    RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);
    re.Match("<quote>inner text</quote>").Groups["inner"].Value == "inner text" re.Match("<quote>a<quote>b</quote>c</quote>").Groups["inner"].Value == "a<quote>b</quote>c"

     

    Matching multiple balanced constructs(匹配多平衡组结构)

    这个例子主要是为了显示如何匹配多种适当的平衡标签与一个单一平衡标签的表达式.
    不过,经过表达式的创造和测试,它出现了一个有趣的问题,举例来说,以确保( )和[ ]得到正确嵌套单独实现容易如上所示,
    而是要确保他们获得适当嵌套连同是不可能的. net的正则表达式为了更好地了解该问题考虑下列不正当嵌套实例( [ ) ]或[(()]).
    他们是单独嵌套表达式,但不当的嵌套时,考虑到它们放在一起.下面一个表达式认识到这一点:

    Regex re = new Regex(@"^
    (?>
    (?<LEVEL> \()                 # On opening paren capture ( on stack
    |
    (?(\k<LEVEL>=\()              # Make sure the top of stack is (
    (?<-LEVEL> \) ))              # On closing paren pop ( off stack
    |
    (?<LEVEL> \[ )                # On opening bracket capture [ on stack
    |
    (?(\k<LEVEL>=\])              # Make sure the top of stack is [
    (?<-LEVEL> \] ))              # On closing bracket pop [ off stack
    |
    (?! \( | \) | \[ | \] ) .     # Match any char except (, ), [ or ]
    )+
    (?(LEVEL)(?!))                    # If level exists then fail
    $", RegexOptions.IgnorePatternWhitespace);

    这个正则表达式不能正常运行它只是作为一个示例来说明.

    上面说了这么多,想必大家出都看出这个匹配多平衡组的原理了,其实质上也只是一个堆栈的原理.首先把左操作符压入栈.
    在遇上右操作符的时候并不是马上把左操作符弹出,而是多了一个判定,判断其栈的最顶元素是否就是当前匹配到的右操作符
    相对应的左操作符,如果是,则弹出,相反失败.
    说了这么多,现在来解决问题了,很明显我们应该采用最后一种结构,匹配多平衡组.(由于时间的原因,这组答案没能完成)
    采用前面匹配括号的方法来实现

    <td\s*id="td2"[^>]*>((?><td[^>]*>(?<o>)|</td>(?<-o>)|[\s\S])*)(?(o)(?!))</td>


    做的不怎么满意,还需继续学习.


  • posted on 2007-12-20 11:16  西门潇洒  阅读(1675)  评论(2编辑  收藏  举报