使用正则表达式删除注释

April 3rd, 2010 Categories: 问答

问题

以下摘自某网友来信:

javascript正则中的否定前瞻

难点

  1. javascript不支持点号匹配换行符, 因此无法直接进行多行匹配;
  2. 处理前面没有http://, 当然要用否定前瞻( negative lookbehine)了:(?<!http:)\/\/. 可惜javascript不支持.
    javascript正则中的否定前瞻

思路

关于多行匹配

这个问题, 之前我已经说过, 要点是使用[\S\s]来模拟匹配换行符的点号. 原文在这里:《DIY万能通配符》. 可以以此写出这样的javascript代码来消除多行注释:

1
2
3
4
5
//to uncomment C-style multiple line comment
function uncomment_multi(str)
{
     return str.replace(/\/\*[\S\s]*?\*\//g, "");
}

单行注释之javascript实现(不完善)

单行注释并没有想像中的那样简单. 如果你认为只要 str.replace("//.*$")即可, 那么必须保证所要处理的文本都是最简单的, 如下:

1
var pig="ase"; //this is a comment.

事实上这是行不通的. 现实程序中下面的例子比比皆是:

1
2
var url="http://iregex.org"; //this is my site.
var url="//not real comment here http://iregex.org"; //this is my site.

我尝试使用javascript写了个模拟否定前瞻的函数, 可以处理http://这种情况, 但是该函数看起来并不令人赏心悦目, 而且也不能处理引号中有双斜杠的情况. 我对javascript的正则式支持的特性之简陋实在很失望. 于是, 我求助于perl完成这一任务. 先看一下我写的javascript的删除单行注释的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function uncomment_single(str)
{
    var result;
    var single=new RegExp("\/\/.","ig");
    var start=0;
    while((result=single.exec(str))!=null)
    {
        var part=str.slice(start,result.index);
        var negLeft=new RegExp("http:$","i");
        if (! negLeft.test(part))
        {
            return str.slice(0,result.index);
        }
        start=result.index+result[0].length-1;
    }
    return str;
}

perl版删除注释思路及源码(相对完善)

待测试文本

好吧, 既然祭出了强大的perl, 之前的小打小闹似的例子就一边去吧. 我将使用如下相对复杂的文本来验证我的程序:

1
<!DOCTYPE h/tml PUBLIC "-//W3C//DTD XHTML\" 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> sdfasdf//real comment here//"

认真分析单行注释的特点

正确地分析其特点, 是写出合理高效的程序的前提. 观察可知, 单行注释的特点如下:

  1. 引号内(包括单引号和双引号)的双斜线不算注释.
  2. 引号是配对出现的, 两个引号之间的以反斜线转义掉的引号不算结束符. 例如"hello \" //world", 这里的//world部分不能算做注释.
  3. 由连续的非引号非斜线部分组成的字符串也不是注释. 特别指出, 单个斜线不能算做注释. 为什么前半部分不但要非引号而且要非斜线呢? 因为[^'"]+是有可能误匹配abcde//real comment "quoted string in comment"这样的情况, 因此我们归纳出一个条件[^'"/]+; 又因为还要避免abcde/real comment "quoted string in comment"这样的情况, 还需要特别补充规定单个的斜线不是注释. 正则式是[^'"/]|(?<!/)/(?!/).
  4. 除去上述内容以外, 以双斜线开始直至行尾的部分就是注释. 因为我们用到了行尾这个概念, 需要在正则式中特别指出是^$匹配行首行尾的多行模式. 使用//m来表示.

正则实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/perl -w
$str = <<"EOF";
<!DOCTYPE h/tml PUBLIC "-//W3C//DTD XHTML\" 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> sdfasdf//real comment here//"
EOF

#print $str;
if ($str=~
    m%
        ^
            (?:
                [^'"/]|
                (?<!/)/(?!/)|
                (?<quote>['
"])
                    (?:\\ \g{quote}|
                    (?!\g{quote}).)*
                \g{quote}
            )*
            (?<comment>//.*)
        $
    %xm)
    {
        print $+{comment};
}

几点补充

  • 该程序在perl5.10版才能运行成功. 因为用到了命名捕获(?<quote>['"])这样比较高阶的特性. 当然, 不使用5.10也并非没有办法, 我们大可以使用numbered capture, 只不过看起来更不直观罢了.
  • 匹配结束后, 命名捕获都保存在hash表%+中了. 使用print $+{comment}这样的方式可以方便地调用.
  • 指定了x模式, 以便加入空白字符和换行, 让正则表达式看起来有层次感. 事实上, 对于复杂的正则表达式, 不使用x模式是极其不明智的做法.
  • 为了在字串中方便地表示单双引号, 使用了heredoc的方式. 个人觉得不如python的三重引号方便.

小结

从正则表达式的角度来说, javascript实在太弱. 当然, 也与本人的javascript功底较浅有关系. perl对于正则表达式的支持实在是强撼且不遗余力. 上面的实现, 应该可以涵盖绝大多数的注释情况了. 如果您测试出现bug, 或者遇到更BT的字串, 欢迎留言讨论.

Tags: , ,

14 Responses to “使用正则表达式删除注释”

  1. April 4th, 2010 at 14:10
    1

    感谢你如此重视这个问题,我现是用手机访问的,我上网时再来细细领悟,呵呵,谢谢

    [Reply]

  2. jlake
    April 5th, 2010 at 17:22
    2

    包括换行文字的匹配问题我也碰到过,我自己用 \w\W\r\n 来匹配。

    javascript 的正则我倒是觉得不弱。
    和 perl 相比,有时候还更方便,比如用回调函数的替换,javascript 实现起来很容易:

    var str = ’2010-04-05′;
    str = str.replcae(/(\d+)\-(\d+)-(\d+)/, function($0, $1, $2, $3) {
    // 这儿可以对 $1, $2, $3 做一些处理
    // 比如年月日的顺序有可能是变的
    return $1 + ‘年’ + $2 + ‘月’ + $3 + ‘日’;
    }
    alert(str);

    用 perl 做这样的替换感觉不大容易。

    [Reply]

    rex Reply:

    这用perl的话也简单:
     $str=~s/(\d+)\-(\d+)-(\d+)/$1$2$3/g;即可. perl虽不支持回调, 但是在正则替换的同时就允许内部执行代码, 现在还允许递归执行.

    [Reply]

  3. jlake
    April 5th, 2010 at 17:24
    3

    刚才发的代码少了个 )

    var str = ’2010-04-05′;
    str = str.replcae(/(\d+)\-(\d+)-(\d+)/, function($0, $1, $2, $3) {
    // 这儿可以对 $1, $2, $3 做一些处理
    // 比如年月日的顺序有可能是变的
    return $1 + ‘年’ + $2 + ‘月’ + $3 + ‘日’;
    });
    alert(str);

    [Reply]

    jlake Reply:

    以上代码有的单引号是中文字符,改正如下

    var str = ’2010-04-05′;
    str = str.replace(/(\d+)\-(\d+)-(\d+)/, function($0, $1, $2, $3) {
    return $1 + ‘年’ + $2 + ‘月’ + $3 + ‘日’;
    });
    alert(str);

    [Reply]

  4. jlake
    April 5th, 2010 at 17:28
    4

    奇怪,我的第一条评论不见了。重新发一下。

    我用 \w\W\r\n 匹配多行文字。
    javascript 的正则其实很好用,尤其是带函数的正则替换,比 perl 方便。

    [Reply]

  5. jlake
    April 5th, 2010 at 17:40
    5

    用 javascript 替换多行注释的程序:

    var str = ‘/*\n’
    + ‘注释1 aasad\n’
    + ‘注释1 aasad\n’
    + ‘*/\n’
    + ‘function() {\n’
    + ‘ //注释2\n’
    + ‘}\n’;

    alert(str);
    str = str.replace(/\/\*[\w\W\r\n]*?\*\//gm, ”).replace(/\/\/.*/gi, ”);
    alert(str);

    [Reply]

    rex Reply:

    以我的经验,

    • [^'"/]+是可以匹配中文的;
    • [\s\S]+[\W\w]+
      都可以匹配大段的文本, 其中包括换行符在内. 因为中括号内的两种情况, 其实包含了互相排斥的两种关系, 因此把所有的字符都包括了. 同时也就不需要再明确写上[\r\n]了.

    供参考. 欢迎继续讨论.

    [Reply]

    jlake Reply:

    加上[\r\n]是我用 js 测试的结果,可能 perl 不用加。

    [Reply]

  6. April 6th, 2010 at 19:00
    6

    问个无关的问题,你的用的是什么代码插件?感觉这样嵌入代码很漂亮,谢谢!

    [Reply]

    rex Reply:

    用的是CodeColorer,它支持4种内置的theme,允许自定义css,支持inline模式。链接是:http://wordpress.org/extend/plugins/codecolorer/

    [Reply]

  7. April 21st, 2010 at 11:34
    7

    很少见的主题博客,不错,专注一个方向。
    有时间了好好看看你的文章。

    [Reply]

    rex Reply:

    欢迎可可常来:) 你的 python/飞信系列文章,也让我受益匪浅..

    [Reply]

Leave a Comment