正規表現を使用して複数行のテキストを照合する


174

Javaを使用して複数行のテキストを照合しようとしています。PatternクラスをPattern.MULTILINE修飾子とともに使用すると、一致させることはできますが、(?m).

と同じパターンを(?m)使用String.matchesしても機能しないようです。

私は何かが欠けていると確信していますが、何がわからないのですか?正規表現はあまり得意ではありません。

これは私が試したものです

String test = "User Comments: This is \t a\ta \n test \n\n message \n";

String pattern1 = "User Comments: (\\W)*(\\S)*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true

String pattern2 = "(?m)User Comments: (\\W)*(\\S)*";
System.out.println(test.matches(pattern2));  //false - why?

回答:


298

まず、誤った仮定の下で修飾子を使用しています。

Pattern.MULTILINEまたは(?m)Javaはアンカーを受け入れるように指示^し、$(そうでない場合にのみ、文字列全体の開始/終了時に一致)各ラインの開始および終了時に一致します。

Pattern.DOTALLまたは(?s)、ドットが改行文字にも一致するようにJavaに指示します。

次に、あなたのケースでは、正規表現が文字列全体matches()と一致することを期待するメソッドを使用しているため、正規表現が失敗します。もちろん、一致した後に残っている文字があるため、これは機能しません。(\\W)*(\\S)*

したがって、単にで始まる文字列を探しているUser Comments:場合は、正規表現を使用します

^\s*User Comments:\s*(.*)

Pattern.DOTALLオプション:

Pattern regex = Pattern.compile("^\\s*User Comments:\\s+(.*)", Pattern.DOTALL);
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
    ResultString = regexMatcher.group(1);
} 

ResultString その後、テキストが含まれます User Comments:


「ユーザーコメント:」で始まるすべての文字列に一致するパターンを見つけようとしています。この後、「ユーザーのコメント:」は、ユーザーがテキストエリアに入力するものであり、したがって、新しい行も含めて何でも含めることができます。正規表現で多くのことを学ぶ必要があるようです...
Nivas

2
これは機能します(ありがとう!)パターンを試しました(?s)User Comments:\s*(.*)。@Amarghoshの回答から、パターンがわかりましたUser Comments: [\\s\\S]*。これらの中には、より良いまたは推奨される方法がありますか、またはこれらは同じことを行う2つの異なる方法ですか?
Nivas

3
どちらも同じ意味です。[\s\S]はもう少し明示的(「空白または空白以外の文字に一致する」)で.読みやすくなりますが、改行が含まれているかどうかを確認するには、(?s)orまたはDOTALL修飾子を探す必要があります。フラグを設定.したほうがよいPattern.DOTALL(これは(?s)、私の意見よりも読みやすく、覚えやすいです。最も使いやすいものを使用してください。)
Tim Pietzcker

.*with DOTALLはより読みやすくなっています。もう1つを使用して、問題がフラグではなくstr.matchesとmatcher.findの違いにあることを示しました。+1
Amarghosh

私は.*with を好みますがPattern.DOTALL、を使用する必要があるため、(?s)を使用する必要がありますString.matches
Nivas

42

これはMULTILINEフラグとは関係ありません。あなたが見ているのはfind()matches()メソッドの違いです。 正規表現が文字列全体と一致することを期待しながら、ターゲット文字列のどこfind()かに一致が見つかると成功しますmatches()

Pattern p = Pattern.compile("xyz");

Matcher m = p.matcher("123xyzabc");
System.out.println(m.find());    // true
System.out.println(m.matches()); // false

Matcher m = p.matcher("xyz");
System.out.println(m.matches()); // true

さらに、MULTILINEそれがあなたの考えていることを意味するのではありません。多くの人は、ターゲット文字列に改行が含まれている場合、つまり、複数の論理行が含まれている場合、そのフラグを使用する必要があるという結論にジャンプするようです。私は、その旨をSOにここにいくつかの答えを見てきましたが、実際には、すべてそのフラグはありませんアンカーの動作を変更され、^そして$

通常^、ターゲット文字列の最初に$一致し、最後に一致します(または最後の改行の前に一致しますが、ここでは省略します)。文字列に改行が含まれている場合しかし、あなたはのために選択することができます^し、$MULTILINEフラグを設定することにより、任意の論理行ではなく、文字列全体のほんの開始と終了の開始と終了時に一致します。

だからMULTILINE 意味を忘れて、それ何をするのか覚えておいてください^$アンカーの動作を変更します。 DOTALLモードは当初「単一行」と呼ばれ(Perlや.NETを含む一部のフレーバーにはまだ含まれています)、常に同じような混乱を引き起こしています。その場合、Java開発者がよりわかりやすい名前を付けたのは幸運ですが、「マルチライン」モードの合理的な代替手段はありませんでした。

このすべての狂気が始まったPerlでは、彼らは間違いを認め、Perl 6正規表現の「マルチライン」モードと「シングルライン」モードの両方を取り除きました。さらに20年後には、おそらく世界の他の国々もそれに倣うでしょう。


5
彼らがメソッド名「#matches」を使用して「すべてに一致」することを意味するとは信じがたい
rogerdpack

@ alan-moore申し訳ありませんが、これは正しいのですが[もっと眠る必要があります:)]
Raymond Naseef

22

str.matches(regex) Pattern.matches(regex, str)入力シーケンス全体をパターンと照合し、リターンするよう動作します

true入力シーケンス全体がこのマッチャーのパターンに一致する場合に限り

一方、パターンに一致して返される入力シーケンスの次のサブシーケンスmatcher.find() を見つけようとします

true場合に限り、サブシーケンス入力シーケンスのは、この正規表現エンジンのパターンに一致します

したがって、問題は正規表現にあります。以下を試してください。

String test = "User Comments: This is \t a\ta \ntest\n\n message \n";

String pattern1 = "User Comments: [\\s\\S]*^test$[\\s\\S]*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true

String pattern2 = "(?m)User Comments: [\\s\\S]*^test$[\\s\\S]*";
System.out.println(test.matches(pattern2));  //true

したがって、簡単に言うと、(\\W)*(\\S)*最初の正規表現の部分は空の文字列と一致し、*0回以上の出現を意味します。実際に一致した文字列はUser Comments:、期待どおりの文字列全体ではありません。2番目は文字列全体に一致しようとするため失敗しますが\\W、非単語文字に一致することはできません。つまり[^a-zA-Z0-9_]、最初の文字はT単語文字です。


「ユーザーコメント」で始まる文字列に一致させたいので、文字列に改行を含めることもできます。だから私はパターンを使用しUser Comments: [\\s\\S]*、これはうまくいきました。(ありがとう!)@Timの回答から、パターンを取得しましたUser Comments:(.*)。これも問題ありません。これらの方法のうち、推奨される方法またはより良い方法はありますか、それとも2つの方法で同じですか?
Nivas

@Nivas賢明なパフォーマンスの違いはないと思います。しかし(.*)DOTALLフラグと一緒にすると、より明白で読みやすいと思います([\\s\\S]*)
Amarghosh

これが最良の答えです。マルチライン機能について、Javaコードとパターン文字列オプションの両方にアクセスできます。
GoldBishop

0

複数行のフラグは、正規表現に文字列全体ではなく、各行にパターンを一致させるように正規表現に指示します。ワイルドカードで十分です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.