ハンス、私は餌を取り、私の以前の答えを具体化します。「もっと完全なもの」が欲しいと言ったので、長い答えを気にしないでください。いくつかの背景から始めましょう。
まず、これは素晴らしい質問です。特定のコンテキスト(たとえば、コードブロック内またはかっこ内)を除いて、特定のパターンの照合についてよく質問されます。これらの質問は、しばしばかなり厄介な解決策を生み出します。したがって、複数のコンテキストに関する質問は特別な課題です。
驚き
驚くべきことに、一般的で、実装が簡単で、維持する楽しみがある、少なくとも1つの効率的なソリューションがあります。これは、すべての正規表現の風味と連携し、あなたのコード内でキャプチャグループを検査することができます。そして、最初はあなたとは違うように聞こえるかもしれないいくつかの一般的な質問に偶然に答えます:「ドーナツ以外のすべてに一致」、「すべてを置換...」、「私の母のブラックリストにあるもの以外のすべての単語に一致」、「無視タグ」、「イタリック体でない限り温度に一致」...
悲しいことに、この手法はよく知られていません。私はそれを使用できる20のSO質問のうち、1つだけがそれについて言及する回答を1つだけ持っていると推定します。コメントでコビとの私の交換を見てください。この手法については、この記事の中で(楽観的に)「これまでで最高の正規表現のトリック」と呼んでいます。詳細は省きますが、このテクニックがどのように機能するかをしっかりと把握しておこうと思います。詳細とさまざまな言語のコードサンプルについては、そのリソースを参照することをお勧めします。
既知のバリエーション
同じことを実行するPerlとPHPに固有の構文を使用するバリエーションがあります。それは、CasimiretHippolyteやHamZaなどの正規表現マスターの手に渡って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)(?!)
このバージョンのデモ
用途
このテクニックで簡単に解決できるいくつかの一般的な問題を次に示します。単語を選択すると、これらの問題の一部が実際には実質的に同じであるにもかかわらず、異なって聞こえることがあります。
- どのように私はタグのどこかを除いてfooを一致させることができ
<a stuff...>...</a>
ますか?
<i>
タグまたはJavaScriptスニペット以外の条件でfooを一致させるにはどうすればよいですか(より多くの条件)?
- このブラックリストにないすべての単語を一致させるにはどうすればよいですか?
- SUB ... END SUBブロック内のすべてを無視するにはどうすればよいですか?
- どうすれば... 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に感謝します。
交換
これは私たちに代替品をもたらします、それについて私は簡単に触れます。
- 何も入れ替えない場合は、上記の「削除」トリックを参照してください。
- 置換する場合、PerlまたはPCREを使用している場合は、
(*SKIP)(*F)
上記のバリエーションを使用して、希望するものと正確に一致させ、直接置換します。
- 他のフレーバーでは、置換関数呼び出し内で、コールバックまたはラムダを使用して一致を検査し、グループ1が設定されている場合は置換します。これについてサポートが必要な場合は、すでに参照されている記事でさまざまな言語のコードが提供されます。
楽しんで!
いいえ、お待ちください。まだまだあります。
あ、いや、私はそれを次の春にリリースされる20巻の回想録のために保存します。
\K
特別なPHP構文はありません。言いたいことを詳しく説明してください。「複雑な」ソリューションを必要としないことを私たちに伝えることを目的とする場合は、何が複雑で何が理由かを言わなければなりません。