尝试在注释或文字中查找关键字的所有实例



我试图在某些 Java 代码(使用 Python 脚本)中找到关键字"public"的所有实例,这些实例不在注释或字符串中,也就是在//后面找不到,在/**/之间,不在双引号或单引号之间,并且不是变量名称的一部分——即它们前面必须有一个空格, 制表符或换行符,并且必须后跟相同的。

这就是我目前所拥有的——

//.*spublics.*n
/*.*spublics.**/
".*spublics.*"
'.*spublics.*'

我把这个搞砸了吗?

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

我已经发现这可能使用负面的前瞻和后视,但我仍然无法完全拼凑在一起。另外,对于/**/正则表达式,我担心.*与换行符不匹配,因此它无法识别此public在注释中:

/*
public
*/

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


编辑:

我敢说(?<!//).*public.*会在单行评论中不匹配任何内容,所以我正在掌握事情的窍门。我认为。但仍然不确定如何组合所有内容。

编辑2:

所以——按照这个想法,我|他们都得到了——

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

但我不确定。//public不会与第一个候补匹配,但会与第二个候补匹配。我需要和前瞻和后视,而不是或整个事情。

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

首先,为什么你的模式没有你想象的那么好?(对于在这些文字匹配public的任务;类似的问题将适用于反转逻辑)

正如您已经认识到的那样,您将遇到换行符问题(在/*...*/的情况下)。这可以通过使用修饰符/选项/标志re.S(这会改变.的行为)或使用[sS]而不是.(因为前者匹配任何字符)来解决。

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

下一件事:匹配不能重叠。但是您的匹配包括从开头文字到结束文字的所有内容。因此,如果您有"public public"那只会导致一场比赛。而捕捉在这里无法帮助你。通常,避免这种情况的诀窍是使用环顾四周(不包括在匹配中)。但是(正如我们稍后将看到的)后视并不像您想象的那么好,因为它不能是任意长度(仅在 .NET 中是可能的)。

现在最糟糕的。如果您在评论中有"怎么办?这应该不算数,对吧?如果字符串中有///**/怎么办?这应该不算数,对吧?"字符串内部的''字符串内部的"呢?更糟糕的是,"字符串内部的"呢?因此,为了获得 100% 的稳健性,您还必须对周围的分隔符进行类似的检查。这通常是正则表达式达到其功能极限的地方,这就是为什么您需要一个适当的解析器来遍历输入字符串并构建整个代码树的原因。

但是假设字符串中从来没有注释文字,注释中永远不会有引号(或者只有匹配的引号,因为它们会构成字符串,无论如何我们都不希望public字符串中)。因此,我们基本上假设每个有问题的文字都正确匹配,并且它们永远不会嵌套。在这种情况下,您可以使用前瞻来检查您是在其中一个文本内部还是外部(实际上是多个前瞻)。我很快就会谈到这一点。

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

但是,让我们看看如何确保我们真的在字符串之外。我们可以使用前瞻一直看到输入的末尾,并检查途中是否有偶数个引号。

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

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

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

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

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

(以下所有模式都隐含re.M)

这是它寻找单引号字符串的内容:

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

对于块注释,这要容易一些,因为我们只需要查找字符串的末尾或/*(这次实际上是整个字符串的末尾),而不会在途中遇到*/。这是通过对每个位置进行负面展望来完成的,直到搜索结束:

public(?=(?:(?![*]/)[sS])*(?:/[*]|Z))

但正如我所说,我们现在被单行评论难住了。但无论如何,我们可以将最后三个正则表达式合并为一个,因为前瞻实际上并没有推进正则表达式引擎在目标字符串上的位置:

public(?=[^"rn]*("(?:[^"rn\]|\.)*"[^"rn]*)*$)(?=[^'rn]*('(?:[^'rn\]|\.)*'[^'rn]*)*$)(?=(?:(?![*]/)[sS])*(?:/[*]|Z))

现在那些单行评论呢?模拟可变长度回溯的诀窍通常是反转字符串和模式 - 这使得后视成为前瞻:

cilbup(?!.*//)

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

cilbup(?=[^"rn]*("[^"rn]*"[^"rn]*)*$)(?=[^'rn]*('[^'rn]*'[^'rn]*)*$)(?=(?:(?![*]/)[sS])*(?:/[*]|Z))(?!.*//)

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

现在逃跑呢?现在这很烦人,因为我们必须跳过引号,如果它们后面跟着反斜杠。由于一些回溯问题,我们需要在五个地方解决这个问题。三次,当使用非引号字符时(因为我们现在也需要允许"。两次,在使用引号字符时(使用负前瞻以确保它们后面没有反斜杠)。让我们看一下双引号:

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

(它看起来很可怕,但如果你把它与忽略逃生的模式进行比较,你会发现一些差异。

因此,将其合并到上述模式中:

cilbup(?=(?:[^"rn]|"\)*(?:"(?!\)(?:[^"rn]|"\)*"(?!\)(?:[^"rn]|"\)*)*$)(?=(?:[^'rn]|'\)*(?:'(?!\)(?:[^'rn]|'\)*'(?!\)(?:[^'rn]|'\)*)*$)(?=(?:(?![*]/)[sS])*(?:/[*]|Z))(?!.*//)

因此,这实际上可以在很多情况下做到这一点。但正如你所看到的,它很可怕,几乎不可能阅读,而且绝对不可能维护。

有哪些注意事项?字符串内没有注释文本,其他类型的字符串中没有字符串文本,注释内没有字符串文本。另外,我们有四个独立的展望,这可能需要一些时间(至少我认为我的大部分回溯都是无效的)。

无论如何,我相信这与正则表达式尽可能接近。

编辑:

我刚刚意识到我忘记了public不能成为较长文字的一部分的条件。您包含了空格,但如果它是输入中的第一件事怎么办?最简单的方法是使用b.这匹配了单词字符和非单词字符之间的位置(不包括周围字符)。但是,Java标识符可能包含任何Unicode字母或数字,我不确定Python的b是否能够识别Unicode。此外,Java 标识符可能包含$。无论如何,这会打破它。环顾四周救援!与其断言每边都有一个空格字符,不如断言没有非空格字符。因为我们需要负面的环顾,所以我们将获得不免费将这些角色包含在匹配中的优势:

(?<!S)cilbup(?!S)(?=(?:[^"rn]|"\)*(?:"(?!\)(?:[^"rn]|"\)*"(?!\)(?:[^"rn]|"\)*)*$)(?=(?:[^'rn]|'\)*(?:'(?!\)(?:[^'rn]|'\)*'(?!\)(?:[^'rn]|'\)*)*$)(?=(?:(?![*]/)[sS])*(?:/[*]|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
(?:[^"rn]|"\)*
# 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
(?:[^"rn]|"\)*
# we've seen that before
"(?!\)  # a quote that is not followed by a backslash
(?:[^"rn]|"\)*
# 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
(?=(?:[^'rn]|'\)*(?:'(?!\)(?:[^'rn]|'\)*'(?!\)(?:[^'rn]|'\)*)*$)
# 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
[sS]   # 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

您是否考虑过使用re sub()方法将所有注释以及单引号和双引号字符串文字替换为空字符串。然后只需对您要查找的单词的结果文件进行简单的搜索/匹配/查找?

这至少会给你单词所在的行号。您可以使用该信息来编辑原始文件。

您可以使用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

要仅使用正则表达式执行此操作,请参阅 SO 上的regex-negation标签,例如,正则表达式以匹配不包含单词的字符串? 或使用更少的正则表达式功能 正则表达式:通过排除进行匹配,无需提前查看 - 可能吗?简单的方法是使用正匹配并跳过匹配的注释,带引号的字符串。结果是其余的比赛。

它找到了相反的东西,因为这正是你想要的。 :)

我不知道在单个正则表达式中匹配它们的方法(尽管理论上应该是可能的,因为常规语言在补码和交集下是封闭的)。但是您绝对可以搜索所有公共实例,然后删除与您的"坏"正则表达式之一匹配的任何实例。例如,尝试在match.start上使用set.difference,并从re.finditermatch.end属性。

最新更新