最初にこれを読んでください!
この投稿は、問題に対する「すべての正規表現」アプローチを支持するのではなく、可能性を示すことを目的としています。著者は 3 ~ 4 のバリエーションを作成しましたが、現在のソリューションにたどり着く前に、それぞれに微妙なバグがあり、検出が困難です。
特定の例では、区切り文字に沿って一致を一致させたり分割したりするなど、より保守しやすい他のより良いソリューションがあります。
この投稿では、特定の例を扱います。完全に一般化できるとは思えませんが、背後にあるアイデアは同様のケースで再利用できます。
概要
- .NET は、
CaptureCollection
クラスを使用した繰り返しパターンのキャプチャをサポートしています。
- 後読みをサポートする言語
\G
の場合、グローバル マッチング関数で動作する正規表現を構築できる場合があります。完全に正しく書くのは簡単ではなく、微妙にバグのある正規表現を書くのは簡単です。
\G
後方参照をサポートしていない言語の場合:単一の一致後に入力文字列をチョッピングすることにより、 で\G
エミュレートできます。^
(この回答ではカバーされていません)。
解決
このソリューションは、正規表現エンジンが\G
一致境界、先読み(?=pattern)
、および後読みをサポートしていることを前提としてい(?<=pattern)
ます。Java、Perl、PCRE、.NET、Ruby 正規表現は、上記の高度な機能をすべてサポートしています。
ただし、.NET で正規表現を使用できます。CaptureCollection
.NET は、クラスを介して繰り返されるキャプチャ グループによって一致する のすべてのインスタンスのキャプチャをサポートしているためです。
\G
あなたの場合、一致境界と先読みを使用して繰り返し回数を制限することで、1つの正規表現で実行できます。
(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)
デモ。構築が\w+-
繰り返され、その後\w+:end
.
(?:start:(?=\w+(?:-\w+){2,9}:end)|(?!^)\G-)(\w+)
デモ。構築は\w+
最初の項目のためのものであり、その後-\w+
繰り返されます。(提案してくれたka ᵠに感謝します)。この構造は、変更が少ないため、その正確性について簡単に推論できます。
\G
境界の一致は、トークン化を行う必要がある場合に特に役立ちます。この場合、エンジンが先にスキップせず、無効であるはずのものを照合する必要があります。
説明
正規表現を分解してみましょう:
(?:
start:(?=\w+(?:-\w+){2,9}:end)
|
(?<=-)\G
)
(\w+)
(?:-|:end)
認識するのが最も簡単な部分は(\w+)
、最後の行の前の行です。これは、キャプチャしたい単語です。
最後の行も非常に簡単に認識できます。一致する単語の後に-
orが続く場合があります:end
。
正規表現がstring 内のどこからでも自由にマッチングを開始できるようにします。つまり、start:...:end
文字列内のどこにでも何回でも出現できます。正規表現は単にすべての単語に一致します。返された配列を処理して、一致したトークンが実際にどこから来たのかを分離するだけで済みます。
説明としては、正規表現の先頭で文字列 の存在を確認しstart:
、次の先読みで単語数が指定された制限内にあり、 で終わることを確認し:end
ます。それか、前の一致の前の文字が であることを確認して、前の一致-
から続行します。
他の構造の場合:
(?:
start:(?=\w+(?:-\w+){2,9}:end)
|
(?!^)\G-
)
(\w+)
start:\w+
フォームの繰り返しを照合する前に最初に照合することを除いて、すべてがほぼ同じ-\w+
です。最初に一致する最初の構成とは対照的に、(または最後の繰り返し) のstart:\w+-
インスタンスが繰り返されます。\w+-
\w+:end
この正規表現を文字列の途中で一致させるのは非常に難しいです:
(元の正規表現の要件の一部として)start:
との間の単語数を確認する必要があります。:end
\G
文字列の先頭にも一致します! (?!^)
この動作を防ぐために必要です。これに注意しないと、正規表現が存在しない場合でも一致が生成される可能性がありますstart:
。
最初の構成では、後読みは(?<=-)
既にこのケースを防止しています ((?!^)
によって暗示されてい(?<=-)
ます)。
最初の構築(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)
では、 の後に面白いものと一致しないようにする必要があり:end
ます。:end
後読みはその目的のためのものです。一致後のガベージを防ぎます。
間にあるすべてのトークンを一致させた後で:
(of )でスタックするため、2 番目の構成ではこの問題は発生しません。:end
検証バージョン
入力文字列がフォーマットに従っていること (前後に余分なものがないこと) を検証し、データを抽出する場合は、次のようにアンカーを追加できます。
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G-)(\w+)
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G)(\w+)(?:-|:end)
(後読みも必要ありませんが、文字列の先頭が一致(?!^)
しないようにする必要があります)。\G
工事
繰り返しのすべてのインスタンスをキャプチャしたいすべての問題について、正規表現を変更する一般的な方法は存在しないと思います。変換が「難しい」(または不可能?) ケースの 1 つの例は、一致する特定の条件を満たすために繰り返しが 1 つまたは複数のループをバックトラックする必要がある場合です。
元の正規表現が入力文字列全体を記述している場合 (検証型)、文字列の途中から照合しようとする正規表現 (照合型) に比べて、通常は変換が容易です。ただし、いつでも元の正規表現との照合を行うことができ、照合タイプの問題を検証タイプの問題に変換します。
次の手順を実行して、そのような正規表現を構築します。
- 繰り返しの前の部分をカバーする正規表現を記述します (例:
start:
)。このプレフィックスを regexと呼びましょう。
- 最初のインスタンスを一致させてキャプチャします。(例
(\w+)
)
(この時点で、最初のインスタンスと区切り文字が一致している必要があります)
\G
を代替として追加します。通常、文字列の先頭と一致しないようにする必要もあります。
- 区切り文字を追加します (存在する場合)。(例
-
)
(このステップの後、最後の多分を除いて、残りのトークンも一致しているはずです)
- 繰り返しの後の部分を覆う部分を追加します (必要な場合) (例
:end
)。繰り返しサフィックスの後の部分を regexと呼びましょう(構造に追加するかどうかは問題ではありません)。
- 今、難しい部分です。次のことを確認する必要があります。
- プレフィックス regex以外に、一致を開始する方法はありません。
\G
枝に注意してください。
- サフィックスの正規表現が一致した後に一致を開始する方法はありません。ブランチがマッチを開始する方法に注意してください。
\G
- 最初の構成では、接尾辞の正規表現 (例:
:end
) と区切り文字 (例: -
) を交互に混在させる場合は、接尾辞の正規表現を区切り文字として許可しないようにしてください。