一致する正規表現パターン、次の場合を除く... /間を除く


108

-編集 -現在の回答にはいくつかの便利なアイデアがありますが、100%理解して再利用できるより完全なものを求めています。それが私が賞金を設定した理由です。また、どこでも機能するアイデアは、次のような標準的な構文よりも私にとって優れています\K

この質問は、一部の状況s1 s2 s3を除いて、パターンをどのように照合できるかについてです。私は自分の意味を示すために具体的な例を示しますが、他の状況で再利用できるように、100%理解できる一般的な回答を好みます。

を使用して5桁で一致させたい\b\d{5}\bが、3つの状況では一致させたくないs1 s2 s3:

s1:この文のようなピリオドで終わる行にはありません。

s2:括弧内のどこにもありません。

s3:で始まりif(、終わるブロック内ではない//endif

特にC#の先読みまたは\KPHP で、先読みと後読みを使用してs1 s2 s3のいずれかを解決する方法を知っています。

例えば

s1 (?m)(?!\d+.*?\.$)\d+

s3とC#のlookbehind (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

PHP \ Kを使用したs3 (?:(?:if\(.*?//endif)\D*)*\K\d+

しかし、条件の混合は私の頭を爆発させます。さらに悪いことに、別のときに他の条件s4 s5を追加する必要があるかもしれません。

朗報ですが、PHP、C#、Pythonなどの最も一般的な言語を使用してファイルを処理しても、隣人の洗濯機を使用してもかまいません。:)私はPythonとJavaの初心者ですが、解決策があるかどうかを知りたいと思っています。

だから私は誰かが柔軟なレシピを考えるかどうかを確かめるためにここに来ました。

ヒントは大丈夫です。完全なコードを提供する必要はありません。:)

ありがとうございました。


1
\K特別なPHP構文はありません。言いたいことを詳しく説明してください。「複雑な」ソリューションを必要としないことを私たちに伝えることを目的とする場合は、何が複雑で何が理由かを言わなければなりません。
2014年

@hakreそれは、rubyが今それを使用していて、perlで始まったからですか?
Hans Schindler

1
いいえ、それはPHP(またはRuby)ではないPCREだからです。Perlは異なりますが、PCRE Perl Regexとの互換性を目指しています。
2014年

s2とs3の要件は矛盾しているようです。s2は、括弧が常に一致し、ネストされる可能性があることを意味しますが、s3では、:ではなく、:で括弧を"if("閉じる必要があります。また、s3の場合、if句を次のように閉じる必要があることを本当に意味している場合、s3要件はs2のサブセットです。")""//endif""//endif)"
リッジランナー2014年

@hakreはいPCREを知っていますが、説明すると、プログラミング言語についての質問です...それは言いespecially in C# lookbehind or \K in PHPます...しかし、C#はC#だけではなく.NETであるため、文句を言うこともできます。おにぐらではなくRubyも悪いです。PCREを使用する別の言語はありますか?Notepad ++やサーバーツールについて話していません。これは言語での機能の使用に関する質問です。説明が間違っているとすみません
Hans Schindler

回答:


205

ハンス、私は餌を取り、私の以前の答えを具体化します。「もっと完全なもの」が欲しいと言ったので、長い答えを気にしないでください。いくつかの背景から始めましょう。

まず、これは素晴らしい質問です。特定のコンテキスト(たとえば、コードブロック内またはかっこ内)を除いて、特定のパターンの照合についてよく質問されます。これらの質問は、しばしばかなり厄介な解決策を生み出します。したがって、複数のコンテキストに関する質問は特別な課題です。

驚き

驚くべきことに、一般的で、実装が簡単で、維持する楽しみがある、少なくとも1つの効率的なソリューションがあります。これは、すべての正規表現の風味と連携し、あなたのコード内でキャプチャグループを検査することができます。そして、最初はあなたとは違うように聞こえるかもしれないいくつかの一般的な質問に偶然に答えます:「ドーナツ以外のすべてに一致」、「すべてを置換...」、「私の母のブラックリストにあるもの以外のすべての単語に一致」、「無視タグ」、「イタリック体でない限り温度に一致」...

悲しいことに、この手法はよく知られていません。私はそれを使用できる20のSO質問のうち、1つだけがそれについて言及する回答を1つだけ持っていると推定します。コメントでコビとの私の交換を見てください。この手法については、この記事の中で(楽観的に)「これまでで最高の正規表現のトリック」と呼んでいます。詳細は省きますが、このテクニックがどのように機能するかをしっかりと把握しておこうと思います。詳細とさまざまな言語のコードサンプルについては、そのリソースを参照することをお勧めします。

既知のバリエーション

同じことを実行するPerlとPHPに固有の構文を使用するバリエーションがあります。それは、CasimiretHippolyteHamZaなどの正規表現マスターの手に渡ってSOで確認できます。これについては以下で詳しく説明しますが、ここでは、すべての正規表現フレーバーで機能する一般的なソリューションに重点を置きます(コード内のキャプチャグループを検査できる限り)。

すべての背景をありがとう、zx81 ...しかし、レシピは何ですか?

重要な事実

このメソッドは、グループ1キャプチャの一致を返します。全体的な試合についてはまったく気にしません。

実際、コツは、必要のないさまざまなコンテキストに一致させること|ORまたは代替を使用してこれらのコンテキストをチェーンして、「それらを中和する」ことです。すべての不要なコンテキストに合致した後、私たちはどのような交代試合の最後の部分ですグループ1にそれをしたいとキャプチャ。

一般的なレシピは

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

これはに一致Not_this_contextしますが、ある意味では、全体的な一致は確認しないため、一致はゴミ箱に入ります。グループ1のキャプチャのみを確認します。

あなたの場合、あなたの数字とあなたの3つのコンテキストを無視して、私たちは次のことができます:

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

s1、s2、およびs3は、ルックアラウンドで回避しようとするのではなく、実際に照合するため、s1、s2、およびs3の個々の式は、1日として明確なままであることに注意してください。(それらはaの両側の部分式です|

式全体は次のように書くことができます:

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

このデモをご覧ください(ただし、右下のペインのキャプチャグループに注目してください)。

この正規表現を|区切り文字ごとに精神的に分割しようとすると、実際には4つの一連の非常に単純な式のみになります。

フリースペースをサポートするフレーバーの場合、これは特によく読みます。

(?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に取り込まれます。

正規表現の可視化

Debuggexデモ

Perl / PCREバリエーション

上記の一般的なソリューションとは対照的に、少なくとも@CasimiretHippolyteや@HamZaなどの正規表現の神の手に渡って、SOでよく見られるPerlとPCREのバリエーションが存在します。それは:

(?: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. <i>タグまたはJavaScriptスニペット以外の条件でfooを一致させるにはどうすればよいですか(より多くの条件)?
  3. このブラックリストにないすべての単語を一致させるにはどうすればよいですか?
  4. SUB ... END SUBブロック内のすべてを無視するにはどうすればよいですか?
  5. どうすれば... s1 s2 s3以外のすべてを照合できますか?

グループ1のキャプチャをプログラムする方法

コードについてはそうではありませんでしたが、完了のために...グループ1を検査するコードは、選択した言語によって明らかに異なります。とにかく、一致の検査に使用するコードに数行以上追加するべきではありません。

よくわからない場合は、前述の記事のコードサンプルセクションをご覧になることをお勧めします。

代替案

質問の複雑さと、使用する正規表現エンジンに応じて、いくつかの選択肢があります。複数の条件を含め、ほとんどの状況に当てはまる2つを次に示します。私の見解では、どちらもどちらもs1|s2|s3|(whatYouWant)レシピほど魅力的ではありません。なぜなら、明快さが常に勝っているからです。

1.交換してから一致させます。

ハックに聞こえるが多くの環境でうまく機能する優れたソリューションは、2つのステップで機能することです。最初の正規表現は、競合する可能性のある文字列を置き換えることにより、無視したいコンテキストを無効にします。一致させるだけの場合は、空の文字列に置き換えて、2番目のステップで一致を実行できます。置き換える場合は、最初に、無視する文字列を独特の何かで置き換えることができます。たとえば、数字を固定幅のチェーンで囲みます@@@。この置換後、本当に必要なものを自由に置換できます。その後、固有の@@@文字列を元に戻す必要があります。

2.ルックアラウンド。

元の投稿では、ルックアラウンドを使用して単一の条件を除外する方法を理解していることが示されていました。これにはC#が最適だとおっしゃっていましたが、その通りですが、それが唯一の選択肢ではありません。C#、VB.NET、Visual C ++などで見られる.NET正規表現のフレーバーと、Pythonでregex置き換えるためのまだ実験的なモジュールreは、無限幅の後読みをサポートする唯一の2つのエンジンです。これらのツールを使用すると、1つの後読み内の1つの条件で、後ろだけでなく一致部分だけでなく、一致部分以降も確認できるため、先読みとの調整が不要になります。その他の条件?その他のルックアラウンド。

C#でs3に使用した正規表現をリサイクルすると、パターン全体は次のようになります。

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

しかし、今では、私がこれを推奨していないことを知っていますよね?

削除

@HamZaと@Jerryは、単に削除したい場合の追加のトリックについて言及することを提案しましたWhatYouWant。マッチするレシピWhatYouWant(それをグループ1に取り込む)がだったことを覚えていs1|s2|s3|(WhatYouWant)ますか?のすべてのインスタンスを削除するにWhatYouWantは、正規表現を

(s1|s2|s3)|WhatYouWant

置換文字列には、を使用します$1。ここで何が起こるかs1|s2|s3とは、一致するインスタンスごとに、置換$1によってそのインスタンスがそれ自体(によって参照される$1)に置き換えられるということです。一方、WhatYouWantが一致すると、空のグループに置き換えられ、それ以外は何もないため、削除されます。このデモをご覧ください。この素晴らしい追加を提案してくれた@HamZaと@Jerryに感謝します。

交換

これは私たちに代替品をもたらします、それについて私は簡単に触れます。

  1. 何も入れ替えない場合は、上記の「削除」トリックを参照してください。
  2. 置換する場合、PerlまたはPCREを使用している場合は、(*SKIP)(*F)上記のバリエーションを使用して、希望するものと正確に一致させ、直接置換します。
  3. 他のフレーバーでは、置換関数呼び出し内で、コールバックまたはラムダを使用して一致を検査し、グループ1が設定されている場合は置換します。これについてサポートが必要な場合は、すでに参照されている記事でさまざまな言語のコードが提供されます。

楽しんで!

いいえ、お待ちください。まだまだあります。

あ、いや、私はそれを次の春にリリースされる20巻の回想録のために保存します。


2
@Kobi 2部構成の返信。はい、昨夜執筆に夢中になり、その下で私が寝て後で片付けると書いた。:)はい、トリックは単純ですが、除外の問題を解決するために人々が使用する一般的なツールの一部ではないように思われるため、「基本」であるという認識を共有しません。私がSOで「例外」、「不可能な」、または「内部ではない」問題をググると、1つの回答(投票なし)だけがそれを示唆し、他の回答はそうではありませんでした。ちなみに私はあなたの答えを見ていませんでした。:)
zx81 2014年

2
申し訳ありませんが、レックスの「ベストトリック」は機能しません(確実に)。と一致させたいがTarzan、二重引用符内のどこにも一致していないとします。:/no|no|(yes)/トリック正規表現は次のようになります:(/"[^"]*"|Tarzan/エスケープされた文字を無視します)。これは多くの場合に機能しますが、次の有効なJavaScriptテキストに適用すると完全に失敗しますvar bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";。レックスのトリックは、すべての可能な構造が一致する場合にのみ機能します。つまり、100%の精度を保証するには、テキストを完全に解析する必要があります。
リッジランナー2014

1
耳障りに聞こえたら申し訳ありません-それは確かに私の意図ではありませんでした。私のポイント(上記の元の質問に対する2番目のコメントのように)は、正しい解決策は検索対象のテキストに大きく依存しているということです。私の例では、単一引用符で囲まれた文字列で囲まれた1つの二重引用符を持つターゲットテキストとしてJavaScriptソースコードを使用しています。これは、次のような文字どおりのRegExpでも同じように簡単にできvar bug1 = /"[^"]*"|(Tarzan)/gi;、同じ効果がありました(この2番目の例は確かにエッジケースではありません)。この手法が確実に機能しない場合に引用できる例は他にもたくさんあります。
リッジランナー2014

1
@ridgerunner私はいつもあなたからのお便りを楽しんでいます、それは私に不当に過酷に聞こえるだけです。文字列に「誤った警告」が含まれている可能性があることがわかった場合、すべてのパターンを調整します。たとえば、文字列マッチャーをスローする可能性があるエスケープされた引用符が含まれている可能性のある文字列に一致させるに(?<!\\)"(?:\\"|[^"\r\n])*+" は、理由がない限り、大きな銃を引っ張らないでください。ソリューションの原則はまだ有効です。左側に配置するパターンを表現できない場合、それは別の話です。別のソリューションが必要です。しかし、ソリューションはそれが宣伝することを行います。
zx81 14

1
この回答は、ユーザー@funkwurmによってスタックオーバーフローの正規表現に関するFAQに追加されました。
aliteralmind 2014年

11

3つの異なる一致を行い、プログラム内の条件付きロジックを使用して、3つの状況の組み合わせを処理します。1つの巨大な正規表現ですべてを処理する必要はありません。

編集:質問がもっと面白くなったので、少し拡張させてください:-)

ここでキャプチャしようとしている一般的なアイデアは、特定の正規表現パターンと照合することですが、テスト文字列に特定の他の(任意の数の可能性がある)パターンが存在する場合は一致しません。幸い、あなたはあなたのプログラミング言語を利用することができます:正規表現を単純にして、単に複合条件を使用してください。ベストプラクティスは、このアイデアを再利用可能なコンポーネントに取り込むことです。そこで、それを実装するクラスとメソッドを作成しましょう。

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));
    }
  }
}

上記のように、検索文字列(5桁)、複数の例外文字列(ユーザーのs1s2、およびs3)を設定してから、いくつかのテスト文字列との照合を試みます。印刷された結果は、各テスト文字列の横のコメントに示されているとおりです。


2
たぶん、続けて3つの正規表現に一致するようなものですか?正規表現1は状況1を排除し(多分単に無効な数字を削除します)、r2はs2を削除し、r3はs3を削除し、残りの数字を照合しますか?それは面白い考えです。
ハンスシンドラー

ハ、確かに、それが私があなたに賛成した理由です。:)私を誤解しないでください、私はまだこの特定のケースでは私の答えはより効率的で保守可能だと思います。昨日追加した無料スペースバージョンを見たことがありますか?これはワンパスであり、非常に読みやすく、保守も簡単です。しかし、私はあなたの仕事とあなたの拡張された答えが好きです。申し訳ありませんが、再度投票することはできません。:)
zx81

2

すべてのケースを満足させるのは不可能ではなく、括弧の中にないというあなたの要件。つまり、どうにか(して左と)右にa を見つけることができる場合でも、常に括弧内にいるとは限りません。例えば。

(....) + 55555 + (.....)-ない括弧内のまだある()左右に

今、あなたは自分が賢いと考えて(、あなたが)前に遭遇したことがない場合にのみ左側を探し、そしてその逆も同様です。これはこのケースでは機能しません:

((.....) + 55555 + (.....))-閉じて)おり(、左と右にあるにもかかわらず、括弧内。

正規表現を使用して括弧内にいるかどうかを確認することはできません。正規表現では、開いている括弧の数と閉じている括弧の数を数えることができないためです。

この簡単なタスクを考えてみましょう:正規表現を使用して、文字列内のすべての(ネストされている可能性がある)括弧が閉じている(かどうかを確認します)。あなたが解決することは不可能であることがわかります、そしてそれを正規表現で解決できない場合、単語がすべてのケースで括弧内にあるかどうかを知ることはできません。なぜなら、文字列のいくつかの位置で理解できないからです。先行するすべてに(対応するがあり)ます。


2
ネストされた括弧については誰も何も言わず、あなたのケース#1はzx81の答えによってうまく処理されます。
Dan Bechard、2014年

素敵な考えをありがとう:)しかし、入れ子になった括弧はこの質問について私を心配しませんそれは悪い状況のアイデアについての詳細ですs1 s2 s3
ハンス・シンドラー

もちろんそれは不可能ではありません!これがまさに、現在解析している括弧のレベルを追跡する必要がある理由です。
MrWonderful 2014年

まあ、OPのように何らかのCFGを解析している場合は、LALRまたはこれに問題のない同様のパーサーを生成するほうが適切です。
RokL 2014年

2

よろしければ、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
12345と一致する必要があります
12345と一致する必要があります
12345と一致する必要があります

2

これがあなたに役立つかどうかはわかりませんが、私は次の仮定を考慮した解決策を提供しています-

  1. すべての条件をチェックするためのエレガントなソリューションが必要です
  2. 状況は将来、いつでも変わる可能性があります。
  3. ある条件が他の条件に依存するべきではありません。

しかし、私は以下も考慮しました-

  1. 指定されたファイルのエラーは最小限です。もしそうなら、私のコードはそれに対処するためにいくつかの修正を必要とするかもしれません。
  2. Stackを使用してif(ブロックを追跡しました。

ここに解決策があります-

C#とMEF(Microsoft Extensibility Framework)を使用して、構成可能なパーサーを実装しました。アイデアは、単一のパーサーを使用して解析し、構成可能なバリデータークラスのリストを使用して行を検証し、検証に基づいて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();
            }
        }
    }

テストのためにTest.txt、次の行が含まれている@Tiagoのサンプルファイルを使用しました-

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

@ zx81と同じです(*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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.