最近公司的即时通讯产品中需要用到正则表达式来匹配聊天信息中的网址,在表现形式上跟微信和QQ基本一致,但是这个正则写起来挺头疼的,正则表达式匹配网址历来就很有争议,没有完美的正则,只有满足业务场景的正则。之前用的是Android自带的表达式(android.util.Patterns),但是在不同的ROM上表现形式是不一样的,在一些比较诡异的case上基本识别不出来,后来不得不自己写一个,大体思路跟网上的差不多,我写的这个只是满足了我当前的业务需求,并不一定适合其他场景。
第一版用的是这个,HOST_NAME 用的Patterns类中的代码
String pattern = "([a-zA-z]+://[^\\s]*" + "|" + HOST_NAME + ")";
这个表达式只能笼统的匹配出来,但是会有很多错误,但是可以满足80%的业务场景。
sss://bac.bug
这种也会被匹配出来,而且像
http://www.baidu.com后面没有空格只有中文
会将后面的中文也一并匹配,只有遇到空格后才会停止
<u>http://www.baidu.com后面没有空格只有中文</u>
在QA发现越来越多的问题后,果断改了一下,后面所有的中文都将不会被匹配
String pattern = "([a-zA-z]+://[^\\u4e00-\\u9fa5\\s]*" + "|" + HOST_NAME + ")";
这个表达式基本可以满足90%的业务场景,但是遇到下面这种:
http://www.baidu.com555先数字后中文
也会将数字匹配到
<u>http://www.baidu.com555</u>先数字后中文
而且针对不带协议头的网址识别就需要靠HOST_NAME
这部分,这部分在不同ROM上识别结果又不一样,随着测试的深入,失败的case越来越多,最终也被淘汰了。
第三版也是现在正在用的正则:
// all domain names
private static final String[] ext = {
"top", "com.cn", "com", "net", "org", "edu", "gov", "int", "mil", "cn", "tel", "biz", "cc", "tv", "info",
"name", "hk", "mobi", "asia", "cd", "travel", "pro", "museum", "coop", "aero", "ad", "ae", "af",
"ag", "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au", "aw", "az", "ba", "bb", "bd",
"be", "bf", "bg", "bh", "bi", "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz",
"ca", "cc", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cq", "cr", "cu", "cv", "cx",
"cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "es", "et", "ev", "fi",
"fj", "fk", "fm", "fo", "fr", "ga", "gb", "gd", "ge", "gf", "gh", "gi", "gl", "gm", "gn", "gp",
"gr", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "in", "io",
"iq", "ir", "is", "it", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw",
"ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md",
"mg", "mh", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mv", "mw", "mx", "my", "mz",
"na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nt", "nu", "nz", "om", "qa", "pa",
"pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "pt", "pw", "py", "re", "ro", "ru", "rw",
"sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st",
"su", "sy", "sz", "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to", "tp", "tr", "tt",
"tv", "tw", "tz", "ua", "ug", "uk", "us", "uy", "va", "vc", "ve", "vg", "vn", "vu", "wf", "ws",
"ye", "yu", "za", "zm", "zr", "zw"
};
static {
StringBuilder sb = new StringBuilder();
sb.append("(");
for (int i = 0; i < ext.length; i++) {
sb.append(ext[i]);
sb.append("|");
}
sb.deleteCharAt(sb.length() - 1);
sb.append(")");
// final pattern str
String pattern = "((https?|s?ftp|irc[6s]?|git|afp|telnet|smb)://)?((\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})|((www\\.|[a-zA-Z\\.\\-]+\\.)?[a-zA-Z0-9\\-]+\\." + sb.toString() + "(:[0-9]{1,5})?))((/[a-zA-Z0-9\\./,;\\?'\\+&%\\$#=~_\\-]*)|([^\\u4e00-\\u9fa5\\s0-9a-zA-Z\\./,;\\?'\\+&%\\$#=~_\\-]*))";
// Log.v(TAG, "pattern = " + pattern);
WEB_URL = Pattern.compile(pattern);
}
这个正则基本上可以满足90%以上的业务场景,QA提出的case也完美通过,当然,有人可能会说有些这个表达式根本不严谨,好多匹配到的根本不是我想要的。确实,一个正则要完美匹配出所有的网址基本上是不可能的,而且还有IPv4,IPv6类的网址,更令人痛苦的还有中文域名,或者包含中文(没有被浏览器转义)的url,所以这里才会以业务场景为中心去编写正则,满足我所有的业务场景,那产品在这个环节就不会出现bug,这对我来说就已经OK了。