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方法,仔細看一下,原來主要是這個正則表達式在起做用:
- 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前面加上以下內容:
- ENV["RAILS_ENV"] = "development"
- require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
再運行test.rb,果真!中文又被包括進去了,看來就是rails作了手腳。
再回過頭仔細看這個正則表達式,只有[\w]和字符串處理有關係,爲了驗證這一點,咱們作以下試驗:
建立一個char.rb文件,內容以下:
請注意!該文件保存格式請必須使用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系統類庫的定義:
- 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能夠正確截斷中文了。