JavaEye3.0開發手記之二 - rails的UTF-8支持形成的正則表達式問題

rails的ActionView::Helpers::TextHepler模塊提供了不少實用的方法,這些方法對於論壇類應用很是有用,例如auto_link這個方法能夠自動檢測傳入字符串當中的URL,並將其自動轉換爲HTML超連接格式,這對於顯示帖子的內容來講很不錯。 

可是在開發JavaEye3.0的時候,卻發現auto_link有bug,一旦帖子當中的URL後面緊跟中文的話,auto_link就會把URL後面全部的中文當作URL的一部分進行格式化,直到碰到空格爲止,例如: 

引用
http://www.iteye.com網站很不錯


就會被格式化爲: 

引用
<a href="http://www.iteye.com網站很不錯">http://www.iteye.com網站很不錯</a>


看來獲得rails的源代碼裏找答案了。 

打開netbeans,敲快捷鍵Ctrl+O,在彈出窗口輸入:texthelper,回車,netbeans已經幫我打開了text_helper.rb源代碼,經過Navigator窗口,很方便的定位到auto_link方法,仔細看一下,原來主要是這個正則表達式在起做用: 

Ruby代碼    收藏代碼
  1. AUTO_LINK_RE = %r{  
  2.                 (                          # leading text  
  3.                   <\w+.*?>|                # leading HTML tag, or  
  4.                   [^=!:'"/]|               # leading punctuation, or   
  5.                   ^                        # beginning of line  
  6.                 )  
  7.                 (  
  8.                   (?:https?://)|           # protocol spec, or  
  9.                   (?:www\.)                # www.*  
  10.                 )   
  11.                 (  
  12.                   [-\w]+                   # subdomain or domain  
  13.                   (?:\.[-\w]+)*            # remaining subdomains or domain  
  14.                   (?::\d+)?                # port  
  15.                   (?:/(?:(?:[~\w\+%-]|(?:[,.;:][^\s$]))+)?)* # path  
  16.                   (?:\?[\w\+%&=.;-]+)?     # query string  
  17.                   (?:\#[\w\-]*)?           # trailing anchor  
  18.                 )  
  19.                 ([[:punct:]]|\s|<|$)       # trailing text  
  20.                }x unless const_defined?(:AUTO_LINK_RE)  


但這個正則表達式上看下看,左看右看都沒有啥問題阿。因而把這個正則表達式拷貝出來,放在一個ruby文件裏面test.rb,一點點單獨調試,但怎麼調試都正常,即便把上面那個URL放進去,也能夠正常截斷中文。 

難道是由於rails作了手腳?爲了驗證這一點,在test.rb前面加上以下內容: 

Ruby代碼    收藏代碼
  1. ENV["RAILS_ENV"] = "development"  
  2. require File.expand_path(File.dirname(__FILE__) + "/../config/environment")  


再運行test.rb,果真!中文又被包括進去了,看來就是rails作了手腳。 

再回過頭仔細看這個正則表達式,只有[\w]和字符串處理有關係,爲了驗證這一點,咱們作以下試驗: 

建立一個char.rb文件,內容以下: 

Ruby代碼    收藏代碼
  1. def name  
  2.   return "範凱"  
  3. end  


請注意!該文件保存格式請必須使用UTF-8!! 
而後打開irb,進行以下交互: 

引用
irb(main):001:0> load "char.rb" 
=> true 
irb(main):002:0> name 
=> "\350\214\203\345\207\257"
 
irb(main):003:0> name.match /[A-Za-z0-9_]+/ 
=> nil 
irb(main):004:0> name.match /\w+/ 
=> nil


請注意標記爲紅色的行,在ruby的內存中,中文字符串的編碼使用的是unicode格式,中文字符串不可以匹配到/[\w]+/上面去,而/[A-Za-z0-9_]+/與/\w+/是同義詞。 

好了,如今啓動rails的環境: 
引用
$ ./script/console 
Loading development environment. 
>>  load "char.rb" 
=> [] 
>> name 
=> "鑼冨嚡"
 
>>  name.match /[A-Za-z0-9_]+/ 
=> nil 
>>  name.match /\w+/ 
=> #<MatchData:0x474693c>


哈哈,水落石出了!!因爲rails的ActiveSupport的引入,在ruby的內存當中,字符串被轉換爲UTF-8格式了(顯示亂碼是由於個人Windows操做系統是GBK編碼),而中文字符串竟然能夠匹配/\w+/了! 

咱們能夠看到,因爲rails在內存當中以UTF-8格式操做中文字符串,而不是ruby默認的unicode格式,這就致使了正則表達式的歧義:/[A-Za-z0-9_]+/不能匹配中文,可是/\w+/能夠匹配中文,但實際上在ruby當中,這兩個正則表達式本應該是同義詞。 

明白了問題的根源,就清楚瞭如何去解決auto_link的bug,修改正則表達式和相關方法,將\w替換爲A-Za-z0-9,並將其放入你的rails項目的application_helper.rb當中,這樣就能夠在項目啓動之後覆蓋rails系統類庫的定義: 

Ruby代碼    收藏代碼
  1. AUTO_LINK_RE = %r{  
  2.                       (                          # leading text  
  3.                         <\w+.*?>|                # leading HTML tag, or  
  4.                         [^=!:'"/]|               # leading punctuation, or   
  5.                         ^                        # beginning of line  
  6.                       )  
  7.                       (  
  8.                         (?:https?://)|           # protocol spec, or  
  9.                         (?:www\.)                # www.*  
  10.                       )   
  11.                       (  
  12.                         [-0-9A-Za-z_]+           # subdomain or domain  
  13.                         (?:\.[-0-9A-Za-z_]+)*    # remaining subdomains or domain  
  14.                         (?::\d+)?                # port  
  15.                         (?:/(?:(?:[~0-9A-Za-z_\+%-]|(?:[,.;:][^\s$]))+)?)* # path  
  16.                         (?:\?[0-9A-Za-z_\+%&=.;-]+)?     # query string  
  17.                         (?:\#[0-9A-Za-z_\-]*)?   # trailing anchor  
  18.                       )  
  19. }x unless const_defined?(:AUTO_LINK_RE)  
  20.   
  21. def auto_link_urls(text, href_options = {})  
  22.   extra_options = tag_options(href_options.stringify_keys) || ""  
  23.   text.gsub(AUTO_LINK_RE) do  
  24.     all, a, b, c = $&, $1$2$3  
  25.     if a =~ /<a\s/i # don't replace URL's that are already linked  
  26.       all  
  27.     else  
  28.       text = b + c  
  29.       text = yield(text) if block_given?  
  30.       %(#{a}<a href="#{b=="www."?"http://www.":b}#{c}"#{extra_options}>#{text}</a>)  
  31.     end  
  32.   end  
  33. end  


OK,搞定了,這下auto_link能夠正確截斷中文了。
相關文章
相關標籤/搜索