子模式
上一篇文章的最后部分中的var reUrl = /^(http):\/\/nowamagic\.(net)$/gi;已经涉及到子模式了。用来指定重复次数的元字符只能作用于紧挨着它的字符或元字符,而在实际应用中我们需要进行重复匹配的字符往往不一定就只是一个字符或元字符,就如reUrl中所要匹配的“http”和“net”就是多个字符,这时候就可以使用(和)将多个字符括起来作为一个独立的元素来使用。
同样在上一篇文章中所构造的验证email地址的正则表达式var reMail = /\w+@\w+\.\w+/i;并不完善,一个有效的用户名除了可以是字母、数字、下划线外,还可以是点号,同时域名部分也不能保证是mail.com这行的形式,也完全有可能是mail.mymail.com这样的形式,所以一个更为完善的匹配有效email地址的正则表达式是这样的:
1 | var reEmail = /(\w+\.)*\w+@(\w+\.)+\w+/i; |
子模式允许多重嵌套,而且这种嵌套在理论上是没有限制的,但在实际应用中还是应该根据实际情况适可而止。
回溯引用
在web开发中,我们经常需要去匹配HTML标签,大多数的HTML标签都有一个开始标记和结束标记如<h1></h1>,<div></div>,如果只需单纯的匹配H1和DIV我们可以很容易的构造出该正则表达式:
1 | var reH1 = /<h1>.*?<\/h1>/gi; |
2 | var reDiv = /<div>.*?<\/div>/gi; |
但是我们所要匹配的并不是某个或某几个HTML标签,事实上HTML具体是什么样的形式我们完全是未知的,比如XML的标记我们是完全无法预计的,所以分组匹配在这里完全排不上用场。幸运的是,在正则表达式中回溯引用允许正则表达式模式引用前面的匹配结果。具体应用可以参考下面匹配HTML标签的正则表达式。
1 | var html = "<h1>nowamagic</h1>" ; |
2 | var reTag = /<(\w+\d?)>.*?<\/\1>/gi; |
3 | document.write(html.match(reTag)); |
reTag最后部分的\1便是一个回溯引用,引用的前面的第一个子模式(\w+\d?),当然如果前面还存在第二个子模式我们也可以使用\2引用、。注意:回溯引用只能引用前面已经匹配过的结果,而下面这样的写法就是错误的。
1 | var reTag = /<\1>.*?<\/(\w+\d?)>/gi; |
回溯引用在替换操作中有着十分广泛的应用。比如我们要将一段文本中的所有网址自动添加上其对应的超链接,即是将“http://nowamagic.net”的字符串替换成nowamagic的形式。我们就可以这样处理:
2 | var reUrl = /(http[s]*:\/{2}(\w+\.)+\w+)/gi; |
4 | document.write(url.replace(reUrl, '<a href="$1">$1</a>' )); |
$1引用了前面的子模式(http[s]*:\/{2}(\w+\.)+\w+)。注意:javascript中进行替换操作时回溯引用使用”$”而不是”\”。
前后查找
如果我们需要获取h1标签中的文本(包含在h1标签中的文本,不包括h1本身),这个正则表达式应该如何写?比如”<h1>front-end</h1>”,在所有介绍过的方法中,似乎都还没有提及过要匹配某个字符串,但却只返回某些字符前或后的字符串的情况,正则表达式中确实是存在这样的语法。
1 | var fe = "<h1>front-end</h1>" ; |
2 | var reInnerText = /(?<=<h1>).*?(?=<\/h1>))/i; |
在reInnerText和/<h1>.*?<\/h1>/i的匹配模式是相同的,唯一不同的返回结果,/<h1>.*?<\/h1>/i会返回整个fe字符串,而reInnerText只返回”front-end”,比较这两个正则表达式可以发现两处不同的写法:(?<=<h1>),(?=<\/h1>)。(?<=<h1>)定义了一个向后查找模式,即匹配结果只包括”<h1>”后面的部分;(?=<\/h1>)则定义的是一个向前查找模式,匹配结果只返回”</h1>”前的结果;所以reInnerText的匹配结果只返回”<h1>”和”</h1>”之间的内容!前后查找的语法很简单,向前查找是一个以”?=”开头的字表达式,而向后查找确实一个以”?<=”开头的字表达式。
遗憾的javascript并不支持正则表达式的向后查找,所以事实上reInnerText的写法在javascript是有语法错误的。有条件可以使用其他支持前后查找的语言进行验证,比如PHP。
1 | $title = '<h1>front-end</h1>' ; |
2 | if (preg_match( '/(?<=<h1>).*?(?=<\/h1>)/i' , $title , $rst )){ |