最近公司的即時通訊產品中需要用到正則表達式來匹配聊天信息中的網(wǎng)址,在表現(xiàn)形式上跟微信和QQ基本一致,但是這個正則寫起來挺頭疼的,正則表達式匹配網(wǎng)址歷來就很有爭議,沒有完美的正則,只有滿足業(yè)務場景的正則。之前用的是Android自帶的表達式(android.util.Patterns),但是在不同的ROM上表現(xiàn)形式是不一樣的,在一些比較詭異的case上基本識別不出來,后來不得不自己寫一個,大體思路跟網(wǎng)上的差不多,我寫的這個只是滿足了我當前的業(yè)務需求,并不一定適合其他場景。
第一版用的是這個,HOST_NAME 用的Patterns類中的代碼
String pattern = "([a-zA-z]+://[^\\s]*" + "|" + HOST_NAME + ")";
這個表達式只能籠統(tǒng)的匹配出來,但是會有很多錯誤,但是可以滿足80%的業(yè)務場景。
sss://bac.bug 這種也會被匹配出來,而且像
http://www.baidu.com后面沒有空格只有中文
會將后面的中文也一并匹配,只有遇到空格后才會停止
<u>http://www.baidu.com后面沒有空格只有中文</u>
在QA發(fā)現(xiàn)越來越多的問題后,果斷改了一下,后面所有的中文都將不會被匹配
String pattern = "([a-zA-z]+://[^\\u4e00-\\u9fa5\\s]*" + "|" + HOST_NAME + ")";
這個表達式基本可以滿足90%的業(yè)務場景,但是遇到下面這種:
http://www.baidu.com555先數(shù)字后中文
也會將數(shù)字匹配到
<u>http://www.baidu.com555</u>先數(shù)字后中文
而且針對不帶協(xié)議頭的網(wǎng)址識別就需要靠HOST_NAME這部分,這部分在不同ROM上識別結果又不一樣,隨著測試的深入,失敗的case越來越多,最終也被淘汰了。
第三版也是現(xiàn)在正在用的正則:
// 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%以上的業(yè)務場景,QA提出的case也完美通過,當然,有人可能會說有些這個表達式根本不嚴謹,好多匹配到的根本不是我想要的。確實,一個正則要完美匹配出所有的網(wǎng)址基本上是不可能的,而且還有IPv4,IPv6類的網(wǎng)址,更令人痛苦的還有中文域名,或者包含中文(沒有被瀏覽器轉義)的url,所以這里才會以業(yè)務場景為中心去編寫正則,滿足我所有的業(yè)務場景,那產品在這個環(huán)節(jié)就不會出現(xiàn)bug,這對我來說就已經OK了。