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可以正确截断中文了。

转载于:https://my.oschina.net/u/185728/blog/41440

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值