正则表达式从 C# 中删除行注释

Posted

技术标签:

【中文标题】正则表达式从 C# 中删除行注释【英文标题】:Regex to strip line comments from C# 【发布时间】:2011-04-01 06:13:45 【问题描述】:

我正在开发一个例程,从一些 C# 代码中去除块 行 cmets。我查看了网站上的其他示例,但没有找到我正在寻找的确切答案。

我可以使用带有 RegexOptions.Singleline 的正则表达式来完整匹配块 cmets(/* 注释 */):

(/\*[\w\W]*\*/)

我可以使用带有 RegexOptions.Multiline 的正则表达式来完整匹配行 cmets(// 注释):

(//((?!\*/).)*)(?!\*/)[^\r\n]

注意:我使用的是[^\r\n] 而不是$,因为$ 在匹配中也包含\r

但是,这并没有完全按我想要的方式工作。

这是我要匹配的测试代码:

// remove whole line comments
bool broken = false; // remove partial line comments
if (broken == true)

    return "BROKEN";

/* remove block comments
else

    return "FIXED";
 // do not remove nested comments */ bool working = !broken;
return "NO COMMENT";

块表达式匹配

/* remove block comments
else

    return "FIXED";
 // do not remove nested comments */

这很好,但是行表达式匹配

// remove whole line comments
// remove partial line comments

// do not remove nested comments

另外,如果我在行表达式中没有两次 */ 肯定前瞻,它匹配

// do not remove nested comments *

真的不想要。

我想要的是一个表达式,它将匹配从// 开始到行尾的字符,但 not// 和行尾之间是否包含*/

另外,为了满足我的好奇心,谁能解释为什么我需要两次前瞻? (//((?!\*/).)*)[^\r\n](//(.)*)(?!\*/)[^\r\n] 都会包含 *,但 (//((?!\*/).)*)(?!\*/)[^\r\n](//((?!\*/).)*(?!\*/))[^\r\n] 不会。

【问题讨论】:

你是否也考虑过string foo = "http://***.com;"的情况 您的 /* ... */ 模式由于贪婪而过度匹配,例如考虑/* comment1 */ not-a-comment! /* comment2 */ 您可以考虑使用 C# 解析器:***.com/questions/81406/parser-for-c LOL...对于这个问题,使用成熟的 C# 解析器绝对是矫枉过正。 一个绝对无价的设计、理解和测试 RegEx 的工具是 expresso:ultrapico.com/Expresso.htm。 【参考方案1】:

您的两个正则表达式(用于块和行 cmets)都有错误。如果你愿意,我可以描述这些错误,但我觉得如果我写新的可能会更有效率,特别是因为我打算写一个匹配两者的一个。

问题是,每次/*// 以及文字字符串相互“干扰”时,总是首先开始的那个优先。这非常方便,因为这正是正则表达式的工作原理:首先找到第一个匹配项。

所以让我们定义一个正则表达式来匹配这四个标记中的每一个:

var blockComments = @"/\*(.*?)\*/";
var lineComments = @"//(.*?)\r?\n";
var strings = @"""((\\[^\n]|[^""\n])*)""";
var verbatimStrings = @"@(""[^""]*"")+";

要回答标题中的问题(strip cmets),我们需要:

用空替换块 cmets 用换行符替换 cmets 行(因为正则表达式会吃掉换行符) 将文字字符串保留在原处。

Regex.Replace 可以使用 MatchEvaluator 函数轻松做到这一点:

string noComments = Regex.Replace(input,
    blockComments + "|" + lineComments + "|" + strings + "|" + verbatimStrings,
    me => 
        if (me.Value.StartsWith("/*") || me.Value.StartsWith("//"))
            return me.Value.StartsWith("//") ? Environment.NewLine : "";
        // Keep the literal strings
        return me.Value;
    ,
    RegexOptions.Singleline);

我在 Holystream 提供的所有示例以及我能想到的各种其他案例上运行了这段代码,它就像一个魅力。如果你能提供一个失败的例子,我很乐意为你调整代码。

【讨论】:

我不需要提取 cmets,只需将它们从我的源脚本中删除即可。我试过你的代码,效果很好。理想情况下,如果该行仅包含 cmets,我想完全删除任何行。例如注释所在的位置没有空行。但是,这不是要求,只是格式偏好。谢谢。 @Welton:好吧,你可以在之后对结果运行Regex.Replace(@"^(\s*\r?\n)2,", Environment.Newline, RegexOptions.Multiline),但这会删除没有也有评论的空白双行. 我看到你测试过这个:csharp.pastebin.com/0aqBdFE5 但是当你有这样的东西时:string input = "1 + 2 //cmets";由于三元运算符中的Environment.Newline,它失败了它给你结果“1 + 2 \r\n” @juFo:当我尝试你的输入时,它失败了:它实际上留下了评论。(这是意料之中的,因为正则表达式需要一个换行符。)我已经解决了这个问题:@ 987654322@ 非常优雅的解决方案。根据您的解决方案,我在此处为删除 SQL cmets 做了类似的事情:***.com/a/33947706/3606250【参考方案2】:

您可以使用如下表达式标记代码:

@(?:"[^"]*")+|"(?:[^"\n\\]+|\\.)*"|'(?:[^'\n\\]+|\\.)*'|//.*|/\*(?s:.*?)\*/

它也会匹配一些无效的转义/结构(例如'foo'),但可能会匹配所有感兴趣的有效标记(除非我忘记了什么),因此适用于有效代码。

在替换中使用它并捕获您想要保留的部分将为您提供所需的结果。即:

static string StripComments(string code)

    var re = @"(@(?:""[^""]*"")+|""(?:[^""\n\\]+|\\.)*""|'(?:[^'\n\\]+|\\.)*')|//.*|/\*(?s:.*?)\*/";
    return Regex.Replace(code, re, "$1");


Example app:

using System;
using System.Text.RegularExpressions;

namespace Regex01

    class Program
    
        static string StripComments(string code)
        
            var re = @"(@(?:""[^""]*"")+|""(?:[^""\n\\]+|\\.)*""|'(?:[^'\n\\]+|\\.)*')|//.*|/\*(?s:.*?)\*/";
            return Regex.Replace(code, re, "$1");
        

        static void Main(string[] args)
        
            var input = "hello /* world */ oh \" '\\\" // ha/*i*/\" and // bai";
            Console.WriteLine(input);

            var noComments = StripComments(input);
            Console.WriteLine(noComments);
        
    

输出:

hello /* world */ oh " '\" // ha/*i*/" and // bai
hello  oh " '\" // ha/*i*/" and

【讨论】:

我试试看。谢谢。 等等,为什么我在被问、回答和接受后 2 年才回答这个问题?给出几乎相同的答案?它是怎么出现在我的名单上的?一定是有什么bug什么的,我不做这样的事情。 (笑) 我发现这对我来说是完美的答案(C#),但是正则表达式不适用于 javascript【参考方案3】:

在你实现它之前,你需要先为它创建测试用例

    简单的 cmets /* */, //, /// 多行 cmets /* This\nis\na\ntest*/ 代码行后的注释 var a = "apple"; // 测试或 /* 测试 */ cmets 中的注释 /* This // is a test /, or // This / is a test */ 看起来像 cmets 并出现在引号中的简单非 cmets var comment= "/* This is a test*/", or var url = "http://***.com"; 复杂的非 cmets 看起来像 cmets:var abc = @" this /* \n 是引号中的注释\n*/",在 " 和 /* 或 */ 和 " 之间有或没有空格

可能还有更多案例。

一旦你拥有了所有这些,你就可以为它们中的每一个创建一个解析规则,或者对其中的一些进行分组。

仅使用正则表达式解决这个问题可能会非常困难且容易出错,难以测试,并且您和其他程序员也难以维护。

【讨论】:

Holystream,我确实有你提到的一些测试用例,但不是全部。我上面的示例涵盖了 1(部分)、2、3 和 4。5 和 6 是我没有考虑过的好点。 Holystream,我相信你做得比现在更难。使用正则表达式匹配两种注释样式非常容易——事实上,C#(和 C++)词法分析器可能会这样做。这与 html 之类的东西形成对比,后者很难与正则表达式匹配,因为 HTML 标记可以嵌套,而且它们有太多不同的种类。 @Timwi:实际上,.NET 使用词法分析器。注释符号只是标记。 en.wikipedia.org/wiki/Lexical_analysis @Timwi:你能给我一个适用于上述情况的例子吗?我很想知道通过这些测试用例的正则表达式。 /*(.*?)*/|//.*?\r?\n 很多测试用例都失败了。 @Holystream:您在我的回答中尝试过正则表达式吗?您似乎已从中删除了两个反斜杠。如果我的正则表达式失败,请提供一个失败的具体示例,并评论我的答案而不是这个答案。谢谢!【参考方案4】:

我在http://gskinner.com/RegExr/ 找到了这个(名为“.Net Comments aspx”)

(//[\t|\s|\w|\d|\.]*[\r\n|\n])|([\s|\t]*/\*[\t|\s|\w|\W|\d|\.|\r|\n]*\*/)|(\<[!%][ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t%]*)\>)

当我测试它时,它似乎删除了所有 // cmets 和 /* cmets */ ,而将引号内的那些留在后面。

尚未对其进行大量测试,但似乎运行良好(尽管它是一条可怕的正则表达式)。

【讨论】:

好的.. 经过一些测试后,我注意到包含减号 (-) 和多个多行 cmets 的 cmets 存在问题(/* 评论 / 不评论 / 再次评论*/)。但如果有人想解决这个问题,我认为这是一个很好的解决方案。【参考方案5】:

对于块注释(/* ... */)你可以使用这个exp:

/\*([^\*/])*\*/

它也适用于多行 cmets。

【讨论】:

请问为什么要降级这个答案?【参考方案6】:

另请参阅我的 C# 代码压缩项目:CSharp-Minifier

除了从代码中删除 cmets、空格和换行符之外,目前它能够压缩局部变量名称并进行其他缩小。

【讨论】:

以上是关于正则表达式从 C# 中删除行注释的主要内容,如果未能解决你的问题,请参考以下文章

从补丁文件中去除 C 注释的方法

正则表达式在 C# 源文件中查找注释

用正则表达式批量删除注释(//abc和/*abc*/)

使用正则表达式(.net 和 C#)识别行尾

防止在正则表达式上回溯以查找非注释行(不以缩进的“#”开头)

正则表达式匹配 MySQL 注释