我在前年有寫過一篇【 使用 Regular Expression 驗證密碼複雜度 】文章,當時撰寫的技巧完全是針對 .NET 提供的 Regular Expression 而寫,雖然我的文章在標籤的地方有特別提到 .NET,但還是有人將文章裡提供的 Regular Expression 直接抄去給 JavaScript 使用,結果當然是養出一堆莫名其妙的臭蟲(Bug)。
我在當時的文章中採用的 .NET Regular Expression 的右合樣(英文稱為 Lookahead 或稱為 Positive Lookahead ),該功能其實在所有瀏覽器都有支援,但是邪惡的 IE5 , IE6 , IE7 卻實做出錯誤的樣式比對規則。至於 Regular Expression 的左合樣 (Negative Lookbehind) 則是在任何瀏覽器的 JavaScript 都不支援。
我當時文章的範例語法如下:
^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,30}$ 而我自己設計了一個 JavaScript 單元測試案例,共有 44 個 Test Case 去測試一個較簡單的樣式,終於研究出 IE7 以下版本對於右合樣的處理行為!
如下樣式,用來比對測試的字串是否符合:至少一個小寫字母、一個數字,且最少 5 個字元以上!
^(?=.*[a-z])(?=.*\d).{5,}$ 首先,這個樣式在 IE 8 , Firefox 3.6 , Google Chrome 皆可正確比對,我另外利用 IETester 測試了 IE5 , IE6 , IE7 等不同瀏覽器版本,得到的結果不太一樣,其測試結果如下:
| IE8 ,Firefox 3, Chrome 3/4 | IE5, IE5.5, IE6, IE7 |
| 1. Testing 12345 Failed 2. Testing abcde Failed 3. Testing a1 Failed 4. Testing a12 Failed 5. Testing a123 Failed 6. Testing a1234 OK 7. Testing a12345 OK 8. Testing a123456 OK 9. Testing ab1 Failed 10. Testing ab12 Failed 11. Testing ab123 OK 12. Testing ab1234 OK 13. Testing ab12345 OK 14. Testing ab123456 OK 15. Testing abc1 Failed 16. Testing abc12 OK 17. Testing abc123 OK 18. Testing abc1234 OK 19. Testing abc12345 OK 20. Testing abc123456 OK 21. Testing 1a Failed 22. Testing 1ab Failed 23. Testing 1abc Failed 24. Testing 1abcd OK 25. Testing 1abcde OK 26. Testing 1abcdef OK 27. Testing 12a Failed 28. Testing 12ab Failed 29. Testing 12abc OK 30. Testing 12abcd OK 31. Testing 12abcde OK 32. Testing 12abcdef OK 33. Testing 123a Failed 34. Testing 123ab OK 35. Testing 123abc OK 36. Testing 123abcd OK 37. Testing 123abcde OK 38. Testing 123abcdef OK 39. Testing 123a Failed 40. Testing 123a1 OK 41. Testing 123a1b OK 42. Testing 123a1b2 OK 43. Testing 123a1b2c OK 44. Testing 123a1b2c3 OK
| 1. Testing 12345 Failed 2. Testing abcde Failed 3. Testing a1 Failed 4. Testing a12 Failed 5. Testing a123 Failed 6. Testing a1234 Failed 7. Testing a12345 OK 8. Testing a123456 OK 9. Testing ab1 Failed 10. Testing ab12 Failed 11. Testing ab123 Failed 12. Testing ab1234 Failed 13. Testing ab12345 OK 14. Testing ab123456 OK 15. Testing abc1 Failed 16. Testing abc12 Failed 17. Testing abc123 Failed 18. Testing abc1234 Failed 19. Testing abc12345 OK 20. Testing abc123456 OK 21. Testing 1a Failed 22. Testing 1ab Failed 23. Testing 1abc Failed 24. Testing 1abcd Failed 25. Testing 1abcde OK 26. Testing 1abcdef OK 27. Testing 12a Failed 28. Testing 12ab Failed 29. Testing 12abc Failed 30. Testing 12abcd Failed 31. Testing 12abcde OK 32. Testing 12abcdef OK 33. Testing 123a Failed 34. Testing 123ab Failed 35. Testing 123abc Failed 36. Testing 123abcd Failed 37. Testing 123abcde OK 38. Testing 123abcdef OK 39. Testing 123a Failed 40. Testing 123a1 Failed 41. Testing 123a1b Failed 42. Testing 123a1b2 Failed 43. Testing 123a1b2c OK 44. Testing 123a1b2c3 OK
|
因為右、左合樣的寬度永遠為零,所以照理說處理上述樣式時 (?=.*[a-z]) 或 (?=.*\d) 樣式不應該佔用任何比對的空間,但是在 IE5, IE6, IE7 卻會佔用,因而導致樣式處理發生邏輯不正確的情況!
拿上述測試案例的 abc1234 來說,它先比對 (?=.*[a-z]) 樣式,發現沒比對到,便跳至 (?=.*\d) 樣式比對,這個樣式比對後便把 .* (即 abc 部分) 的比對給吃掉了,也就是之後的 .{5,} 樣式不會再比對 abc 這三個字元,所以光是比對 1234 就會因為不足 5 個字元而發生樣式比對失敗!
拿上述測試案例的 abc12345 來說,它先比對 (?=.*[a-z]) 樣式,發現沒比對到,便跳至 (?=.*\d) 樣式比對,這個樣式比對後便把 .* (即 abc 部分) 的比對給吃掉了,也就是之後的 .{5,} 樣式不會再比對 abc 這三個字元,接著再比對 12345 就會因為符合至少 5 個字元的樣式要求而比對成功!
另外,我也利用 Browsershots 網站幫我額外測試另外 46 個在 Windows 平台上不同種類、版本的瀏覽器,以及 56 個跨平台各主要瀏覽器,我不得不說在大多數唸的出名字的瀏覽器中只有 IE 有這種邏輯錯誤的狀況 ( 當然我們還是要給 IE8 鼓鼓掌 )。
所以若有人要在 JavaScript 中實做 Regular Expression 必須特別特別小心,否則寫出了 Bug 還不自知,孰知這個 Bug 不是開發人員的錯,但客戶總是怪我們對吧!遇到這種瀏覽器的 Bug 真是如同天色暗了、天空陰了、空氣涼了這樣的感覺。
各位有興趣也可以自行測試看看,以下是測試網頁:
再次聲明:Regular Expression 是學一次用一輩子的技能,我認為是任何程式設計師必學的技能之一。
相關連結