试图在评论或文字中找到关键字的所有实例?

tem*_*ame 7 python regex regex-negation

我试图在一些Java代码(使用Python脚本)中查找关键字"public"的所有实例,这些代码不在注释或字符串中,也就是//在a /*和a 之间找不到*/,而不是在double或single之间引号,它们不是变量名的一部分 - 即它们必须以空格,制表符或换行符开头,并且必须后跟相同的名称.

所以这就是我现在所拥有的 -

//.*\spublic\s.*\n
/\*.*\spublic\s.*\*/
".*\spublic\s.*"
'.*\spublic\s.*'
Run Code Online (Sandbox Code Playgroud)

我搞砸了吗?

但那找到了我不想要的东西.我怎样才能将它翻转并搜索这四个表达式之和的倒数,作为单个正则表达式

我已经想到这可能会使用负面前瞻和后视,但我仍然不能将它拼凑在一起.另外,对于/**/regex,我担心它.*与换行符不匹配,因此它无法识别出这public是在评论中:

/*
public
*/
Run Code Online (Sandbox Code Playgroud)

低于这一点的一切都是我在纸上思考,可以忽略不计.这些想法并不完全准确.


编辑:

我敢说(?<!//).*public.*,不会在单行注释中匹配任何东西,所以我得到了很多东西.我认为.但仍然不确定如何结合一切.

EDIT2:

那么 - 按照这个想法,我|把它们全部搞定 -

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

但我不确定.//public将不会与第一个替代匹配,但它将与第二个匹配.我需要和前瞻和后视,而不是整个事情.

Mar*_*der 5

对不起,但我必须把这个消息告诉你,你要做的事情是不可能的.原因主要是因为Java不是常规语言.众所周知,大多数正则表达式引擎都提供非常规功能,但Python特别缺乏递归(PCRE)或平衡组(.NET)等功能.但让我们更深入地研究一下.

首先,为什么你的模式不如你想象的那么好?(用于public 这些文字内部进行匹配的任务;类似的问题将适用于反转逻辑)

正如您已经认识到的那样,您将遇到换行问题(在这种情况下/*...*/).这可以通过使用modifier/option/flag re.S(改变行为.)或使用[\s\S]而不是.(因为前者匹配任何字符)来解决.

但还有其他问题.您只想查找字符串或注释文字的周围出现.你实际上并没有确定它们是专门围绕着这个public问题.我不确定你可以在Java中用一行代替多少,但是如果你有一个任意的字符串,然后public是一行,然后是一行的另一个字符串,那么你的正则表达式会匹配,public因为它可以找到"之前和在它之后.即使这是不可能的,如果在同一输入中有两个块注释,那么public这两个块注释之间的任何一个都会导致匹配.因此,您需要找到一种方法来断言您public内部 是真的,"..."或者/*...*/不仅仅是这些文字可以在它左边的任何地方找到.

下一件事:比赛不能重叠.但是你的匹配包括从开头文字到结尾文字的所有内容.所以如果你有"public public"这个只会导致一场比赛.捕捉无法帮助你.通常避免这种情况的诀窍是使用lookarounds(匹配中不包括).但是(我们将在后面看到),lookbehind并不像你想象的那样好用,因为它不能是任意长度的(只有在.NET中才有可能).

现在最糟糕的是.如果你有"评论内容怎么办?这不应该算,对吗?如果你有///*或者*/一个字符串里面?这不应该算,对吗?什么'里面"-strings和"内部'-strings?更糟糕的是,\"内部"-string怎么样?因此,对于100%的稳健性,您还必须对周围的分隔符进行类似的检查.这通常是正则表达式达到其功能结束的地方,这就是为什么你需要一个适当的解析器来遍历输入字符串并构建整个代码树.

但是你说你从来没有在字符串中有注释文字而且你的注释中没有引号(或者只有匹配的引号,因为它们构成一个字符串,我们不想要public内部字符串).所以我们基本上假设所讨论的每个文字都是正确匹配的,并且它们永远不会嵌套.在这种情况下,您可以使用前瞻来检查您是否在其中一个文字内部(实际上是多个前瞻).我很快就会谈到这一点.

但还剩下一件事.什么不起作用(?<!//).*public.*?为了匹配,它足以(?<!//)匹配任何单个位置.例如,如果您刚刚输入,// public则引擎将在字符串的开头处尝试负向lookbehind(在字符串开头的左侧),找不到//,然后使用.*消耗//和空格然后匹配public.你真正想要的是什么(?<!//.*)public.这将从起始位置开始向后看,并从public当前线一直向左看.但是......这是一个可变长度的lookbehind,只有.NET支持.

但是,让我们看看我们如何确保我们真的不在字符串之外.我们可以使用前瞻一直查看输入的结尾,并检查路上是否有偶数引号.

public(?=[^"]*("[^"]*"[^"]*)*$)
Run Code Online (Sandbox Code Playgroud)

现在,如果我们努力尝试,我们也可以在字符串内部忽略转义引号:

public(?=[^"]*("(?:[^"\\]|\\.)*"[^"]*)*$)
Run Code Online (Sandbox Code Playgroud)

因此,一旦我们遇到一个,"我们将接受非引号,非反斜杠字符或反斜杠字符及其后的任何内容(允许转义反斜杠字符,以便"a string\\"我们不会将结果"视为转义).我们可以使用多行模式(re.M)来避免一直到输入的结尾(因为行的末尾就足够了):

public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$)
Run Code Online (Sandbox Code Playgroud)

(re.M暗示所有以下模式)

这就是它查找单引号字符串:

public(?=[^'\r\n]*('(?:[^'\r\n\\]|\\.)*'[^'\r\n]*)*$)
Run Code Online (Sandbox Code Playgroud)

对于块注释,它更容易一些,因为我们只需要查找/*或结束字符串(这次真的是整个字符串的结尾),而不会遇到任何问题*/.在搜索结束之前,每个位置都有一个负前瞻:

public(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))
Run Code Online (Sandbox Code Playgroud)

但正如我所说,我们现在对单行评论感到困惑.但无论如何,我们可以将最后三个正则表达式合并为一个,因为前瞻实际上并没有提升目标字符串上正则表达式引擎的位置:

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

那么这些单行评论呢?模拟可变长度lookbehinds的技巧通常是反转字符串和模式 - 这使得lookbehind成为一个前瞻:

cilbup(?!.*//)
Run Code Online (Sandbox Code Playgroud)

当然,这意味着我们也必须扭转所有其他模式.好消息是,如果我们不关心转义,它们看起来完全一样(因为引号和块注释都是对称的).所以你可以在反向输入上运行这个模式:

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

然后,您可以使用在实际输入中找到匹配位置inputLength -foundMatchPosition - foundMatchLength.

现在逃跑怎么样?现在这很烦人,因为我们必须跳过引号,如果它们后跟反斜杠.由于一些回溯问题,我们需要在五个地方处理这个问题.三次,当消耗非引号字符时(因为我们现在需要允许)"\.两次,当消耗引号字符时(使用负向前瞻以确保它们之后没有反斜杠).让我们看看双引号:

cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)
Run Code Online (Sandbox Code Playgroud)

(它看起来很糟糕,但是如果你将它与无视逃逸的模式进行比较,你会注意到一些差异.)

所以将其纳入上述模式:

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

所以这可能实际上在很多情况下都可以.但是你可以看到它很可怕,几乎不可能阅读,而且绝对无法维持.

有什么警告?字符串中没有注释文字,其他类型的字符串中没有字符串文字,注释中没有字符串文字.另外,我们有四个独立的前瞻,这可能需要一些时间(至少我认为我已经取消了大部分回溯).

无论如何,我相信这与正则表达式一样接近.

编辑:

我刚刚意识到我忘记了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))(?!.*//)
Run Code Online (Sandbox Code Playgroud)

并且因为只是将这个代码片段滚动到右边的代码片段并不能完全理解这个正则表达式是多么荒谬,这里它在freespacing模式(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
Run Code Online (Sandbox Code Playgroud)