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

时间:2012-12-11 05:31:51

标签: python regex regex-negation

我正在尝试在某些Java代码(使用Python脚本)中查找关键字“public”的所有实例,这些代码不在注释或字符串中,在//之后找不到/*之间的{{{ 1}}和*/,而不是在双引号或单引号之间,而不是变量名称的一部分 - 即它们必须以空格,制表符或换行符开头,并且必须后跟相同。

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

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

我是不是搞砸了这个?

但这找到了我不想要的东西。如何将其翻转并搜索这四个表达式之和的倒数,作为单个正则表达式

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

/*
public
*/

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


编辑:

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

EDIT2:

那么 - 遵循这个想法,我|编辑了所有 -

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

但我不确定。 //public将不会与第一个备用匹配,但它将与第二个匹配。我需要和前瞻和后视,而不是整个事物。

4 个答案:

答案 0 :(得分:5)

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

首先,为什么你的模式不如你想象的那么好? (用于匹配{/ 1}} 里面这些文字的任务;类似的问题将适用于逆转逻辑)

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

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

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

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

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

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

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

(?<!//.*)public

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

public

因此,一旦遇到public(?=[^"]*("[^"]*"[^"]*)*$) ,我们将接受非引号,非反斜杠字符或反斜杠字符及其后的任何内容(允许转义反斜杠字符,以便在{{ 1}}我们不会将结束public(?=[^"]*("(?:[^"\\]|\\.)*"[^"]*)*$) 视为被转义)。我们可以使用多行模式(")来避免一直到输入的结尾(因为行的末尾就足够了):

"a string\\"

(以下所有模式均隐含"

这就是它所寻找的单引号字符串:

re.M

对于块注释,它更容易一些,因为我们只需要查找public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$) 或字符串的结尾(这次真的是整个字符串的结尾),而不会遇到{{1 }} 在途中。在搜索结束之前,每个位置都有一个负前瞻:

re.M

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

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

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

/*

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

*/

然后,您可以使用public(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z)) 找到实际输入中的匹配位置。

现在逃跑怎么样?现在这很烦人,因为我们必须跳过引号,如果他们跟着反斜杠。由于一些回溯问题,我们需要在五个地方处理这个问题。三次,当消耗非引号字符时(因为我们现在也需要允许public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$)(?=[^'\r\n]*('(?:[^'\r\n\\]|\\.)*'[^'\r\n]*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z)) 。两次,当消耗引号字符时(使用负向前瞻以确保它们之后没有反斜杠)。让&#39 ;看看双引号:

cilbup(?!.*//)

(看起来很糟糕,但是如果你把它与忽略逃避的模式进行比较,你会注意到一些差异。)

因此将其纳入上述模式:

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

因此,对于许多情况,这实际上可能会这样做。但是你可以看到它很可怕,几乎不可能阅读,而且绝对无法维持。

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

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

修改

我刚刚意识到我忘记了inputLength -foundMatchPosition - foundMatchLength不能成为较长文字的一部分的条件。你包括空格,但如果它是输入的第一件事怎么办?最简单的方法是使用"\。这匹配在单词字符和非单词字符之间的位置(不包括周围的字符)。但是,Java标识符可能包含任何Unicode字母或数字,并且我不确定Python的cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$) 是否支持Unicode。此外,Java标识符可能包含cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)(?=(?:[^'\r\n]|'\\)*(?:'(?!\\)(?:[^'\r\n]|'\\)*'(?!\\)(?:[^'\r\n]|'\\)*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//) 。无论如何哪个会打破这个。救援的外观!我们不断言每一方都有空格字符,而是断言没有非空格字符。因为我们需要负面的解释,我们将获得免费在比赛中不包括这些角色的优势:

public

因为只是将这个代码片段滚动到正确的代码片段并不能完全理解这个正则表达式有多么荒谬,所以它在freespacing模式(\b)中带有一些注释:

\b

答案 1 :(得分:2)

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

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

答案 2 :(得分: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-negation tag on SO,例如Regular expression to match string not containing a word?或使用更少的正则表达式功能Regex: Matching by exclusion, without look-ahead - is it possible?。简单的方法是使用正匹配并跳过匹配的注释,引用字符串。结果是剩下的比赛。

答案 3 :(得分:0)

它发现了相反的结果,因为这正是你所要求的。 :)

我不知道在单个正则表达式中匹配它们的方法(尽管理论上应该是可行的,因为常规语言在补语和交叉点下是封闭的)。但你肯定可以搜索所有公共实例,然后删除任何与你的“坏”正则表达式匹配的实例。尝试在set.difference的{​​{1}}和match.start属性上使用match.end。{/ p>