45看点带你看尽天下事 网站导航/网站地图 手机版
首页 科技看点 .NET正则基础之平衡组实用指南

.NET正则基础之平衡组实用指南

2026-09-02 0


                      ");
MatchCollection mc = reg.Matches(test);
foreach (Match m in mc)
{
richTextBox2.Text += m.Value + "\n--------------------\n";
}
/*--------输出--------

    0

--------------------

    1
   

        2
   


--------------------
*/

理解这一步时,在单字符嵌套结构里,采用排除型字符组“[^()]+”,与分组构造外的匹配优先量词“*” 达到贪婪模式匹配效果。在字符序列嵌套结构中,要排除的是一个子串,而不是轻松的几个无序字符,所以不能采用排除型字符组,此时需用到顺序否定环视来达到这一目的。“(?:(?!”或“

”的字符,这样的字符重复0次或任意多次。关于环视的细节,能够参考 正则基础之——环视。

理解这一步时,而由于这种否定环视包含两种状态,所以在与固化分组结合采用时,会与后面的开始或结束标记形成包含关系,所以与固化分组一起采用时,不能放在左侧,只能放在右侧。

3.2.2 根据id提取div嵌套标签

落到代码里,根据id提取div时,改变的只是最外层div的结构,对内分组构造内部结构没有影响。但是因为id是变化的,所以正则需动态生成。下面给出实现,源字符串和输出结果由于比较影响篇幅,就不再给出了。

string id = Regex.Escape(textBox1.Text);                    //动态获取id
Regex reg = new Regex(@"(?isx)
                      ]*>        #开始标记“
                          (?>                         #分组构造,用来限定量词“*”修饰范围
                              ]*>  (?)   #命名捕获组,遇到开始标记,入栈,Open计数加1
                          |                           #分支结构
                             
 (?<-Open>)      #狭义平衡组,遇到结束标记,出栈,Open计数减1
                          |                           #分支结构
                              (?:(?!                          )*                          #以上子串出现0次或任意多次
                          (?(Open)(?!))               #判断是否还有'OPEN',有则说明不配对,什么都不匹配
                     
                         #结束标记“

                     ");
MatchCollection mc = reg.Matches(test);
foreach (Match m in mc)
{
     richTextBox2.Text += m.Value + "\n--------------------\n";
}

理解这一步时,在动态生成正则表达式时,由于输入的字符串中可能存在正则中有特殊意义的元字符,如果不进行转义的话,正则解析时会抛出异常。所以用Regex.Escape(string str)来对动态输入的字符串进行转义处理,确保不会因动态输入的内容而抛异常。比如上面的例子,如果id不进行转义处理时,输入“abc(def”就会抛“) 不足”这样的异常。

3.2.3 根据id提取任意嵌套标签

从实现思路看,再扩展一下,根据id属性取任意嵌套标签。实现如下所示,具体实现细节和讨论参考 就是借助id获得一个html标签块。以下正则相对于帖子对个别细节做了调整。

string html = @"



   

       

           
               
                   
               
           

                       

                   

       

   

   

        csdn
   




";
Console.WriteLine(html);
string[] idList = { "div1", "div2", "div3", "div4", "table1", "div5", "abc(def" };
string pattern = @"<([a-z]+)(?:(?!\bid\b)[^<>])*id=([""']?){0}\2[^>]*>(?><\1[^>]*>(?)|(?<-o>)|(?:(?!";
foreach (string id in idList)
{
     Match match = Regex.Match(html, string.Format(pattern, Regex.Escape(id)),
                    RegexOptions.Singleline | RegexOptions.IgnoreCase);
     Console.WriteLine("--------begin {0}--------", id);
     if (match.Success)
          Console.WriteLine(match.Value);
     else
          Console.WriteLine("o(╯□╰)o");
     Console.WriteLine("--------end {0}--------", id);
}
Console.ReadLine();

3.2.4 根据标签取外层嵌套结构

根据动态输入的tag,取相应的最外层的嵌套标签,实现如下所示。

string html = @"



   

       

           
               
                   
               
           

                       

                   

       

   

   

        csdn
   




";
Console.WriteLine(html);
string[] tagList = { "html", "body", "div", "table", "abc(def" };
string pattern = @"(?isx)
                      <({0})\b[^>]*>                  #开始标记“
                          (?>                         #分组构造,用来限定量词“*”修饰范围
                              <\1[^>]*>  (?)    #命名捕获组,遇到开始标记,入栈,Open计数加1
                          |                           #分支结构
                               (?<-Open>)       #狭义平衡组,遇到结束标记,出栈,Open计数减1
                          |                           #分支结构
                              (?:(?!                          )*                          #以上子串出现0次或任意多次
                          (?(Open)(?!))               #判断是否还有'OPEN',有则说明不配对,什么都不匹配
                                                #结束标记“”
                     ";
foreach (string tag in tagList)
{
     Match match = Regex.Match(html, string.Format(pattern, Regex.Escape(tag)));
     Console.WriteLine("--------begin {0}--------", tag);
     if (match.Success)
         Console.WriteLine(match.Value);
     else
         Console.WriteLine("o(╯□╰)o");
    Console.WriteLine("--------end {0}--------", tag);
}
Console.ReadLine();

3.2.5 条件判断结构扩展应用

结合项目来看,条件判断结构的作用不只限于验证开始和结束标记是否配对,根据需求的不同,还能够有其它一些应用。比如在匹配div标签时,只取内部“存在”嵌套的外层标签。

string test = @"

    0


    1
   

        2
   

";
Regex reg = new Regex(@"(?isx)                              #匹配模式,忽略大小写,“.”匹配任意字符
                      ]*>                              #开始标记“
                          (?>                                 #分组构造,用来限定量词“*”修饰范围
                              ]*>  (?)(?)  #遇到开始标记,入栈,Open和Mask计数各加1
                          |                                   #分支结构
                             
 (?<-Open>)              #遇到结束标记,出栈,Open计数减1
                          |                                   #分支结构
                              (?:(?!                          )*                                  #以上子串出现0次或任意多次
                          (?(Open)(?!))(?(Mask)|(?!))         #'OPEN'保证标记配对,'Mask'保证内部有嵌套
                                                       #结束标记“”
                      ");
MatchCollection mc = reg.Matches(test);
foreach (Match m in mc)
{
     richTextBox2.Text += m.Value + "\n--------------------\n";
}
/*--------输出--------

    1
   

        2
   


--------------------
*/

落到代码里,命名捕获组“(?)”只入栈不出栈,如果内部有嵌套,则“(?)”一定有匹配,此时匹配“(?(Mask)yes|no)”中的“yes”子表达式,也就是什么都不做;如果内部没有嵌套,则“(?)”没有匹配,此时匹配“(?(Mask)yes|no)”中的“no”子表达式,也就是报告匹配失败。这里省略的是“(?(Mask)yes|no)”中的“yes”子表达式。

从实现思路看,对于匹配内部没有嵌套的标签,也就是最内层标签,能够采用上面的正则表达式,将“(?(Mask)yes|no)”中的“yes”子表达式设为“(?!)”,将“yes”子表达式省略。不过这样做有些浪费,完全能够用顺序否定环视来实现这一需求。

string test = @"

    0


    1
   

        2
   

";
Regex reg = new Regex(@"(?is)]*>(?:(?!");
MatchCollection mc = reg.Matches(test);
foreach (Match m in mc)
{
      richTextBox2.Text += m.Value + "\n--------------------\n";
}
/*--------输出--------

    0

--------------------

        2
   

--------------------
*/

4、平衡组应用范围探讨

落到代码里,平衡组能够用来匹配嵌套结构,这是一个很大的创新,但是否就认为平衡组适合用来解决任何嵌套问题呢?事实当然不会是这样。

比如下面这个需求,(参考 请问一个正则表达式):

源字符串:1+Sum(1,Sum(2, Sum(3), 4), 5)*4+5+Sum(9,Sum(8, Sum(7), 6), 5)*6+7

要求输出:

Sum(1,Sum(2, Sum(3), 4), 5)

Sum(2, Sum(3), 4)

Sum(3)

Sum(9,Sum(8, Sum(7), 6), 5)

Sum(8, Sum(7), 6)

Sum(7)

这种需求采用平衡组+递归的方式能够完成,实现代码如下所示:

//递归方法
private void getNesting(string src, Regex reg, List list)
{
    MatchCollection mc = reg.Matches(src);
    foreach(Match m in mc)
    {
        list.Add(m.Value);
        src = m.Value.Remove(m.Value.Length-1, 1);
        if (reg.IsMatch(src))
        {
             getNesting(src, reg, list);
        }
    }
}
//调用
string test = "1+Sum(1,Sum(2, Sum(3), 4), 5)*4+5+Sum(9,Sum(8, Sum(7), 6), 5)*6+7";
List list = new List();
Regex reg = new Regex(@"(?i)Sum(?<-o>))*(?(o)(?!))\)", RegexOptions.Compiled);
getNesting(test, reg, list);
foreach (string s in list)
{
     richTextBox2.Text += s + "\n";
}

落到代码里,平衡组虽然能够完成要求,但除非你对效率没有要求,否则这一类需求通常是不适合用正则来实现的。因为平衡组同时不是为这一功能而设计的,在实现过程中做了很多额外的尝试。效率上自然要大打折扣。

在这个场景下,类似这样的需求,能够自己写有穷自动机来实现,毕竟正则也只不过是一种有穷自动机的实现而已。

            string test = @"1+Sum(1,Sum(2, Sum(3), 4), 5)*4+5+Sum(9,Sum(8, Sum(7), 6), 5)*6+7 ";
            StringBuilder nesting = new StringBuilder(64);
            List list = new List();
            List groups = new List();
            int level = 0;
            int state = 0;
            foreach (char c in test)
            {
                if ((c == 'S' || c == 's') && state == 0)
                {
                    state = 1;
                    nesting.Append(c);
                }
                else if ((c == 'U' || c == 'u') && state == 1)
                {
                    state = 2;
                    nesting.Append(c);
                }
                else if ((c == 'M' || c == 'm') && state == 2)
                {
                    state = 3;
                    nesting.Append(c);
                }
                else if (c == '(' && state == 3)
                {
                    state = 0;
                    level++;
                }
                else
                {
                    state = 0;
                    nesting = new StringBuilder(64);
                }
                if (c == ')')
                {
                    if (level > 0)
                    {
                        level--;
                        groups.Add(list[level].ToString() + c);
                        list.Remove(list[level]);
                    }
                }
                if (level > 0)
                {
                    while(list.Count < level)
                    {
                        list.Add(nesting);
                    }
                    for (int i = 0; i < level; i++)
                    {
                        list[i].Append(c);
                    }
                }
            }
            foreach (string s in groups)
            {
                Console.WriteLine(s);
            }
            Console.ReadLine();

5、其它声明

从实现思路看,到此为止,平衡组的基本应用场景和性能调优都已讨论完了,本文对于平衡组匹配原理讲得相对比较少,以应用场景分析为主。主要是因为能够采用平衡组来解决问题的人,通常已经对正则的基本语法有了一定程度的理解。而如果事实确实如此,那么对于平衡组的理解,也是水到渠成的了。

理解这一步时,以上正则实现中,采用的多是宽松排列模式,主要是为了加注释,使得阅读清晰。而宽松排列模式通常用来教学目的,实际采用过程中,如果不是为了可读性的考虑,能够去掉这些注释和宽松排列模式参数。

实际处理时,上面给出了很多平衡组的应用,这里需说明的是,我提供的只是一些方法和思路,从来不建议把正则当作模板来用,虽然有些时候,它确实能够当作模板来用,但我还是希望你能真正的掌握这些语法规则之后,再去应用平衡组。当然,如果你认为能用就行,不需知道为什么能够这样用,只是把它当作模板来套,我也无话可说。

到此这篇关于.NET正则基础之平衡组的文章就介绍到这了,更多相关正则平衡组内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多兼容脚本之家!

郑重声明:本站发布内容宗旨在传播更多信息,仅提供查阅,与本站立场无关,不拥有所有权,不承担相关法律责任。不具有任何效益,仅供参考。如果需要专业知识建议,请咨询相关专业人士。如有侵权请联系邮箱。一经查实,立即删除!
喜欢(0)
上一篇

匹配数字小数和逗号的正则表达式实用指南

下一篇

Web 前端常用正则校验规则整理(常用示例)实用指南

猜你喜欢

大家都在看

Unity游戏脚本开发的生命周期函数完整指南(Update/FixedUpdate)

2026-08-27

.NET正则基础之.NET正则匹配模式实用指南

2026-09-02

Ornith-1.0-35B-GGUF 项目介绍:GitCode 开源模型信息整理

2026-08-28

爆料称iPhone 17有“动作按钮Pro”,是什么意思?比15 Pro的更强?

2026-08-29

iPhone 15夜间护眼模式怎么开启-步骤快捷面板和传感器校准

2026-08-30