我有一个ActiveRecord
模型Foo
,它有一个name
字段。我希望用户能够按名称搜索,但我希望搜索忽略大小写和任何重音。因此,我还存储了一个要搜索的canonical_name
字段:
class Foo
validates_presence_of :name
before_validate :set_canonical_name
private
def set_canonical_name
self.canonical_name ||= canonicalize(self.name) if self.name
end
def canonicalize(x)
x.downcase. # something here
end
end
我需要填写"something here"来替换重音字符。有什么比更好的吗
x.downcase.gsub(/[àáâãäå]/,'a').gsub(/æ/,'ae').gsub(/ç/, 'c').gsub(/[èéêë]/,'e')....
而且,由于我不使用Ruby1.9,所以我不能在代码中放入那些Unicode文字。实际的正则表达式看起来会更难看。
ActiveSupport::Inflector.transliterate
(需要Rails 2.2.1+和Ruby 1.9或1.8.7)
示例:
>> ActiveSupport::Inflector.transliterate("àáâãäå").to_s
=> "aaaaaa"
Rails已经有了一个用于规范化的内置程序,您只需要使用它将字符串规范化为KD,然后删除其他字符(即重音标记),如下所示:
>> "àáâãäå".mb_chars.normalize(:kd).gsub(/[^x00-x7F]/n,'').downcase.to_s
=> "aaaaaa"
更好的方法是使用I18n:
1.9.3-p392 :001 > require "i18n"
=> false
1.9.3-p392 :002 > I18n.transliterate("Olá Mundo!")
=> "Ola Mundo!"
我尝试了很多这种方法,但它们没有达到其中的一个或多个要求:
- 尊重空间
- 尊重"ñ"字
- 尊重大小写(我知道这不是原始问题的要求,但将字符串移动到小写并不困难)
一直是这样:
# coding: utf-8
string.tr(
"ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
"AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
)
–http://blog.slashpoundbang.com/post/12938588984/remove-all-accents-and-diacritics-from-string-in-ruby
你必须稍微修改一下字符列表,以尊重"ñ"字符,但这是一项简单的工作。
我的答案:String#参数化方法:
"Le cœur de la crémiére".parameterize
=> "le-coeur-de-la-cremiere"
对于非Rails程序:
安装activesupport:gem install activesupport
,然后安装
require 'active_support/inflector'
"a&]'s--3 14xC2àáâã3D".parameterize
# => "a-s-3-3d"
分解字符串并从中删除非间隔标记。
irb -ractive_support/all
> "àáâãäå".mb_chars.normalize(:kd).gsub(/p{Mn}/, '')
aaaaaa
如果在.rb文件中使用,您可能还需要它。
# coding: utf-8
这里的normalize(:kd)
部分在可能的情况下分割出变音符号(例如:"n with tilda"单个字符被分割成一个n,后面跟着一个组合变音tilda字符),然后gsub
部分删除所有变音字符。
我想你可能真的不知道该怎么走。如果你正在为一个有这种字母的市场开发,你的用户可能会认为你是一个pip。因为对用户来说,"å"甚至在任何意义上都不接近"a"。走另一条路,以非ascii的方式阅读有关搜索的信息。这只是有人发明了unicode和排序规则的案例之一。
非常晚的PS:
http://www.w3.org/International/wiki/Case_foldinghttp://www.w3.org/TR/charmod-norm/#sec-WhyNormalization
除此之外,我无法将整理链接转到msdn页面,但我将其留在了那里。应该是http://www.unicode.org/reports/tr10/
这假设您使用Rails
"anything".parameterize.underscore.humanize.downcase
考虑到您的需求,我可能会这么做……我认为它简洁、简单,并且在未来的Rails和Ruby版本中保持最新。
更新:dgilperez指出,parameterize
采用分隔符参数,因此"anything".parameterize(" ")
(已弃用)或"anything".parameterize(separator: " ")
更短、更干净。
将文本转换为标准化形式D,删除所有带有unicode类别非间隔标记(Mn)的代码点,并将其转换回标准化形式C。这将剥离所有变音符号,您的问题将简化为不区分大小写的搜索。
请参阅http://www.siao2.com/2005/02/19/376617.aspx和http://www.siao2.com/2007/05/14/2629747.aspx详细信息。
关键是在数据库中使用两列:canonical_text
和original_text
。使用original_text
进行显示,使用canonical_text
进行搜索。这样,如果用户搜索"Visual Cafe",她就会看到"Visual Café"的结果。如果她真的想要一个名为"Visual Cafe"的不同项目,可以单独保存。
要获得Ruby 1.8源文件中的规范文本字符,请执行以下操作:
register_replacement([0x008A].pack('U'), 'S')
您可能想要Unicode分解("NFD")。分解字符串后,只需过滤掉[A-Za-z]中没有的任何内容。æ将分解为"ae",ã为"a~"(近似-二临界将成为一个单独的字符),因此滤波留下了一个合理的近似值。
图标:
http://groups.google.com/group/ruby-talk-google/browse_frm/thread/8064dcac15d688ce?
===============
一个我无法理解的perl模块:
http://www.ahinea.com/en/tech/accented-translate.html
====================
蛮力(有很多这种小动物!:
http://projects.jkraemer.net/acts_as_ferret/wiki#UTF-8支持
http://snippets.dzone.com/posts/show/2384
我在获取foo.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.toos解决方案时遇到问题。我没有使用Rails,与我的activesupport/ruby版本有一些冲突,我无法深入了解。
使用ruby unf gem似乎是一个很好的替代品:
require 'unf'
foo.to_nfd.gsub(/[^x00-x7F]/n,'').downcase
据我所知,这与.mb_chars.normalize(:kd)的作用相同。这正确吗?谢谢
如果你使用PostgreSQL=>9.4作为你的数据库适配器,也许你可以在迁移中添加一个"未使用"的扩展,我认为它可以满足你的需求,比如:
def self.up
enable_extension "unaccent" # No falla si ya existe
end
为了进行测试,在控制台中:
2.3.1 :045 > ActiveRecord::Base.connection.execute("SELECT unaccent('unaccent', 'àáâãäåÁÄ')").first
=> {"unaccent"=>"aaaaaaAA"}
请注意,到目前为止有区分大小写的功能。
然后,也许可以在一个范围内使用它,比如:
scope :with_canonical_name, -> (name) {
where("unaccent(foos.name) iLIKE unaccent('#{name}')")
}
iLIKE运算符使搜索不区分大小写。还有另一种方法,使用citext数据类型。以下是关于这两种方法的讨论。还要注意,不建议使用PosgreSQL的lower()函数。
这将为您节省一些数据库空间,因为您将不再需要cannonical_name字段,并且可能会使您的模型更简单,而在每个查询中需要一些额外的处理,具体数量取决于您使用的是iLIKE还是citext,以及您的数据集。
如果你使用的是MySQL也许你可以使用这个简单的解决方案,但我还没有测试过。
lol。。我刚试过这个。。它正在发挥作用。。我还是不太清楚为什么。。但当我使用这4行代码时:
- str=str.gsub(/[^a-zA-Z0-9]/,")
- str=str.gsub(/[]+/,")
- str=str.gsub(//,"-")
- str=str.downcase
它会自动删除文件名中的任何重音符号。。我试图删除它(从文件名中删除重音并重命名它们)希望它能有所帮助:)