对不起,我得告诉你一个消息,你想做的事是不可能的。原因主要是因为 Java 不是常规语言。我们现在都知道,大多数正则表达式引擎都提供非常规的功能,但 Python 尤其缺乏可以解决问题的递归 (PCRE) 或平衡组 (.NET) 之类的东西。但让我们更深入地研究一下。
首先,为什么你的模式没有你想象的那么好?(用于匹配这些文字public
内部的任务;类似的问题将适用于反转逻辑)
正如您已经认识到的那样,您将遇到换行问题(在 的情况下/*...*/
)。这可以通过使用修饰符/选项/标志re.S
(改变 的行为.
)或使用[\s\S]
代替.
(因为前者匹配任何字符)来解决。
但还有其他问题。您只想查找周围出现的字符串或注释文字。您实际上并没有确保它们是专门围绕有public
问题的。我不确定你可以在 Java 中的单行上放多少,但如果你有一个任意字符串,然后是 a public
,然后是单行上的另一个字符串,那么你的正则表达式将匹配,public
因为它可以找到"
之前和在它之后。即使这是不可能的,如果您在同一个输入中有两个块注释,那么public
这两个块注释之间的任何一个都会导致匹配。所以你需要找到一种方法来断言你public
真的在里面 "..."
或/*...*/
不仅仅是这些文字可以在它左边的任何地方找到。
下一件事:匹配不能重叠。但是您的匹配包括从开头文字到结尾文字的所有内容。因此,如果您这样做"public public"
,只会导致一场比赛。捕获在这里无法为您提供帮助。通常避免这种情况的技巧是使用环视(不包括在匹配中)。但是(我们稍后会看到)lookbehind 并没有你想象的那么好,因为它不能是任意长度(只有在 .NET 中才有可能)。
现在是最糟糕的。如果您有"
内部评论怎么办?这应该不算数吧?如果你有//
或/*
或*/
在一个字符串中怎么办?这应该不算数吧?那么'
inside "
-strings 和"
inside '
-strings 呢?更糟糕的是,\"
inside "
-string 呢?因此,为了 100% 的稳健性,您还必须对周围的分隔符进行类似的检查。这通常是正则表达式达到其功能极限的地方,这就是为什么您需要一个适当的解析器来遍历输入字符串并构建整个代码树的原因。
但是假设您在字符串中没有注释文字,并且在注释中没有引号(或者只有匹配的引号,因为它们会构成一个字符串,而且我们也不希望public
在字符串中)。所以我们基本上假设每个有问题的文字都正确匹配,并且它们从不嵌套。在这种情况下,您可以使用前瞻来检查您是在其中一个文字内部还是外部(实际上是多个前瞻)。我很快就会谈到这一点。
但是还剩下一件事。什么不起作用(?<!//).*public.*
?为此,(?<!//)
匹配任何单个位置就足够了。例如,如果您刚刚输入// public
,引擎会在字符串开头(字符串开头的左侧)尝试否定的lookbehind,会发现 no //
,然后使用.*
to 消耗//
和空间,然后 match public
。你真正想要的是(?<!//.*)public
. 这将从 的起始位置开始向后看,public
并一直向左看当前行。但是......这是一个可变长度的lookbehind,仅受.NET支持。
但是让我们看看如何确保我们真的在字符串之外。我们可以使用前瞻来一直查看输入的末尾,并检查途中是否有偶数个引号。
public(?=[^"]*("[^"]*"[^"]*)*$)
现在,如果我们真的很努力,我们也可以在字符串中忽略转义的引号:
public(?=[^"]*("(?:[^"\\]|\\.)*"[^"]*)*$)
因此,一旦我们遇到 a"
我们将接受非引号、非反斜杠字符或反斜杠字符以及它后面的任何内容(也允许转义反斜杠字符,因此"a string\\"
我们不会将关闭"
视为被转义)。我们可以将它与多行模式 ( re.M
) 一起使用,以避免一直走到输入的末尾(因为行尾就足够了):
public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$)
(re.M
对于以下所有模式都是隐含的)
这是它寻找单引号字符串的内容:
public(?=[^'\r\n]*('(?:[^'\r\n\\]|\\.)*'[^'\r\n]*)*$)
对于块注释,它更容易一些,因为我们只需要寻找/*
或字符串的结尾(这次真的是整个字符串的结尾),而不会*/
在途中遇到。这是通过对每个位置进行负前瞻来完成的,直到搜索结束:
public(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))
但正如我所说,我们现在被单行注释难住了。但无论如何,我们可以将最后三个正则表达式合并为一个,因为前瞻实际上并没有提前正则表达式引擎在目标字符串上的位置:
public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$)(?=[^'\r\n]*('(?:[^'\r\n\\]|\\.)*'[^'\r\n]*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))
现在那些单行注释呢?模拟可变长度lookbehinds的技巧通常是反转字符串和模式 - 这使得lookbehind成为一个lookahead:
cilbup(?!.*//)
当然,这意味着我们也必须反转所有其他模式。好消息是,如果我们不关心转义,它们看起来完全一样(因为引号和块注释都是对称的)。因此,您可以在反向输入上运行此模式:
cilbup(?=[^"\r\n]*("[^"\r\n]*"[^"\r\n]*)*$)(?=[^'\r\n]*('[^'\r\n]*'[^'\r\n]*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//)
然后,您可以使用inputLength -foundMatchPosition - foundMatchLength
.
现在逃跑怎么办?这现在很烦人,因为如果引号后面跟着反斜杠,我们必须跳过引号。由于一些回溯问题,我们需要在五个地方解决这个问题。三次,在使用非引号字符时(因为我们"\
现在也需要允许。两次,在使用引号字符时(使用负前瞻来确保它们后面没有反斜杠)。让我们看看双引号:
cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)
(它看起来很可怕,但如果你将它与忽略转义的模式进行比较,你会注意到一些差异。)
因此将其合并到上述模式中:
cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)(?=(?:[^'\r\n]|'\\)*(?:'(?!\\)(?:[^'\r\n]|'\\)*'(?!\\)(?:[^'\r\n]|'\\)*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//)
因此,这实际上可能适用于许多情况。但正如你所见,它很糟糕,几乎无法阅读,而且绝对无法维护。
有什么注意事项?字符串中没有注释文字,其他类型的字符串中没有字符串文字,注释中没有字符串文字。另外,我们有四个独立的前瞻,这可能需要一些时间(至少我认为我有一个无效的大部分回溯)。
无论如何,我相信这与正则表达式一样接近。
编辑:
我刚刚意识到我忘记了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