7

コメントや文字列にないJavaコード(Pythonスクリプトを使用)でキーワード「public」のすべてのインスタンスを見つけようとしています。別名//、a/*とaの間にあり*/、doubleまたはsingleの間にありません。引用符であり、変数名の一部ではありません。つまり、前にスペース、タブ、または改行を付け、その後に同じものを続ける必要があります。

これが私が今持っているものです-

//.*\spublic\s.*\n
/\*.*\spublic\s.*\*/
".*\spublic\s.*"
'.*\spublic\s.*'

私はこれをまったく台無しにしていますか?

しかし、それは私が探していないものを正確に見つけます。どうすればそれを元に戻して、これら4つの式の合計の逆数を単一の正規表現として検索できますか?

これはおそらくネガティブな先読みと後読みを使用していると思いましたが、それでも完全に組み合わせることができません。また、/ ** /正規表現の場合、.*改行と一致しないことが懸念されるため、これpublicがコメントに含まれていることを認識できません。

/*
public
*/

この点より下のすべては私が紙で考えていることであり、無視することができます。これらの考えは完全には正確ではありません。


編集:

私はあえて(?<!//).*public.*一行のコメントにないものと一致するので、物事のコツをつかんでいます。おもう。しかし、それでもすべてを組み合わせる方法がわかりません。

Edit2:

それで、その考えに従って、私は|それらすべてを取得するように編集しました-

(?<!//).*public.*|(?<!/\*).*public.\*/(?!\*/)|(?<!").*public.*(?!")|(?<!').*public.*(?!')

しかし、それについてはよくわかりません。//public最初の代替案とは一致しませんが、2番目の代替案とは一致します。全体ではなく、先読みと後読みをANDする必要があります。

4

4 に答える 4

5

申し訳ありませんが、あなたがやろうとしていることは不可能であるというニュースをあなたに伝えなければなりません。その主な理由は、Javaが正規言語ではないためです。今では誰もが知っているように、ほとんどの正規表現エンジンは非正規機能を提供しますが、特にPythonには、そのトリックを実行できる再帰(PCRE)やバランシンググループ(.NET)などの機能がありません。しかし、それをさらに詳しく調べてみましょう。

まず第一に、なぜあなたのパターンはあなたが思っているほど良くないのですか?public (これらのリテラル内でのマッチングのタスクの場合。同様の問題がロジックの反転に適用されます)

すでに認識しているように、改行の問題が発生します(の場合/*...*/)。これは、修飾子/オプション/フラグre.S(の動作を変更する.)を使用するか、[\s\S]代わりに.(前者は任意の文字と一致するため)を使用することで解決できます。

しかし、他にも問題があります。文字列またはコメントリテラルの周囲のオカレンスのみを検索する必要があります。あなたは実際にそれらがpublic問題の周りに具体的に包まれていることを確認していません。Javaで1行にどれだけ入力できるかはわかりませんが、任意の文字列があり、後でpublic1行に別の文字列がある場合、正規表現は前とpublicを見つけることができるため、一致します。"その後。それが不可能な場合でも、同じ入力にpublic2つのブロックコメントがある場合、それら2つのブロックコメントの間に一致が発生します。publicだからあなたはあなたが本当に中に いることだけを主張する方法を見つける必要があるでしょ"..."/*...*/そして、これらのリテラルがその右側の左側のどこにでもあるというだけではありません。

次のこと:一致は重複できません。しかし、あなたの試合には、最初の文字から最後の文字まですべてが含まれます。したがって、それがあっ"public public"た場合、一致は1つだけになります。そして、キャプチャはここであなたを助けることはできません。通常、これを回避するための秘訣は、ルックアラウンド(マッチに含まれていない)を使用することです。ただし、(後で説明するように)後読みは、任意の長さにすることはできないため(.NETでのみ可能)、思ったほどうまく機能しません。

今、すべての中で最悪。"コメントの中にある場合はどうなりますか?それは数えるべきではありませんよね?//文字列がある場合、または文字列内にある場合はどうなります/**/?それは数えるべきではありませんよね?'Inside "-stringsと"Inside -stringsはどう'ですか?さらに悪いことに、文字列\"の内側は"どうですか?したがって、100%の堅牢性を得るには、周囲の区切り文字についても同様のチェックを行う必要があります。そして、これは通常、正規表現がその機能の終わりに達する場所であり、これが、入力文字列をウォークしてコードのツリー全体を構築する適切なパーサーが必要な理由です。

ただし、文字列内にコメントリテラルがなく、コメント内に引用符がない場合(または、文字列を構成するため、一致する引用符のみが含まれ、文字列public内は必要ありません)とします。したがって、基本的に、問題のリテラルはすべて正しく一致し、ネストされることはないと想定しています。その場合、先読みを使用して、リテラルの1つ(実際には複数の先読み)の内側にあるか外側にあるかを確認できます。すぐにそれについて説明します。

しかし、もう1つ残っています。何が機能し(?<!//).*public.*ないのですか?これを一致させるには(?<!//)、任意の1つの位置で一致させるだけで十分です。たとえば、入力したばかりの場合// public、エンジンは文字列の先頭(文字列の先頭の左側)でネガティブルックビハインドを試し、noを見つけ//てから、スペースとスペース.*を消費//してから一致させpublicます。あなたが実際に欲しいのはです(?<!//.*)public。これにより、の開始位置から後読みが開始されpublic、現在の行を左端まで見ることができます。しかし...これは可変長のルックビハインドであり、.NETでのみサポートされています。

しかし、私たちが本当に文字列の外にいることを確認する方法を見てみましょう。先読みを使用して、入力の最後まで調べ、途中で引用符が偶数になっていることを確認できます。

public(?=[^"]*("[^"]*"[^"]*)*$)

これで、本当に一生懸命努力すれば、文字列内でエスケープされた引用符を無視することもできます。

public(?=[^"]*("(?:[^"\\]|\\.)*"[^"]*)*$)

したがって、に遭遇すると、"引用符、バックスラッシュ以外の文字、またはバックスラッシュ文字とそれに続く文字のいずれかを受け入れます(これにより、バックスラッシュ文字もエスケープできるため、クロージングがエスケープされたものとして"a string\\"扱われなくなります")。これを複数行モード(re.M)で使用して、入力の最後まで移動しないようにすることができます(行の終わりで十分であるため)。

public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$)

re.M以下のすべてのパターンに含まれます)

これは、一重引用符で囲まれた文字列を検索するものです。

public(?=[^'\r\n]*('(?:[^'\r\n\\]|\\.)*'[^'\r\n]*)*$)

ブロックコメントの場合は、途中/*で遭遇することなく、文字列の終わり(今回は実際には文字列全体の終わり)を探すだけでよいため、少し簡単です。*/これは、検索が終了するまで、すべての位置でネガティブな先読みで行われます。

public(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))

しかし、私が言ったように、私たちは今のところ単一行のコメントに困惑しています。しかし、とにかく、先読みは実際にはターゲット文字列上の正規表現エンジンの位置を進めないため、最後の3つの正規表現を1つに組み合わせることができます。

public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$)(?=[^'\r\n]*('(?:[^'\r\n\\]|\\.)*'[^'\r\n]*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))

では、これらの1行のコメントはどうでしょうか。可変長後読みをエミュレートする秘訣は、通常、文字列とパターンを逆にすることです。これにより、後読みが先読みになります。

cilbup(?!.*//)

もちろん、それは他のすべてのパターンも逆にする必要があることを意味します。良いニュースは、エスケープを気にしない場合、それらはまったく同じに見えることです(引用符とブロックコメントの両方が対称であるため)。したがって、このパターンを逆入力で実行できます。

cilbup(?=[^"\r\n]*("[^"\r\n]*"[^"\r\n]*)*$)(?=[^'\r\n]*('[^'\r\n]*'[^'\r\n]*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//)

次に、を使用して、実際の入力で一致位置を見つけることができますinputLength -foundMatchPosition - foundMatchLength

さて、脱出はどうですか?引用符の後にバックスラッシュが続く場合は、引用符をスキップする必要があるため、これは非常に厄介です。いくつかのバックトラックの問題があるため、5か所でそれを処理する必要があります。3回、引用符以外の文字を使用する場合(これも許可する必要があるため。2"\回、引用符を使用する場合(負の先読みを使用して、後ろに円記号がないことを確認します)。二重引用符を見てみましょう。

cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)

(恐ろしいように見えますが、エスケープを無視するパターンと比較すると、いくつかの違いに気付くでしょう。)

したがって、それを上記のパターンに組み込むと、次のようになります。

cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)(?=(?:[^'\r\n]|'\\)*(?:'(?!\\)(?:[^'\r\n]|'\\)*'(?!\\)(?:[^'\r\n]|'\\)*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//)

したがって、これは実際には多くの場合にそれを行う可能性があります。しかし、ご覧のとおり、それは恐ろしく、読むことはほとんど不可能であり、維持することは間違いなく不可能です。

警告は何でしたか?文字列内にコメントリテラル、他のタイプの文字列内に文字列リテラル、コメント内に文字列リテラルはありません。さらに、4つの独立した先読みがありますが、これにはおそらく時間がかかります(少なくとも、バックトラックのほとんどが無効になっていると思います)。

いずれにせよ、これは正規表現で得られる限り近いと思います。

編集:

public長い文字の一部であってはならない条件を忘れてしまったことに気づきました。スペースを含めましたが、それが入力の最初のものである場合はどうなりますか?最も簡単なのはを使用すること\bです。これは、単語文字と非単語文字の間にある位置(周囲の文字を含まない)と一致します。ただし、Java識別子にはUnicodeの文字または数字が含まれている可能性があり、Python\bがUnicodeに対応しているかどうかはわかりません。また、Java識別子には。が含まれる場合があります$。とにかくそれを壊すでしょう。救助への見回し!すべての側にスペース文字があると主張する代わりに、非スペース文字がないと主張しましょう。そのためにはネガティブなルックアラウンドが必要なので、これらのキャラクターを無料で試合に含めないという利点があります。

(?<!\S)cilbup(?!\S)(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)(?=(?:[^'\r\n]|'\\)*(?:'(?!\\)(?:[^'\r\n]|'\\)*'(?!\\)(?:[^'\r\n]|'\\)*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//)

そして、このコードスニペットを右にスクロールしただけでは、この正規表現がどれほど途方もなく巨大であるかを完全に把握することはできないため、ここではre.Xいくつかの注釈付きのフリースペースモード()になっています。

(?<!\S)      # make sure there is no trailing non-whitespace character
cilbup       # public
(?!\S)       # make sure there is no leading non-whitespace character
(?=          # lookahead (effectively lookbehind!) to ensure we are not inside a
             # string
  (?:[^"\r\n]|"\\)*
             # consume everything except for line breaks and quotes, unless the
             # quote is followed by a backslash (preceded in the actual input)
  (?:        # subpattern that matches two (unescaped) quotes
    "(?!\\)  # a quote that is not followed by a backslash
    (?:[^"\r\n]|"\\)*
             # we've seen that before
    "(?!\\)  # a quote that is not followed by a backslash
    (?:[^"\r\n]|"\\)*
             # we've seen that before
  )*         # end of subpattern - repeat 0 or more times (ensures even no. of ")
  $          # end of line (start of line in actual input)
)            # end of double-quote lookahead
(?=(?:[^'\r\n]|'\\)*(?:'(?!\\)(?:[^'\r\n]|'\\)*'(?!\\)(?:[^'\r\n]|'\\)*)*$)
             # the same horrible bastard again for single quotes
(?=          # lookahead (effectively lookbehind) for block comments
  (?:        # subgroup to consume anything except */
    (?![*]/) # make sure there is no */ coming up
    [\s\S]   # consume an arbitrary character
  )*         # repeat
  (?:/[*]|\Z)# require to find either /* or the end of the string
)            # end of lookahead for block comments
(?!.*//)     # make sure there is no // on this line
于 2012-12-13T09:16:47.127 に答える
2

メソッドを使用して、すべてのコメントと一重引用符および二重引用符で囲まれた文字列リテラルをnull文字列に置き換えることを検討しましたかre sub()。次に、探している単語の結果ファイルの単純な検索/一致/検索を実行しますか?

これにより、少なくとも単語が配置されている行番号がわかります。その情報を使用して、元のファイルを編集できる場合があります。

于 2012-12-11T09:10:27.543 に答える
1

コメントまたは二重引用符で囲まれた文字列の外側のキーワードpyparsingを検索するために使用できます。public

from pyparsing import Keyword, javaStyleComment, dblQuotedString

keyword = "public"
expr = Keyword(keyword).ignore(javaStyleComment | dblQuotedString)

for [token], start, end in expr.scanString(r"""{keyword} should match
    /*
    {keyword} should not match "
    */
    // this {keyword} also shouldn't match
    "neither this \" {keyword}"
    but this {keyword} will
    re{keyword} is ignored
    '{keyword}' - also match (only double quoted strings are ignored)
    """.format(keyword=keyword)):
    assert token == keyword and len(keyword) == (end - start)
    print("Found at %d" % start)

出力

Found at 0
Found at 146
Found at 187

quotedString一重引用符で囲まれた文字列も無視するには、の代わりにを使用できますdblQuotedString

正規表現のみでそれを行うには、regex-negationSOのタグを参照してください。たとえば、単語を含まない文字列に一致する正規表現?またはさらに少ない正規表現機能を使用する正規表現:先読みなしで除外によるマッチング-それは可能ですか?。簡単な方法は、正の一致を使用し、一致したコメント、引用符で囲まれた文字列をスキップすることです。結果は残りの試合です。

于 2012-12-11T18:56:28.283 に答える
0

それはあなたが求めているものだからです。:)

単一の正規表現でそれらすべてを一致させる方法はわかりません(ただし、正規言語は補数と共通部分の下で閉じられているため、理論的には可能です)。ただし、publicのすべてのインスタンスを確実に検索してから、「不良」正規表現の1つと一致するインスタンスをすべて削除することができます。たとえばset.differencematch.startとのmatch.endプロパティを使用してみてくださいre.finditer

于 2012-12-11T06:08:11.083 に答える