rails的ActionView::Helpers::TextHepler模塊提供了很多實用的方法,這些方法對於論壇類應用非 常有用,例如 auto_link這個方法可以自動檢測傳入字符串當中的URL,並將其自動轉換為HTML超鏈接格 式,這對於顯示帖子的內容來說很不錯。
但是在開發JavaEye3.0的時候,卻發現auto_link有bug,一旦帖子當中的URL後面緊跟中文的話, auto_link就會把URL後面所有的中文當做URL的一部分進行格式化,直到碰到空格為止,例如:
引用
http://www.javaeye.com網站很不錯
就會被格式化為:
引用
<a href="http://www.javaeye.com網站很不錯">http://www.javaeye.com網站很不錯 </a>
看來得到rails的源代碼裡找答案了。
打開netbeans,敲快捷鍵Ctrl+O,在彈出窗口輸入:texthelper,回車,netbeans已經幫我打開了 text_helper.rb源代碼,通過Navigator窗口,很方便的定位到auto_link方法,仔細看一下,原來主要是 這個正則表達式在起作用:
Ruby代碼
AUTO_LINK_RE = %r{ ( # leading text <\w+.*?>| # leading HTML tag, or [^=!:'"/]| # leading punctuation, or ^ # beginning of line ) ( (?:https?://)| # protocol spec, or (?:www\.) # www.* ) ( [-\w]+ # subdomain or domain (?:\.[-\w]+)* # remaining subdomains or domain (?::\d+)? # port (?:/(?:(?:[~\w\+%-]|(?:[,.;:][^\s$]))+)?)* # path (?:\?[\w\+%&=.;-]+)? # query string (?:\#[\w\-]*)? # trailing anchor ) ([[:punct:]]|\s|<|$) # trailing text }x unless const_defined?(:AUTO_LINK_RE)
但這個正則表達式上看下看,左看右看都沒有啥問題阿。於是把這個正則表達式拷貝出來,放在一個 ruby文件裡面test.rb,一點點單獨調試,但怎麼調試都正常,即使把上面那個URL放進去,也可以正常截 斷中文。
難道是因為rails做了手腳?為了驗證這一點,在test.rb前面加上如下內容:
Ruby代碼
ENV["RAILS_ENV"] = "development" require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
再運行test.rb,果然!中文又被包括進去了,看來就是rails做了手腳。
再回過頭仔細看這個正則表達式,只有[\w]和字符串處理有關系,為了驗證這一點,我們做如下試驗 :
創建一個char.rb文件,內容如下:
Ruby代碼
def name return "范凱" 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+/ => #
哈哈,水落石出了!!由於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代碼
AUTO_LINK_RE = %r{ ( # leading text <\w+.*?>| # leading HTML tag, or [^=!:'"/]| # leading punctuation, or ^ # beginning of line ) ( (?:https?://)| # protocol spec, or (?:www\.) # www.* ) ( [-0-9A-Za-z_]+ # subdomain or domain (?:\.[-0-9A-Za-z_]+)* # remaining subdomains or domain (?::\d+)? # port (?:/(?:(?:[~0-9A-Za-z_\+%-]|(?:[,.;:][^\s$]))+)?)* # path (?:\?[0-9A-Za-z_\+%&=.;-]+)? # query string (?:\#[0-9A-Za-z_\-]*)? # trailing anchor ) }x unless const_defined?(:AUTO_LINK_RE) def auto_link_urls(text, href_options = {}) extra_options = tag_options(href_options.stringify_keys) || "" text.gsub(AUTO_LINK_RE) do all, a, b, c = $&, $1, $2, $3 if a =~ /<a\s/i # don't replace URL's that are already linked all else text = b + c text = yield(text) if block_given? %(#{a}<a href="#{b=="www."?"http://www.":b}#{c}"#{extra_options}>#{text} </a>) end end end
OK,搞定了,這下auto_link可以正確截斷中文了。