要匹配的正则表达式模式,排除何时... / 除此以外

Regex Pattern to Match, Excluding when... / Except between

提问人:Hans Schindler 提问时间:5/11/2014 最后编辑:zx81Hans Schindler 更新时间:9/27/2023 访问量:41512

问:

--编辑--目前的答案有一些有用的想法,但我想要一些更完整的东西,我可以 100% 理解和重用;这就是我设定赏金的原因。此外,在任何地方都有效的想法对我来说比不标准语法更好,例如\K

这个问题是关于我如何匹配模式,但某些情况除外:s1、s2、s3。我举了一个具体的例子来说明我的意思,但更喜欢一个我可以 100% 理解的一般答案,这样我就可以在其他情况下重复使用它。

我想在三种情况下使用但不匹配五位数字 s1 s2 s3:\b\d{5}\b

S1:不是在以句号结尾的行上,就像这句话一样。

S2:不在parens的任何地方。

S3:不在以 开头和结尾的块内if(//endif

我知道如何通过前瞻和后视来解决 s1、s2、s3 中的任何一个,尤其是在 C# lookbehind 或 PHP 中。\K

例如

S1型(?m)(?!\d+.*?\.$)\d+

s3 与 C# 的后视(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

s3 与 PHP \K(?:(?:if\(.*?//endif)\D*)*\K\d+

但各种条件的混合让我头晕目眩。更坏的消息是,我可能需要在另一个时间添加其他条件 s4 s5。

好消息是,我不在乎我是否使用 PHP、C#、Python 或我邻居的洗衣机等最常用的语言处理文件。:)我几乎是 Python 和 Java 的初学者,但有兴趣了解它是否有解决方案。

所以我来这里,看看是否有人想到一个灵活的食谱。

提示是可以的:你不需要给我完整的代码。:)

谢谢。

正则表达式

评论

1赞 hakre 5/13/2014
\K没有特殊的PHP语法。请详细说明并澄清您想说的内容。如果你的目的是告诉我们你不需要一个“复杂”的解决方案,你必须说出什么对你来说很复杂,以及为什么。
0赞 Hans Schindler 5/14/2014
@hakre 你的意思是因为 ruby 现在使用它,而且它是从 perl 开始的?
1赞 hakre 5/14/2014
不,因为它不是 PHP(也不是 Ruby)的 PCRE。Perl 是不同的,但 PCRE 的目标是与 Perl 正则表达式兼容。
0赞 ridgerunner 5/15/2014
您的 s2 和 s3 要求似乎是矛盾的。s2 表示括号始终匹配并且可以嵌套,但 s3 要求 : open paren 闭合,而不是用 a ,而是用 a: ?如果对于 s3,您真的意味着 if 子句应该用 : 结束,那么 s3 要求是 s2 的子集。"if("")""//endif""//endif)"
0赞 Hans Schindler 5/15/2014
@hakre 是的,我知道 PCRE,但要解释一下,问题是关于编程语言的......它说......但是 C# 不仅要看 C#,还要看 .NET,所以你也可以抱怨,我说 C# 而不是 .NET :)作为回应,我说 Ruby 而不是 Onigurama,这也很糟糕......有没有另一种语言使用PCRE?不是在谈论Notepad ++或服务器工具,这是关于在语言中使用功能的问题,我希望解释一下,如果它看起来不对,对不起especially in C# lookbehind or \K in PHP

答:

225赞 zx81 5/11/2014 #1

汉斯,我会上钩并充实我之前的答案。你说你想要“更完整的东西”,所以我希望你不会介意冗长的答案——只是想取悦。让我们从一些背景开始。

首先,这是一个很好的问题。除了某些上下文(例如,在代码块内或括号内)之外,经常存在有关匹配某些模式的问题。这些问题往往会产生相当尴尬的解决方案。因此,您关于多个上下文的问题是一个特殊的挑战。

惊喜

令人惊讶的是,至少有一种有效的解决方案是通用的、易于实施的、易于维护的。它适用于所有正则表达式风格,允许您检查代码中的捕获组。它恰好回答了一些常见问题,乍一听可能与您的问题不同:“匹配除甜甜圈以外的所有内容”、“替换所有但......”、“匹配除我妈妈黑名单上的单词之外的所有单词”、“忽略标签”、“除非斜体,否则匹配温度”......

可悲的是,这种技术并不为人所知:我估计,在二十个可以使用它的 SO 问题中,只有一个答案提到了它——这意味着可能每五六十个答案中就有一个。在评论中查看我与 Kobi 的交流。本文对该技术进行了深入描述,称其为(乐观地)“有史以来最好的正则表达式技巧”。在不赘述太多细节的情况下,我将尝试让您牢牢掌握该技术的工作原理。有关各种语言的更多详细信息和代码示例,我鼓励您查阅该资源。

一个更广为人知的变体

使用特定于 Perl 和 PHP 的语法的变体可以实现相同的目的。你会在 SO 上看到它掌握在 CasimiretHippolyteHamZa 等正则表达式大师手中。我将在下面详细介绍这一点,但我在这里的重点是适用于所有正则表达式风格的通用解决方案(只要您可以在代码中检查捕获组)。

感谢所有的背景,zx81...但是配方是什么?

关键事实

该方法返回组 1 捕获中的匹配项。它不在乎 所有关于整体比赛。

事实上,诀窍是匹配我们不想要的各种上下文(使用 OR / alternation 链接这些上下文)以“中和它们”。在匹配所有不需要的上下文后,交替的最后一部分会匹配我们想要的内容,并将其捕获到第 1 组。|

一般配方是

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

这将匹配,但从某种意义上说,该匹配会进入垃圾箱,因为我们不会查看整体匹配:我们只查看第 1 组捕获。Not_this_context

在您的情况下,可以忽略您的数字和三个上下文,我们可以做到:

s1|s2|s3|(\b\d+\b)

请注意,由于我们实际上匹配了 s1、s2 和 s3,而不是试图通过环视来避免它们,因此 s1、s2 和 s3 的单个表达式可以保持清晰。(它们是| )

整个表达式可以这样写:

(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)

请参阅此演示(但重点关注右下窗格中的捕获组。

如果你在脑海中尝试在每个分隔符处拆分这个正则表达式,它实际上只是一系列四个非常简单的表达式。|

对于支持自由间距的风格,这读起来特别好。

(?mx)
      ### s1: Match line that ends with a period ###
^.*\.$  
|     ### OR s2: Match anything between parentheses ###
\([^\)]*\)  
|     ### OR s3: Match any if(...//endif block ###
if\(.*?//endif  
|     ### OR capture digits to Group 1 ###
(\b\d+\b)

这非常易于阅读和维护。

扩展正则表达式

当您想忽略更多情况 s4 和 s5 时,您可以在左侧的更多交替中添加它们:

s4|s5|s1|s2|s3|(\b\d+\b)

这是如何工作的?

您不想要的上下文会被添加到左侧的交替列表中:它们将匹配,但这些整体匹配永远不会被检查,因此匹配它们是将它们放入“垃圾箱”的一种方式。

但是,您所需的内容将捕获到组 1。然后,您必须以编程方式检查组 1 是否已设置且不为空。这是一项微不足道的编程任务(我们稍后将讨论它是如何完成的),特别是考虑到它给你留下了一个简单的正则表达式,你可以一目了然地理解它,并根据需要进行修改或扩展。

我并不总是喜欢可视化,但这个很好地展示了该方法的简单性。每条“线”对应一个潜在的匹配项,但只有底线被捕获到第 1 组中。

Regular expression visualization

Debuggex 演示

Perl/PCRE 变体

与上面的一般解决方案相比,Perl 和 PCRE 存在一种变体,这种变体在 SO 上很常见,至少在正则表达式之神(如 @CasimiretHippolyte 和 @HamZa 的手中是这样。是的:

(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant

就您而言:

(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b

这种变体更易于使用,因为在上下文 s1、s2 和 s3 中匹配的内容被简单地跳过,因此您不需要检查组 1 捕获(请注意括号不见了)。匹配项仅包含whatYouWant

请注意,和 都是一回事。如果你想更晦涩,你可以使用(*F)(*FAIL)(?!)(*SKIP)(?!)

此版本的演示

应用

以下是该技术通常可以轻松解决的一些常见问题。你会注意到,这个词的选择可以使其中一些问题听起来不同,而实际上它们实际上是相同的。

  1. 除了标签中的任何地方之外,我如何匹配 foo?<a stuff...>...</a>
  2. 除了标签或javascript片段(更多条件)之外,我如何匹配foo?<i>
  3. 如何匹配所有不在此黑名单上的单词?
  4. 我怎么能忽略 SUB 中的任何内容......结束子块?
  5. 我怎样才能匹配所有东西,除了......S1 S2 S3?

如何对第 1 组捕获进行编程

你没有代码,但是,为了完成......检查组 1 的代码显然取决于您选择的语言。无论如何,它不应该在用于检查匹配项的代码中添加超过几行。

如有疑问,我建议您查看前面提到的文章的代码示例部分,其中提供了许多语言的代码。

选择

根据问题的复杂性和所使用的正则表达式引擎,有几种选择。以下是适用于大多数情况的两种情况,包括多种情况。在我看来,两者都不如食谱那么有吸引力,因为清晰度总是胜出。s1|s2|s3|(whatYouWant)

1. 替换然后匹配。

一个好的解决方案听起来很笨拙,但在许多环境中都运行良好,是分两步工作。第一个正则表达式通过替换可能冲突的字符串来抵消要忽略的上下文。如果您只想匹配,则可以替换为空字符串,然后在第二步中运行匹配。如果要替换,可以先将要忽略的字符串替换为独特的字符串,例如用固定宽度的 .在这次替换之后,你可以自由地替换你真正想要的东西,然后你必须恢复你独特的字符串。@@@@@@

2. 环顾四周。

您的原始帖子表明您了解如何使用环顾来排除单个条件。您说 C# 非常适合此,您是对的,但这不是唯一的选择。例如,在 C#、VB.NET 和 Visual C++ 中发现的 .NET 正则表达式风格,以及在 Python 中替换的仍处于实验阶段的模块,是我所知道的仅有的两个支持无限宽度后视的引擎。有了这些工具,一个情况中的一个情况不仅可以照顾到后面,还可以照顾到比赛和比赛之外,避免了与展望协调的需要。更多条件?更多环顾周。regexre

回收 C# 中 s3 的正则表达式,整个模式将如下所示。

(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

但现在你知道我不推荐这个,对吧?

删除

@HamZa和@Jerry建议我提到一个额外的技巧,当你试图删除.您还记得匹配(将其捕获到第 1 组)的配方是 ,对吧?若要删除 的所有实例,请将正则表达式更改为WhatYouWantWhatYouWants1|s2|s3|(WhatYouWant)WhatYouWant

(s1|s2|s3)|WhatYouWant

对于替换字符串,请使用 .这里发生的情况是,对于匹配的每个实例,替换将该实例替换为其自身(由 引用)。另一方面,当匹配时,它将被一个空组替换,而不是其他任何内容,因此被删除。请看这个演示,感谢@HamZa和@Jerry提出这个精彩的补充。$1s1|s2|s3$1$1WhatYouWant

更换

这就引出了替代品,我将简要介绍一下。

  1. 当替换为任何内容时,请参阅上面的“删除”技巧。
  2. 替换时,如果使用 Perl 或 PCRE,请使用上面提到的变体来完全匹配您想要的内容,并直接替换。(*SKIP)(*F)
  3. 在其他风格中,在替换函数调用中,使用回调或 lambda 检查匹配项,如果设置了组 1,则进行替换。如果你需要这方面的帮助,已经引用的文章将为你提供各种语言的代码。

玩得愉快!

不,等等,还有更多!

啊,不,我会把它留到我的回忆录中,共二十卷,明年春天出版。

评论

2赞 zx81 5/14/2014
@Kobi 由两部分组成的回复。是的,昨晚写得意忘形,在底部写着我会睡在上面,稍后再整理。:)是的,诀窍很简单,但我不同意你认为它是“基本”的看法,因为它似乎不是人们用来解决排除问题的常用工具的一部分。当我在 SO 上搜索“除了”或“除非”或“不在内部”的问题时,只有一个答案(没有投票)建议它,其他人都没有。顺便说一句,我没有看到你的答案,这太棒了。:)
2赞 ridgerunner 7/7/2014
对不起,但雷克斯的“最佳技巧”根本不起作用(可靠)。说你想匹配,但当双引号内的任何地方都不匹配。这: trick regex 类似于:(忽略转义字符)。这在许多情况下都有效,但在应用于以下有效的 JavaScript 文本时完全失败:.Rex 的技巧只有在所有可能的结构都匹配时才有效 - 换句话说 - 您需要完全解析文本以保证 100% 的准确性。Tarzan/no|no|(yes)//"[^"]*"|Tarzan/var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";
1赞 ridgerunner 7/8/2014
对不起,如果我听起来很刺耳——那当然不是我的本意。我的观点(正如我对上述原始问题的第二条评论一样)是,正确的解决方案高度依赖于正在搜索的目标文本。我的示例将 JavaScript 源代码作为目标文本,该文本在单引号字符串中包含一个双引号。它本来可以很容易地成为字面上的正则表达式,例如: 并且具有相同的效果(第二个示例当然不是边缘情况)。我可以举出更多例子,其中这种技术无法可靠地工作。var bug1 = /"[^"]*"|(Tarzan)/gi;
1赞 zx81 7/8/2014
@ridgerunner我总是喜欢听到你的消息,但这对我来说听起来是毫无道理的苛刻。当我们知道我们的字符串可能包含“错误警报”时,我们都会调整我们的模式。例如,若要匹配可能包含转义引号的字符串,这些引号可能会使字符串匹配器脱落,则可以使用除非有理由,否则不要拔大枪。该解决方案的原理仍然有效。如果我们无法表达一个模式放在左侧,那就是另一回事了,我们需要一个不同的解决方案。但该解决方案确实做到了它所宣传的。(?<!\\)"(?:\\"|[^"\r\n])*+"
1赞 aliteralmind 10/7/2014
此答案已由用户@funkwurm添加到 Stack Overflow 正则表达式常见问题解答中。
11赞 Yawar 5/11/2014 #2

执行三种不同的匹配,并使用程序内条件逻辑处理三种情况的组合。你不需要在一个巨大的正则表达式中处理所有事情。

编辑:让我扩展一下,因为这个问题变得更加有趣:-)

您在此处尝试捕获的一般思路是与某个正则表达式模式进行匹配,但当测试字符串中存在某些其他(可能是任意数字)模式时,则不匹配。幸运的是,您可以利用您的编程语言:保持正则表达式简单,只使用复合条件。最佳做法是在可重用的组件中捕获此想法,因此让我们创建一个类和一个实现它的方法:

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public class MatcherWithExceptions {
  private string m_searchStr;
  private Regex m_searchRegex;
  private IEnumerable<Regex> m_exceptionRegexes;

  public string SearchString {
    get { return m_searchStr; }
    set {
      m_searchStr = value;
      m_searchRegex = new Regex(value);
    }
  }

  public string[] ExceptionStrings {
    set { m_exceptionRegexes = from es in value select new Regex(es); }
  }

  public bool IsMatch(string testStr) {
    return (
      m_searchRegex.IsMatch(testStr)
      && !m_exceptionRegexes.Any(er => er.IsMatch(testStr))
    );
  }
}

public class App {
  public static void Main() {
    var mwe = new MatcherWithExceptions();

    // Set up the matcher object.
    mwe.SearchString = @"\b\d{5}\b";
    mwe.ExceptionStrings = new string[] {
      @"\.$"
    , @"\(.*" + mwe.SearchString + @".*\)"
    , @"if\(.*" + mwe.SearchString + @".*//endif"
    };

    var testStrs = new string[] {
      "1." // False
    , "11111." // False
    , "(11111)" // False
    , "if(11111//endif" // False
    , "if(11111" // True
    , "11111" // True
    };

    // Perform the tests.
    foreach (var ts in testStrs) {
      System.Console.WriteLine(mwe.IsMatch(ts));
    }
  }
}

因此,在上面,我们设置了搜索字符串(五位数字)、多个异常字符串(s1s2 和 s3),然后尝试与多个测试字符串进行匹配。打印的结果应如每个测试字符串旁边的注释所示。

评论

2赞 Hans Schindler 5/11/2014
你的意思是,也许像连续匹配三个正则表达式?正则表达式 1 消除情况 1(也许只是删除坏数字),r2 删除 s2,r3 删除 s3 并匹配剩余的数字?这是一个有趣的想法。
0赞 zx81 5/14/2014
呵呵,当然,这就是我给你投赞成票的原因。:)不要误会我的意思,我仍然认为在这种特殊情况下,我的答案更有效且更易于维护。你看过我昨天添加的自由间距版本吗?这是一次通过,非常易于阅读和维护。但我确实喜欢你的工作和你扩展的答案。对不起,我不能再投赞成票了,否则我会的。:)
2赞 RokL 5/15/2014 #3

您的要求是它不在parens内部,不可能满足所有情况。 也就是说,如果你能以某种方式在左边和右边找到一个,这并不总是意味着你在parens里面。例如。()

(....) + 55555 + (.....)- 不在parens里面,但左边和右边都有()

现在你可能会认为自己很聪明,只有在你以前没有遇到过的情况下才会向左寻找,反之亦然。这不适用于这种情况:()

((.....) + 55555 + (.....))- 在Parens内部,即使有关闭,向左和向右。)(

使用正则表达式无法确定您是否在 parens 内,因为正则表达式无法计算已打开的 paren 数量和关闭的 parens 数量。

考虑这个更简单的任务:使用正则表达式,找出字符串中的所有(可能是嵌套的)parens 是否都已关闭,即您需要查找的每个 paren。你会发现这是不可能解决的,如果你不能用正则表达式解决这个问题,那么你就无法弄清楚一个词是否在所有情况下都在括号内,因为你无法弄清楚在字符串中的某个位置是否有对应的.()()

评论

2赞 Dan Bechard 5/16/2014
没有人说嵌套括号,zx81 的答案很好地处理了您的案例 #1。
0赞 Hans Schindler 5/19/2014
谢谢你的好想法:)但是嵌套括号并不让我担心这个问题,它更多的是关于糟糕情况的想法 s1 s2 s3
0赞 MrWonderful 5/20/2014
当然,这并非不可能!这正是您需要跟踪当前正在解析的 parens 级别的原因。
0赞 RokL 5/23/2014
好吧,如果您正在解析某种 CFG,就像 OP 似乎正在做的那样,那么最好通过生成 LALR 或类似的解析器来服务,这没有问题。
2赞 Tiago Lopo 5/16/2014 #4

汉斯,如果你不介意的话,我用过你邻居的洗衣机,叫perl:)

编辑:在伪代码下方:

  loop through input
  if line contains 'if(' set skip=true
        if skip= true do nothing
        else
           if line match '\b\d{5}\b' set s0=true
           if line does not match s1 condition  set s1=true
           if line does not match s2 condition  set s2=true
           if s0,s1,s2 are true print line 
  if line contains '//endif' set skip=false

给定文件 input.txt:

tiago@dell:~$ cat input.txt 
this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

脚本 validator.pl:

tiago@dell:~$ cat validator.pl 
#! /usr/bin/perl
use warnings;
use strict;
use Data::Dumper;

sub validate_s0 {
    my $line = $_[0];
    if ( $line =~ \d{5/ ){
        return "true";
    }
    return "false";
}

sub validate_s1 {
    my $line = $_[0];
    if ( $line =~ /\.$/ ){
        return "false";
    }
    return "true";
}

sub validate_s2 {
    my $line = $_[0];
    if ( $line =~ /.*?\(.*\d{5.*?\).*/ ){
        return "false";
    }
    return "true";
}

my $skip = "false";
while (<>){
    my $line = $_; 

    if( $line =~ /if\(/ ){
       $skip = "true";  
    }

    if ( $skip eq "false" ) {
        my $s0_status = validate_s0 "$line"; 
        my $s1_status = validate_s1 "$line";
        my $s2_status = validate_s2 "$line";

        if ( $s0_status eq "true"){
            if ( $s1_status eq "true"){
                if ( $s2_status eq "true"){
                    print "$line";
                }
            }
        }
    } 

    if ( $line =~ /\/\/endif/) {
        $skip="false";
    }
}

执行:

tiago@dell:~$ cat input.txt | perl validator.pl 
it should match 12345
it should match 12345
it should match 12345
2赞 brainless coder 5/20/2014 #5

不确定这是否会对您有所帮助,但我提供了一个考虑到以下假设的解决方案 -

  1. 您需要一个优雅的解决方案来检查所有条件
  2. 未来和任何时候,情况都可能发生变化。
  3. 一个条件不应该依赖于其他条件。

但是,我还考虑了以下几点——

  1. 给定的文件中的错误最小。如果是这样,那么我的代码可能需要一些修改来应对这种情况。
  2. 我使用 Stack 来跟踪块。if(

好的,这是解决方案 -

我使用 C# 和 MEF(Microsoft 扩展性框架)来实现可配置的解析器。这个想法是,使用单个解析器进行解析,并使用可配置的验证器类列表来验证行,并根据验证返回 true 或 false。然后,您可以随时添加或删除任何验证器,也可以根据需要添加新的验证器。到目前为止,我已经为您提到的 S1、S2 和 S3 实现了,请在第 3 点检查类。如果将来需要,您必须为 s4、s5 添加类。

  1. 首先,创建接口 -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.Contracts
    {
        public interface IParser
        {
            String[] GetMatchedLines(String filename);
        }
    
        public interface IPatternMatcher
        {
            Boolean IsMatched(String line, Stack<string> stack);
        }
    }
    
  2. 然后是文件读取器和检查器 -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using FileParserDemo.Contracts;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Collections;
    
    namespace FileParserDemo.Parsers
    {
        public class Parser : IParser
        {
            [ImportMany]
            IEnumerable<Lazy<IPatternMatcher>> parsers;
            private CompositionContainer _container;
    
            public void ComposeParts()
            {
                var catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(typeof(IParser).Assembly));
                _container = new CompositionContainer(catalog);
                try
                {
                    this._container.ComposeParts(this);
                }
                catch
                {
    
                }
            }
    
            public String[] GetMatchedLines(String filename)
            {
                var matched = new List<String>();
                var stack = new Stack<string>();
                using (StreamReader sr = File.OpenText(filename))
                {
                    String line = "";
                    while (!sr.EndOfStream)
                    {
                        line = sr.ReadLine();
                        var m = true;
                        foreach(var matcher in this.parsers){
                            m = m && matcher.Value.IsMatched(line, stack);
                        }
                        if (m)
                        {
                            matched.Add(line);
                        }
                     }
                }
                return matched.ToArray();
            }
        }
    }
    
  3. 然后是单个检查器的实现,类名是不言自明的,所以我认为它们不需要更多的描述。

    using FileParserDemo.Contracts;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.PatternMatchers
    {
        [Export(typeof(IPatternMatcher))]
        public class MatchAllNumbers : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\d+");
                return regex.IsMatch(line);
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveIfBlock : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("if\\(");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        //push the if
                        stack.Push(m.ToString());
                    }
                    //ignore current line, and will validate on next line with stack
                    return true;
                }
                regex = new Regex("//endif");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        stack.Pop();
                    }
                }
                return stack.Count == 0; //if stack has an item then ignoring this block
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithEndPeriod : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("(?m)(?!\\d+.*?\\.$)\\d+");
                return regex.IsMatch(line);
            }
        }
    
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithInParenthesis : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\(.*\\d+.*\\)");
                return !regex.IsMatch(line);
            }
        }
    }
    
  4. 程序 -

    using FileParserDemo.Contracts;
    using FileParserDemo.Parsers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var parser = new Parser();
                parser.ComposeParts();
                var matches = parser.GetMatchedLines(Path.GetFullPath("test.txt"));
                foreach (var s in matches)
                {
                    Console.WriteLine(s);
                }
                Console.ReadLine();
            }
        }
    }
    

为了进行测试,我采用了@Tiago的示例文件,其中包含以下几行 -Test.txt

this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

给出输出 -

it should match 12345
it should match 12345
it should match 12345

不知道这是否会对你有所帮助,我确实玩得很开心...... :)

最好的部分是,要添加一个新条件,您所要做的就是提供 的实现,它将自动被调用,从而进行验证。IPatternMatcher

2赞 Avinash Raj 12/28/2014 #6

与@zx81相同,但使用否定的 lookahead 断言。(*SKIP)(*F)

(?m)(?:if\(.*?\/\/endif|\([^()]*\))(*SKIP)(*F)|\b\d+\b(?!.*\.$)

演示

在python中,我可以很容易地做到这一点,

import re
string = """cat 123 sat.
I like 000 not (456) though 111 is fine
222 if(  //endif if(cat==789 stuff  //endif   333"""
for line in string.split('\n'):                                  # Split the input according to the `\n` character and then iterate over the parts.
    if not line.endswith('.'):                                   # Don't consider the part which ends with a dot.
        for i in re.split(r'\([^()]*\)|if\(.*?//endif', line):   # Again split the part by brackets or if condition which endswith `//endif` and then iterate over the inner parts.
            for j in re.findall(r'\b\d+\b', i):                  # Then find all the numbers which are present inside the inner parts and then loop through the fetched numbers.
                print(j)                                         # Prints the number one ny one.

输出:

000
111
222
333