正则表达式的Javascript应用

网上很好的一篇文章: 正则表达式30分钟入门教程


以及一本经典正则表达式权威 master regular expression

 

其中提到了 很少用到的 零宽断言正则表达式 (Zero-Width Assertions),javascript作为脚本语言虽然没有全部实现,但是对于预测式的零宽断言其实也是实现了的。

 


(?=exp)也叫零宽度正预测先行断言(positive lookahead):

它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc。


如:

 

var x=/\b\w+(?=ing\b)/g;
alert("I'm singing while you're dancing.".match(x));

 


(?<=exp)也叫零宽度正回顾后发断言(positive lookbehind 不支持) :

 

 

//not support by javascript from master regular expression
alert("yiminghes book".replace(/(?=s)(?<=yiminghe)/g,"'") == "yiminghe's book");

其实可以用分组回避:

//其实可以用分组捕获来回避的
alert("yiminghes book".replace(/(yiminghe)(?=s)/g,"$1'") == "yiminghe's book");

 

Java 支持 :

 

public class Test {
    public static void main(String[] args){
        //java support lookahead and lookbehind
        System.out.println("yiminghes book".replaceAll("(?=s)(?<=yiminghe)","'") .equals("yiminghe's book"));
       
    }
} 

 

 

 

 

 

 

零宽度负预测先行断言 (?!exp negative lookahead) :

断言此位置的后面不能匹配表达式exp 。例如:\d{3}(?!\d) 匹配三位数字,而且这三位数字的后面不能是数字\b((?!abc)\w)+\b 匹配不包含连续字符串abc的单词


如:

var x=/\b((?!abc)\w)+\b/g;
alert('xyz abc zuc zabc 11'.match(x));

 

也可用来模拟 字符类集合操作 :

 

匹配 a-z 除去 acd

 

alert("z".match(/(?![acd])[a-z]/));

 

举例:匹配页面全部html的例子 (From high performance javascript)

最开始想法:

 

/<html>[\s\S]*?<head>[\s\S]*?<title>[\s\S]*?<\/title>[\s\S]*?<\/head>
[\s\S]*?<body>[\s\S]*?<\/body>[\s\S]*?<\/html>/

 

但是当页面html闭合出问题时,由于回朔(backtracking) 性能会不可接受,利用 (?!)可修改为

 

/<html>(?:(?!<head>)[\s\S])*<head>(?:(?!<title>)[\s\S])*<title>
(?:(?!<\/title>)[\s\S])*<\/title>(?:(?!<\/head>)[\s\S])*<\/head>
(?:(?!<body>)[\s\S])*<body>(?:(?!<\/body>)[\s\S])*<\/body>
(?:(?!<\/html>)[\s\S])*<\/html>/

 大大减少了backtracking,但是毕竟?!性能不高,再加上量词*,这个解决方案仍然不是最好,最好的方案见下文原子分组部分。

 

 

 java 直接支持类操作:

 

System.out.println("a".matches("[[a-z]&&[^acd]]"));
 

 


(?<!exp) ,零宽度负回顾后发断言(不支持 negative lookbehind)

 

 

 

零宽度负预测先行断言在javascript中的一个很好的应用例子:

 

 

/**
 * 组合两个正则表达式。 
 * @param[RegExp] r 要组合的一个字符串
 * @param[String] attr attributes of the final new return RegExp
 * @return[RegExp] 
 */
RegExp.prototype.concat = function(r, attr){
 //零宽度负预测先行断言 ,所有捕捉分组个数 ,()不包括	(?:)
 var i=(this.source.match(/\((?!\?:)/g) || "").length; 
 
 //最终的regexp的第二个正则表达式部分的后向引用要考虑本身regexp已有的后向引用,重新编号
 return new RegExp(this.source+r.source.replace(/\\(\d)/g, function($0, $1){
  return "\\" + (i+($1 | 0)); 
 }), attr);
};
 

使用:

 

//匹配2个重复数字后跟一个字母
var x=/(\d)\1(?:\w)/g;
//然后必须紧跟两个重复字母
x=x.concat(/(\w)\1/,'g');
//x == /(\d)\1(?:\w)(\w)\2/g

alert('11aww  22xzy'.match(x));
 

 

原子分组应用 (?>)  不支持 (atomic group)固化分组

 

一旦匹配则消除由当前原子分组内的所有保存状态


保留小数2-3位,如果第三位不为0保留三位,否则保留两位



例子:


0.55555 -> 0.555
0.550005 -> 0.55
0.50002 -> 0.50


可以用正则式

 

"0.625".replace(/(\.\d\d[1-9]?)\d*/,"$1");
 

但是其实 0.625 可以完全不必被匹配(效率问题),于是 [1-9]? 一旦匹配就不要再放弃了,可得


"0.625".replace(/(\.\d\d(?>[1-9]?))\d+/,"$1");

 

但是 js 不支持哦,但是可以用 positive lookhead 结合 backreference 来取代一下。利用 subexpression 来丢失状态以及捕获分组

 

/(\.\d\d(?=([1-9]?))\2)\d+/.test("0.625");

/(\.\d\d(?=([1-9]?))\2)\d+/.test("0.6250981");

 

lookahead 其实就是一个原子分组,但是如前所述它的匹配不消耗字符,因此可以通过将一个lookahead包裹在一个捕捉分组中,并在其后跟上backreference,而形成了以下模式:

 

(?=(pattern to make atomic))\1
 

多个的话只要注意backreference的计数即可。

 

最好的例子:接?!部分的匹配页面html例子:

 

利用原子分组彻底消除回朔可得:

 

/<html>(?=([\s\S]*?<head>))\1(?=([\s\S]*?<title>))\2(?=([\s\S]*?
<\/title>))\3(?=([\s\S]*?<\/head>))\4(?=([\s\S]*?<body>))\5
(?=([\s\S]*?<\/body>))\6[\s\S]*?<\/html>/
 

 

 

 

JAVA 丢失状态例子2 :离开原子分组后 | 保存的状态没了:

 

String str = "^(?>a+|.+)$";
String s = "aaaaaac";
Pattern p = Pattern.compile(str);
Matcher m = p.matcher(s);
System.out.println(m.replaceAll("!"));
 

 

 

 

 

possessive match (+) 不支持

 

同固化分组,一旦匹配就删除用于回朔的保存状态,一般和量词结合

 

"0.625".replace(/(\.\d\d[1-9]?+)\d+/,"$1");
 

js 不支持 ,java.util.regexp 支持

 

System.out.println(".625".matches("(\\.\\d\\d[1-9]?+)\\d+"));

System.out.println(".625".matches("(\\.\\d\\d[1-9]?)\\d+"));
 

DOTALL 模式 (?s) 不支持

 

不支持 java 中的 Pattern.DOTALL 以及 (?s) ,以及 perl 中的 //s ,s modifier  : .可以匹配回车换行


使用alternation以及character class弥补:

 

/.|[\n\r]/.test("\r");
 

条件判断 :(?if then | else) 不支持

 

java ,javascript 均不支持

 

Pattern p = Pattern.compile("(<a>)?<img/>(?(1)\\s*</a>)");
        String s="<img/>";
        Matcher m=p.matcher(s);
        System.out.println(m.group());
 
/(<a>)?<img/>(?(1)\s*</a>)/.test("<img/>");

 

注意 if 中的正则表达式为零宽向前(?=,?!)或向后预测(?<=,><!)正则表达式。如果不写则为零宽正向向前预测。

 

详细解释: 

 

正则表达式 (<a>)?<img/>(?(1)\s*</a>) 相当于: (<a>)?<img/>(?(1)(\s*</a>)|\s*),可匹配的字符串包括 <a><img/></a> 与 <img/>,而不匹配 <a><img/> 或 <img/></a>。


其中 if (1) 为先前匹配到的分组 (<a>),如果先前出现了 <a> 则接着匹配结束标记 </a>,否则直接匹配成功。

 

后果是代码极其不易读,实际中尽量不要使用!可分成多个正则联合检测


\G anchor 不支持

 

还包括 \A,\Z 等锚点都不支持!\G 在 perl,java 中表示当前匹配开始的位置在上次匹配结束的位置,如果失败则不会从字符串的下一个位置重新匹配。

1.在零匹配成功后,\G 位置为当前位置,而下一次匹配位置被强制为当前位置的下一位置(防止死循环),所以\G后只能一次成功零匹配

2.匹配成功后,如果从当前匹配结束位置不能匹配正则,则整体失败,不会从当前匹配结束位置的下一位置进行下一次匹配。


如:

 

       String[] ps = {"x*", "\\Gx*", "ab", "\\Gab","abc","\\Gabc"};
        String s = "abcabc";
        System.out.println(s + " : ");
        for (String str : ps) {
            System.out.print(str + " : ");
            Pattern p = Pattern.compile(str);
            Matcher m = p.matcher(s);
            System.out.println(m.replaceAll("!"));
        }

 

另外命名捕获,正则表达式内模式切换(Pattern.compile("(?i)T(?-i)Est")),注释,unicode 属性区块也不支持。

 

 

? lazy 描述符   支持

 

可修饰 ? 以及 + ,* 等,不加都是贪婪模式,加了则为懒模式了(能不匹配就不匹配):

 

var reg=/.+?/g;
reg.exec("12");
console.log(reg.lastIndex);

var reg=/.??/g;
reg.exec("12");
console.log(reg.lastIndex);

var reg=/.*?/g;
reg.exec("12");
console.log(reg.lastIndex);

 

 

Bonus :

 

String.prototype.replace 的 javascript 实现比较

 

javascript 正则表达式细节问题

 

正则表达式共享问题

 

 

updated 2010-11-24

 

发现一个关于正则式的专业网站 ,详细比较了各种语言的正则引擎,本文内容都已经被他包含.....

 

updated 2011-01-11

 

发现一个很了解各个浏览器正则引擎的blog,原来浏览器也不是完全按照规范来的,各有不同(bug?):



non-participate capture group 也有问题

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值