正则表达式小白,记录最近一次正则表达式的实践。一直以来觉得正则就是天书,经过这次实践,感觉正则已不是那么可怕,只要学习、细心、认真,就一定能使用好它。
需求:
需要将一段文字中带有url链接的a标签进行高亮展示,因此需要用正则取出整个<a></a>。
关键点:
a标签,带url链接。
实现
首先a标签的格式如下:
<a attrFront="属性值" href="网址链接" attrFront="属性值">显示的文字内容</a>
前面的a标签必须要有href,href里面是有网址链接的。href的前面或者后面可能会有其他的属性,也可能没有。前面后面的属性都可能会有多个。属性的格是:attrFront="属性值"。属性值不仅是英文字母,也可能是中文、方法等。
url链接正则:
((https?|ftp|http)://)?(([\w-]+\.)+[A-Za-z]{2,}|((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]{2,5})(/[\w-./?%&!;#,=|]*)?
可以匹配带path及参数的url,网址可以是域名,也可以是ip:port格式。前面的http前缀有无都可匹配。
属性值正则:
[\w-\.]+\s*=\s*["][:/\.?'();,%&!#|=\w-\u4e00-\u9fa5\s]*["]
[\w-\.]+\s*=\s*['][:/\.?"();,%&!#|=\w-\u4e00-\u9fa5\s]*[']
非常关键的一点:属性值正则,需要两个,因为属性值带方法时,可能会有单引号或者双引号,跟包裹属性值外面的引号冲突,导致无法匹配。所以外面是单引号时,里面是双引号。外面是双引号时,里面是单引号。
带方法的a标签示例:
<a href="" οnclick="window.open(this.href, '', 'resizable=no,status=no,location=no,toolbar=no,menubar=no,fullscreen=no,scrollbars=no,dependent=no,width=700,height=500'); return false;">url地址弹出窗口超链接</a>
最终使用的正则也是两个,引号外双内单、外单内双:
<\s*a\s+([\w-\.]+\s*=\s*["][:/\.?'();,%&!#|=\w-\u4e00-\u9fa5\s]*["]\s*)*\s*href\s*=\s*["](((https?|ftp|http)://)?(([\w-]+\.)+[A-Za-z]{2,}|((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]{2,5})(/[\w-./?%&!;#,=|]*)?)["]\s*([\w-\.]+\s*=\s*["][:/\.?'();,%&!#|=\w-\u4e00-\u9fa5\s]*["]\s*)*\s*>(.+?)</\s*a\s*>?
<\s*a\s+([\w-\.]+\s*=\s*['][:/\.?"();,%&!#|=\w-\u4e00-\u9fa5\s]*[']\s*)*\s*href\s*=\s*['](((https?|ftp|http)://)?(([\w-]+\.)+[A-Za-z]{2,}|((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]{2,5})(/[\w-./?%&!;#,=|]*)?)[']\s*([\w-\.]+\s*=\s*['][:/\.?"();,%&!#|=\w-\u4e00-\u9fa5\s]*[']\s*)*\s*>(.+?)</\s*a\s*>?
正则表达式详细解释,以外双内单为例:
<\s*a\s+([\w-\.]+\s*=\s*["][:/\.?'();,%&!#|=\w-\u4e00-\u9fa5\s]*["]\s*)*\s*href\s*=\s*["](((https?|ftp|http)://)?(([\w-]+\.)+[A-Za-z]{2,}|((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]{2,5})(/[\w-./?%&!;#,=|]*)?)["]\s*([\w-\.]+\s*=\s*["][:/\.?'();,%&!#|=\w-\u4e00-\u9fa5\s]*["]\s*)*\s*>(.+?)</\s*a\s*>?
(ps:可能有些地方解释的不太正确,欢迎指正,文明用语^_^。)
第一部分:<\s*a\s+
用来匹配"<a ",\s的意思是空格,*的意思是可以有0个或多个。\s*的意思是可有0个或多个空格。+的意思是至少有一个,\s+的意思是有至少一个空格。
第二部分:([\w-\.]+\s*=\s*["][:/\.?'();,%&!#|=\w-\u4e00-\u9fa5\s]*["]\s*)*
用来匹配一个或多个属性。括号是分组的意思,括号外面的*代表这个分组可以是0次也可以是多次。
\w匹配英文字母大小写和下划线,\w-就是比\w多了-,\.匹配点号,因为点号本身匹配所有,所以要加\转义。[\w-\.],中括号代表,里面的哪种都行都是或的关系,但只是一个字母。[\w-\.]+,外面放一个+,代表这些元素至少出现一次。在这类代表属性的key至少有一个元素。\s*=\s*代表等号前面或者后面可能有0个以上的空格。["][:/\.?'();,%&!#|=\w-\u4e00-\u9fa5\s]*["]中的红色部分匹配中文。括号中的\s*代表一个属性后接另一个属性时至少有0个空格,这个地方也很关键,在实现的过程中踩了坑,导致匹配问题。
第三部分:\s*
href前的属性,跟href之间有0个以上空格。
第四部分:href\s*=\s*["](((https?|ftp|http)://)?(([\w-]+\.)+[A-Za-z]{2,}|((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]{2,5})(/[\w-./?%&!;#,=|]*)?)["]
匹配href=url。
第五部分:\s*
herf后面至少0个空格
第六部分:([\w-\.]+\s*=\s*["][:/\.?'();,%&!#|=\w-\u4e00-\u9fa5\s]*["]\s*)*
同第二部分。
第七部分:\s*>
最后一个属性值跟后面的尖括号之间至少0个空格
第八部分:(.+?)
a标签中间可以是任意字符。点号指一个任意字符,点号和加号一起,指至少1个任意字符,问号指惰性模式,简单说就是尽少匹配,详细的我也说不太清。
第九部分:</\s*a\s*>
后面的尖括号部分
第十部分:?
惰性模式。如果不加的话几个a标签就会匹配成一个。
总结
这次实践算是入了门。
其中使用到的工具,帮了大忙:在线正则表达式测试
java的工具类
java.util.regex包下的Pattern、Matcher
private static void printMatchInfo(String content, String regex) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
while (m.find()) {
System.out.println(m.group(0));//匹配到的所有内容
System.out.println(m.group(2));//匹配到的url
System.out.println(m.start());//匹配到内容的起始位置
System.out.println(m.end());//匹配到内容的结束位置
}
}